1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsNativeThemeWin.h"
7 
8 #include "mozilla/EventStates.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/WindowsVersion.h"
11 #include "nsDeviceContext.h"
12 #include "nsRect.h"
13 #include "nsSize.h"
14 #include "nsTransform2D.h"
15 #include "nsThemeConstants.h"
16 #include "nsIPresShell.h"
17 #include "nsPresContext.h"
18 #include "nsIContent.h"
19 #include "nsIContentInlines.h"
20 #include "nsIFrame.h"
21 #include "nsNameSpaceManager.h"
22 #include "nsLookAndFeel.h"
23 #include "nsMenuFrame.h"
24 #include "nsGkAtoms.h"
25 #include <malloc.h>
26 #include "nsWindow.h"
27 #include "nsIComboboxControlFrame.h"
28 #include "prinrval.h"
29 #include "WinUtils.h"
30 
31 #include "gfxPlatform.h"
32 #include "gfxContext.h"
33 #include "gfxWindowsPlatform.h"
34 #include "gfxWindowsSurface.h"
35 #include "gfxWindowsNativeDrawing.h"
36 
37 #include "nsUXThemeData.h"
38 #include "nsUXThemeConstants.h"
39 #include <algorithm>
40 
41 using namespace mozilla;
42 using namespace mozilla::widget;
43 
44 extern mozilla::LazyLogModule gWindowsLog;
45 
NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin,nsNativeTheme,nsITheme)46 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeWin, nsNativeTheme, nsITheme)
47 
48 nsNativeThemeWin::nsNativeThemeWin()
49     : mProgressDeterminateTimeStamp(TimeStamp::Now()),
50       mProgressIndeterminateTimeStamp(TimeStamp::Now()),
51       mBorderCacheValid(),
52       mMinimumWidgetSizeCacheValid(),
53       mGutterSizeCacheValid(false) {
54   // If there is a relevant change in forms.css for windows platform,
55   // static widget style variables (e.g. sButtonBorderSize) should be
56   // reinitialized here.
57 }
58 
~nsNativeThemeWin()59 nsNativeThemeWin::~nsNativeThemeWin() { nsUXThemeData::Invalidate(); }
60 
GetTopLevelWindowActiveState(nsIFrame * aFrame)61 static int32_t GetTopLevelWindowActiveState(nsIFrame* aFrame) {
62   // Used by window frame and button box rendering. We can end up in here in
63   // the content process when rendering one of these moz styles freely in a
64   // page. Bail in this case, there is no applicable window focus state.
65   if (!XRE_IsParentProcess()) {
66     return mozilla::widget::themeconst::FS_INACTIVE;
67   }
68   // All headless windows are considered active so they are painted.
69   if (gfxPlatform::IsHeadless()) {
70     return mozilla::widget::themeconst::FS_ACTIVE;
71   }
72   // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
73   // until it finds a real window.
74   nsIWidget* widget = aFrame->GetNearestWidget();
75   nsWindowBase* window = static_cast<nsWindowBase*>(widget);
76   if (!window) return mozilla::widget::themeconst::FS_INACTIVE;
77   if (widget && !window->IsTopLevelWidget() &&
78       !(window = window->GetParentWindowBase(false)))
79     return mozilla::widget::themeconst::FS_INACTIVE;
80 
81   if (window->GetWindowHandle() == ::GetActiveWindow())
82     return mozilla::widget::themeconst::FS_ACTIVE;
83   return mozilla::widget::themeconst::FS_INACTIVE;
84 }
85 
GetWindowFrameButtonState(nsIFrame * aFrame,EventStates eventState)86 static int32_t GetWindowFrameButtonState(nsIFrame* aFrame,
87                                          EventStates eventState) {
88   if (GetTopLevelWindowActiveState(aFrame) ==
89       mozilla::widget::themeconst::FS_INACTIVE) {
90     if (eventState.HasState(NS_EVENT_STATE_HOVER))
91       return mozilla::widget::themeconst::BS_HOT;
92     return mozilla::widget::themeconst::BS_INACTIVE;
93   }
94 
95   if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
96     if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
97       return mozilla::widget::themeconst::BS_PUSHED;
98     return mozilla::widget::themeconst::BS_HOT;
99   }
100   return mozilla::widget::themeconst::BS_NORMAL;
101 }
102 
GetClassicWindowFrameButtonState(EventStates eventState)103 static int32_t GetClassicWindowFrameButtonState(EventStates eventState) {
104   if (eventState.HasState(NS_EVENT_STATE_ACTIVE) &&
105       eventState.HasState(NS_EVENT_STATE_HOVER))
106     return DFCS_BUTTONPUSH | DFCS_PUSHED;
107   return DFCS_BUTTONPUSH;
108 }
109 
IsTopLevelMenu(nsIFrame * aFrame)110 static bool IsTopLevelMenu(nsIFrame* aFrame) {
111   bool isTopLevel(false);
112   nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
113   if (menuFrame) {
114     isTopLevel = menuFrame->IsOnMenuBar();
115   }
116   return isTopLevel;
117 }
118 
GetCheckboxMargins(HANDLE theme,HDC hdc)119 static MARGINS GetCheckboxMargins(HANDLE theme, HDC hdc) {
120   MARGINS checkboxContent = {0};
121   GetThemeMargins(theme, hdc, MENU_POPUPCHECK, MCB_NORMAL, TMT_CONTENTMARGINS,
122                   nullptr, &checkboxContent);
123   return checkboxContent;
124 }
125 
GetCheckboxBGSize(HANDLE theme,HDC hdc)126 static SIZE GetCheckboxBGSize(HANDLE theme, HDC hdc) {
127   SIZE checkboxSize;
128   GetThemePartSize(theme, hdc, MENU_POPUPCHECK, MC_CHECKMARKNORMAL, nullptr,
129                    TS_TRUE, &checkboxSize);
130 
131   MARGINS checkboxMargins = GetCheckboxMargins(theme, hdc);
132 
133   int leftMargin = checkboxMargins.cxLeftWidth;
134   int rightMargin = checkboxMargins.cxRightWidth;
135   int topMargin = checkboxMargins.cyTopHeight;
136   int bottomMargin = checkboxMargins.cyBottomHeight;
137 
138   int width = leftMargin + checkboxSize.cx + rightMargin;
139   int height = topMargin + checkboxSize.cy + bottomMargin;
140   SIZE ret;
141   ret.cx = width;
142   ret.cy = height;
143   return ret;
144 }
145 
GetCheckboxBGBounds(HANDLE theme,HDC hdc)146 static SIZE GetCheckboxBGBounds(HANDLE theme, HDC hdc) {
147   MARGINS checkboxBGSizing = {0};
148   MARGINS checkboxBGContent = {0};
149   GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
150                   TMT_SIZINGMARGINS, nullptr, &checkboxBGSizing);
151   GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
152                   TMT_CONTENTMARGINS, nullptr, &checkboxBGContent);
153 
154 #define posdx(d) ((d) > 0 ? d : 0)
155 
156   int dx =
157       posdx(checkboxBGContent.cxRightWidth - checkboxBGSizing.cxRightWidth) +
158       posdx(checkboxBGContent.cxLeftWidth - checkboxBGSizing.cxLeftWidth);
159   int dy =
160       posdx(checkboxBGContent.cyTopHeight - checkboxBGSizing.cyTopHeight) +
161       posdx(checkboxBGContent.cyBottomHeight - checkboxBGSizing.cyBottomHeight);
162 
163 #undef posdx
164 
165   SIZE ret(GetCheckboxBGSize(theme, hdc));
166   ret.cx += dx;
167   ret.cy += dy;
168   return ret;
169 }
170 
GetGutterSize(HANDLE theme,HDC hdc)171 static SIZE GetGutterSize(HANDLE theme, HDC hdc) {
172   SIZE gutterSize;
173   GetThemePartSize(theme, hdc, MENU_POPUPGUTTER, 0, nullptr, TS_TRUE,
174                    &gutterSize);
175 
176   SIZE checkboxBGSize(GetCheckboxBGBounds(theme, hdc));
177 
178   SIZE itemSize;
179   GetThemePartSize(theme, hdc, MENU_POPUPITEM, MPI_NORMAL, nullptr, TS_TRUE,
180                    &itemSize);
181 
182   // Figure out how big the menuitem's icon will be (if present) at current DPI
183   // Needs the system scale for consistency with Windows Theme API.
184   double scaleFactor = WinUtils::SystemScaleFactor();
185   int iconDevicePixels = NSToIntRound(16 * scaleFactor);
186   SIZE iconSize = {iconDevicePixels, iconDevicePixels};
187   // Not really sure what margins should be used here, but this seems to work in
188   // practice...
189   MARGINS margins = {0};
190   GetThemeMargins(theme, hdc, MENU_POPUPCHECKBACKGROUND, MCB_NORMAL,
191                   TMT_CONTENTMARGINS, nullptr, &margins);
192   iconSize.cx += margins.cxLeftWidth + margins.cxRightWidth;
193   iconSize.cy += margins.cyTopHeight + margins.cyBottomHeight;
194 
195   int width = std::max(
196       itemSize.cx, std::max(iconSize.cx, checkboxBGSize.cx) + gutterSize.cx);
197   int height = std::max(itemSize.cy, std::max(iconSize.cy, checkboxBGSize.cy));
198 
199   SIZE ret;
200   ret.cx = width;
201   ret.cy = height;
202   return ret;
203 }
204 
GetCachedGutterSize(HANDLE theme)205 SIZE nsNativeThemeWin::GetCachedGutterSize(HANDLE theme) {
206   if (mGutterSizeCacheValid) {
207     return mGutterSizeCache;
208   }
209 
210   mGutterSizeCache = GetGutterSize(theme, nullptr);
211   mGutterSizeCacheValid = true;
212 
213   return mGutterSizeCache;
214 }
215 
216 /* DrawThemeBGRTLAware - render a theme part based on rtl state.
217  * Some widgets are not direction-neutral and need to be drawn reversed for
218  * RTL.  Windows provides a way to do this with SetLayout, but this reverses
219  * the entire drawing area of a given device context, which means that its
220  * use will also affect the positioning of the widget.  There are two ways
221  * to work around this:
222  *
223  * Option 1: Alter the position of the rect that we send so that we cancel
224  *           out the positioning effects of SetLayout
225  * Option 2: Create a memory DC with the widgetRect's dimensions, draw onto
226  *           that, and then transfer the results back to our DC
227  *
228  * This function tries to implement option 1, under the assumption that the
229  * correct way to reverse the effects of SetLayout is to translate the rect
230  * such that the offset from the DC bitmap's left edge to the old rect's
231  * left edge is equal to the offset from the DC bitmap's right edge to the
232  * new rect's right edge.  In other words,
233  * (oldRect.left + vpOrg.x) == ((dcBMP.width - vpOrg.x) - newRect.right)
234  */
DrawThemeBGRTLAware(HANDLE aTheme,HDC aHdc,int aPart,int aState,const RECT * aWidgetRect,const RECT * aClipRect,bool aIsRtl)235 static HRESULT DrawThemeBGRTLAware(HANDLE aTheme, HDC aHdc, int aPart,
236                                    int aState, const RECT* aWidgetRect,
237                                    const RECT* aClipRect, bool aIsRtl) {
238   NS_ASSERTION(aTheme, "Bad theme handle.");
239   NS_ASSERTION(aHdc, "Bad hdc.");
240   NS_ASSERTION(aWidgetRect, "Bad rect.");
241   NS_ASSERTION(aClipRect, "Bad clip rect.");
242 
243   if (!aIsRtl) {
244     return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
245                                aClipRect);
246   }
247 
248   HGDIOBJ hObj = GetCurrentObject(aHdc, OBJ_BITMAP);
249   BITMAP bitmap;
250   POINT vpOrg;
251 
252   if (hObj && GetObject(hObj, sizeof(bitmap), &bitmap) &&
253       GetViewportOrgEx(aHdc, &vpOrg)) {
254     RECT newWRect(*aWidgetRect);
255     newWRect.left = bitmap.bmWidth - (aWidgetRect->right + 2 * vpOrg.x);
256     newWRect.right = bitmap.bmWidth - (aWidgetRect->left + 2 * vpOrg.x);
257 
258     RECT newCRect;
259     RECT* newCRectPtr = nullptr;
260 
261     if (aClipRect) {
262       newCRect.top = aClipRect->top;
263       newCRect.bottom = aClipRect->bottom;
264       newCRect.left = bitmap.bmWidth - (aClipRect->right + 2 * vpOrg.x);
265       newCRect.right = bitmap.bmWidth - (aClipRect->left + 2 * vpOrg.x);
266       newCRectPtr = &newCRect;
267     }
268 
269     SetLayout(aHdc, LAYOUT_RTL);
270     HRESULT hr = DrawThemeBackground(aTheme, aHdc, aPart, aState, &newWRect,
271                                      newCRectPtr);
272     SetLayout(aHdc, 0);
273     if (SUCCEEDED(hr)) {
274       return hr;
275     }
276   }
277   return DrawThemeBackground(aTheme, aHdc, aPart, aState, aWidgetRect,
278                              aClipRect);
279 }
280 
281 /*
282  *  Caption button padding data - 'hot' button padding.
283  *  These areas are considered hot, in that they activate
284  *  a button when hovered or clicked. The button graphic
285  *  is drawn inside the padding border. Unrecognized themes
286  *  are treated as their recognized counterparts for now.
287  *                       left      top    right   bottom
288  *  classic min             1        2        0        1
289  *  classic max             0        2        1        1
290  *  classic close           1        2        2        1
291  *
292  *  aero basic min          1        2        0        2
293  *  aero basic max          0        2        1        2
294  *  aero basic close        1        2        1        2
295  *
296  *  'cold' button padding - generic button padding, should
297  *  be handled in css.
298  *                       left      top    right   bottom
299  *  classic min             0        0        0        0
300  *  classic max             0        0        0        0
301  *  classic close           0        0        0        0
302  *
303  *  aero basic min          0        0        1        0
304  *  aero basic max          1        0        0        0
305  *  aero basic close        0        0        0        0
306  */
307 
308 enum CaptionDesktopTheme {
309   CAPTION_CLASSIC = 0,
310   CAPTION_BASIC,
311 };
312 
313 enum CaptionButton {
314   CAPTIONBUTTON_MINIMIZE = 0,
315   CAPTIONBUTTON_RESTORE,
316   CAPTIONBUTTON_CLOSE,
317 };
318 
319 struct CaptionButtonPadding {
320   RECT hotPadding[3];
321 };
322 
323 // RECT: left, top, right, bottom
324 static CaptionButtonPadding buttonData[3] = {
325     {{{1, 2, 0, 1}, {0, 2, 1, 1}, {1, 2, 2, 1}}},
326     {{{1, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}},
327     {{{0, 2, 0, 2}, {0, 2, 1, 2}, {1, 2, 2, 2}}}};
328 
329 // Adds "hot" caption button padding to minimum widget size.
AddPaddingRect(LayoutDeviceIntSize * aSize,CaptionButton button)330 static void AddPaddingRect(LayoutDeviceIntSize* aSize, CaptionButton button) {
331   if (!aSize) return;
332   RECT offset;
333   if (!IsAppThemed())
334     offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
335   else
336     offset = buttonData[CAPTION_BASIC].hotPadding[button];
337   aSize->width += offset.left + offset.right;
338   aSize->height += offset.top + offset.bottom;
339 }
340 
341 // If we've added padding to the minimum widget size, offset
342 // the area we draw into to compensate.
OffsetBackgroundRect(RECT & rect,CaptionButton button)343 static void OffsetBackgroundRect(RECT& rect, CaptionButton button) {
344   RECT offset;
345   if (!IsAppThemed())
346     offset = buttonData[CAPTION_CLASSIC].hotPadding[button];
347   else
348     offset = buttonData[CAPTION_BASIC].hotPadding[button];
349   rect.left += offset.left;
350   rect.top += offset.top;
351   rect.right -= offset.right;
352   rect.bottom -= offset.bottom;
353 }
354 
355 /*
356  * Notes on progress track and meter part constants:
357  * xp and up:
358  * PP_BAR(_VERT)            - base progress track
359  * PP_TRANSPARENTBAR(_VERT) - transparent progress track. this only works if
360  *                            the underlying surface supports alpha. otherwise
361  *                            theme lib's DrawThemeBackground falls back on
362  *                            opaque PP_BAR. we currently don't use this.
363  * PP_CHUNK(_VERT)          - xp progress meter. this does not draw an xp style
364  *                            progress w/chunks, it draws fill using the chunk
365  *                            graphic.
366  * vista and up:
367  * PP_FILL(_VERT)           - progress meter. these have four states/colors.
368  * PP_PULSEOVERLAY(_VERT)   - white reflection - an overlay, not sure what this
369  *                            is used for.
370  * PP_MOVEOVERLAY(_VERT)    - green pulse - the pulse effect overlay on
371  *                            determined progress bars. we also use this for
372  *                            indeterminate chunk.
373  *
374  * Notes on state constants:
375  * PBBS_NORMAL               - green progress
376  * PBBVS_PARTIAL/PBFVS_ERROR - red error progress
377  * PBFS_PAUSED               - yellow paused progress
378  *
379  * There is no common controls style indeterminate part on vista and up.
380  */
381 
382 /*
383  * Progress bar related constants. These values are found by experimenting and
384  * comparing against native widgets used by the system. They are very unlikely
385  * exact but try to not be too wrong.
386  */
387 // The amount of time we animate progress meters parts across the frame.
388 static const double kProgressDeterminateTimeSpan = 3.0;
389 static const double kProgressIndeterminateTimeSpan = 5.0;
390 // The width of the overlay used to animate the horizontal progress bar (Vista
391 // and later).
392 static const int32_t kProgressHorizontalOverlaySize = 120;
393 // The height of the overlay used to animate the vertical progress bar (Vista
394 // and later).
395 static const int32_t kProgressVerticalOverlaySize = 45;
396 // The height of the overlay used for the vertical indeterminate progress bar
397 // (Vista and later).
398 static const int32_t kProgressVerticalIndeterminateOverlaySize = 60;
399 // The width of the overlay used to animate the indeterminate progress bar
400 // (Windows Classic).
401 static const int32_t kProgressClassicOverlaySize = 40;
402 
403 /*
404  * GetProgressOverlayStyle - returns the proper overlay part for themed
405  * progress bars based on os and orientation.
406  */
GetProgressOverlayStyle(bool aIsVertical)407 static int32_t GetProgressOverlayStyle(bool aIsVertical) {
408   return aIsVertical ? PP_MOVEOVERLAYVERT : PP_MOVEOVERLAY;
409 }
410 
411 /*
412  * GetProgressOverlaySize - returns the minimum width or height for themed
413  * progress bar overlays. This includes the width of indeterminate chunks
414  * and vista pulse overlays.
415  */
GetProgressOverlaySize(bool aIsVertical,bool aIsIndeterminate)416 static int32_t GetProgressOverlaySize(bool aIsVertical, bool aIsIndeterminate) {
417   if (aIsVertical) {
418     return aIsIndeterminate ? kProgressVerticalIndeterminateOverlaySize
419                             : kProgressVerticalOverlaySize;
420   }
421   return kProgressHorizontalOverlaySize;
422 }
423 
424 /*
425  * IsProgressMeterFilled - Determines if a progress meter is at 100% fill based
426  * on a comparison of the current value and maximum.
427  */
IsProgressMeterFilled(nsIFrame * aFrame)428 static bool IsProgressMeterFilled(nsIFrame* aFrame) {
429   NS_ENSURE_TRUE(aFrame, false);
430   nsIFrame* parentFrame = aFrame->GetParent();
431   NS_ENSURE_TRUE(parentFrame, false);
432   return nsNativeTheme::GetProgressValue(parentFrame) ==
433          nsNativeTheme::GetProgressMaxValue(parentFrame);
434 }
435 
436 /*
437  * CalculateProgressOverlayRect - returns the padded overlay animation rect
438  * used in rendering progress bars. Resulting rects are used in rendering
439  * vista+ pulse overlays and indeterminate progress meters. Graphics should
440  * be rendered at the origin.
441  */
CalculateProgressOverlayRect(nsIFrame * aFrame,RECT * aWidgetRect,bool aIsVertical,bool aIsIndeterminate,bool aIsClassic)442 RECT nsNativeThemeWin::CalculateProgressOverlayRect(nsIFrame* aFrame,
443                                                     RECT* aWidgetRect,
444                                                     bool aIsVertical,
445                                                     bool aIsIndeterminate,
446                                                     bool aIsClassic) {
447   NS_ASSERTION(aFrame, "bad frame pointer");
448   NS_ASSERTION(aWidgetRect, "bad rect pointer");
449 
450   int32_t frameSize = aIsVertical ? aWidgetRect->bottom - aWidgetRect->top
451                                   : aWidgetRect->right - aWidgetRect->left;
452 
453   // Recycle a set of progress pulse timers - these timers control the position
454   // of all progress overlays and indeterminate chunks that get rendered.
455   double span = aIsIndeterminate ? kProgressIndeterminateTimeSpan
456                                  : kProgressDeterminateTimeSpan;
457   TimeDuration period;
458   if (!aIsIndeterminate) {
459     if (TimeStamp::Now() >
460         (mProgressDeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
461       mProgressDeterminateTimeStamp = TimeStamp::Now();
462     }
463     period = TimeStamp::Now() - mProgressDeterminateTimeStamp;
464   } else {
465     if (TimeStamp::Now() >
466         (mProgressIndeterminateTimeStamp + TimeDuration::FromSeconds(span))) {
467       mProgressIndeterminateTimeStamp = TimeStamp::Now();
468     }
469     period = TimeStamp::Now() - mProgressIndeterminateTimeStamp;
470   }
471 
472   double percent = period / TimeDuration::FromSeconds(span);
473 
474   if (!aIsVertical && IsFrameRTL(aFrame)) percent = 1 - percent;
475 
476   RECT overlayRect = *aWidgetRect;
477   int32_t overlaySize;
478   if (!aIsClassic) {
479     overlaySize = GetProgressOverlaySize(aIsVertical, aIsIndeterminate);
480   } else {
481     overlaySize = kProgressClassicOverlaySize;
482   }
483 
484   // Calculate a bounds that is larger than the meters frame such that the
485   // overlay starts and ends completely off the edge of the frame:
486   // [overlay][frame][overlay]
487   // This also yields a nice delay on rotation. Use overlaySize as the minimum
488   // size for [overlay] based on the graphics dims. If [frame] is larger, use
489   // the frame size instead.
490   int trackWidth = frameSize > overlaySize ? frameSize : overlaySize;
491   if (!aIsVertical) {
492     int xPos = aWidgetRect->left - trackWidth;
493     xPos += (int)ceil(((double)(trackWidth * 2) * percent));
494     overlayRect.left = xPos;
495     overlayRect.right = xPos + overlaySize;
496   } else {
497     int yPos = aWidgetRect->bottom + trackWidth;
498     yPos -= (int)ceil(((double)(trackWidth * 2) * percent));
499     overlayRect.bottom = yPos;
500     overlayRect.top = yPos - overlaySize;
501   }
502   return overlayRect;
503 }
504 
505 /*
506  * DrawProgressMeter - render an appropriate progress meter based on progress
507  * meter style, orientation, and os. Note, this does not render the underlying
508  * progress track.
509  *
510  * @param aFrame       the widget frame
511  * @param aWidgetType  type of widget
512  * @param aTheme       progress theme handle
513  * @param aHdc         hdc returned by gfxWindowsNativeDrawing
514  * @param aPart        the PP_X progress part
515  * @param aState       the theme state
516  * @param aWidgetRect  bounding rect for the widget
517  * @param aClipRect    dirty rect that needs drawing.
518  * @param aAppUnits    app units per device pixel
519  */
DrawThemedProgressMeter(nsIFrame * aFrame,int aWidgetType,HANDLE aTheme,HDC aHdc,int aPart,int aState,RECT * aWidgetRect,RECT * aClipRect)520 void nsNativeThemeWin::DrawThemedProgressMeter(nsIFrame* aFrame,
521                                                int aWidgetType, HANDLE aTheme,
522                                                HDC aHdc, int aPart, int aState,
523                                                RECT* aWidgetRect,
524                                                RECT* aClipRect) {
525   if (!aFrame || !aTheme || !aHdc) return;
526 
527   NS_ASSERTION(aWidgetRect, "bad rect pointer");
528   NS_ASSERTION(aClipRect, "bad clip rect pointer");
529 
530   RECT adjWidgetRect, adjClipRect;
531   adjWidgetRect = *aWidgetRect;
532   adjClipRect = *aClipRect;
533 
534   nsIFrame* parentFrame = aFrame->GetParent();
535   if (!parentFrame) {
536     // We have no parent to work with, just bail.
537     NS_WARNING("No parent frame for progress rendering. Can't paint.");
538     return;
539   }
540 
541   EventStates eventStates = GetContentState(parentFrame, aWidgetType);
542   bool vertical = IsVerticalProgress(parentFrame) ||
543                   aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
544   bool indeterminate = IsIndeterminateProgress(parentFrame, eventStates);
545   bool animate = indeterminate;
546 
547   // Vista and up progress meter is fill style, rendered here. We render
548   // the pulse overlay in the follow up section below.
549   DrawThemeBackground(aTheme, aHdc, aPart, aState, &adjWidgetRect,
550                       &adjClipRect);
551   if (!IsProgressMeterFilled(aFrame)) {
552     animate = true;
553   }
554 
555   if (animate) {
556     // Indeterminate rendering
557     int32_t overlayPart = GetProgressOverlayStyle(vertical);
558     RECT overlayRect = CalculateProgressOverlayRect(
559         aFrame, &adjWidgetRect, vertical, indeterminate, false);
560     DrawThemeBackground(aTheme, aHdc, overlayPart, aState, &overlayRect,
561                         &adjClipRect);
562 
563     if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
564       NS_WARNING("unable to animate progress widget!");
565     }
566   }
567 }
568 
GetCachedWidgetBorder(nsIFrame * aFrame,HTHEME aTheme,nsUXThemeClass aThemeClass,uint8_t aWidgetType,int32_t aPart,int32_t aState,nsIntMargin * aResult)569 nsresult nsNativeThemeWin::GetCachedWidgetBorder(
570     nsIFrame* aFrame, HTHEME aTheme, nsUXThemeClass aThemeClass,
571     uint8_t aWidgetType, int32_t aPart, int32_t aState, nsIntMargin* aResult) {
572   int32_t cacheIndex = aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + aPart;
573   int32_t cacheBitIndex = cacheIndex / 8;
574   uint8_t cacheBit = 1u << (cacheIndex % 8);
575 
576   if (mBorderCacheValid[cacheBitIndex] & cacheBit) {
577     *aResult = mBorderCache[cacheIndex];
578     return NS_OK;
579   }
580 
581   // Get our info.
582   RECT outerRect;  // Create a fake outer rect.
583   outerRect.top = outerRect.left = 100;
584   outerRect.right = outerRect.bottom = 200;
585   RECT contentRect(outerRect);
586   HRESULT res = GetThemeBackgroundContentRect(aTheme, nullptr, aPart, aState,
587                                               &outerRect, &contentRect);
588 
589   if (FAILED(res)) {
590     return NS_ERROR_FAILURE;
591   }
592 
593   // Now compute the delta in each direction and place it in our
594   // nsIntMargin struct.
595   aResult->top = contentRect.top - outerRect.top;
596   aResult->bottom = outerRect.bottom - contentRect.bottom;
597   aResult->left = contentRect.left - outerRect.left;
598   aResult->right = outerRect.right - contentRect.right;
599 
600   mBorderCacheValid[cacheBitIndex] |= cacheBit;
601   mBorderCache[cacheIndex] = *aResult;
602 
603   return NS_OK;
604 }
605 
GetCachedMinimumWidgetSize(nsIFrame * aFrame,HANDLE aTheme,nsUXThemeClass aThemeClass,uint8_t aWidgetType,int32_t aPart,int32_t aState,THEMESIZE aSizeReq,mozilla::LayoutDeviceIntSize * aResult)606 nsresult nsNativeThemeWin::GetCachedMinimumWidgetSize(
607     nsIFrame* aFrame, HANDLE aTheme, nsUXThemeClass aThemeClass,
608     uint8_t aWidgetType, int32_t aPart, int32_t aState, THEMESIZE aSizeReq,
609     mozilla::LayoutDeviceIntSize* aResult) {
610   int32_t cachePart = aPart;
611 
612   if (aWidgetType == NS_THEME_BUTTON && aSizeReq == TS_MIN) {
613     // In practice, NS_THEME_BUTTON is the only widget type which has an
614     // aSizeReq that varies for us, and it can only be TS_MIN or TS_TRUE. Just
615     // stuff that extra bit into the aPart part of the cache, since BP_Count is
616     // well below THEME_PART_DISTINCT_VALUE_COUNT anyway.
617     cachePart = BP_Count;
618   }
619 
620   MOZ_ASSERT(aPart < THEME_PART_DISTINCT_VALUE_COUNT);
621   int32_t cacheIndex =
622       aThemeClass * THEME_PART_DISTINCT_VALUE_COUNT + cachePart;
623   int32_t cacheBitIndex = cacheIndex / 8;
624   uint8_t cacheBit = 1u << (cacheIndex % 8);
625 
626   if (mMinimumWidgetSizeCacheValid[cacheBitIndex] & cacheBit) {
627     *aResult = mMinimumWidgetSizeCache[cacheIndex];
628     return NS_OK;
629   }
630 
631   HDC hdc = ::GetDC(NULL);
632   if (!hdc) {
633     return NS_ERROR_FAILURE;
634   }
635 
636   SIZE sz;
637   GetThemePartSize(aTheme, hdc, aPart, aState, nullptr, aSizeReq, &sz);
638   aResult->width = sz.cx;
639   aResult->height = sz.cy;
640 
641   switch (aWidgetType) {
642     case NS_THEME_INNER_SPIN_BUTTON:
643     case NS_THEME_SPINNER_UPBUTTON:
644     case NS_THEME_SPINNER_DOWNBUTTON:
645       aResult->width++;
646       aResult->height = aResult->height / 2 + 1;
647       break;
648 
649     case NS_THEME_MENUSEPARATOR: {
650       SIZE gutterSize(GetGutterSize(aTheme, hdc));
651       aResult->width += gutterSize.cx;
652       break;
653     }
654 
655     case NS_THEME_MENUARROW: {
656       // Use the width of the arrow glyph as padding. See the drawing
657       // code for details.
658       aResult->width *= 2;
659       break;
660     }
661   }
662 
663   ::ReleaseDC(nullptr, hdc);
664 
665   mMinimumWidgetSizeCacheValid[cacheBitIndex] |= cacheBit;
666   mMinimumWidgetSizeCache[cacheIndex] = *aResult;
667 
668   return NS_OK;
669 }
670 
GetThemeClass(uint8_t aWidgetType)671 mozilla::Maybe<nsUXThemeClass> nsNativeThemeWin::GetThemeClass(
672     uint8_t aWidgetType) {
673   switch (aWidgetType) {
674     case NS_THEME_BUTTON:
675     case NS_THEME_RADIO:
676     case NS_THEME_CHECKBOX:
677     case NS_THEME_GROUPBOX:
678       return Some(eUXButton);
679     case NS_THEME_NUMBER_INPUT:
680     case NS_THEME_TEXTFIELD:
681     case NS_THEME_TEXTFIELD_MULTILINE:
682     case NS_THEME_FOCUS_OUTLINE:
683       return Some(eUXEdit);
684     case NS_THEME_TOOLTIP:
685       return Some(eUXTooltip);
686     case NS_THEME_TOOLBOX:
687       return Some(eUXRebar);
688     case NS_THEME_WIN_MEDIA_TOOLBOX:
689       return Some(eUXMediaRebar);
690     case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
691       return Some(eUXCommunicationsRebar);
692     case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
693       return Some(eUXBrowserTabBarRebar);
694     case NS_THEME_TOOLBAR:
695     case NS_THEME_TOOLBARBUTTON:
696     case NS_THEME_SEPARATOR:
697       return Some(eUXToolbar);
698     case NS_THEME_PROGRESSBAR:
699     case NS_THEME_PROGRESSBAR_VERTICAL:
700     case NS_THEME_PROGRESSCHUNK:
701     case NS_THEME_PROGRESSCHUNK_VERTICAL:
702       return Some(eUXProgress);
703     case NS_THEME_TAB:
704     case NS_THEME_TABPANEL:
705     case NS_THEME_TABPANELS:
706       return Some(eUXTab);
707     case NS_THEME_SCROLLBAR:
708     case NS_THEME_SCROLLBAR_SMALL:
709     case NS_THEME_SCROLLBAR_VERTICAL:
710     case NS_THEME_SCROLLBAR_HORIZONTAL:
711     case NS_THEME_SCROLLBARBUTTON_UP:
712     case NS_THEME_SCROLLBARBUTTON_DOWN:
713     case NS_THEME_SCROLLBARBUTTON_LEFT:
714     case NS_THEME_SCROLLBARBUTTON_RIGHT:
715     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
716     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
717       return Some(eUXScrollbar);
718     case NS_THEME_RANGE:
719     case NS_THEME_RANGE_THUMB:
720     case NS_THEME_SCALE_HORIZONTAL:
721     case NS_THEME_SCALE_VERTICAL:
722     case NS_THEME_SCALETHUMB_HORIZONTAL:
723     case NS_THEME_SCALETHUMB_VERTICAL:
724       return Some(eUXTrackbar);
725     case NS_THEME_INNER_SPIN_BUTTON:
726     case NS_THEME_SPINNER_UPBUTTON:
727     case NS_THEME_SPINNER_DOWNBUTTON:
728       return Some(eUXSpin);
729     case NS_THEME_STATUSBAR:
730     case NS_THEME_STATUSBARPANEL:
731     case NS_THEME_RESIZERPANEL:
732     case NS_THEME_RESIZER:
733       return Some(eUXStatus);
734     case NS_THEME_MENULIST:
735     case NS_THEME_MENULIST_BUTTON:
736       return Some(eUXCombobox);
737     case NS_THEME_TREEHEADERCELL:
738     case NS_THEME_TREEHEADERSORTARROW:
739       return Some(eUXHeader);
740     case NS_THEME_LISTBOX:
741     case NS_THEME_LISTITEM:
742     case NS_THEME_TREEVIEW:
743     case NS_THEME_TREETWISTYOPEN:
744     case NS_THEME_TREEITEM:
745       return Some(eUXListview);
746     case NS_THEME_MENUBAR:
747     case NS_THEME_MENUPOPUP:
748     case NS_THEME_MENUITEM:
749     case NS_THEME_CHECKMENUITEM:
750     case NS_THEME_RADIOMENUITEM:
751     case NS_THEME_MENUCHECKBOX:
752     case NS_THEME_MENURADIO:
753     case NS_THEME_MENUSEPARATOR:
754     case NS_THEME_MENUARROW:
755     case NS_THEME_MENUIMAGE:
756     case NS_THEME_MENUITEMTEXT:
757       return Some(eUXMenu);
758     case NS_THEME_WINDOW_TITLEBAR:
759     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
760     case NS_THEME_WINDOW_FRAME_LEFT:
761     case NS_THEME_WINDOW_FRAME_RIGHT:
762     case NS_THEME_WINDOW_FRAME_BOTTOM:
763     case NS_THEME_WINDOW_BUTTON_CLOSE:
764     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
765     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
766     case NS_THEME_WINDOW_BUTTON_RESTORE:
767     case NS_THEME_WINDOW_BUTTON_BOX:
768     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
769     case NS_THEME_WIN_GLASS:
770     case NS_THEME_WIN_BORDERLESS_GLASS:
771       return Some(eUXWindowFrame);
772   }
773   return Nothing();
774 }
775 
776 HANDLE
GetTheme(uint8_t aWidgetType)777 nsNativeThemeWin::GetTheme(uint8_t aWidgetType) {
778   mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
779   if (themeClass.isNothing()) {
780     return nullptr;
781   }
782   return nsUXThemeData::GetTheme(themeClass.value());
783 }
784 
StandardGetState(nsIFrame * aFrame,uint8_t aWidgetType,bool wantFocused)785 int32_t nsNativeThemeWin::StandardGetState(nsIFrame* aFrame,
786                                            uint8_t aWidgetType,
787                                            bool wantFocused) {
788   EventStates eventState = GetContentState(aFrame, aWidgetType);
789   if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
790     return TS_ACTIVE;
791   if (eventState.HasState(NS_EVENT_STATE_HOVER)) return TS_HOVER;
792   if (wantFocused && eventState.HasState(NS_EVENT_STATE_FOCUS))
793     return TS_FOCUSED;
794 
795   return TS_NORMAL;
796 }
797 
IsMenuActive(nsIFrame * aFrame,uint8_t aWidgetType)798 bool nsNativeThemeWin::IsMenuActive(nsIFrame* aFrame, uint8_t aWidgetType) {
799   nsIContent* content = aFrame->GetContent();
800   if (content->IsXULElement() &&
801       content->NodeInfo()->Equals(nsGkAtoms::richlistitem))
802     return CheckBooleanAttr(aFrame, nsGkAtoms::selected);
803 
804   return CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
805 }
806 
807 /**
808  * aPart is filled in with the UXTheme part code. On return, values > 0
809  * are the actual UXTheme part code; -1 means the widget will be drawn by
810  * us; 0 means that we should use part code 0, which isn't a real part code
811  * but elicits some kind of default behaviour from UXTheme when drawing
812  * (but isThemeBackgroundPartiallyTransparent may not work).
813  */
GetThemePartAndState(nsIFrame * aFrame,uint8_t aWidgetType,int32_t & aPart,int32_t & aState)814 nsresult nsNativeThemeWin::GetThemePartAndState(nsIFrame* aFrame,
815                                                 uint8_t aWidgetType,
816                                                 int32_t& aPart,
817                                                 int32_t& aState) {
818   switch (aWidgetType) {
819     case NS_THEME_BUTTON: {
820       aPart = BP_BUTTON;
821       if (!aFrame) {
822         aState = TS_NORMAL;
823         return NS_OK;
824       }
825 
826       EventStates eventState = GetContentState(aFrame, aWidgetType);
827       if (IsDisabled(aFrame, eventState)) {
828         aState = TS_DISABLED;
829         return NS_OK;
830       } else if (IsOpenButton(aFrame) || IsCheckedButton(aFrame)) {
831         aState = TS_ACTIVE;
832         return NS_OK;
833       }
834 
835       aState = StandardGetState(aFrame, aWidgetType, true);
836 
837       // Check for default dialog buttons.  These buttons should always look
838       // focused.
839       if (aState == TS_NORMAL && IsDefaultButton(aFrame)) aState = TS_FOCUSED;
840       return NS_OK;
841     }
842     case NS_THEME_CHECKBOX:
843     case NS_THEME_RADIO: {
844       bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
845       aPart = isCheckbox ? BP_CHECKBOX : BP_RADIO;
846 
847       enum InputState { UNCHECKED = 0, CHECKED, INDETERMINATE };
848       InputState inputState = UNCHECKED;
849       bool isXULCheckboxRadio = false;
850 
851       if (!aFrame) {
852         aState = TS_NORMAL;
853       } else {
854         if (GetCheckedOrSelected(aFrame, !isCheckbox)) {
855           inputState = CHECKED;
856         }
857         if (isCheckbox && GetIndeterminate(aFrame)) {
858           inputState = INDETERMINATE;
859         }
860 
861         EventStates eventState = GetContentState(
862             isXULCheckboxRadio ? aFrame->GetParent() : aFrame, aWidgetType);
863         if (IsDisabled(aFrame, eventState)) {
864           aState = TS_DISABLED;
865         } else {
866           aState = StandardGetState(aFrame, aWidgetType, false);
867         }
868       }
869 
870       // 4 unchecked states, 4 checked states, 4 indeterminate states.
871       aState += inputState * 4;
872       return NS_OK;
873     }
874     case NS_THEME_GROUPBOX: {
875       aPart = BP_GROUPBOX;
876       aState = TS_NORMAL;
877       // Since we don't support groupbox disabled and GBS_DISABLED looks the
878       // same as GBS_NORMAL don't bother supporting GBS_DISABLED.
879       return NS_OK;
880     }
881     case NS_THEME_NUMBER_INPUT:
882     case NS_THEME_TEXTFIELD:
883     case NS_THEME_TEXTFIELD_MULTILINE: {
884       EventStates eventState = GetContentState(aFrame, aWidgetType);
885 
886       /* Note: the NOSCROLL type has a rounded corner in each corner.  The more
887        * specific HSCROLL, VSCROLL, HVSCROLL types have side and/or top/bottom
888        * edges rendered as straight horizontal lines with sharp corners to
889        * accommodate a scrollbar.  However, the scrollbar gets rendered on top
890        * of this for us, so we don't care, and can just use NOSCROLL here.
891        */
892       aPart = TFP_EDITBORDER_NOSCROLL;
893 
894       if (!aFrame) {
895         aState = TFS_EDITBORDER_NORMAL;
896       } else if (IsDisabled(aFrame, eventState)) {
897         aState = TFS_EDITBORDER_DISABLED;
898       } else if (IsReadOnly(aFrame)) {
899         /* no special read-only state */
900         aState = TFS_EDITBORDER_NORMAL;
901       } else {
902         nsIContent* content = aFrame->GetContent();
903 
904         /* XUL textboxes don't get focused themselves, because they have child
905          * html:input.. but we can check the XUL focused attributes on them
906          */
907         if (content && content->IsXULElement() && IsFocused(aFrame))
908           aState = TFS_EDITBORDER_FOCUSED;
909         else if (eventState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
910                                                   NS_EVENT_STATE_FOCUS))
911           aState = TFS_EDITBORDER_FOCUSED;
912         else if (eventState.HasState(NS_EVENT_STATE_HOVER))
913           aState = TFS_EDITBORDER_HOVER;
914         else
915           aState = TFS_EDITBORDER_NORMAL;
916       }
917 
918       return NS_OK;
919     }
920     case NS_THEME_FOCUS_OUTLINE: {
921       // XXX the EDITBORDER values don't respect DTBG_OMITCONTENT
922       aPart = TFP_TEXTFIELD;  // TFP_EDITBORDER_NOSCROLL;
923       aState = TS_FOCUSED;    // TFS_EDITBORDER_FOCUSED;
924       return NS_OK;
925     }
926     case NS_THEME_TOOLTIP: {
927       aPart = TTP_STANDARD;
928       aState = TS_NORMAL;
929       return NS_OK;
930     }
931     case NS_THEME_PROGRESSBAR:
932     case NS_THEME_PROGRESSBAR_VERTICAL: {
933       // Note IsVerticalProgress only tests for orient css attrribute,
934       // NS_THEME_PROGRESSBAR_VERTICAL is dedicated to -moz-appearance:
935       // progressbar-vertical.
936       bool vertical = IsVerticalProgress(aFrame) ||
937                       aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL;
938       aPart = vertical ? PP_BARVERT : PP_BAR;
939       aState = PBBS_NORMAL;
940       return NS_OK;
941     }
942     case NS_THEME_PROGRESSCHUNK:
943     case NS_THEME_PROGRESSCHUNK_VERTICAL: {
944       nsIFrame* parentFrame = aFrame->GetParent();
945       if (aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
946           IsVerticalProgress(parentFrame)) {
947         aPart = PP_FILLVERT;
948       } else {
949         aPart = PP_FILL;
950       }
951 
952       aState = PBBVS_NORMAL;
953       return NS_OK;
954     }
955     case NS_THEME_TOOLBARBUTTON: {
956       aPart = BP_BUTTON;
957       if (!aFrame) {
958         aState = TS_NORMAL;
959         return NS_OK;
960       }
961 
962       EventStates eventState = GetContentState(aFrame, aWidgetType);
963       if (IsDisabled(aFrame, eventState)) {
964         aState = TS_DISABLED;
965         return NS_OK;
966       }
967       if (IsOpenButton(aFrame)) {
968         aState = TS_ACTIVE;
969         return NS_OK;
970       }
971 
972       if (eventState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE))
973         aState = TS_ACTIVE;
974       else if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
975         if (IsCheckedButton(aFrame))
976           aState = TB_HOVER_CHECKED;
977         else
978           aState = TS_HOVER;
979       } else {
980         if (IsCheckedButton(aFrame))
981           aState = TB_CHECKED;
982         else
983           aState = TS_NORMAL;
984       }
985 
986       return NS_OK;
987     }
988     case NS_THEME_SEPARATOR: {
989       aPart = TP_SEPARATOR;
990       aState = TS_NORMAL;
991       return NS_OK;
992     }
993     case NS_THEME_SCROLLBARBUTTON_UP:
994     case NS_THEME_SCROLLBARBUTTON_DOWN:
995     case NS_THEME_SCROLLBARBUTTON_LEFT:
996     case NS_THEME_SCROLLBARBUTTON_RIGHT: {
997       aPart = SP_BUTTON;
998       aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) * 4;
999       EventStates eventState = GetContentState(aFrame, aWidgetType);
1000       if (!aFrame)
1001         aState += TS_NORMAL;
1002       else if (IsDisabled(aFrame, eventState))
1003         aState += TS_DISABLED;
1004       else {
1005         nsIFrame* parent = aFrame->GetParent();
1006         EventStates parentState =
1007             GetContentState(parent, parent->StyleDisplay()->mAppearance);
1008         if (eventState.HasAllStates(NS_EVENT_STATE_HOVER |
1009                                     NS_EVENT_STATE_ACTIVE))
1010           aState += TS_ACTIVE;
1011         else if (eventState.HasState(NS_EVENT_STATE_HOVER))
1012           aState += TS_HOVER;
1013         else if (parentState.HasState(NS_EVENT_STATE_HOVER))
1014           aState = (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP) +
1015                    SP_BUTTON_IMPLICIT_HOVER_BASE;
1016         else
1017           aState += TS_NORMAL;
1018       }
1019       return NS_OK;
1020     }
1021     case NS_THEME_SCROLLBAR_HORIZONTAL:
1022     case NS_THEME_SCROLLBAR_VERTICAL: {
1023       aPart = (aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL)
1024                   ? SP_TRACKSTARTHOR
1025                   : SP_TRACKSTARTVERT;
1026       aState = TS_NORMAL;
1027       return NS_OK;
1028     }
1029     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
1030     case NS_THEME_SCROLLBARTHUMB_VERTICAL: {
1031       aPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL)
1032                   ? SP_THUMBHOR
1033                   : SP_THUMBVERT;
1034       EventStates eventState = GetContentState(aFrame, aWidgetType);
1035       if (!aFrame)
1036         aState = TS_NORMAL;
1037       else if (IsDisabled(aFrame, eventState))
1038         aState = TS_DISABLED;
1039       else {
1040         if (eventState.HasState(NS_EVENT_STATE_ACTIVE))  // Hover is not also a
1041                                                          // requirement for the
1042                                                          // thumb, since the
1043                                                          // drag is not canceled
1044                                                          // when you move
1045                                                          // outside the thumb.
1046           aState = TS_ACTIVE;
1047         else if (eventState.HasState(NS_EVENT_STATE_HOVER))
1048           aState = TS_HOVER;
1049         else
1050           aState = TS_NORMAL;
1051       }
1052       return NS_OK;
1053     }
1054     case NS_THEME_RANGE:
1055     case NS_THEME_SCALE_HORIZONTAL:
1056     case NS_THEME_SCALE_VERTICAL: {
1057       if (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
1058           (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) {
1059         aPart = TKP_TRACK;
1060         aState = TRS_NORMAL;
1061       } else {
1062         aPart = TKP_TRACKVERT;
1063         aState = TRVS_NORMAL;
1064       }
1065       return NS_OK;
1066     }
1067     case NS_THEME_RANGE_THUMB:
1068     case NS_THEME_SCALETHUMB_HORIZONTAL:
1069     case NS_THEME_SCALETHUMB_VERTICAL: {
1070       if (aWidgetType == NS_THEME_RANGE_THUMB) {
1071         if (IsRangeHorizontal(aFrame)) {
1072           aPart = TKP_THUMBBOTTOM;
1073         } else {
1074           aPart = IsFrameRTL(aFrame) ? TKP_THUMBLEFT : TKP_THUMBRIGHT;
1075         }
1076       } else {
1077         aPart = (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL) ? TKP_THUMB
1078                                                                 : TKP_THUMBVERT;
1079       }
1080       EventStates eventState = GetContentState(aFrame, aWidgetType);
1081       if (!aFrame)
1082         aState = TS_NORMAL;
1083       else if (IsDisabled(aFrame, eventState)) {
1084         aState = TKP_DISABLED;
1085       } else {
1086         if (eventState.HasState(NS_EVENT_STATE_ACTIVE))  // Hover is not also a
1087                                                          // requirement for the
1088                                                          // thumb, since the
1089                                                          // drag is not canceled
1090                                                          // when you move
1091                                                          // outside the thumb.
1092           aState = TS_ACTIVE;
1093         else if (eventState.HasState(NS_EVENT_STATE_FOCUS))
1094           aState = TKP_FOCUSED;
1095         else if (eventState.HasState(NS_EVENT_STATE_HOVER))
1096           aState = TS_HOVER;
1097         else
1098           aState = TS_NORMAL;
1099       }
1100       return NS_OK;
1101     }
1102     case NS_THEME_INNER_SPIN_BUTTON:
1103     case NS_THEME_SPINNER_UPBUTTON:
1104     case NS_THEME_SPINNER_DOWNBUTTON: {
1105       aPart = (aWidgetType == NS_THEME_SPINNER_UPBUTTON) ? SPNP_UP : SPNP_DOWN;
1106       EventStates eventState = GetContentState(aFrame, aWidgetType);
1107       if (!aFrame)
1108         aState = TS_NORMAL;
1109       else if (IsDisabled(aFrame, eventState))
1110         aState = TS_DISABLED;
1111       else
1112         aState = StandardGetState(aFrame, aWidgetType, false);
1113       return NS_OK;
1114     }
1115     case NS_THEME_TOOLBOX:
1116     case NS_THEME_WIN_MEDIA_TOOLBOX:
1117     case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
1118     case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
1119     case NS_THEME_STATUSBAR:
1120     case NS_THEME_SCROLLBAR:
1121     case NS_THEME_SCROLLBAR_SMALL: {
1122       aState = 0;
1123       aPart = RP_BACKGROUND;
1124       return NS_OK;
1125     }
1126     case NS_THEME_TOOLBAR: {
1127       // Use -1 to indicate we don't wish to have the theme background drawn
1128       // for this item. We will pass any nessessary information via aState,
1129       // and will render the item using separate code.
1130       aPart = -1;
1131       aState = 0;
1132       if (aFrame) {
1133         nsIContent* content = aFrame->GetContent();
1134         nsIContent* parent = content->GetParent();
1135         // XXXzeniko hiding the first toolbar will result in an unwanted margin
1136         if (parent && parent->GetFirstChild() == content) {
1137           aState = 1;
1138         }
1139       }
1140       return NS_OK;
1141     }
1142     case NS_THEME_STATUSBARPANEL:
1143     case NS_THEME_RESIZERPANEL:
1144     case NS_THEME_RESIZER: {
1145       aPart = (aWidgetType - NS_THEME_STATUSBARPANEL) + 1;
1146       aState = TS_NORMAL;
1147       return NS_OK;
1148     }
1149     case NS_THEME_TREEVIEW:
1150     case NS_THEME_LISTBOX: {
1151       aPart = TREEVIEW_BODY;
1152       aState = TS_NORMAL;
1153       return NS_OK;
1154     }
1155     case NS_THEME_TABPANELS: {
1156       aPart = TABP_PANELS;
1157       aState = TS_NORMAL;
1158       return NS_OK;
1159     }
1160     case NS_THEME_TABPANEL: {
1161       aPart = TABP_PANEL;
1162       aState = TS_NORMAL;
1163       return NS_OK;
1164     }
1165     case NS_THEME_TAB: {
1166       aPart = TABP_TAB;
1167       if (!aFrame) {
1168         aState = TS_NORMAL;
1169         return NS_OK;
1170       }
1171 
1172       EventStates eventState = GetContentState(aFrame, aWidgetType);
1173       if (IsDisabled(aFrame, eventState)) {
1174         aState = TS_DISABLED;
1175         return NS_OK;
1176       }
1177 
1178       if (IsSelectedTab(aFrame)) {
1179         aPart = TABP_TAB_SELECTED;
1180         aState = TS_ACTIVE;  // The selected tab is always "pressed".
1181       } else
1182         aState = StandardGetState(aFrame, aWidgetType, true);
1183 
1184       return NS_OK;
1185     }
1186     case NS_THEME_TREEHEADERSORTARROW: {
1187       // XXX Probably will never work due to a bug in the Luna theme.
1188       aPart = 4;
1189       aState = 1;
1190       return NS_OK;
1191     }
1192     case NS_THEME_TREEHEADERCELL: {
1193       aPart = 1;
1194       if (!aFrame) {
1195         aState = TS_NORMAL;
1196         return NS_OK;
1197       }
1198 
1199       aState = StandardGetState(aFrame, aWidgetType, true);
1200 
1201       return NS_OK;
1202     }
1203     case NS_THEME_MENULIST: {
1204       nsIContent* content = aFrame->GetContent();
1205       bool isHTML = content && content->IsHTMLElement();
1206       bool isChrome = aFrame->GetContent()->IsInChromeDocument();
1207       bool useDropBorder = isHTML || (isChrome && IsMenuListEditable(aFrame));
1208       EventStates eventState = GetContentState(aFrame, aWidgetType);
1209 
1210       /* On Vista/Win7, we use CBP_DROPBORDER instead of DROPFRAME for HTML
1211        * content or for editable menulists; this gives us the thin outline,
1212        * instead of the gradient-filled background */
1213       if (useDropBorder)
1214         aPart = CBP_DROPBORDER;
1215       else
1216         aPart = CBP_DROPFRAME;
1217 
1218       if (IsDisabled(aFrame, eventState)) {
1219         aState = TS_DISABLED;
1220       } else if (IsReadOnly(aFrame)) {
1221         aState = TS_NORMAL;
1222       } else if (IsOpenButton(aFrame)) {
1223         aState = TS_ACTIVE;
1224       } else {
1225         if (useDropBorder &&
1226             (eventState.HasState(NS_EVENT_STATE_FOCUS) || IsFocused(aFrame)))
1227           aState = TS_ACTIVE;
1228         else if (eventState.HasAllStates(NS_EVENT_STATE_HOVER |
1229                                          NS_EVENT_STATE_ACTIVE))
1230           aState = TS_ACTIVE;
1231         else if (eventState.HasState(NS_EVENT_STATE_HOVER))
1232           aState = TS_HOVER;
1233         else
1234           aState = TS_NORMAL;
1235       }
1236 
1237       return NS_OK;
1238     }
1239     case NS_THEME_MENULIST_BUTTON: {
1240       bool isHTML = IsHTMLContent(aFrame);
1241       nsIFrame* parentFrame = aFrame->GetParent();
1242       bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1243       bool isOpen = false;
1244 
1245       // HTML select and XUL menulist dropdown buttons get state from the
1246       // parent.
1247       if (isHTML || isMenulist) aFrame = parentFrame;
1248 
1249       EventStates eventState = GetContentState(aFrame, aWidgetType);
1250       aPart = CBP_DROPMARKER_VISTA;
1251 
1252       // For HTML controls with author styling, we should fall
1253       // back to the old dropmarker style to avoid clashes with
1254       // author-specified backgrounds and borders (bug #441034)
1255       if (isHTML &&
1256           IsWidgetStyled(aFrame->PresContext(), aFrame, NS_THEME_MENULIST))
1257         aPart = CBP_DROPMARKER;
1258 
1259       if (IsDisabled(aFrame, eventState)) {
1260         aState = TS_DISABLED;
1261         return NS_OK;
1262       }
1263 
1264       if (isHTML) {
1265         nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame);
1266         isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
1267       } else
1268         isOpen = IsOpenButton(aFrame);
1269 
1270       bool isChrome = aFrame->GetContent()->IsInChromeDocument();
1271       if (isHTML || (isChrome && IsMenuListEditable(aFrame))) {
1272         if (isOpen) {
1273           /* Hover is propagated, but we need to know whether we're hovering
1274            * just the combobox frame, not the dropdown frame. But, we can't get
1275            * that information, since hover is on the content node, and they
1276            * share the same content node.  So, instead, we cheat -- if the
1277            * dropdown is open, we always show the hover state.  This looks fine
1278            * in practice.
1279            */
1280           aState = TS_HOVER;
1281           return NS_OK;
1282         }
1283       } else {
1284         /* The dropdown indicator on a menulist button in chrome is not given a
1285          * hover effect. When the frame isn't isn't HTML content, we cheat and
1286          * force the dropdown state to be normal. (Bug 430434)
1287          */
1288         aState = TS_NORMAL;
1289         return NS_OK;
1290       }
1291 
1292       aState = TS_NORMAL;
1293 
1294       // Dropdown button active state doesn't need :hover.
1295       if (eventState.HasState(NS_EVENT_STATE_ACTIVE)) {
1296         if (isOpen && (isHTML || isMenulist)) {
1297           // XXX Button should look active until the mouse is released, but
1298           //     without making it look active when the popup is clicked.
1299           return NS_OK;
1300         }
1301         aState = TS_ACTIVE;
1302       } else if (eventState.HasState(NS_EVENT_STATE_HOVER)) {
1303         // No hover effect for XUL menulists and autocomplete dropdown buttons
1304         // while the dropdown menu is open.
1305         if (isOpen) {
1306           // XXX HTML select dropdown buttons should have the hover effect when
1307           //     hovering the combobox frame, but not the popup frame.
1308           return NS_OK;
1309         }
1310         aState = TS_HOVER;
1311       }
1312       return NS_OK;
1313     }
1314     case NS_THEME_MENUPOPUP: {
1315       aPart = MENU_POPUPBACKGROUND;
1316       aState = MB_ACTIVE;
1317       return NS_OK;
1318     }
1319     case NS_THEME_MENUITEM:
1320     case NS_THEME_CHECKMENUITEM:
1321     case NS_THEME_RADIOMENUITEM: {
1322       bool isTopLevel = false;
1323       bool isOpen = false;
1324       bool isHover = false;
1325       nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
1326       EventStates eventState = GetContentState(aFrame, aWidgetType);
1327 
1328       isTopLevel = IsTopLevelMenu(aFrame);
1329 
1330       if (menuFrame) isOpen = menuFrame->IsOpen();
1331 
1332       isHover = IsMenuActive(aFrame, aWidgetType);
1333 
1334       if (isTopLevel) {
1335         aPart = MENU_BARITEM;
1336 
1337         if (isOpen)
1338           aState = MBI_PUSHED;
1339         else if (isHover)
1340           aState = MBI_HOT;
1341         else
1342           aState = MBI_NORMAL;
1343 
1344         // the disabled states are offset by 3
1345         if (IsDisabled(aFrame, eventState)) aState += 3;
1346       } else {
1347         aPart = MENU_POPUPITEM;
1348 
1349         if (isHover)
1350           aState = MPI_HOT;
1351         else
1352           aState = MPI_NORMAL;
1353 
1354         // the disabled states are offset by 2
1355         if (IsDisabled(aFrame, eventState)) aState += 2;
1356       }
1357 
1358       return NS_OK;
1359     }
1360     case NS_THEME_MENUSEPARATOR:
1361       aPart = MENU_POPUPSEPARATOR;
1362       aState = 0;
1363       return NS_OK;
1364     case NS_THEME_MENUARROW: {
1365       aPart = MENU_POPUPSUBMENU;
1366       EventStates eventState = GetContentState(aFrame, aWidgetType);
1367       aState = IsDisabled(aFrame, eventState) ? MSM_DISABLED : MSM_NORMAL;
1368       return NS_OK;
1369     }
1370     case NS_THEME_MENUCHECKBOX:
1371     case NS_THEME_MENURADIO: {
1372       EventStates eventState = GetContentState(aFrame, aWidgetType);
1373 
1374       aPart = MENU_POPUPCHECK;
1375       aState = MC_CHECKMARKNORMAL;
1376 
1377       // Radio states are offset by 2
1378       if (aWidgetType == NS_THEME_MENURADIO) aState += 2;
1379 
1380       // the disabled states are offset by 1
1381       if (IsDisabled(aFrame, eventState)) aState += 1;
1382 
1383       return NS_OK;
1384     }
1385     case NS_THEME_MENUITEMTEXT:
1386     case NS_THEME_MENUIMAGE:
1387       aPart = -1;
1388       aState = 0;
1389       return NS_OK;
1390 
1391     case NS_THEME_WINDOW_TITLEBAR:
1392       aPart = mozilla::widget::themeconst::WP_CAPTION;
1393       aState = GetTopLevelWindowActiveState(aFrame);
1394       return NS_OK;
1395     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
1396       aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
1397       aState = GetTopLevelWindowActiveState(aFrame);
1398       return NS_OK;
1399     case NS_THEME_WINDOW_FRAME_LEFT:
1400       aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
1401       aState = GetTopLevelWindowActiveState(aFrame);
1402       return NS_OK;
1403     case NS_THEME_WINDOW_FRAME_RIGHT:
1404       aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
1405       aState = GetTopLevelWindowActiveState(aFrame);
1406       return NS_OK;
1407     case NS_THEME_WINDOW_FRAME_BOTTOM:
1408       aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
1409       aState = GetTopLevelWindowActiveState(aFrame);
1410       return NS_OK;
1411     case NS_THEME_WINDOW_BUTTON_CLOSE:
1412       aPart = mozilla::widget::themeconst::WP_CLOSEBUTTON;
1413       aState = GetWindowFrameButtonState(aFrame,
1414                                          GetContentState(aFrame, aWidgetType));
1415       return NS_OK;
1416     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1417       aPart = mozilla::widget::themeconst::WP_MINBUTTON;
1418       aState = GetWindowFrameButtonState(aFrame,
1419                                          GetContentState(aFrame, aWidgetType));
1420       return NS_OK;
1421     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1422       aPart = mozilla::widget::themeconst::WP_MAXBUTTON;
1423       aState = GetWindowFrameButtonState(aFrame,
1424                                          GetContentState(aFrame, aWidgetType));
1425       return NS_OK;
1426     case NS_THEME_WINDOW_BUTTON_RESTORE:
1427       aPart = mozilla::widget::themeconst::WP_RESTOREBUTTON;
1428       aState = GetWindowFrameButtonState(aFrame,
1429                                          GetContentState(aFrame, aWidgetType));
1430       return NS_OK;
1431     case NS_THEME_WINDOW_BUTTON_BOX:
1432     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
1433     case NS_THEME_WIN_GLASS:
1434     case NS_THEME_WIN_BORDERLESS_GLASS:
1435       aPart = -1;
1436       aState = 0;
1437       return NS_OK;
1438   }
1439 
1440   aPart = 0;
1441   aState = 0;
1442   return NS_ERROR_FAILURE;
1443 }
1444 
AssumeThemePartAndStateAreTransparent(int32_t aPart,int32_t aState)1445 static bool AssumeThemePartAndStateAreTransparent(int32_t aPart,
1446                                                   int32_t aState) {
1447   if (!(IsWin8Point1OrLater() && nsUXThemeData::IsHighContrastOn()) &&
1448       aPart == MENU_POPUPITEM && aState == MBI_NORMAL) {
1449     return true;
1450   }
1451   return false;
1452 }
1453 
1454 // When running with per-monitor DPI (on Win8.1+), and rendering on a display
1455 // with a different DPI setting from the system's default scaling, we need to
1456 // apply scaling to native-themed elements as the Windows theme APIs assume
1457 // the system default resolution.
GetThemeDpiScaleFactor(nsIFrame * aFrame)1458 static inline double GetThemeDpiScaleFactor(nsIFrame* aFrame) {
1459   if (WinUtils::IsPerMonitorDPIAware() ||
1460       nsIWidget::DefaultScaleOverride() > 0.0) {
1461     nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
1462     if (rootWidget) {
1463       double systemScale = WinUtils::SystemScaleFactor();
1464       return rootWidget->GetDefaultScale().scale / systemScale;
1465     }
1466   }
1467   return 1.0;
1468 }
1469 
1470 NS_IMETHODIMP
DrawWidgetBackground(gfxContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,const nsRect & aRect,const nsRect & aDirtyRect)1471 nsNativeThemeWin::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1472                                        uint8_t aWidgetType, const nsRect& aRect,
1473                                        const nsRect& aDirtyRect) {
1474   HANDLE theme = GetTheme(aWidgetType);
1475   if (!theme)
1476     return ClassicDrawWidgetBackground(aContext, aFrame, aWidgetType, aRect,
1477                                        aDirtyRect);
1478 
1479   // ^^ without the right sdk, assume xp theming and fall through.
1480   if (nsUXThemeData::CheckForCompositor()) {
1481     switch (aWidgetType) {
1482       case NS_THEME_WINDOW_TITLEBAR:
1483       case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
1484       case NS_THEME_WINDOW_FRAME_LEFT:
1485       case NS_THEME_WINDOW_FRAME_RIGHT:
1486       case NS_THEME_WINDOW_FRAME_BOTTOM:
1487         // Nothing to draw, these areas are glass. Minimum dimensions
1488         // should be set, so xul content should be layed out correctly.
1489         return NS_OK;
1490         break;
1491       case NS_THEME_WINDOW_BUTTON_CLOSE:
1492       case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1493       case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1494       case NS_THEME_WINDOW_BUTTON_RESTORE:
1495         // Not conventional bitmaps, can't be retrieved. If we fall
1496         // through here and call the theme library we'll get aero
1497         // basic bitmaps.
1498         return NS_OK;
1499         break;
1500       case NS_THEME_WIN_GLASS:
1501       case NS_THEME_WIN_BORDERLESS_GLASS:
1502         // Nothing to draw, this is the glass background.
1503         return NS_OK;
1504       case NS_THEME_WINDOW_BUTTON_BOX:
1505       case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
1506         // We handle these through nsIWidget::UpdateThemeGeometries
1507         return NS_OK;
1508         break;
1509     }
1510   }
1511 
1512   int32_t part, state;
1513   nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
1514   if (NS_FAILED(rv)) return rv;
1515 
1516   if (AssumeThemePartAndStateAreTransparent(part, state)) {
1517     return NS_OK;
1518   }
1519 
1520   RefPtr<gfxContext> ctx = aContext;
1521   gfxContextMatrixAutoSaveRestore save(ctx);
1522 
1523   double themeScale = GetThemeDpiScaleFactor(aFrame);
1524   if (themeScale != 1.0) {
1525     ctx->SetMatrix(ctx->CurrentMatrix().PreScale(themeScale, themeScale));
1526   }
1527 
1528   gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
1529   RECT widgetRect;
1530   RECT clipRect;
1531   gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
1532       dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
1533          aDirtyRect.Height());
1534 
1535   tr.Scale(1.0 / (p2a * themeScale));
1536   dr.Scale(1.0 / (p2a * themeScale));
1537 
1538   gfxWindowsNativeDrawing nativeDrawing(
1539       ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
1540 
1541 RENDER_AGAIN:
1542 
1543   HDC hdc = nativeDrawing.BeginNativeDrawing();
1544   if (!hdc) return NS_ERROR_FAILURE;
1545 
1546   nativeDrawing.TransformToNativeRect(tr, widgetRect);
1547   nativeDrawing.TransformToNativeRect(dr, clipRect);
1548 
1549 #if 0
1550   {
1551     MOZ_LOG(gWindowsLog, LogLevel::Error,
1552            (stderr, "xform: %f %f %f %f [%f %f]\n", m._11, m._21, m._12, m._22,
1553             m._31, m._32));
1554     MOZ_LOG(gWindowsLog, LogLevel::Error,
1555            (stderr, "tr: [%d %d %d %d]\ndr: [%d %d %d %d]\noff: [%f %f]\n",
1556             tr.x, tr.y, tr.width, tr.height, dr.x, dr.y, dr.width, dr.height,
1557             offset.x, offset.y));
1558   }
1559 #endif
1560 
1561   if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
1562     // Clip out the left and right corners of the frame, all we want in
1563     // is the middle section.
1564     widgetRect.left -= GetSystemMetrics(SM_CXFRAME);
1565     widgetRect.right += GetSystemMetrics(SM_CXFRAME);
1566   } else if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
1567     // The origin of the window is off screen when maximized and windows
1568     // doesn't compensate for this in rendering the background. Push the
1569     // top of the bitmap down by SM_CYFRAME so we get the full graphic.
1570     widgetRect.top += GetSystemMetrics(SM_CYFRAME);
1571   } else if (aWidgetType == NS_THEME_TAB) {
1572     // For left edge and right edge tabs, we need to adjust the widget
1573     // rects and clip rects so that the edges don't get drawn.
1574     bool isLeft = IsLeftToSelectedTab(aFrame);
1575     bool isRight = !isLeft && IsRightToSelectedTab(aFrame);
1576 
1577     if (isLeft || isRight) {
1578       // HACK ALERT: There appears to be no way to really obtain this value, so
1579       // we're forced to just use the default value for Luna (which also happens
1580       // to be correct for all the other skins I've tried).
1581       int32_t edgeSize = 2;
1582 
1583       // Armed with the size of the edge, we now need to either shift to the
1584       // left or to the right.  The clip rect won't include this extra area, so
1585       // we know that we're effectively shifting the edge out of view (such that
1586       // it won't be painted).
1587       if (isLeft)
1588         // The right edge should not be drawn.  Extend our rect by the edge
1589         // size.
1590         widgetRect.right += edgeSize;
1591       else
1592         // The left edge should not be drawn.  Move the widget rect's left coord
1593         // back.
1594         widgetRect.left -= edgeSize;
1595     }
1596   } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
1597     OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
1598   } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
1599              aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
1600     OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
1601   } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
1602     OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
1603   }
1604 
1605   // widgetRect is the bounding box for a widget, yet the scale track is only
1606   // a small portion of this size, so the edges of the scale need to be
1607   // adjusted to the real size of the track.
1608   if (aWidgetType == NS_THEME_RANGE ||
1609       aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
1610       aWidgetType == NS_THEME_SCALE_VERTICAL) {
1611     RECT contentRect;
1612     GetThemeBackgroundContentRect(theme, hdc, part, state, &widgetRect,
1613                                   &contentRect);
1614 
1615     SIZE siz;
1616     GetThemePartSize(theme, hdc, part, state, &widgetRect, TS_TRUE, &siz);
1617 
1618     // When rounding is necessary, we round the position of the track
1619     // away from the chevron of the thumb to make it look better.
1620     if (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
1621         (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) {
1622       contentRect.top += (contentRect.bottom - contentRect.top - siz.cy) / 2;
1623       contentRect.bottom = contentRect.top + siz.cy;
1624     } else {
1625       if (!IsFrameRTL(aFrame)) {
1626         contentRect.left += (contentRect.right - contentRect.left - siz.cx) / 2;
1627         contentRect.right = contentRect.left + siz.cx;
1628       } else {
1629         contentRect.right -=
1630             (contentRect.right - contentRect.left - siz.cx) / 2;
1631         contentRect.left = contentRect.right - siz.cx;
1632       }
1633     }
1634 
1635     DrawThemeBackground(theme, hdc, part, state, &contentRect, &clipRect);
1636   } else if (aWidgetType == NS_THEME_MENUCHECKBOX ||
1637              aWidgetType == NS_THEME_MENURADIO) {
1638     bool isChecked = false;
1639     isChecked = CheckBooleanAttr(aFrame, nsGkAtoms::checked);
1640 
1641     if (isChecked) {
1642       int bgState = MCB_NORMAL;
1643       EventStates eventState = GetContentState(aFrame, aWidgetType);
1644 
1645       // the disabled states are offset by 1
1646       if (IsDisabled(aFrame, eventState)) bgState += 1;
1647 
1648       SIZE checkboxBGSize(GetCheckboxBGSize(theme, hdc));
1649 
1650       RECT checkBGRect = widgetRect;
1651       if (IsFrameRTL(aFrame)) {
1652         checkBGRect.left = checkBGRect.right - checkboxBGSize.cx;
1653       } else {
1654         checkBGRect.right = checkBGRect.left + checkboxBGSize.cx;
1655       }
1656 
1657       // Center the checkbox background vertically in the menuitem
1658       checkBGRect.top +=
1659           (checkBGRect.bottom - checkBGRect.top) / 2 - checkboxBGSize.cy / 2;
1660       checkBGRect.bottom = checkBGRect.top + checkboxBGSize.cy;
1661 
1662       DrawThemeBackground(theme, hdc, MENU_POPUPCHECKBACKGROUND, bgState,
1663                           &checkBGRect, &clipRect);
1664 
1665       MARGINS checkMargins = GetCheckboxMargins(theme, hdc);
1666       RECT checkRect = checkBGRect;
1667       checkRect.left += checkMargins.cxLeftWidth;
1668       checkRect.right -= checkMargins.cxRightWidth;
1669       checkRect.top += checkMargins.cyTopHeight;
1670       checkRect.bottom -= checkMargins.cyBottomHeight;
1671       DrawThemeBackground(theme, hdc, MENU_POPUPCHECK, state, &checkRect,
1672                           &clipRect);
1673     }
1674   } else if (aWidgetType == NS_THEME_MENUPOPUP) {
1675     DrawThemeBackground(theme, hdc, MENU_POPUPBORDERS, /* state */ 0,
1676                         &widgetRect, &clipRect);
1677     SIZE borderSize;
1678     GetThemePartSize(theme, hdc, MENU_POPUPBORDERS, 0, nullptr, TS_TRUE,
1679                      &borderSize);
1680 
1681     RECT bgRect = widgetRect;
1682     bgRect.top += borderSize.cy;
1683     bgRect.bottom -= borderSize.cy;
1684     bgRect.left += borderSize.cx;
1685     bgRect.right -= borderSize.cx;
1686 
1687     DrawThemeBackground(theme, hdc, MENU_POPUPBACKGROUND, /* state */ 0,
1688                         &bgRect, &clipRect);
1689 
1690     SIZE gutterSize(GetGutterSize(theme, hdc));
1691 
1692     RECT gutterRect;
1693     gutterRect.top = bgRect.top;
1694     gutterRect.bottom = bgRect.bottom;
1695     if (IsFrameRTL(aFrame)) {
1696       gutterRect.right = bgRect.right;
1697       gutterRect.left = gutterRect.right - gutterSize.cx;
1698     } else {
1699       gutterRect.left = bgRect.left;
1700       gutterRect.right = gutterRect.left + gutterSize.cx;
1701     }
1702 
1703     DrawThemeBGRTLAware(theme, hdc, MENU_POPUPGUTTER, /* state */ 0,
1704                         &gutterRect, &clipRect, IsFrameRTL(aFrame));
1705   } else if (aWidgetType == NS_THEME_MENUSEPARATOR) {
1706     SIZE gutterSize(GetGutterSize(theme, hdc));
1707 
1708     RECT sepRect = widgetRect;
1709     if (IsFrameRTL(aFrame))
1710       sepRect.right -= gutterSize.cx;
1711     else
1712       sepRect.left += gutterSize.cx;
1713 
1714     DrawThemeBackground(theme, hdc, MENU_POPUPSEPARATOR, /* state */ 0,
1715                         &sepRect, &clipRect);
1716   } else if (aWidgetType == NS_THEME_MENUARROW) {
1717     // We're dpi aware and as such on systems that have dpi > 96 set, the
1718     // theme library expects us to do proper positioning and scaling of glyphs.
1719     // For NS_THEME_MENUARROW, layout may hand us a widget rect larger than the
1720     // glyph rect we request in GetMinimumWidgetSize. To prevent distortion we
1721     // have to position and scale what we draw.
1722 
1723     SIZE glyphSize;
1724     GetThemePartSize(theme, hdc, part, state, nullptr, TS_TRUE, &glyphSize);
1725 
1726     int32_t widgetHeight = widgetRect.bottom - widgetRect.top;
1727 
1728     RECT renderRect = widgetRect;
1729 
1730     // We request (glyph width * 2, glyph height) in GetMinimumWidgetSize. In
1731     // Firefox some menu items provide the full height of the item to us, in
1732     // others our widget rect is the exact dims of our arrow glyph. Adjust the
1733     // vertical position by the added space, if any exists.
1734     renderRect.top += ((widgetHeight - glyphSize.cy) / 2);
1735     renderRect.bottom = renderRect.top + glyphSize.cy;
1736     // I'm using the width of the arrow glyph for the arrow-side padding.
1737     // AFAICT there doesn't appear to be a theme constant we can query
1738     // for this value. Generally this looks correct, and has the added
1739     // benefit of being a dpi adjusted value.
1740     if (!IsFrameRTL(aFrame)) {
1741       renderRect.right = widgetRect.right - glyphSize.cx;
1742       renderRect.left = renderRect.right - glyphSize.cx;
1743     } else {
1744       renderRect.left = glyphSize.cx;
1745       renderRect.right = renderRect.left + glyphSize.cx;
1746     }
1747     DrawThemeBGRTLAware(theme, hdc, part, state, &renderRect, &clipRect,
1748                         IsFrameRTL(aFrame));
1749   }
1750   // The following widgets need to be RTL-aware
1751   else if (aWidgetType == NS_THEME_RESIZER ||
1752            aWidgetType == NS_THEME_MENULIST_BUTTON) {
1753     DrawThemeBGRTLAware(theme, hdc, part, state, &widgetRect, &clipRect,
1754                         IsFrameRTL(aFrame));
1755   } else if (aWidgetType == NS_THEME_NUMBER_INPUT ||
1756              aWidgetType == NS_THEME_TEXTFIELD ||
1757              aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
1758     DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1759     if (state == TFS_EDITBORDER_DISABLED) {
1760       InflateRect(&widgetRect, -1, -1);
1761       ::FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(COLOR_BTNFACE + 1));
1762     }
1763   } else if (aWidgetType == NS_THEME_PROGRESSBAR ||
1764              aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL) {
1765     // DrawThemeBackground renders each corner with a solid white pixel.
1766     // Restore these pixels to the underlying color. Tracks are rendered
1767     // using alpha recovery, so this makes the corners transparent.
1768     COLORREF color;
1769     color = GetPixel(hdc, widgetRect.left, widgetRect.top);
1770     DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1771     SetPixel(hdc, widgetRect.left, widgetRect.top, color);
1772     SetPixel(hdc, widgetRect.right - 1, widgetRect.top, color);
1773     SetPixel(hdc, widgetRect.right - 1, widgetRect.bottom - 1, color);
1774     SetPixel(hdc, widgetRect.left, widgetRect.bottom - 1, color);
1775   } else if (aWidgetType == NS_THEME_PROGRESSCHUNK ||
1776              aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL) {
1777     DrawThemedProgressMeter(aFrame, aWidgetType, theme, hdc, part, state,
1778                             &widgetRect, &clipRect);
1779   } else if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
1780     // Inflate 'widgetRect' with the focus outline size.
1781     nsIntMargin border;
1782     if (NS_SUCCEEDED(GetWidgetBorder(aFrame->PresContext()->DeviceContext(),
1783                                      aFrame, aWidgetType, &border))) {
1784       widgetRect.left -= border.left;
1785       widgetRect.right += border.right;
1786       widgetRect.top -= border.top;
1787       widgetRect.bottom += border.bottom;
1788     }
1789 
1790     DTBGOPTS opts = {sizeof(DTBGOPTS), DTBG_OMITCONTENT | DTBG_CLIPRECT,
1791                      clipRect};
1792     DrawThemeBackgroundEx(theme, hdc, part, state, &widgetRect, &opts);
1793   }
1794   // If part is negative, the element wishes us to not render a themed
1795   // background, instead opting to be drawn specially below.
1796   else if (part >= 0) {
1797     DrawThemeBackground(theme, hdc, part, state, &widgetRect, &clipRect);
1798   }
1799 
1800   // Draw focus rectangles for range and scale elements
1801   // XXX it'd be nice to draw these outside of the frame
1802   if (aWidgetType == NS_THEME_RANGE ||
1803       aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
1804       aWidgetType == NS_THEME_SCALE_VERTICAL) {
1805     EventStates contentState = GetContentState(aFrame, aWidgetType);
1806 
1807     if (contentState.HasState(NS_EVENT_STATE_FOCUS)) {
1808       POINT vpOrg;
1809       HPEN hPen = nullptr;
1810 
1811       uint8_t id = SaveDC(hdc);
1812 
1813       ::SelectClipRgn(hdc, nullptr);
1814       ::GetViewportOrgEx(hdc, &vpOrg);
1815       ::SetBrushOrgEx(hdc, vpOrg.x + widgetRect.left, vpOrg.y + widgetRect.top,
1816                       nullptr);
1817       ::SetTextColor(hdc, 0);
1818       ::DrawFocusRect(hdc, &widgetRect);
1819       ::RestoreDC(hdc, id);
1820       if (hPen) {
1821         ::DeleteObject(hPen);
1822       }
1823     }
1824   } else if (aWidgetType == NS_THEME_TOOLBAR && state == 0) {
1825     // Draw toolbar separator lines above all toolbars except the first one.
1826     // The lines are part of the Rebar theme, which is loaded for
1827     // NS_THEME_TOOLBOX.
1828     theme = GetTheme(NS_THEME_TOOLBOX);
1829     if (!theme) return NS_ERROR_FAILURE;
1830 
1831     widgetRect.bottom = widgetRect.top + TB_SEPARATOR_HEIGHT;
1832     DrawThemeEdge(theme, hdc, RP_BAND, 0, &widgetRect, EDGE_ETCHED, BF_TOP,
1833                   nullptr);
1834   } else if (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL ||
1835              aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL) {
1836     // Draw the decorative gripper for the scrollbar thumb button, if it fits
1837 
1838     SIZE gripSize;
1839     MARGINS thumbMgns;
1840     int gripPart = (aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL)
1841                        ? SP_GRIPPERHOR
1842                        : SP_GRIPPERVERT;
1843 
1844     if (GetThemePartSize(theme, hdc, gripPart, state, nullptr, TS_TRUE,
1845                          &gripSize) == S_OK &&
1846         GetThemeMargins(theme, hdc, part, state, TMT_CONTENTMARGINS, nullptr,
1847                         &thumbMgns) == S_OK &&
1848         gripSize.cx + thumbMgns.cxLeftWidth + thumbMgns.cxRightWidth <=
1849             widgetRect.right - widgetRect.left &&
1850         gripSize.cy + thumbMgns.cyTopHeight + thumbMgns.cyBottomHeight <=
1851             widgetRect.bottom - widgetRect.top) {
1852       DrawThemeBackground(theme, hdc, gripPart, state, &widgetRect, &clipRect);
1853     }
1854   }
1855 
1856   nativeDrawing.EndNativeDrawing();
1857 
1858   if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
1859 
1860   nativeDrawing.PaintToContext();
1861 
1862   return NS_OK;
1863 }
1864 
ScaleForFrameDPI(nsIntMargin * aMargin,nsIFrame * aFrame)1865 static void ScaleForFrameDPI(nsIntMargin* aMargin, nsIFrame* aFrame) {
1866   double themeScale = GetThemeDpiScaleFactor(aFrame);
1867   if (themeScale != 1.0) {
1868     aMargin->top = NSToIntRound(aMargin->top * themeScale);
1869     aMargin->left = NSToIntRound(aMargin->left * themeScale);
1870     aMargin->bottom = NSToIntRound(aMargin->bottom * themeScale);
1871     aMargin->right = NSToIntRound(aMargin->right * themeScale);
1872   }
1873 }
1874 
ScaleForFrameDPI(LayoutDeviceIntSize * aSize,nsIFrame * aFrame)1875 static void ScaleForFrameDPI(LayoutDeviceIntSize* aSize, nsIFrame* aFrame) {
1876   double themeScale = GetThemeDpiScaleFactor(aFrame);
1877   if (themeScale != 1.0) {
1878     aSize->width = NSToIntRound(aSize->width * themeScale);
1879     aSize->height = NSToIntRound(aSize->height * themeScale);
1880   }
1881 }
1882 
1883 NS_IMETHODIMP
GetWidgetBorder(nsDeviceContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,nsIntMargin * aResult)1884 nsNativeThemeWin::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
1885                                   uint8_t aWidgetType, nsIntMargin* aResult) {
1886   mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
1887   HTHEME theme = NULL;
1888   if (!themeClass.isNothing()) {
1889     theme = nsUXThemeData::GetTheme(themeClass.value());
1890   }
1891   nsresult rv = NS_OK;
1892   if (!theme) {
1893     rv = ClassicGetWidgetBorder(aContext, aFrame, aWidgetType, aResult);
1894     ScaleForFrameDPI(aResult, aFrame);
1895     return rv;
1896   }
1897 
1898   aResult->top = aResult->bottom = aResult->left = aResult->right = 0;
1899 
1900   if (!WidgetIsContainer(aWidgetType) || aWidgetType == NS_THEME_TOOLBOX ||
1901       aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
1902       aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
1903       aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
1904       aWidgetType == NS_THEME_STATUSBAR || aWidgetType == NS_THEME_RESIZER ||
1905       aWidgetType == NS_THEME_TABPANEL ||
1906       aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL ||
1907       aWidgetType == NS_THEME_SCROLLBAR_VERTICAL ||
1908       aWidgetType == NS_THEME_MENUITEM ||
1909       aWidgetType == NS_THEME_CHECKMENUITEM ||
1910       aWidgetType == NS_THEME_RADIOMENUITEM ||
1911       aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_MENUIMAGE ||
1912       aWidgetType == NS_THEME_MENUITEMTEXT ||
1913       aWidgetType == NS_THEME_SEPARATOR ||
1914       aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
1915       aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
1916       aWidgetType == NS_THEME_WIN_GLASS ||
1917       aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS)
1918     return NS_OK;  // Don't worry about it.
1919 
1920   int32_t part, state;
1921   rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
1922   if (NS_FAILED(rv)) return rv;
1923 
1924   if (aWidgetType == NS_THEME_TOOLBAR) {
1925     // make space for the separator line above all toolbars but the first
1926     if (state == 0) aResult->top = TB_SEPARATOR_HEIGHT;
1927     return NS_OK;
1928   }
1929 
1930   rv = GetCachedWidgetBorder(aFrame, theme, themeClass.value(), aWidgetType,
1931                              part, state, aResult);
1932   NS_ENSURE_SUCCESS(rv, rv);
1933 
1934   // Remove the edges for tabs that are before or after the selected tab,
1935   if (aWidgetType == NS_THEME_TAB) {
1936     if (IsLeftToSelectedTab(aFrame))
1937       // Remove the right edge, since we won't be drawing it.
1938       aResult->right = 0;
1939     else if (IsRightToSelectedTab(aFrame))
1940       // Remove the left edge, since we won't be drawing it.
1941       aResult->left = 0;
1942   }
1943 
1944   if (aFrame && (aWidgetType == NS_THEME_NUMBER_INPUT ||
1945                  aWidgetType == NS_THEME_TEXTFIELD ||
1946                  aWidgetType == NS_THEME_TEXTFIELD_MULTILINE)) {
1947     nsIContent* content = aFrame->GetContent();
1948     if (content && content->IsHTMLElement()) {
1949       // We need to pad textfields by 1 pixel, since the caret will draw
1950       // flush against the edge by default if we don't.
1951       aResult->top++;
1952       aResult->left++;
1953       aResult->bottom++;
1954       aResult->right++;
1955     }
1956   }
1957 
1958   ScaleForFrameDPI(aResult, aFrame);
1959   return rv;
1960 }
1961 
GetWidgetPadding(nsDeviceContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,nsIntMargin * aResult)1962 bool nsNativeThemeWin::GetWidgetPadding(nsDeviceContext* aContext,
1963                                         nsIFrame* aFrame, uint8_t aWidgetType,
1964                                         nsIntMargin* aResult) {
1965   switch (aWidgetType) {
1966     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1967     // and have a meaningful baseline, so they can't have
1968     // author-specified padding.
1969     case NS_THEME_CHECKBOX:
1970     case NS_THEME_RADIO:
1971       aResult->SizeTo(0, 0, 0, 0);
1972       return true;
1973   }
1974 
1975   bool ok = true;
1976 
1977   if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX ||
1978       aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
1979     aResult->SizeTo(0, 0, 0, 0);
1980 
1981     // aero glass doesn't display custom buttons
1982     if (nsUXThemeData::CheckForCompositor()) return true;
1983 
1984     // button padding for standard windows
1985     if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX) {
1986       aResult->top = GetSystemMetrics(SM_CXFRAME);
1987     }
1988     ScaleForFrameDPI(aResult, aFrame);
1989     return ok;
1990   }
1991 
1992   // Content padding
1993   if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
1994       aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED) {
1995     aResult->SizeTo(0, 0, 0, 0);
1996     // XXX Maximized windows have an offscreen offset equal to
1997     // the border padding. This should be addressed in nsWindow,
1998     // but currently can't be, see UpdateNonClientMargins.
1999     if (aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED)
2000       aResult->top =
2001           GetSystemMetrics(SM_CXFRAME) + GetSystemMetrics(SM_CXPADDEDBORDER);
2002     return ok;
2003   }
2004 
2005   HANDLE theme = GetTheme(aWidgetType);
2006   if (!theme) {
2007     ok = ClassicGetWidgetPadding(aContext, aFrame, aWidgetType, aResult);
2008     ScaleForFrameDPI(aResult, aFrame);
2009     return ok;
2010   }
2011 
2012   if (aWidgetType == NS_THEME_MENUPOPUP) {
2013     SIZE popupSize;
2014     GetThemePartSize(theme, nullptr, MENU_POPUPBORDERS, /* state */ 0, nullptr,
2015                      TS_TRUE, &popupSize);
2016     aResult->top = aResult->bottom = popupSize.cy;
2017     aResult->left = aResult->right = popupSize.cx;
2018     ScaleForFrameDPI(aResult, aFrame);
2019     return ok;
2020   }
2021 
2022   if (aWidgetType == NS_THEME_NUMBER_INPUT ||
2023       aWidgetType == NS_THEME_TEXTFIELD ||
2024       aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
2025       aWidgetType == NS_THEME_MENULIST) {
2026     // If we have author-specified padding for these elements, don't do the
2027     // fixups below.
2028     if (aFrame->PresContext()->HasAuthorSpecifiedRules(
2029             aFrame, NS_AUTHOR_SPECIFIED_PADDING))
2030       return false;
2031   }
2032 
2033   /* textfields need extra pixels on all sides, otherwise they wrap their
2034    * content too tightly.  The actual border is drawn 1px inside the specified
2035    * rectangle, so Gecko will end up making the contents look too small.
2036    * Instead, we add 2px padding for the contents and fix this. (Used to be 1px
2037    * added, see bug 430212)
2038    */
2039   if (aWidgetType == NS_THEME_NUMBER_INPUT ||
2040       aWidgetType == NS_THEME_TEXTFIELD ||
2041       aWidgetType == NS_THEME_TEXTFIELD_MULTILINE) {
2042     aResult->top = aResult->bottom = 2;
2043     aResult->left = aResult->right = 2;
2044     ScaleForFrameDPI(aResult, aFrame);
2045     return ok;
2046   } else if (IsHTMLContent(aFrame) && aWidgetType == NS_THEME_MENULIST) {
2047     /* For content menulist controls, we need an extra pixel so that we have
2048      * room to draw our focus rectangle stuff. Otherwise, the focus rect might
2049      * overlap the control's border.
2050      */
2051     aResult->top = aResult->bottom = 1;
2052     aResult->left = aResult->right = 1;
2053     ScaleForFrameDPI(aResult, aFrame);
2054     return ok;
2055   }
2056 
2057   int32_t right, left, top, bottom;
2058   right = left = top = bottom = 0;
2059   switch (aWidgetType) {
2060     case NS_THEME_MENUIMAGE:
2061       right = 8;
2062       left = 3;
2063       break;
2064     case NS_THEME_MENUCHECKBOX:
2065     case NS_THEME_MENURADIO:
2066       right = 8;
2067       left = 0;
2068       break;
2069     case NS_THEME_MENUITEMTEXT:
2070       // There seem to be exactly 4 pixels from the edge
2071       // of the gutter to the text: 2px margin (CSS) + 2px padding (here)
2072       {
2073         SIZE size(GetGutterSize(theme, nullptr));
2074         left = size.cx + 2;
2075       }
2076       break;
2077     case NS_THEME_MENUSEPARATOR: {
2078       SIZE size(GetGutterSize(theme, nullptr));
2079       left = size.cx + 5;
2080       top = 10;
2081       bottom = 7;
2082     } break;
2083     default:
2084       return false;
2085   }
2086 
2087   if (IsFrameRTL(aFrame)) {
2088     aResult->right = left;
2089     aResult->left = right;
2090   } else {
2091     aResult->right = right;
2092     aResult->left = left;
2093   }
2094 
2095   ScaleForFrameDPI(aResult, aFrame);
2096   return ok;
2097 }
2098 
GetWidgetOverflow(nsDeviceContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,nsRect * aOverflowRect)2099 bool nsNativeThemeWin::GetWidgetOverflow(nsDeviceContext* aContext,
2100                                          nsIFrame* aFrame, uint8_t aWidgetType,
2101                                          nsRect* aOverflowRect) {
2102 /* This is disabled for now, because it causes invalidation problems --
2103  * see bug 420381.  The effect of not updating the overflow area is that
2104  * for dropdown buttons in content areas, there is a 1px border on 3 sides
2105  * where, if invalidated, the dropdown control probably won't be repainted.
2106  * This is fairly minor, as by default there is nothing in that area, and
2107  * a border only shows up if the widget is being hovered.
2108  */
2109 #if 0
2110   /* We explicitly draw dropdown buttons in HTML content 1px bigger up, right,
2111    * and bottom so that they overlap the dropdown's border like they're
2112    * supposed to.
2113    */
2114   if (aWidgetType == NS_THEME_MENULIST_BUTTON &&
2115       IsHTMLContent(aFrame) &&
2116       !IsWidgetStyled(aFrame->GetParent()->PresContext(),
2117                       aFrame->GetParent(),
2118                       NS_THEME_MENULIST))
2119   {
2120     int32_t p2a = aContext->AppUnitsPerDevPixel();
2121     /* Note: no overflow on the left */
2122     nsMargin m(p2a, p2a, p2a, 0);
2123     aOverflowRect->Inflate (m);
2124     return true;
2125   }
2126 #endif
2127 
2128   if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
2129     nsIntMargin border;
2130     nsresult rv = GetWidgetBorder(aContext, aFrame, aWidgetType, &border);
2131     if (NS_SUCCEEDED(rv)) {
2132       int32_t p2a = aContext->AppUnitsPerDevPixel();
2133       nsMargin m(NSIntPixelsToAppUnits(border.top, p2a),
2134                  NSIntPixelsToAppUnits(border.right, p2a),
2135                  NSIntPixelsToAppUnits(border.bottom, p2a),
2136                  NSIntPixelsToAppUnits(border.left, p2a));
2137       aOverflowRect->Inflate(m);
2138       return true;
2139     }
2140   }
2141 
2142   return false;
2143 }
2144 
2145 NS_IMETHODIMP
GetMinimumWidgetSize(nsPresContext * aPresContext,nsIFrame * aFrame,uint8_t aWidgetType,LayoutDeviceIntSize * aResult,bool * aIsOverridable)2146 nsNativeThemeWin::GetMinimumWidgetSize(nsPresContext* aPresContext,
2147                                        nsIFrame* aFrame, uint8_t aWidgetType,
2148                                        LayoutDeviceIntSize* aResult,
2149                                        bool* aIsOverridable) {
2150   aResult->width = aResult->height = 0;
2151   *aIsOverridable = true;
2152   nsresult rv = NS_OK;
2153 
2154   mozilla::Maybe<nsUXThemeClass> themeClass = GetThemeClass(aWidgetType);
2155   HTHEME theme = NULL;
2156   if (!themeClass.isNothing()) {
2157     theme = nsUXThemeData::GetTheme(themeClass.value());
2158   }
2159   if (!theme) {
2160     rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult,
2161                                      aIsOverridable);
2162     ScaleForFrameDPI(aResult, aFrame);
2163     return rv;
2164   }
2165 
2166   switch (aWidgetType) {
2167     case NS_THEME_GROUPBOX:
2168     case NS_THEME_NUMBER_INPUT:
2169     case NS_THEME_TEXTFIELD:
2170     case NS_THEME_TOOLBOX:
2171     case NS_THEME_WIN_MEDIA_TOOLBOX:
2172     case NS_THEME_WIN_COMMUNICATIONS_TOOLBOX:
2173     case NS_THEME_WIN_BROWSERTABBAR_TOOLBOX:
2174     case NS_THEME_TOOLBAR:
2175     case NS_THEME_STATUSBAR:
2176     case NS_THEME_PROGRESSCHUNK:
2177     case NS_THEME_PROGRESSCHUNK_VERTICAL:
2178     case NS_THEME_TABPANELS:
2179     case NS_THEME_TABPANEL:
2180     case NS_THEME_LISTBOX:
2181     case NS_THEME_TREEVIEW:
2182     case NS_THEME_MENUITEMTEXT:
2183     case NS_THEME_WIN_GLASS:
2184     case NS_THEME_WIN_BORDERLESS_GLASS:
2185       return NS_OK;  // Don't worry about it.
2186   }
2187 
2188   if (aWidgetType == NS_THEME_MENUITEM && IsTopLevelMenu(aFrame))
2189     return NS_OK;  // Don't worry about it for top level menus
2190 
2191   // Call GetSystemMetrics to determine size for WinXP scrollbars
2192   // (GetThemeSysSize API returns the optimal size for the theme, but
2193   //  Windows appears to always use metrics when drawing standard scrollbars)
2194   THEMESIZE sizeReq = TS_TRUE;  // Best-fit size
2195   switch (aWidgetType) {
2196     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
2197     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
2198     case NS_THEME_SCROLLBARBUTTON_UP:
2199     case NS_THEME_SCROLLBARBUTTON_DOWN:
2200     case NS_THEME_SCROLLBARBUTTON_LEFT:
2201     case NS_THEME_SCROLLBARBUTTON_RIGHT:
2202     case NS_THEME_SCROLLBAR_HORIZONTAL:
2203     case NS_THEME_SCROLLBAR_VERTICAL:
2204     case NS_THEME_MENULIST_BUTTON: {
2205       rv = ClassicGetMinimumWidgetSize(aFrame, aWidgetType, aResult,
2206                                        aIsOverridable);
2207       ScaleForFrameDPI(aResult, aFrame);
2208       return rv;
2209     }
2210     case NS_THEME_MENUITEM:
2211     case NS_THEME_CHECKMENUITEM:
2212     case NS_THEME_RADIOMENUITEM:
2213       if (!IsTopLevelMenu(aFrame)) {
2214         SIZE gutterSize(GetCachedGutterSize(theme));
2215         aResult->width = gutterSize.cx;
2216         aResult->height = gutterSize.cy;
2217         ScaleForFrameDPI(aResult, aFrame);
2218         return rv;
2219       }
2220       break;
2221 
2222     case NS_THEME_MENUIMAGE:
2223     case NS_THEME_MENUCHECKBOX:
2224     case NS_THEME_MENURADIO: {
2225       SIZE boxSize(GetCachedGutterSize(theme));
2226       aResult->width = boxSize.cx + 2;
2227       aResult->height = boxSize.cy;
2228       *aIsOverridable = false;
2229       ScaleForFrameDPI(aResult, aFrame);
2230       return rv;
2231     }
2232 
2233     case NS_THEME_MENUITEMTEXT:
2234       return NS_OK;
2235 
2236     case NS_THEME_PROGRESSBAR:
2237     case NS_THEME_PROGRESSBAR_VERTICAL:
2238       // Best-fit size for progress meters is too large for most
2239       // themes. We want these widgets to be able to really shrink
2240       // down, so use the min-size request value (of 0).
2241       sizeReq = TS_MIN;
2242       break;
2243 
2244     case NS_THEME_RESIZER:
2245       *aIsOverridable = false;
2246       break;
2247 
2248     case NS_THEME_RANGE_THUMB:
2249     case NS_THEME_SCALETHUMB_HORIZONTAL:
2250     case NS_THEME_SCALETHUMB_VERTICAL: {
2251       *aIsOverridable = false;
2252       // On Vista, GetThemePartAndState returns odd values for
2253       // scale thumbs, so use a hardcoded size instead.
2254       if (aWidgetType == NS_THEME_SCALETHUMB_HORIZONTAL ||
2255           (aWidgetType == NS_THEME_RANGE_THUMB && IsRangeHorizontal(aFrame))) {
2256         aResult->width = 12;
2257         aResult->height = 20;
2258       } else {
2259         aResult->width = 20;
2260         aResult->height = 12;
2261       }
2262       ScaleForFrameDPI(aResult, aFrame);
2263       return rv;
2264     }
2265 
2266     case NS_THEME_SCROLLBAR: {
2267       if (nsLookAndFeel::GetInt(nsLookAndFeel::eIntID_UseOverlayScrollbars) !=
2268           0) {
2269         aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
2270                         ::GetSystemMetrics(SM_CYVSCROLL));
2271         ScaleForFrameDPI(aResult, aFrame);
2272         return rv;
2273       }
2274       break;
2275     }
2276 
2277     case NS_THEME_SEPARATOR:
2278       // that's 2px left margin, 2px right margin and 2px separator
2279       // (the margin is drawn as part of the separator, though)
2280       aResult->width = 6;
2281       ScaleForFrameDPI(aResult, aFrame);
2282       return rv;
2283 
2284     case NS_THEME_BUTTON:
2285       // We should let HTML buttons shrink to their min size.
2286       // FIXME bug 403934: We should probably really separate
2287       // GetPreferredWidgetSize from GetMinimumWidgetSize, so callers can
2288       // use the one they want.
2289       if (aFrame->GetContent()->IsHTMLElement()) {
2290         sizeReq = TS_MIN;
2291       }
2292       break;
2293 
2294     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
2295     case NS_THEME_WINDOW_BUTTON_RESTORE:
2296       // The only way to get accurate titlebar button info is to query a
2297       // window w/buttons when it's visible. nsWindow takes care of this and
2298       // stores that info in nsUXThemeData.
2299       aResult->width =
2300           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cx;
2301       aResult->height =
2302           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_RESTORE).cy;
2303       AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
2304       *aIsOverridable = false;
2305       return rv;
2306 
2307     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
2308       aResult->width =
2309           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cx;
2310       aResult->height =
2311           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_MINIMIZE).cy;
2312       AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
2313       *aIsOverridable = false;
2314       return rv;
2315 
2316     case NS_THEME_WINDOW_BUTTON_CLOSE:
2317       aResult->width =
2318           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cx;
2319       aResult->height =
2320           nsUXThemeData::GetCommandButtonMetrics(CMDBUTTONIDX_CLOSE).cy;
2321       AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
2322       *aIsOverridable = false;
2323       return rv;
2324 
2325     case NS_THEME_WINDOW_TITLEBAR:
2326     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
2327       aResult->height = GetSystemMetrics(SM_CYCAPTION);
2328       aResult->height += GetSystemMetrics(SM_CYFRAME);
2329       aResult->height += GetSystemMetrics(SM_CXPADDEDBORDER);
2330       // On Win8.1, we don't want this scaling, because Windows doesn't scale
2331       // the non-client area of the window, and we can end up with ugly overlap
2332       // of the window frame controls into the tab bar or content area. But on
2333       // Win10, we render the window controls ourselves, and the result looks
2334       // better if we do apply this scaling (particularly with themes such as
2335       // DevEdition; see bug 1267636).
2336       if (IsWin10OrLater()) {
2337         ScaleForFrameDPI(aResult, aFrame);
2338       }
2339       *aIsOverridable = false;
2340       return rv;
2341 
2342     case NS_THEME_WINDOW_BUTTON_BOX:
2343     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
2344       if (nsUXThemeData::CheckForCompositor()) {
2345         aResult->width = nsUXThemeData::GetCommandButtonBoxMetrics().cx;
2346         aResult->height = nsUXThemeData::GetCommandButtonBoxMetrics().cy -
2347                           GetSystemMetrics(SM_CYFRAME) -
2348                           GetSystemMetrics(SM_CXPADDEDBORDER);
2349         if (aWidgetType == NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED) {
2350           aResult->width += 1;
2351           aResult->height -= 2;
2352         }
2353         *aIsOverridable = false;
2354         return rv;
2355       }
2356       break;
2357 
2358     case NS_THEME_WINDOW_FRAME_LEFT:
2359     case NS_THEME_WINDOW_FRAME_RIGHT:
2360     case NS_THEME_WINDOW_FRAME_BOTTOM:
2361       aResult->width = GetSystemMetrics(SM_CXFRAME);
2362       aResult->height = GetSystemMetrics(SM_CYFRAME);
2363       *aIsOverridable = false;
2364       return rv;
2365   }
2366 
2367   int32_t part, state;
2368   rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
2369   if (NS_FAILED(rv)) return rv;
2370 
2371   rv = GetCachedMinimumWidgetSize(aFrame, theme, themeClass.value(),
2372                                   aWidgetType, part, state, sizeReq, aResult);
2373 
2374   ScaleForFrameDPI(aResult, aFrame);
2375   return rv;
2376 }
2377 
2378 NS_IMETHODIMP
WidgetStateChanged(nsIFrame * aFrame,uint8_t aWidgetType,nsAtom * aAttribute,bool * aShouldRepaint,const nsAttrValue * aOldValue)2379 nsNativeThemeWin::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
2380                                      nsAtom* aAttribute, bool* aShouldRepaint,
2381                                      const nsAttrValue* aOldValue) {
2382   // Some widget types just never change state.
2383   if (aWidgetType == NS_THEME_TOOLBOX ||
2384       aWidgetType == NS_THEME_WIN_MEDIA_TOOLBOX ||
2385       aWidgetType == NS_THEME_WIN_COMMUNICATIONS_TOOLBOX ||
2386       aWidgetType == NS_THEME_WIN_BROWSERTABBAR_TOOLBOX ||
2387       aWidgetType == NS_THEME_TOOLBAR || aWidgetType == NS_THEME_STATUSBAR ||
2388       aWidgetType == NS_THEME_STATUSBARPANEL ||
2389       aWidgetType == NS_THEME_RESIZERPANEL ||
2390       aWidgetType == NS_THEME_PROGRESSCHUNK ||
2391       aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
2392       aWidgetType == NS_THEME_PROGRESSBAR ||
2393       aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
2394       aWidgetType == NS_THEME_TOOLTIP || aWidgetType == NS_THEME_TABPANELS ||
2395       aWidgetType == NS_THEME_TABPANEL || aWidgetType == NS_THEME_SEPARATOR ||
2396       aWidgetType == NS_THEME_WIN_GLASS ||
2397       aWidgetType == NS_THEME_WIN_BORDERLESS_GLASS) {
2398     *aShouldRepaint = false;
2399     return NS_OK;
2400   }
2401 
2402   if (aWidgetType == NS_THEME_WINDOW_TITLEBAR ||
2403       aWidgetType == NS_THEME_WINDOW_TITLEBAR_MAXIMIZED ||
2404       aWidgetType == NS_THEME_WINDOW_FRAME_LEFT ||
2405       aWidgetType == NS_THEME_WINDOW_FRAME_RIGHT ||
2406       aWidgetType == NS_THEME_WINDOW_FRAME_BOTTOM ||
2407       aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE ||
2408       aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE ||
2409       aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
2410       aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
2411     *aShouldRepaint = true;
2412     return NS_OK;
2413   }
2414 
2415   // We need to repaint the dropdown arrow in vista HTML combobox controls when
2416   // the control is closed to get rid of the hover effect.
2417   if ((aWidgetType == NS_THEME_MENULIST ||
2418        aWidgetType == NS_THEME_MENULIST_BUTTON) &&
2419       IsHTMLContent(aFrame)) {
2420     *aShouldRepaint = true;
2421     return NS_OK;
2422   }
2423 
2424   // XXXdwh Not sure what can really be done here.  Can at least guess for
2425   // specific widgets that they're highly unlikely to have certain states.
2426   // For example, a toolbar doesn't care about any states.
2427   if (!aAttribute) {
2428     // Hover/focus/active changed.  Always repaint.
2429     *aShouldRepaint = true;
2430   } else {
2431     // Check the attribute to see if it's relevant.
2432     // disabled, checked, dlgtype, default, etc.
2433     *aShouldRepaint = false;
2434     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
2435         aAttribute == nsGkAtoms::selected ||
2436         aAttribute == nsGkAtoms::visuallyselected ||
2437         aAttribute == nsGkAtoms::readonly || aAttribute == nsGkAtoms::open ||
2438         aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::focused)
2439       *aShouldRepaint = true;
2440   }
2441 
2442   return NS_OK;
2443 }
2444 
2445 NS_IMETHODIMP
ThemeChanged()2446 nsNativeThemeWin::ThemeChanged() {
2447   nsUXThemeData::Invalidate();
2448   memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
2449   memset(mMinimumWidgetSizeCacheValid, 0, sizeof(mMinimumWidgetSizeCacheValid));
2450   mGutterSizeCacheValid = false;
2451   return NS_OK;
2452 }
2453 
ThemeSupportsWidget(nsPresContext * aPresContext,nsIFrame * aFrame,uint8_t aWidgetType)2454 bool nsNativeThemeWin::ThemeSupportsWidget(nsPresContext* aPresContext,
2455                                            nsIFrame* aFrame,
2456                                            uint8_t aWidgetType) {
2457   // XXXdwh We can go even further and call the API to ask if support exists for
2458   // specific widgets.
2459 
2460   if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
2461     return true;
2462   }
2463 
2464   HANDLE theme = nullptr;
2465   if (aWidgetType == NS_THEME_CHECKBOX_CONTAINER)
2466     theme = GetTheme(NS_THEME_CHECKBOX);
2467   else if (aWidgetType == NS_THEME_RADIO_CONTAINER)
2468     theme = GetTheme(NS_THEME_RADIO);
2469   else
2470     theme = GetTheme(aWidgetType);
2471 
2472   if (theme && aWidgetType == NS_THEME_RESIZER) return true;
2473 
2474   if ((theme) || (!theme && ClassicThemeSupportsWidget(aFrame, aWidgetType)))
2475     // turn off theming for some HTML widgets styled by the page
2476     return (!IsWidgetStyled(aPresContext, aFrame, aWidgetType));
2477 
2478   return false;
2479 }
2480 
WidgetIsContainer(uint8_t aWidgetType)2481 bool nsNativeThemeWin::WidgetIsContainer(uint8_t aWidgetType) {
2482   // XXXdwh At some point flesh all of this out.
2483   if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
2484       aWidgetType == NS_THEME_RADIO || aWidgetType == NS_THEME_CHECKBOX)
2485     return false;
2486   return true;
2487 }
2488 
ThemeDrawsFocusForWidget(uint8_t aWidgetType)2489 bool nsNativeThemeWin::ThemeDrawsFocusForWidget(uint8_t aWidgetType) {
2490   return false;
2491 }
2492 
ThemeNeedsComboboxDropmarker()2493 bool nsNativeThemeWin::ThemeNeedsComboboxDropmarker() { return true; }
2494 
WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType)2495 bool nsNativeThemeWin::WidgetAppearanceDependsOnWindowFocus(
2496     uint8_t aWidgetType) {
2497   switch (aWidgetType) {
2498     case NS_THEME_WINDOW_TITLEBAR:
2499     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
2500     case NS_THEME_WINDOW_FRAME_LEFT:
2501     case NS_THEME_WINDOW_FRAME_RIGHT:
2502     case NS_THEME_WINDOW_FRAME_BOTTOM:
2503     case NS_THEME_WINDOW_BUTTON_CLOSE:
2504     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
2505     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
2506     case NS_THEME_WINDOW_BUTTON_RESTORE:
2507       return true;
2508     default:
2509       return false;
2510   }
2511 }
2512 
ThemeGeometryTypeForWidget(nsIFrame * aFrame,uint8_t aWidgetType)2513 nsITheme::ThemeGeometryType nsNativeThemeWin::ThemeGeometryTypeForWidget(
2514     nsIFrame* aFrame, uint8_t aWidgetType) {
2515   switch (aWidgetType) {
2516     case NS_THEME_WINDOW_BUTTON_BOX:
2517     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
2518       return eThemeGeometryTypeWindowButtons;
2519     default:
2520       return eThemeGeometryTypeUnknown;
2521   }
2522 }
2523 
ShouldHideScrollbars()2524 bool nsNativeThemeWin::ShouldHideScrollbars() {
2525   return WinUtils::ShouldHideScrollbars();
2526 }
2527 
GetWidgetTransparency(nsIFrame * aFrame,uint8_t aWidgetType)2528 nsITheme::Transparency nsNativeThemeWin::GetWidgetTransparency(
2529     nsIFrame* aFrame, uint8_t aWidgetType) {
2530   switch (aWidgetType) {
2531     case NS_THEME_SCROLLBAR_SMALL:
2532     case NS_THEME_SCROLLBAR:
2533     case NS_THEME_STATUSBAR:
2534       // Knowing that scrollbars and statusbars are opaque improves
2535       // performance, because we create layers for them. This better be
2536       // true across all Windows themes! If it's not true, we should
2537       // paint an opaque background for them to make it true!
2538       return eOpaque;
2539     case NS_THEME_WIN_GLASS:
2540     case NS_THEME_WIN_BORDERLESS_GLASS:
2541     case NS_THEME_SCALE_HORIZONTAL:
2542     case NS_THEME_SCALE_VERTICAL:
2543     case NS_THEME_PROGRESSBAR:
2544     case NS_THEME_PROGRESSBAR_VERTICAL:
2545     case NS_THEME_PROGRESSCHUNK:
2546     case NS_THEME_PROGRESSCHUNK_VERTICAL:
2547     case NS_THEME_RANGE:
2548       return eTransparent;
2549   }
2550 
2551   HANDLE theme = GetTheme(aWidgetType);
2552   // For the classic theme we don't really have a way of knowing
2553   if (!theme) {
2554     // menu backgrounds and tooltips which can't be themed are opaque
2555     if (aWidgetType == NS_THEME_MENUPOPUP || aWidgetType == NS_THEME_TOOLTIP) {
2556       return eOpaque;
2557     }
2558     return eUnknownTransparency;
2559   }
2560 
2561   int32_t part, state;
2562   nsresult rv = GetThemePartAndState(aFrame, aWidgetType, part, state);
2563   // Fail conservatively
2564   NS_ENSURE_SUCCESS(rv, eUnknownTransparency);
2565 
2566   if (part <= 0) {
2567     // Not a real part code, so IsThemeBackgroundPartiallyTransparent may
2568     // not work, so don't call it.
2569     return eUnknownTransparency;
2570   }
2571 
2572   if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
2573     return eTransparent;
2574   return eOpaque;
2575 }
2576 
2577 /* Windows 9x/NT/2000/Classic XP Theme Support */
2578 
ClassicThemeSupportsWidget(nsIFrame * aFrame,uint8_t aWidgetType)2579 bool nsNativeThemeWin::ClassicThemeSupportsWidget(nsIFrame* aFrame,
2580                                                   uint8_t aWidgetType) {
2581   switch (aWidgetType) {
2582     case NS_THEME_RESIZER: {
2583       // The classic native resizer has an opaque grey background which doesn't
2584       // match the usually white background of the scrollable container, so
2585       // only support the native resizer if not in a scrollframe.
2586       nsIFrame* parentFrame = aFrame->GetParent();
2587       return !parentFrame || !parentFrame->IsScrollFrame();
2588     }
2589     case NS_THEME_MENUBAR:
2590     case NS_THEME_MENUPOPUP:
2591       // Classic non-flat menus are handled almost entirely through CSS.
2592       if (!nsUXThemeData::sFlatMenus) return false;
2593     case NS_THEME_BUTTON:
2594     case NS_THEME_NUMBER_INPUT:
2595     case NS_THEME_TEXTFIELD:
2596     case NS_THEME_TEXTFIELD_MULTILINE:
2597     case NS_THEME_CHECKBOX:
2598     case NS_THEME_RADIO:
2599     case NS_THEME_RANGE:
2600     case NS_THEME_RANGE_THUMB:
2601     case NS_THEME_GROUPBOX:
2602     case NS_THEME_SCROLLBARBUTTON_UP:
2603     case NS_THEME_SCROLLBARBUTTON_DOWN:
2604     case NS_THEME_SCROLLBARBUTTON_LEFT:
2605     case NS_THEME_SCROLLBARBUTTON_RIGHT:
2606     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
2607     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
2608     case NS_THEME_SCROLLBAR_VERTICAL:
2609     case NS_THEME_SCROLLBAR_HORIZONTAL:
2610     case NS_THEME_SCROLLBAR_NON_DISAPPEARING:
2611     case NS_THEME_SCALE_HORIZONTAL:
2612     case NS_THEME_SCALE_VERTICAL:
2613     case NS_THEME_SCALETHUMB_HORIZONTAL:
2614     case NS_THEME_SCALETHUMB_VERTICAL:
2615     case NS_THEME_MENULIST_BUTTON:
2616     case NS_THEME_INNER_SPIN_BUTTON:
2617     case NS_THEME_SPINNER_UPBUTTON:
2618     case NS_THEME_SPINNER_DOWNBUTTON:
2619     case NS_THEME_LISTBOX:
2620     case NS_THEME_TREEVIEW:
2621     case NS_THEME_MENULIST_TEXTFIELD:
2622     case NS_THEME_MENULIST:
2623     case NS_THEME_TOOLTIP:
2624     case NS_THEME_STATUSBAR:
2625     case NS_THEME_STATUSBARPANEL:
2626     case NS_THEME_RESIZERPANEL:
2627     case NS_THEME_PROGRESSBAR:
2628     case NS_THEME_PROGRESSBAR_VERTICAL:
2629     case NS_THEME_PROGRESSCHUNK:
2630     case NS_THEME_PROGRESSCHUNK_VERTICAL:
2631     case NS_THEME_TAB:
2632     case NS_THEME_TABPANEL:
2633     case NS_THEME_TABPANELS:
2634     case NS_THEME_MENUITEM:
2635     case NS_THEME_CHECKMENUITEM:
2636     case NS_THEME_RADIOMENUITEM:
2637     case NS_THEME_MENUCHECKBOX:
2638     case NS_THEME_MENURADIO:
2639     case NS_THEME_MENUARROW:
2640     case NS_THEME_MENUSEPARATOR:
2641     case NS_THEME_MENUITEMTEXT:
2642     case NS_THEME_WINDOW_TITLEBAR:
2643     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
2644     case NS_THEME_WINDOW_FRAME_LEFT:
2645     case NS_THEME_WINDOW_FRAME_RIGHT:
2646     case NS_THEME_WINDOW_FRAME_BOTTOM:
2647     case NS_THEME_WINDOW_BUTTON_CLOSE:
2648     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
2649     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
2650     case NS_THEME_WINDOW_BUTTON_RESTORE:
2651     case NS_THEME_WINDOW_BUTTON_BOX:
2652     case NS_THEME_WINDOW_BUTTON_BOX_MAXIMIZED:
2653       return true;
2654   }
2655   return false;
2656 }
2657 
ClassicGetWidgetBorder(nsDeviceContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,nsIntMargin * aResult)2658 nsresult nsNativeThemeWin::ClassicGetWidgetBorder(nsDeviceContext* aContext,
2659                                                   nsIFrame* aFrame,
2660                                                   uint8_t aWidgetType,
2661                                                   nsIntMargin* aResult) {
2662   switch (aWidgetType) {
2663     case NS_THEME_GROUPBOX:
2664     case NS_THEME_BUTTON:
2665       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2666           2;
2667       break;
2668     case NS_THEME_STATUSBAR:
2669       (*aResult).bottom = (*aResult).left = (*aResult).right = 0;
2670       (*aResult).top = 2;
2671       break;
2672     case NS_THEME_LISTBOX:
2673     case NS_THEME_TREEVIEW:
2674     case NS_THEME_MENULIST:
2675     case NS_THEME_MENULIST_TEXTFIELD:
2676     case NS_THEME_TAB:
2677     case NS_THEME_NUMBER_INPUT:
2678     case NS_THEME_TEXTFIELD:
2679     case NS_THEME_TEXTFIELD_MULTILINE:
2680     case NS_THEME_FOCUS_OUTLINE:
2681       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2682           2;
2683       break;
2684     case NS_THEME_STATUSBARPANEL:
2685     case NS_THEME_RESIZERPANEL: {
2686       (*aResult).top = 1;
2687       (*aResult).left = 1;
2688       (*aResult).bottom = 1;
2689       (*aResult).right = aFrame->GetNextSibling() ? 3 : 1;
2690       break;
2691     }
2692     case NS_THEME_TOOLTIP:
2693       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2694           1;
2695       break;
2696     case NS_THEME_PROGRESSBAR:
2697     case NS_THEME_PROGRESSBAR_VERTICAL:
2698       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2699           1;
2700       break;
2701     case NS_THEME_MENUBAR:
2702       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2703           0;
2704       break;
2705     case NS_THEME_MENUPOPUP:
2706       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2707           3;
2708       break;
2709     default:
2710       (*aResult).top = (*aResult).bottom = (*aResult).left = (*aResult).right =
2711           0;
2712       break;
2713   }
2714   return NS_OK;
2715 }
2716 
ClassicGetWidgetPadding(nsDeviceContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,nsIntMargin * aResult)2717 bool nsNativeThemeWin::ClassicGetWidgetPadding(nsDeviceContext* aContext,
2718                                                nsIFrame* aFrame,
2719                                                uint8_t aWidgetType,
2720                                                nsIntMargin* aResult) {
2721   switch (aWidgetType) {
2722     case NS_THEME_MENUITEM:
2723     case NS_THEME_CHECKMENUITEM:
2724     case NS_THEME_RADIOMENUITEM: {
2725       int32_t part, state;
2726       bool focused;
2727 
2728       if (NS_FAILED(ClassicGetThemePartAndState(aFrame, aWidgetType, part,
2729                                                 state, focused)))
2730         return false;
2731 
2732       if (part == 1) {  // top-level menu
2733         if (nsUXThemeData::sFlatMenus || !(state & DFCS_PUSHED)) {
2734           (*aResult).top = (*aResult).bottom = (*aResult).left =
2735               (*aResult).right = 2;
2736         } else {
2737           // make top-level menus look sunken when pushed in the Classic look
2738           (*aResult).top = (*aResult).left = 3;
2739           (*aResult).bottom = (*aResult).right = 1;
2740         }
2741       } else {
2742         (*aResult).top = 0;
2743         (*aResult).bottom = (*aResult).left = (*aResult).right = 2;
2744       }
2745       return true;
2746     }
2747     case NS_THEME_PROGRESSBAR:
2748     case NS_THEME_PROGRESSBAR_VERTICAL:
2749       (*aResult).top = (*aResult).left = (*aResult).bottom = (*aResult).right =
2750           1;
2751       return true;
2752     default:
2753       return false;
2754   }
2755 }
2756 
ClassicGetMinimumWidgetSize(nsIFrame * aFrame,uint8_t aWidgetType,LayoutDeviceIntSize * aResult,bool * aIsOverridable)2757 nsresult nsNativeThemeWin::ClassicGetMinimumWidgetSize(
2758     nsIFrame* aFrame, uint8_t aWidgetType, LayoutDeviceIntSize* aResult,
2759     bool* aIsOverridable) {
2760   (*aResult).width = (*aResult).height = 0;
2761   *aIsOverridable = true;
2762   switch (aWidgetType) {
2763     case NS_THEME_RADIO:
2764     case NS_THEME_CHECKBOX:
2765       (*aResult).width = (*aResult).height = 13;
2766       break;
2767     case NS_THEME_MENUCHECKBOX:
2768     case NS_THEME_MENURADIO:
2769     case NS_THEME_MENUARROW:
2770       (*aResult).width = ::GetSystemMetrics(SM_CXMENUCHECK);
2771       (*aResult).height = ::GetSystemMetrics(SM_CYMENUCHECK);
2772       break;
2773     case NS_THEME_INNER_SPIN_BUTTON:
2774     case NS_THEME_SPINNER_UPBUTTON:
2775     case NS_THEME_SPINNER_DOWNBUTTON:
2776       (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
2777       (*aResult).height = 8;  // No good metrics available for this
2778       *aIsOverridable = false;
2779       break;
2780     case NS_THEME_SCROLLBARBUTTON_UP:
2781     case NS_THEME_SCROLLBARBUTTON_DOWN:
2782       (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
2783       (*aResult).height = ::GetSystemMetrics(SM_CYVSCROLL);
2784       *aIsOverridable = false;
2785       break;
2786     case NS_THEME_SCROLLBARBUTTON_LEFT:
2787     case NS_THEME_SCROLLBARBUTTON_RIGHT:
2788       (*aResult).width = ::GetSystemMetrics(SM_CXHSCROLL);
2789       (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
2790       *aIsOverridable = false;
2791       break;
2792     case NS_THEME_SCROLLBAR_VERTICAL:
2793       // XXX HACK We should be able to have a minimum height for the scrollbar
2794       // track.  However, this causes problems when uncollapsing a scrollbar
2795       // inside a tree.  See bug 201379 for details.
2796 
2797       //      (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB) << 1;
2798       break;
2799     case NS_THEME_SCROLLBAR_NON_DISAPPEARING: {
2800       aResult->SizeTo(::GetSystemMetrics(SM_CXHSCROLL),
2801                       ::GetSystemMetrics(SM_CYVSCROLL));
2802       break;
2803     }
2804     case NS_THEME_RANGE_THUMB: {
2805       if (IsRangeHorizontal(aFrame)) {
2806         (*aResult).width = 12;
2807         (*aResult).height = 20;
2808       } else {
2809         (*aResult).width = 20;
2810         (*aResult).height = 12;
2811       }
2812       *aIsOverridable = false;
2813       break;
2814     }
2815     case NS_THEME_SCALETHUMB_HORIZONTAL:
2816       (*aResult).width = 12;
2817       (*aResult).height = 20;
2818       *aIsOverridable = false;
2819       break;
2820     case NS_THEME_SCALETHUMB_VERTICAL:
2821       (*aResult).width = 20;
2822       (*aResult).height = 12;
2823       *aIsOverridable = false;
2824       break;
2825     case NS_THEME_MENULIST_BUTTON:
2826       (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
2827       break;
2828     case NS_THEME_MENULIST:
2829     case NS_THEME_BUTTON:
2830     case NS_THEME_GROUPBOX:
2831     case NS_THEME_LISTBOX:
2832     case NS_THEME_TREEVIEW:
2833     case NS_THEME_NUMBER_INPUT:
2834     case NS_THEME_TEXTFIELD:
2835     case NS_THEME_TEXTFIELD_MULTILINE:
2836     case NS_THEME_MENULIST_TEXTFIELD:
2837     case NS_THEME_STATUSBAR:
2838     case NS_THEME_STATUSBARPANEL:
2839     case NS_THEME_RESIZERPANEL:
2840     case NS_THEME_PROGRESSCHUNK:
2841     case NS_THEME_PROGRESSCHUNK_VERTICAL:
2842     case NS_THEME_TOOLTIP:
2843     case NS_THEME_PROGRESSBAR:
2844     case NS_THEME_PROGRESSBAR_VERTICAL:
2845     case NS_THEME_TAB:
2846     case NS_THEME_TABPANEL:
2847     case NS_THEME_TABPANELS:
2848       // no minimum widget size
2849       break;
2850     case NS_THEME_RESIZER: {
2851       NONCLIENTMETRICS nc;
2852       nc.cbSize = sizeof(nc);
2853       if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(nc), &nc, 0))
2854         (*aResult).width = (*aResult).height =
2855             abs(nc.lfStatusFont.lfHeight) + 4;
2856       else
2857         (*aResult).width = (*aResult).height = 15;
2858       *aIsOverridable = false;
2859       break;
2860       case NS_THEME_SCROLLBARTHUMB_VERTICAL:
2861         (*aResult).width = ::GetSystemMetrics(SM_CXVSCROLL);
2862         (*aResult).height = ::GetSystemMetrics(SM_CYVTHUMB);
2863         // Without theming, divide the thumb size by two in order to look more
2864         // native
2865         if (!GetTheme(aWidgetType)) (*aResult).height >>= 1;
2866         *aIsOverridable = false;
2867         break;
2868       case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
2869         (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB);
2870         (*aResult).height = ::GetSystemMetrics(SM_CYHSCROLL);
2871         // Without theming, divide the thumb size by two in order to look more
2872         // native
2873         if (!GetTheme(aWidgetType)) (*aResult).width >>= 1;
2874         *aIsOverridable = false;
2875         break;
2876       case NS_THEME_SCROLLBAR_HORIZONTAL:
2877         (*aResult).width = ::GetSystemMetrics(SM_CXHTHUMB) << 1;
2878         break;
2879     }
2880     case NS_THEME_MENUSEPARATOR: {
2881       aResult->width = 0;
2882       aResult->height = 10;
2883       break;
2884     }
2885 
2886     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
2887     case NS_THEME_WINDOW_TITLEBAR:
2888       aResult->height = GetSystemMetrics(SM_CYCAPTION);
2889       aResult->height += GetSystemMetrics(SM_CYFRAME);
2890       aResult->width = 0;
2891       break;
2892     case NS_THEME_WINDOW_FRAME_LEFT:
2893     case NS_THEME_WINDOW_FRAME_RIGHT:
2894       aResult->width = GetSystemMetrics(SM_CXFRAME);
2895       aResult->height = 0;
2896       break;
2897 
2898     case NS_THEME_WINDOW_FRAME_BOTTOM:
2899       aResult->height = GetSystemMetrics(SM_CYFRAME);
2900       aResult->width = 0;
2901       break;
2902 
2903     case NS_THEME_WINDOW_BUTTON_CLOSE:
2904     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
2905     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
2906     case NS_THEME_WINDOW_BUTTON_RESTORE:
2907       aResult->width = GetSystemMetrics(SM_CXSIZE);
2908       aResult->height = GetSystemMetrics(SM_CYSIZE);
2909       // XXX I have no idea why these caption metrics are always off,
2910       // but they are.
2911       aResult->width -= 2;
2912       aResult->height -= 4;
2913       if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
2914         AddPaddingRect(aResult, CAPTIONBUTTON_MINIMIZE);
2915       } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
2916                  aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
2917         AddPaddingRect(aResult, CAPTIONBUTTON_RESTORE);
2918       } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
2919         AddPaddingRect(aResult, CAPTIONBUTTON_CLOSE);
2920       }
2921       break;
2922 
2923     default:
2924       return NS_ERROR_FAILURE;
2925   }
2926   return NS_OK;
2927 }
2928 
ClassicGetThemePartAndState(nsIFrame * aFrame,uint8_t aWidgetType,int32_t & aPart,int32_t & aState,bool & aFocused)2929 nsresult nsNativeThemeWin::ClassicGetThemePartAndState(nsIFrame* aFrame,
2930                                                        uint8_t aWidgetType,
2931                                                        int32_t& aPart,
2932                                                        int32_t& aState,
2933                                                        bool& aFocused) {
2934   aFocused = false;
2935   switch (aWidgetType) {
2936     case NS_THEME_BUTTON: {
2937       EventStates contentState;
2938 
2939       aPart = DFC_BUTTON;
2940       aState = DFCS_BUTTONPUSH;
2941       aFocused = false;
2942 
2943       contentState = GetContentState(aFrame, aWidgetType);
2944       if (IsDisabled(aFrame, contentState))
2945         aState |= DFCS_INACTIVE;
2946       else if (IsOpenButton(aFrame))
2947         aState |= DFCS_PUSHED;
2948       else if (IsCheckedButton(aFrame))
2949         aState |= DFCS_CHECKED;
2950       else {
2951         if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE |
2952                                       NS_EVENT_STATE_HOVER)) {
2953           aState |= DFCS_PUSHED;
2954           const nsStyleUserInterface* uiData = aFrame->StyleUserInterface();
2955           // The down state is flat if the button is focusable
2956           if (uiData->mUserFocus == StyleUserFocus::Normal) {
2957             if (!aFrame->GetContent()->IsHTMLElement()) aState |= DFCS_FLAT;
2958 
2959             aFocused = true;
2960           }
2961         }
2962         if (contentState.HasState(NS_EVENT_STATE_FOCUS) ||
2963             (aState == DFCS_BUTTONPUSH && IsDefaultButton(aFrame))) {
2964           aFocused = true;
2965         }
2966       }
2967 
2968       return NS_OK;
2969     }
2970     case NS_THEME_CHECKBOX:
2971     case NS_THEME_RADIO: {
2972       EventStates contentState;
2973       aFocused = false;
2974 
2975       aPart = DFC_BUTTON;
2976       aState = 0;
2977       nsIContent* content = aFrame->GetContent();
2978       bool isCheckbox = (aWidgetType == NS_THEME_CHECKBOX);
2979       bool isChecked = GetCheckedOrSelected(aFrame, !isCheckbox);
2980       bool isIndeterminate = isCheckbox && GetIndeterminate(aFrame);
2981 
2982       if (isCheckbox) {
2983         // indeterminate state takes precedence over checkedness.
2984         if (isIndeterminate) {
2985           aState = DFCS_BUTTON3STATE | DFCS_CHECKED;
2986         } else {
2987           aState = DFCS_BUTTONCHECK;
2988         }
2989       } else {
2990         aState = DFCS_BUTTONRADIO;
2991       }
2992       if (isChecked) {
2993         aState |= DFCS_CHECKED;
2994       }
2995 
2996       contentState = GetContentState(aFrame, aWidgetType);
2997       if (!content->IsXULElement() &&
2998           contentState.HasState(NS_EVENT_STATE_FOCUS)) {
2999         aFocused = true;
3000       }
3001 
3002       if (IsDisabled(aFrame, contentState)) {
3003         aState |= DFCS_INACTIVE;
3004       } else if (contentState.HasAllStates(NS_EVENT_STATE_ACTIVE |
3005                                            NS_EVENT_STATE_HOVER)) {
3006         aState |= DFCS_PUSHED;
3007       }
3008 
3009       return NS_OK;
3010     }
3011     case NS_THEME_MENUITEM:
3012     case NS_THEME_CHECKMENUITEM:
3013     case NS_THEME_RADIOMENUITEM: {
3014       bool isTopLevel = false;
3015       bool isOpen = false;
3016       nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
3017       EventStates eventState = GetContentState(aFrame, aWidgetType);
3018 
3019       // We indicate top-level-ness using aPart. 0 is a normal menu item,
3020       // 1 is a top-level menu item. The state of the item is composed of
3021       // DFCS_* flags only.
3022       aPart = 0;
3023       aState = 0;
3024 
3025       if (menuFrame) {
3026         // If this is a real menu item, we should check if it is part of the
3027         // main menu bar or not, and if it is a container, as these affect
3028         // rendering.
3029         isTopLevel = menuFrame->IsOnMenuBar();
3030         isOpen = menuFrame->IsOpen();
3031       }
3032 
3033       if (IsDisabled(aFrame, eventState)) aState |= DFCS_INACTIVE;
3034 
3035       if (isTopLevel) {
3036         aPart = 1;
3037         if (isOpen) aState |= DFCS_PUSHED;
3038       }
3039 
3040       if (IsMenuActive(aFrame, aWidgetType)) aState |= DFCS_HOT;
3041 
3042       return NS_OK;
3043     }
3044     case NS_THEME_MENUCHECKBOX:
3045     case NS_THEME_MENURADIO:
3046     case NS_THEME_MENUARROW: {
3047       aState = 0;
3048       EventStates eventState = GetContentState(aFrame, aWidgetType);
3049 
3050       if (IsDisabled(aFrame, eventState)) aState |= DFCS_INACTIVE;
3051       if (IsMenuActive(aFrame, aWidgetType)) aState |= DFCS_HOT;
3052 
3053       if (aWidgetType == NS_THEME_MENUCHECKBOX ||
3054           aWidgetType == NS_THEME_MENURADIO) {
3055         if (IsCheckedButton(aFrame)) aState |= DFCS_CHECKED;
3056       } else if (IsFrameRTL(aFrame)) {
3057         aState |= DFCS_RTL;
3058       }
3059       return NS_OK;
3060     }
3061     case NS_THEME_LISTBOX:
3062     case NS_THEME_TREEVIEW:
3063     case NS_THEME_NUMBER_INPUT:
3064     case NS_THEME_FOCUS_OUTLINE:
3065     case NS_THEME_TEXTFIELD:
3066     case NS_THEME_TEXTFIELD_MULTILINE:
3067     case NS_THEME_MENULIST:
3068     case NS_THEME_MENULIST_TEXTFIELD:
3069     case NS_THEME_RANGE:
3070     case NS_THEME_RANGE_THUMB:
3071     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
3072     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
3073     case NS_THEME_SCROLLBAR_VERTICAL:
3074     case NS_THEME_SCROLLBAR_HORIZONTAL:
3075     case NS_THEME_SCALE_HORIZONTAL:
3076     case NS_THEME_SCALE_VERTICAL:
3077     case NS_THEME_SCALETHUMB_HORIZONTAL:
3078     case NS_THEME_SCALETHUMB_VERTICAL:
3079     case NS_THEME_STATUSBAR:
3080     case NS_THEME_STATUSBARPANEL:
3081     case NS_THEME_RESIZERPANEL:
3082     case NS_THEME_PROGRESSCHUNK:
3083     case NS_THEME_PROGRESSCHUNK_VERTICAL:
3084     case NS_THEME_TOOLTIP:
3085     case NS_THEME_PROGRESSBAR:
3086     case NS_THEME_PROGRESSBAR_VERTICAL:
3087     case NS_THEME_TAB:
3088     case NS_THEME_TABPANEL:
3089     case NS_THEME_TABPANELS:
3090     case NS_THEME_MENUBAR:
3091     case NS_THEME_MENUPOPUP:
3092     case NS_THEME_GROUPBOX:
3093       // these don't use DrawFrameControl
3094       return NS_OK;
3095     case NS_THEME_MENULIST_BUTTON: {
3096       aPart = DFC_SCROLL;
3097       aState = DFCS_SCROLLCOMBOBOX;
3098 
3099       nsIFrame* parentFrame = aFrame->GetParent();
3100       bool isHTML = IsHTMLContent(aFrame);
3101       bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
3102       bool isOpen = false;
3103 
3104       // HTML select and XUL menulist dropdown buttons get state from the
3105       // parent.
3106       if (isHTML || isMenulist) aFrame = parentFrame;
3107 
3108       EventStates eventState = GetContentState(aFrame, aWidgetType);
3109 
3110       if (IsDisabled(aFrame, eventState)) {
3111         aState |= DFCS_INACTIVE;
3112         return NS_OK;
3113       }
3114 
3115       if (isHTML) {
3116         nsIComboboxControlFrame* ccf = do_QueryFrame(aFrame);
3117         isOpen = (ccf && ccf->IsDroppedDownOrHasParentPopup());
3118       } else
3119         isOpen = IsOpenButton(aFrame);
3120 
3121       // XXX Button should look active until the mouse is released, but
3122       //     without making it look active when the popup is clicked.
3123       if (isOpen && (isHTML || isMenulist)) return NS_OK;
3124 
3125       // Dropdown button active state doesn't need :hover.
3126       if (eventState.HasState(NS_EVENT_STATE_ACTIVE))
3127         aState |= DFCS_PUSHED | DFCS_FLAT;
3128 
3129       return NS_OK;
3130     }
3131     case NS_THEME_SCROLLBARBUTTON_UP:
3132     case NS_THEME_SCROLLBARBUTTON_DOWN:
3133     case NS_THEME_SCROLLBARBUTTON_LEFT:
3134     case NS_THEME_SCROLLBARBUTTON_RIGHT: {
3135       EventStates contentState = GetContentState(aFrame, aWidgetType);
3136 
3137       aPart = DFC_SCROLL;
3138       switch (aWidgetType) {
3139         case NS_THEME_SCROLLBARBUTTON_UP:
3140           aState = DFCS_SCROLLUP;
3141           break;
3142         case NS_THEME_SCROLLBARBUTTON_DOWN:
3143           aState = DFCS_SCROLLDOWN;
3144           break;
3145         case NS_THEME_SCROLLBARBUTTON_LEFT:
3146           aState = DFCS_SCROLLLEFT;
3147           break;
3148         case NS_THEME_SCROLLBARBUTTON_RIGHT:
3149           aState = DFCS_SCROLLRIGHT;
3150           break;
3151       }
3152 
3153       if (IsDisabled(aFrame, contentState))
3154         aState |= DFCS_INACTIVE;
3155       else {
3156         if (contentState.HasAllStates(NS_EVENT_STATE_HOVER |
3157                                       NS_EVENT_STATE_ACTIVE))
3158           aState |= DFCS_PUSHED | DFCS_FLAT;
3159       }
3160 
3161       return NS_OK;
3162     }
3163     case NS_THEME_INNER_SPIN_BUTTON:
3164     case NS_THEME_SPINNER_UPBUTTON:
3165     case NS_THEME_SPINNER_DOWNBUTTON: {
3166       EventStates contentState = GetContentState(aFrame, aWidgetType);
3167 
3168       aPart = DFC_SCROLL;
3169       switch (aWidgetType) {
3170         case NS_THEME_SPINNER_UPBUTTON:
3171           aState = DFCS_SCROLLUP;
3172           break;
3173         case NS_THEME_INNER_SPIN_BUTTON:
3174         case NS_THEME_SPINNER_DOWNBUTTON:
3175           aState = DFCS_SCROLLDOWN;
3176           break;
3177       }
3178 
3179       if (IsDisabled(aFrame, contentState))
3180         aState |= DFCS_INACTIVE;
3181       else {
3182         if (contentState.HasAllStates(NS_EVENT_STATE_HOVER |
3183                                       NS_EVENT_STATE_ACTIVE))
3184           aState |= DFCS_PUSHED;
3185       }
3186 
3187       return NS_OK;
3188     }
3189     case NS_THEME_RESIZER:
3190       aPart = DFC_SCROLL;
3191       aState =
3192           (IsFrameRTL(aFrame)) ? DFCS_SCROLLSIZEGRIPRIGHT : DFCS_SCROLLSIZEGRIP;
3193       return NS_OK;
3194     case NS_THEME_MENUSEPARATOR:
3195       aPart = 0;
3196       aState = 0;
3197       return NS_OK;
3198     case NS_THEME_WINDOW_TITLEBAR:
3199       aPart = mozilla::widget::themeconst::WP_CAPTION;
3200       aState = GetTopLevelWindowActiveState(aFrame);
3201       return NS_OK;
3202     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
3203       aPart = mozilla::widget::themeconst::WP_MAXCAPTION;
3204       aState = GetTopLevelWindowActiveState(aFrame);
3205       return NS_OK;
3206     case NS_THEME_WINDOW_FRAME_LEFT:
3207       aPart = mozilla::widget::themeconst::WP_FRAMELEFT;
3208       aState = GetTopLevelWindowActiveState(aFrame);
3209       return NS_OK;
3210     case NS_THEME_WINDOW_FRAME_RIGHT:
3211       aPart = mozilla::widget::themeconst::WP_FRAMERIGHT;
3212       aState = GetTopLevelWindowActiveState(aFrame);
3213       return NS_OK;
3214     case NS_THEME_WINDOW_FRAME_BOTTOM:
3215       aPart = mozilla::widget::themeconst::WP_FRAMEBOTTOM;
3216       aState = GetTopLevelWindowActiveState(aFrame);
3217       return NS_OK;
3218     case NS_THEME_WINDOW_BUTTON_CLOSE:
3219       aPart = DFC_CAPTION;
3220       aState = DFCS_CAPTIONCLOSE | GetClassicWindowFrameButtonState(
3221                                        GetContentState(aFrame, aWidgetType));
3222       return NS_OK;
3223     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
3224       aPart = DFC_CAPTION;
3225       aState = DFCS_CAPTIONMIN | GetClassicWindowFrameButtonState(
3226                                      GetContentState(aFrame, aWidgetType));
3227       return NS_OK;
3228     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
3229       aPart = DFC_CAPTION;
3230       aState = DFCS_CAPTIONMAX | GetClassicWindowFrameButtonState(
3231                                      GetContentState(aFrame, aWidgetType));
3232       return NS_OK;
3233     case NS_THEME_WINDOW_BUTTON_RESTORE:
3234       aPart = DFC_CAPTION;
3235       aState = DFCS_CAPTIONRESTORE | GetClassicWindowFrameButtonState(
3236                                          GetContentState(aFrame, aWidgetType));
3237       return NS_OK;
3238   }
3239   return NS_ERROR_FAILURE;
3240 }
3241 
3242 // Draw classic Windows tab
3243 // (no system API for this, but DrawEdge can draw all the parts of a tab)
DrawTab(HDC hdc,const RECT & R,int32_t aPosition,bool aSelected,bool aDrawLeft,bool aDrawRight)3244 static void DrawTab(HDC hdc, const RECT& R, int32_t aPosition, bool aSelected,
3245                     bool aDrawLeft, bool aDrawRight) {
3246   int32_t leftFlag, topFlag, rightFlag, lightFlag, shadeFlag;
3247   RECT topRect, sideRect, bottomRect, lightRect, shadeRect;
3248   int32_t selectedOffset, lOffset, rOffset;
3249 
3250   selectedOffset = aSelected ? 1 : 0;
3251   lOffset = aDrawLeft ? 2 : 0;
3252   rOffset = aDrawRight ? 2 : 0;
3253 
3254   // Get info for tab orientation/position (Left, Top, Right, Bottom)
3255   switch (aPosition) {
3256     case BF_LEFT:
3257       leftFlag = BF_TOP;
3258       topFlag = BF_LEFT;
3259       rightFlag = BF_BOTTOM;
3260       lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
3261       shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
3262 
3263       ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
3264       ::SetRect(&sideRect, R.left + 2, R.top, R.right - 2 + selectedOffset,
3265                 R.bottom);
3266       ::SetRect(&bottomRect, R.right - 2, R.top, R.right, R.bottom);
3267       ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
3268       ::SetRect(&shadeRect, R.left + 1, R.bottom - 2, R.left + 2, R.bottom - 1);
3269       break;
3270     case BF_TOP:
3271       leftFlag = BF_LEFT;
3272       topFlag = BF_TOP;
3273       rightFlag = BF_RIGHT;
3274       lightFlag = BF_DIAGONAL_ENDTOPRIGHT;
3275       shadeFlag = BF_DIAGONAL_ENDBOTTOMRIGHT;
3276 
3277       ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
3278       ::SetRect(&sideRect, R.left, R.top + 2, R.right,
3279                 R.bottom - 1 + selectedOffset);
3280       ::SetRect(&bottomRect, R.left, R.bottom - 1, R.right, R.bottom);
3281       ::SetRect(&lightRect, R.left, R.top, R.left + 3, R.top + 3);
3282       ::SetRect(&shadeRect, R.right - 2, R.top + 1, R.right - 1, R.top + 2);
3283       break;
3284     case BF_RIGHT:
3285       leftFlag = BF_TOP;
3286       topFlag = BF_RIGHT;
3287       rightFlag = BF_BOTTOM;
3288       lightFlag = BF_DIAGONAL_ENDTOPLEFT;
3289       shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
3290 
3291       ::SetRect(&topRect, R.left, R.top + lOffset, R.right, R.bottom - rOffset);
3292       ::SetRect(&sideRect, R.left + 2 - selectedOffset, R.top, R.right - 2,
3293                 R.bottom);
3294       ::SetRect(&bottomRect, R.left, R.top, R.left + 2, R.bottom);
3295       ::SetRect(&lightRect, R.right - 3, R.top, R.right - 1, R.top + 2);
3296       ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
3297       break;
3298     case BF_BOTTOM:
3299       leftFlag = BF_LEFT;
3300       topFlag = BF_BOTTOM;
3301       rightFlag = BF_RIGHT;
3302       lightFlag = BF_DIAGONAL_ENDTOPLEFT;
3303       shadeFlag = BF_DIAGONAL_ENDBOTTOMLEFT;
3304 
3305       ::SetRect(&topRect, R.left + lOffset, R.top, R.right - rOffset, R.bottom);
3306       ::SetRect(&sideRect, R.left, R.top + 2 - selectedOffset, R.right,
3307                 R.bottom - 2);
3308       ::SetRect(&bottomRect, R.left, R.top, R.right, R.top + 2);
3309       ::SetRect(&lightRect, R.left, R.bottom - 3, R.left + 2, R.bottom - 1);
3310       ::SetRect(&shadeRect, R.right - 2, R.bottom - 3, R.right, R.bottom - 1);
3311       break;
3312     default:
3313       MOZ_CRASH();
3314   }
3315 
3316   // Background
3317   ::FillRect(hdc, &R, (HBRUSH)(COLOR_3DFACE + 1));
3318 
3319   // Tab "Top"
3320   ::DrawEdge(hdc, &topRect, EDGE_RAISED, BF_SOFT | topFlag);
3321 
3322   // Tab "Bottom"
3323   if (!aSelected) ::DrawEdge(hdc, &bottomRect, EDGE_RAISED, BF_SOFT | topFlag);
3324 
3325   // Tab "Sides"
3326   if (!aDrawLeft) leftFlag = 0;
3327   if (!aDrawRight) rightFlag = 0;
3328   ::DrawEdge(hdc, &sideRect, EDGE_RAISED, BF_SOFT | leftFlag | rightFlag);
3329 
3330   // Tab Diagonal Corners
3331   if (aDrawLeft) ::DrawEdge(hdc, &lightRect, EDGE_RAISED, BF_SOFT | lightFlag);
3332 
3333   if (aDrawRight) ::DrawEdge(hdc, &shadeRect, EDGE_RAISED, BF_SOFT | shadeFlag);
3334 }
3335 
DrawMenuImage(HDC hdc,const RECT & rc,int32_t aComponent,uint32_t aColor)3336 static void DrawMenuImage(HDC hdc, const RECT& rc, int32_t aComponent,
3337                           uint32_t aColor) {
3338   // This procedure creates a memory bitmap to contain the check mark, draws
3339   // it into the bitmap (it is a mask image), then composes it onto the menu
3340   // item in appropriate colors.
3341   HDC hMemoryDC = ::CreateCompatibleDC(hdc);
3342   if (hMemoryDC) {
3343     // XXXjgr We should ideally be caching these, but we wont be notified when
3344     // they change currently, so we can't do so easily. Same for the bitmap.
3345     int checkW = ::GetSystemMetrics(SM_CXMENUCHECK);
3346     int checkH = ::GetSystemMetrics(SM_CYMENUCHECK);
3347 
3348     HBITMAP hMonoBitmap = ::CreateBitmap(checkW, checkH, 1, 1, nullptr);
3349     if (hMonoBitmap) {
3350       HBITMAP hPrevBitmap = (HBITMAP)::SelectObject(hMemoryDC, hMonoBitmap);
3351       if (hPrevBitmap) {
3352         // XXXjgr This will go pear-shaped if the image is bigger than the
3353         // provided rect. What should we do?
3354         RECT imgRect = {0, 0, checkW, checkH};
3355         POINT imgPos = {rc.left + (rc.right - rc.left - checkW) / 2,
3356                         rc.top + (rc.bottom - rc.top - checkH) / 2};
3357 
3358         // XXXzeniko Windows renders these 1px lower than you'd expect
3359         if (aComponent == DFCS_MENUCHECK || aComponent == DFCS_MENUBULLET)
3360           imgPos.y++;
3361 
3362         ::DrawFrameControl(hMemoryDC, &imgRect, DFC_MENU, aComponent);
3363         COLORREF oldTextCol = ::SetTextColor(hdc, 0x00000000);
3364         COLORREF oldBackCol = ::SetBkColor(hdc, 0x00FFFFFF);
3365         ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0,
3366                  SRCAND);
3367         ::SetTextColor(hdc, ::GetSysColor(aColor));
3368         ::SetBkColor(hdc, 0x00000000);
3369         ::BitBlt(hdc, imgPos.x, imgPos.y, checkW, checkH, hMemoryDC, 0, 0,
3370                  SRCPAINT);
3371         ::SetTextColor(hdc, oldTextCol);
3372         ::SetBkColor(hdc, oldBackCol);
3373         ::SelectObject(hMemoryDC, hPrevBitmap);
3374       }
3375       ::DeleteObject(hMonoBitmap);
3376     }
3377     ::DeleteDC(hMemoryDC);
3378   }
3379 }
3380 
DrawCheckedRect(HDC hdc,const RECT & rc,int32_t fore,int32_t back,HBRUSH defaultBack)3381 void nsNativeThemeWin::DrawCheckedRect(HDC hdc, const RECT& rc, int32_t fore,
3382                                        int32_t back, HBRUSH defaultBack) {
3383   static WORD patBits[8] = {0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55};
3384 
3385   HBITMAP patBmp = ::CreateBitmap(8, 8, 1, 1, patBits);
3386   if (patBmp) {
3387     HBRUSH brush = (HBRUSH)::CreatePatternBrush(patBmp);
3388     if (brush) {
3389       COLORREF oldForeColor = ::SetTextColor(hdc, ::GetSysColor(fore));
3390       COLORREF oldBackColor = ::SetBkColor(hdc, ::GetSysColor(back));
3391       POINT vpOrg;
3392 
3393       ::UnrealizeObject(brush);
3394       ::GetViewportOrgEx(hdc, &vpOrg);
3395       ::SetBrushOrgEx(hdc, vpOrg.x + rc.left, vpOrg.y + rc.top, nullptr);
3396       HBRUSH oldBrush = (HBRUSH)::SelectObject(hdc, brush);
3397       ::FillRect(hdc, &rc, brush);
3398       ::SetTextColor(hdc, oldForeColor);
3399       ::SetBkColor(hdc, oldBackColor);
3400       ::SelectObject(hdc, oldBrush);
3401       ::DeleteObject(brush);
3402     } else
3403       ::FillRect(hdc, &rc, defaultBack);
3404 
3405     ::DeleteObject(patBmp);
3406   }
3407 }
3408 
ClassicDrawWidgetBackground(gfxContext * aContext,nsIFrame * aFrame,uint8_t aWidgetType,const nsRect & aRect,const nsRect & aDirtyRect)3409 nsresult nsNativeThemeWin::ClassicDrawWidgetBackground(
3410     gfxContext* aContext, nsIFrame* aFrame, uint8_t aWidgetType,
3411     const nsRect& aRect, const nsRect& aDirtyRect) {
3412   int32_t part, state;
3413   bool focused;
3414   nsresult rv;
3415   rv = ClassicGetThemePartAndState(aFrame, aWidgetType, part, state, focused);
3416   if (NS_FAILED(rv)) return rv;
3417 
3418   if (AssumeThemePartAndStateAreTransparent(part, state)) {
3419     return NS_OK;
3420   }
3421 
3422   gfxFloat p2a = gfxFloat(aFrame->PresContext()->AppUnitsPerDevPixel());
3423   RECT widgetRect;
3424   gfxRect tr(aRect.X(), aRect.Y(), aRect.Width(), aRect.Height()),
3425       dr(aDirtyRect.X(), aDirtyRect.Y(), aDirtyRect.Width(),
3426          aDirtyRect.Height());
3427 
3428   tr.Scale(1.0 / p2a);
3429   dr.Scale(1.0 / p2a);
3430 
3431   RefPtr<gfxContext> ctx = aContext;
3432 
3433   gfxWindowsNativeDrawing nativeDrawing(
3434       ctx, dr, GetWidgetNativeDrawingFlags(aWidgetType));
3435 
3436 RENDER_AGAIN:
3437 
3438   HDC hdc = nativeDrawing.BeginNativeDrawing();
3439   if (!hdc) return NS_ERROR_FAILURE;
3440 
3441   nativeDrawing.TransformToNativeRect(tr, widgetRect);
3442 
3443   rv = NS_OK;
3444   switch (aWidgetType) {
3445     // Draw button
3446     case NS_THEME_BUTTON: {
3447       if (focused) {
3448         // draw dark button focus border first
3449         HBRUSH brush;
3450         brush = ::GetSysColorBrush(COLOR_3DDKSHADOW);
3451         if (brush) ::FrameRect(hdc, &widgetRect, brush);
3452         InflateRect(&widgetRect, -1, -1);
3453       }
3454       // fall-through...
3455     }
3456     // Draw controls supported by DrawFrameControl
3457     case NS_THEME_CHECKBOX:
3458     case NS_THEME_RADIO:
3459     case NS_THEME_SCROLLBARBUTTON_UP:
3460     case NS_THEME_SCROLLBARBUTTON_DOWN:
3461     case NS_THEME_SCROLLBARBUTTON_LEFT:
3462     case NS_THEME_SCROLLBARBUTTON_RIGHT:
3463     case NS_THEME_INNER_SPIN_BUTTON:
3464     case NS_THEME_SPINNER_UPBUTTON:
3465     case NS_THEME_SPINNER_DOWNBUTTON:
3466     case NS_THEME_MENULIST_BUTTON:
3467     case NS_THEME_RESIZER: {
3468       int32_t oldTA;
3469       // setup DC to make DrawFrameControl draw correctly
3470       oldTA = ::SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
3471       ::DrawFrameControl(hdc, &widgetRect, part, state);
3472       ::SetTextAlign(hdc, oldTA);
3473       break;
3474     }
3475     // Draw controls with 2px 3D inset border
3476     case NS_THEME_NUMBER_INPUT:
3477     case NS_THEME_TEXTFIELD:
3478     case NS_THEME_TEXTFIELD_MULTILINE:
3479     case NS_THEME_LISTBOX:
3480     case NS_THEME_MENULIST:
3481     case NS_THEME_MENULIST_TEXTFIELD: {
3482       // Draw inset edge
3483       ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
3484       EventStates eventState = GetContentState(aFrame, aWidgetType);
3485 
3486       // Fill in background
3487       if (IsDisabled(aFrame, eventState) ||
3488           (aFrame->GetContent()->IsXULElement() && IsReadOnly(aFrame)))
3489         ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
3490       else
3491         ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
3492 
3493       break;
3494     }
3495     case NS_THEME_TREEVIEW: {
3496       // Draw inset edge
3497       ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
3498 
3499       // Fill in window color background
3500       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_WINDOW + 1));
3501 
3502       break;
3503     }
3504     // Draw ToolTip background
3505     case NS_THEME_TOOLTIP:
3506       ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_WINDOWFRAME));
3507       InflateRect(&widgetRect, -1, -1);
3508       ::FillRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_INFOBK));
3509 
3510       break;
3511     case NS_THEME_GROUPBOX:
3512       ::DrawEdge(hdc, &widgetRect, EDGE_ETCHED, BF_RECT | BF_ADJUST);
3513       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
3514       break;
3515     // Draw 3D face background controls
3516     case NS_THEME_PROGRESSBAR:
3517     case NS_THEME_PROGRESSBAR_VERTICAL:
3518       // Draw 3D border
3519       ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
3520       InflateRect(&widgetRect, -1, -1);
3521       // fall through
3522     case NS_THEME_TABPANEL:
3523     case NS_THEME_STATUSBAR:
3524     case NS_THEME_RESIZERPANEL: {
3525       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_BTNFACE + 1));
3526 
3527       break;
3528     }
3529     // Draw 3D inset statusbar panel
3530     case NS_THEME_STATUSBARPANEL: {
3531       if (aFrame->GetNextSibling())
3532         widgetRect.right -= 2;  // space between sibling status panels
3533 
3534       ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT | BF_MIDDLE);
3535 
3536       break;
3537     }
3538     // Draw scrollbar thumb
3539     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
3540     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
3541       ::DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
3542 
3543       break;
3544     case NS_THEME_RANGE_THUMB:
3545     case NS_THEME_SCALETHUMB_VERTICAL:
3546     case NS_THEME_SCALETHUMB_HORIZONTAL: {
3547       EventStates eventState = GetContentState(aFrame, aWidgetType);
3548 
3549       ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
3550                  BF_RECT | BF_SOFT | BF_MIDDLE | BF_ADJUST);
3551       if (IsDisabled(aFrame, eventState)) {
3552         DrawCheckedRect(hdc, widgetRect, COLOR_3DFACE, COLOR_3DHILIGHT,
3553                         (HBRUSH)COLOR_3DHILIGHT);
3554       }
3555 
3556       break;
3557     }
3558     // Draw scrollbar track background
3559     case NS_THEME_SCROLLBAR_VERTICAL:
3560     case NS_THEME_SCROLLBAR_HORIZONTAL: {
3561       // Windows fills in the scrollbar track differently
3562       // depending on whether these are equal
3563       DWORD color3D, colorScrollbar, colorWindow;
3564 
3565       color3D = ::GetSysColor(COLOR_3DFACE);
3566       colorWindow = ::GetSysColor(COLOR_WINDOW);
3567       colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR);
3568 
3569       if ((color3D != colorScrollbar) && (colorWindow != colorScrollbar))
3570         // Use solid brush
3571         ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_SCROLLBAR + 1));
3572       else {
3573         DrawCheckedRect(hdc, widgetRect, COLOR_3DHILIGHT, COLOR_3DFACE,
3574                         (HBRUSH)COLOR_SCROLLBAR + 1);
3575       }
3576       // XXX should invert the part of the track being clicked here
3577       // but the track is never :active
3578 
3579       break;
3580     }
3581     // Draw scale track background
3582     case NS_THEME_RANGE:
3583     case NS_THEME_SCALE_VERTICAL:
3584     case NS_THEME_SCALE_HORIZONTAL: {
3585       const int32_t trackWidth = 4;
3586       // When rounding is necessary, we round the position of the track
3587       // away from the chevron of the thumb to make it look better.
3588       if (aWidgetType == NS_THEME_SCALE_HORIZONTAL ||
3589           (aWidgetType == NS_THEME_RANGE && IsRangeHorizontal(aFrame))) {
3590         widgetRect.top += (widgetRect.bottom - widgetRect.top - trackWidth) / 2;
3591         widgetRect.bottom = widgetRect.top + trackWidth;
3592       } else {
3593         if (!IsFrameRTL(aFrame)) {
3594           widgetRect.left +=
3595               (widgetRect.right - widgetRect.left - trackWidth) / 2;
3596           widgetRect.right = widgetRect.left + trackWidth;
3597         } else {
3598           widgetRect.right -=
3599               (widgetRect.right - widgetRect.left - trackWidth) / 2;
3600           widgetRect.left = widgetRect.right - trackWidth;
3601         }
3602       }
3603 
3604       ::DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
3605       ::FillRect(hdc, &widgetRect, (HBRUSH)GetStockObject(GRAY_BRUSH));
3606 
3607       break;
3608     }
3609     case NS_THEME_PROGRESSCHUNK_VERTICAL:
3610       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3611       break;
3612 
3613     case NS_THEME_PROGRESSCHUNK: {
3614       nsIFrame* stateFrame = aFrame->GetParent();
3615       EventStates eventStates = GetContentState(stateFrame, aWidgetType);
3616 
3617       bool indeterminate = IsIndeterminateProgress(stateFrame, eventStates);
3618       bool vertical = IsVerticalProgress(stateFrame) ||
3619                       aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL;
3620 
3621       nsIContent* content = aFrame->GetContent();
3622       if (!indeterminate || !content) {
3623         ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3624         break;
3625       }
3626 
3627       RECT overlayRect = CalculateProgressOverlayRect(
3628           aFrame, &widgetRect, vertical, indeterminate, true);
3629 
3630       ::FillRect(hdc, &overlayRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3631 
3632       if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
3633         NS_WARNING("unable to animate progress widget!");
3634       }
3635       break;
3636     }
3637 
3638     // Draw Tab
3639     case NS_THEME_TAB: {
3640       DrawTab(hdc, widgetRect, IsBottomTab(aFrame) ? BF_BOTTOM : BF_TOP,
3641               IsSelectedTab(aFrame), !IsRightToSelectedTab(aFrame),
3642               !IsLeftToSelectedTab(aFrame));
3643 
3644       break;
3645     }
3646     case NS_THEME_TABPANELS:
3647       ::DrawEdge(hdc, &widgetRect, EDGE_RAISED,
3648                  BF_SOFT | BF_MIDDLE | BF_LEFT | BF_RIGHT | BF_BOTTOM);
3649 
3650       break;
3651     case NS_THEME_MENUBAR:
3652       break;
3653     case NS_THEME_MENUPOPUP:
3654       NS_ASSERTION(nsUXThemeData::sFlatMenus,
3655                    "Classic menus are styled entirely through CSS");
3656       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_MENU + 1));
3657       ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_BTNSHADOW));
3658       break;
3659     case NS_THEME_MENUITEM:
3660     case NS_THEME_CHECKMENUITEM:
3661     case NS_THEME_RADIOMENUITEM:
3662       // part == 0 for normal items
3663       // part == 1 for top-level menu items
3664       if (nsUXThemeData::sFlatMenus) {
3665         // Not disabled and hot/pushed.
3666         if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
3667           ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_MENUHILIGHT + 1));
3668           ::FrameRect(hdc, &widgetRect, ::GetSysColorBrush(COLOR_HIGHLIGHT));
3669         }
3670       } else {
3671         if (part == 1) {
3672           if ((state & DFCS_INACTIVE) == 0) {
3673             if ((state & DFCS_PUSHED) != 0) {
3674               ::DrawEdge(hdc, &widgetRect, BDR_SUNKENOUTER, BF_RECT);
3675             } else if ((state & DFCS_HOT) != 0) {
3676               ::DrawEdge(hdc, &widgetRect, BDR_RAISEDINNER, BF_RECT);
3677             }
3678           }
3679         } else {
3680           if ((state & (DFCS_HOT | DFCS_PUSHED)) != 0) {
3681             ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_HIGHLIGHT + 1));
3682           }
3683         }
3684       }
3685       break;
3686     case NS_THEME_MENUCHECKBOX:
3687     case NS_THEME_MENURADIO:
3688       if (!(state & DFCS_CHECKED)) break;  // nothin' to do
3689     case NS_THEME_MENUARROW: {
3690       uint32_t color = COLOR_MENUTEXT;
3691       if ((state & DFCS_INACTIVE))
3692         color = COLOR_GRAYTEXT;
3693       else if ((state & DFCS_HOT))
3694         color = COLOR_HIGHLIGHTTEXT;
3695 
3696       if (aWidgetType == NS_THEME_MENUCHECKBOX)
3697         DrawMenuImage(hdc, widgetRect, DFCS_MENUCHECK, color);
3698       else if (aWidgetType == NS_THEME_MENURADIO)
3699         DrawMenuImage(hdc, widgetRect, DFCS_MENUBULLET, color);
3700       else if (aWidgetType == NS_THEME_MENUARROW)
3701         DrawMenuImage(hdc, widgetRect,
3702                       (state & DFCS_RTL) ? DFCS_MENUARROWRIGHT : DFCS_MENUARROW,
3703                       color);
3704       break;
3705     }
3706     case NS_THEME_MENUSEPARATOR: {
3707       // separators are offset by a bit (see menu.css)
3708       widgetRect.left++;
3709       widgetRect.right--;
3710 
3711       // This magic number is brought to you by the value in menu.css
3712       widgetRect.top += 4;
3713       // Our rectangles are 1 pixel high (see border size in menu.css)
3714       widgetRect.bottom = widgetRect.top + 1;
3715       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DSHADOW + 1));
3716       widgetRect.top++;
3717       widgetRect.bottom++;
3718       ::FillRect(hdc, &widgetRect, (HBRUSH)(COLOR_3DHILIGHT + 1));
3719       break;
3720     }
3721 
3722     case NS_THEME_WINDOW_TITLEBAR:
3723     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED: {
3724       RECT rect = widgetRect;
3725       int32_t offset = GetSystemMetrics(SM_CXFRAME);
3726 
3727       // first fill the area to the color of the window background
3728       FillRect(hdc, &rect, (HBRUSH)(COLOR_3DFACE + 1));
3729 
3730       // inset the caption area so it doesn't overflow.
3731       rect.top += offset;
3732       // if enabled, draw a gradient titlebar background, otherwise
3733       // fill with a solid color.
3734       BOOL bFlag = TRUE;
3735       SystemParametersInfo(SPI_GETGRADIENTCAPTIONS, 0, &bFlag, 0);
3736       if (!bFlag) {
3737         if (state == mozilla::widget::themeconst::FS_ACTIVE)
3738           FillRect(hdc, &rect, (HBRUSH)(COLOR_ACTIVECAPTION + 1));
3739         else
3740           FillRect(hdc, &rect, (HBRUSH)(COLOR_INACTIVECAPTION + 1));
3741       } else {
3742         DWORD startColor, endColor;
3743         if (state == mozilla::widget::themeconst::FS_ACTIVE) {
3744           startColor = GetSysColor(COLOR_ACTIVECAPTION);
3745           endColor = GetSysColor(COLOR_GRADIENTACTIVECAPTION);
3746         } else {
3747           startColor = GetSysColor(COLOR_INACTIVECAPTION);
3748           endColor = GetSysColor(COLOR_GRADIENTINACTIVECAPTION);
3749         }
3750 
3751         TRIVERTEX vertex[2];
3752         vertex[0].x = rect.left;
3753         vertex[0].y = rect.top;
3754         vertex[0].Red = GetRValue(startColor) << 8;
3755         vertex[0].Green = GetGValue(startColor) << 8;
3756         vertex[0].Blue = GetBValue(startColor) << 8;
3757         vertex[0].Alpha = 0;
3758 
3759         vertex[1].x = rect.right;
3760         vertex[1].y = rect.bottom;
3761         vertex[1].Red = GetRValue(endColor) << 8;
3762         vertex[1].Green = GetGValue(endColor) << 8;
3763         vertex[1].Blue = GetBValue(endColor) << 8;
3764         vertex[1].Alpha = 0;
3765 
3766         GRADIENT_RECT gRect;
3767         gRect.UpperLeft = 0;
3768         gRect.LowerRight = 1;
3769         // available on win2k & up
3770         GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H);
3771       }
3772 
3773       if (aWidgetType == NS_THEME_WINDOW_TITLEBAR) {
3774         // frame things up with a top raised border.
3775         DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_TOP);
3776       }
3777       break;
3778     }
3779 
3780     case NS_THEME_WINDOW_FRAME_LEFT:
3781       DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_LEFT);
3782       break;
3783 
3784     case NS_THEME_WINDOW_FRAME_RIGHT:
3785       DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_RIGHT);
3786       break;
3787 
3788     case NS_THEME_WINDOW_FRAME_BOTTOM:
3789       DrawEdge(hdc, &widgetRect, EDGE_RAISED, BF_BOTTOM);
3790       break;
3791 
3792     case NS_THEME_WINDOW_BUTTON_CLOSE:
3793     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
3794     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
3795     case NS_THEME_WINDOW_BUTTON_RESTORE: {
3796       if (aWidgetType == NS_THEME_WINDOW_BUTTON_MINIMIZE) {
3797         OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_MINIMIZE);
3798       } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_MAXIMIZE ||
3799                  aWidgetType == NS_THEME_WINDOW_BUTTON_RESTORE) {
3800         OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_RESTORE);
3801       } else if (aWidgetType == NS_THEME_WINDOW_BUTTON_CLOSE) {
3802         OffsetBackgroundRect(widgetRect, CAPTIONBUTTON_CLOSE);
3803       }
3804       int32_t oldTA = SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
3805       DrawFrameControl(hdc, &widgetRect, part, state);
3806       SetTextAlign(hdc, oldTA);
3807       break;
3808     }
3809 
3810     default:
3811       rv = NS_ERROR_FAILURE;
3812       break;
3813   }
3814 
3815   nativeDrawing.EndNativeDrawing();
3816 
3817   if (NS_FAILED(rv)) return rv;
3818 
3819   if (nativeDrawing.ShouldRenderAgain()) goto RENDER_AGAIN;
3820 
3821   nativeDrawing.PaintToContext();
3822 
3823   return rv;
3824 }
3825 
GetWidgetNativeDrawingFlags(uint8_t aWidgetType)3826 uint32_t nsNativeThemeWin::GetWidgetNativeDrawingFlags(uint8_t aWidgetType) {
3827   switch (aWidgetType) {
3828     case NS_THEME_BUTTON:
3829     case NS_THEME_NUMBER_INPUT:
3830     case NS_THEME_FOCUS_OUTLINE:
3831     case NS_THEME_TEXTFIELD:
3832     case NS_THEME_TEXTFIELD_MULTILINE:
3833 
3834     case NS_THEME_MENULIST:
3835     case NS_THEME_MENULIST_TEXTFIELD:
3836       return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3837              gfxWindowsNativeDrawing::CAN_AXIS_ALIGNED_SCALE |
3838              gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3839 
3840     // need to check these others
3841     case NS_THEME_RANGE:
3842     case NS_THEME_RANGE_THUMB:
3843     case NS_THEME_SCROLLBARBUTTON_UP:
3844     case NS_THEME_SCROLLBARBUTTON_DOWN:
3845     case NS_THEME_SCROLLBARBUTTON_LEFT:
3846     case NS_THEME_SCROLLBARBUTTON_RIGHT:
3847     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
3848     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
3849     case NS_THEME_SCROLLBAR_VERTICAL:
3850     case NS_THEME_SCROLLBAR_HORIZONTAL:
3851     case NS_THEME_SCALE_HORIZONTAL:
3852     case NS_THEME_SCALE_VERTICAL:
3853     case NS_THEME_SCALETHUMB_HORIZONTAL:
3854     case NS_THEME_SCALETHUMB_VERTICAL:
3855     case NS_THEME_INNER_SPIN_BUTTON:
3856     case NS_THEME_SPINNER_UPBUTTON:
3857     case NS_THEME_SPINNER_DOWNBUTTON:
3858     case NS_THEME_LISTBOX:
3859     case NS_THEME_TREEVIEW:
3860     case NS_THEME_TOOLTIP:
3861     case NS_THEME_STATUSBAR:
3862     case NS_THEME_STATUSBARPANEL:
3863     case NS_THEME_RESIZERPANEL:
3864     case NS_THEME_RESIZER:
3865     case NS_THEME_PROGRESSBAR:
3866     case NS_THEME_PROGRESSBAR_VERTICAL:
3867     case NS_THEME_PROGRESSCHUNK:
3868     case NS_THEME_PROGRESSCHUNK_VERTICAL:
3869     case NS_THEME_TAB:
3870     case NS_THEME_TABPANEL:
3871     case NS_THEME_TABPANELS:
3872     case NS_THEME_MENUBAR:
3873     case NS_THEME_MENUPOPUP:
3874     case NS_THEME_MENUITEM:
3875       break;
3876 
3877     // the dropdown button /almost/ renders correctly with scaling,
3878     // except that the graphic in the dropdown button (the downward arrow)
3879     // doesn't get scaled up.
3880     case NS_THEME_MENULIST_BUTTON:
3881     // these are definitely no; they're all graphics that don't get scaled up
3882     case NS_THEME_CHECKBOX:
3883     case NS_THEME_RADIO:
3884     case NS_THEME_GROUPBOX:
3885     case NS_THEME_CHECKMENUITEM:
3886     case NS_THEME_RADIOMENUITEM:
3887     case NS_THEME_MENUCHECKBOX:
3888     case NS_THEME_MENURADIO:
3889     case NS_THEME_MENUARROW:
3890       return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3891              gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
3892              gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3893   }
3894 
3895   return gfxWindowsNativeDrawing::CANNOT_DRAW_TO_COLOR_ALPHA |
3896          gfxWindowsNativeDrawing::CANNOT_AXIS_ALIGNED_SCALE |
3897          gfxWindowsNativeDrawing::CANNOT_COMPLEX_TRANSFORM;
3898 }
3899 
3900 ///////////////////////////////////////////
3901 // Creation Routine
3902 ///////////////////////////////////////////
3903 
3904 // from nsWindow.cpp
3905 extern bool gDisableNativeTheme;
3906 
NS_NewNativeTheme(nsISupports * aOuter,REFNSIID aIID,void ** aResult)3907 nsresult NS_NewNativeTheme(nsISupports* aOuter, REFNSIID aIID, void** aResult) {
3908   if (gDisableNativeTheme) return NS_ERROR_NO_INTERFACE;
3909 
3910   if (aOuter) return NS_ERROR_NO_AGGREGATION;
3911 
3912   nsNativeThemeWin* theme = new nsNativeThemeWin();
3913   if (!theme) return NS_ERROR_OUT_OF_MEMORY;
3914   return theme->QueryInterface(aIID, aResult);
3915 }
3916