1 /* -*- Mode: C++; tab-width: 2; 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 "nsNativeThemeGTK.h"
7 #include "nsThemeConstants.h"
8 #include "gtkdrawing.h"
9 #include "ScreenHelperGTK.h"
10 
11 #include "gfx2DGlue.h"
12 #include "nsIObserverService.h"
13 #include "nsIServiceManager.h"
14 #include "nsIFrame.h"
15 #include "nsIPresShell.h"
16 #include "nsIContent.h"
17 #include "nsViewManager.h"
18 #include "nsNameSpaceManager.h"
19 #include "nsGfxCIID.h"
20 #include "nsTransform2D.h"
21 #include "nsMenuFrame.h"
22 #include "prlink.h"
23 #include "nsGkAtoms.h"
24 #include "nsAttrValueInlines.h"
25 
26 #include "mozilla/dom/HTMLInputElement.h"
27 #include "mozilla/EventStates.h"
28 #include "mozilla/Services.h"
29 
30 #include <gdk/gdkprivate.h>
31 #include <gtk/gtk.h>
32 #include <gtk/gtkx.h>
33 
34 #include "gfxContext.h"
35 #include "gfxPlatformGtk.h"
36 #include "gfxGdkNativeRenderer.h"
37 #include "mozilla/gfx/BorrowedContext.h"
38 #include "mozilla/gfx/HelpersCairo.h"
39 #include "mozilla/gfx/PathHelpers.h"
40 #include "mozilla/Preferences.h"
41 
42 #ifdef MOZ_X11
43 #ifdef CAIRO_HAS_XLIB_SURFACE
44 #include "cairo-xlib.h"
45 #endif
46 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
47 #include "cairo-xlib-xrender.h"
48 #endif
49 #endif
50 
51 #include <algorithm>
52 #include <dlfcn.h>
53 
54 using namespace mozilla;
55 using namespace mozilla::gfx;
56 using namespace mozilla::widget;
57 using mozilla::dom::HTMLInputElement;
58 
59 NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
60                             nsIObserver)
61 
62 static int gLastGdkError;
63 
64 // Return scale factor of the monitor where the window is located
65 // by the most part or layout.css.devPixelsPerPx pref if set to > 0.
GetMonitorScaleFactor(nsIFrame * aFrame)66 static inline gint GetMonitorScaleFactor(nsIFrame* aFrame) {
67   // When the layout.css.devPixelsPerPx is set the scale can be < 1,
68   // the real monitor scale cannot go under 1.
69   double scale = nsIWidget::DefaultScaleOverride();
70   if (scale <= 0) {
71     nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
72     if (rootWidget) {
73       // We need to use GetDefaultScale() despite it returns monitor scale
74       // factor multiplied by font scale factor because it is the only scale
75       // updated in nsPuppetWidget.
76       // Since we don't want to apply font scale factor for UI elements
77       // (because GTK does not do so) we need to remove that from returned
78       // value. The computed monitor scale factor needs to be rounded before
79       // casting to integer to avoid rounding errors which would lead to
80       // returning 0.
81       int monitorScale = int(round(rootWidget->GetDefaultScale().scale /
82                                    gfxPlatformGtk::GetFontScaleFactor()));
83       // Monitor scale can be negative if it has not been initialized in the
84       // puppet widget yet. We also make sure that we return positive value.
85       if (monitorScale < 1) {
86         return 1;
87       }
88       return monitorScale;
89     }
90   }
91   // Use monitor scaling factor where devPixelsPerPx is set
92   return ScreenHelperGTK::GetGTKMonitorScaleFactor();
93 }
94 
nsNativeThemeGTK()95 nsNativeThemeGTK::nsNativeThemeGTK() {
96   if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
97     memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
98     return;
99   }
100 
101   // We have to call moz_gtk_shutdown before the event loop stops running.
102   nsCOMPtr<nsIObserverService> obsServ =
103       mozilla::services::GetObserverService();
104   obsServ->AddObserver(this, "xpcom-shutdown", false);
105 
106   ThemeChanged();
107 }
108 
~nsNativeThemeGTK()109 nsNativeThemeGTK::~nsNativeThemeGTK() {}
110 
111 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)112 nsNativeThemeGTK::Observe(nsISupports* aSubject, const char* aTopic,
113                           const char16_t* aData) {
114   if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
115     moz_gtk_shutdown();
116   } else {
117     NS_NOTREACHED("unexpected topic");
118     return NS_ERROR_UNEXPECTED;
119   }
120 
121   return NS_OK;
122 }
123 
RefreshWidgetWindow(nsIFrame * aFrame)124 void nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) {
125   nsIPresShell* shell = GetPresShell(aFrame);
126   if (!shell) return;
127 
128   nsViewManager* vm = shell->GetViewManager();
129   if (!vm) return;
130 
131   vm->InvalidateAllViews();
132 }
133 
IsFrameContentNodeInNamespace(nsIFrame * aFrame,uint32_t aNamespace)134 static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
135                                           uint32_t aNamespace) {
136   nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
137   if (!content) return false;
138   return content->IsInNamespace(aNamespace);
139 }
140 
IsWidgetTypeDisabled(uint8_t * aDisabledVector,uint8_t aWidgetType)141 static bool IsWidgetTypeDisabled(uint8_t* aDisabledVector,
142                                  uint8_t aWidgetType) {
143   MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
144   return (aDisabledVector[aWidgetType >> 3] & (1 << (aWidgetType & 7))) != 0;
145 }
146 
SetWidgetTypeDisabled(uint8_t * aDisabledVector,uint8_t aWidgetType)147 static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
148                                   uint8_t aWidgetType) {
149   MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
150   aDisabledVector[aWidgetType >> 3] |= (1 << (aWidgetType & 7));
151 }
152 
GetWidgetStateKey(uint8_t aWidgetType,GtkWidgetState * aWidgetState)153 static inline uint16_t GetWidgetStateKey(uint8_t aWidgetType,
154                                          GtkWidgetState* aWidgetState) {
155   return (aWidgetState->active | aWidgetState->focused << 1 |
156           aWidgetState->inHover << 2 | aWidgetState->disabled << 3 |
157           aWidgetState->isDefault << 4 | aWidgetType << 5);
158 }
159 
IsWidgetStateSafe(uint8_t * aSafeVector,uint8_t aWidgetType,GtkWidgetState * aWidgetState)160 static bool IsWidgetStateSafe(uint8_t* aSafeVector, uint8_t aWidgetType,
161                               GtkWidgetState* aWidgetState) {
162   MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
163   uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
164   return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
165 }
166 
SetWidgetStateSafe(uint8_t * aSafeVector,uint8_t aWidgetType,GtkWidgetState * aWidgetState)167 static void SetWidgetStateSafe(uint8_t* aSafeVector, uint8_t aWidgetType,
168                                GtkWidgetState* aWidgetState) {
169   MOZ_ASSERT(aWidgetType < ThemeWidgetType_COUNT);
170   uint16_t key = GetWidgetStateKey(aWidgetType, aWidgetState);
171   aSafeVector[key >> 3] |= (1 << (key & 7));
172 }
173 
GetTextDirection(nsIFrame * aFrame)174 /* static */ GtkTextDirection nsNativeThemeGTK::GetTextDirection(
175     nsIFrame* aFrame) {
176   // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
177   // horizontal text with direction=RTL), rather than just considering the
178   // text direction.  GtkTextDirection does not have distinct values for
179   // vertical writing modes, but considering the block flow direction is
180   // important for resizers and scrollbar elements, at least.
181   return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
182 }
183 
184 // Returns positive for negative margins (otherwise 0).
GetTabMarginPixels(nsIFrame * aFrame)185 gint nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) {
186   nscoord margin = IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
187                                        : aFrame->GetUsedMargin().bottom;
188 
189   return std::min<gint>(
190       MOZ_GTK_TAB_MARGIN_MASK,
191       std::max(0, aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
192 }
193 
ShouldScrollbarButtonBeDisabled(int32_t aCurpos,int32_t aMaxpos,uint8_t aWidgetType)194 static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
195                                             uint8_t aWidgetType) {
196   return (
197       (aCurpos == 0 && (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
198                         aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT)) ||
199       (aCurpos == aMaxpos && (aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
200                               aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT)));
201 }
202 
GetGtkWidgetAndState(uint8_t aWidgetType,nsIFrame * aFrame,WidgetNodeType & aGtkWidgetType,GtkWidgetState * aState,gint * aWidgetFlags)203 bool nsNativeThemeGTK::GetGtkWidgetAndState(uint8_t aWidgetType,
204                                             nsIFrame* aFrame,
205                                             WidgetNodeType& aGtkWidgetType,
206                                             GtkWidgetState* aState,
207                                             gint* aWidgetFlags) {
208   if (aState) {
209     // For XUL checkboxes and radio buttons, the state of the parent
210     // determines our state.
211     nsIFrame* stateFrame = aFrame;
212     if (aFrame && ((aWidgetFlags && (aWidgetType == NS_THEME_CHECKBOX ||
213                                      aWidgetType == NS_THEME_RADIO)) ||
214                    aWidgetType == NS_THEME_CHECKBOX_LABEL ||
215                    aWidgetType == NS_THEME_RADIO_LABEL)) {
216       nsAtom* atom = nullptr;
217       if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
218         if (aWidgetType == NS_THEME_CHECKBOX_LABEL ||
219             aWidgetType == NS_THEME_RADIO_LABEL) {
220           // Adjust stateFrame so GetContentState finds the correct state.
221           stateFrame = aFrame = aFrame->GetParent()->GetParent();
222         } else {
223           // GetContentState knows to look one frame up for radio/checkbox
224           // widgets, so don't adjust stateFrame here.
225           aFrame = aFrame->GetParent();
226         }
227         if (aWidgetFlags) {
228           if (!atom) {
229             atom = (aWidgetType == NS_THEME_CHECKBOX ||
230                     aWidgetType == NS_THEME_CHECKBOX_LABEL)
231                        ? nsGkAtoms::checked
232                        : nsGkAtoms::selected;
233           }
234           *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
235         }
236       } else {
237         if (aWidgetFlags) {
238           *aWidgetFlags = 0;
239           HTMLInputElement* inputElt =
240               HTMLInputElement::FromContent(aFrame->GetContent());
241           if (inputElt && inputElt->Checked())
242             *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
243 
244           if (GetIndeterminate(aFrame))
245             *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
246         }
247       }
248     } else if (aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
249                aWidgetType == NS_THEME_TREEHEADERSORTARROW ||
250                aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS ||
251                aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
252                aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
253                aWidgetType == NS_THEME_BUTTON_ARROW_DOWN) {
254       // The state of an arrow comes from its parent.
255       stateFrame = aFrame = aFrame->GetParent();
256     }
257 
258     EventStates eventState = GetContentState(stateFrame, aWidgetType);
259 
260     aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
261     aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
262     aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
263     aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
264     aState->isDefault = IsDefaultButton(aFrame);
265     aState->canDefault = FALSE;  // XXX fix me
266     aState->depressed = FALSE;
267 
268     if (aWidgetType == NS_THEME_FOCUS_OUTLINE) {
269       aState->disabled = FALSE;
270       aState->active = FALSE;
271       aState->inHover = FALSE;
272       aState->isDefault = FALSE;
273       aState->canDefault = FALSE;
274 
275       aState->focused = TRUE;
276       aState->depressed = TRUE;  // see moz_gtk_entry_paint()
277     } else if (aWidgetType == NS_THEME_BUTTON ||
278                aWidgetType == NS_THEME_TOOLBARBUTTON ||
279                aWidgetType == NS_THEME_DUALBUTTON ||
280                aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
281                aWidgetType == NS_THEME_MENULIST ||
282                aWidgetType == NS_THEME_MENULIST_BUTTON) {
283       aState->active &= aState->inHover;
284     }
285 
286     if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
287       // For these widget types, some element (either a child or parent)
288       // actually has element focus, so we check the focused attribute
289       // to see whether to draw in the focused state.
290       if (aWidgetType == NS_THEME_NUMBER_INPUT ||
291           aWidgetType == NS_THEME_TEXTFIELD ||
292           aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||
293           aWidgetType == NS_THEME_MENULIST_TEXTFIELD ||
294           aWidgetType == NS_THEME_SPINNER_TEXTFIELD ||
295           aWidgetType == NS_THEME_RADIO_CONTAINER ||
296           aWidgetType == NS_THEME_RADIO_LABEL) {
297         aState->focused = IsFocused(aFrame);
298       } else if (aWidgetType == NS_THEME_RADIO ||
299                  aWidgetType == NS_THEME_CHECKBOX) {
300         // In XUL, checkboxes and radios shouldn't have focus rings, their
301         // labels do
302         aState->focused = FALSE;
303       }
304 
305       if (aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
306           aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) {
307         // for scrollbars we need to go up two to go from the thumb to
308         // the slider to the actual scrollbar object
309         nsIFrame* tmpFrame = aFrame->GetParent()->GetParent();
310 
311         aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
312         aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
313 
314         if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
315           aState->active = TRUE;
316           // Set hover state to emulate Gtk style of active scrollbar thumb
317           aState->inHover = TRUE;
318         }
319       }
320 
321       if (aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
322           aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
323           aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
324           aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) {
325         // set the state to disabled when the scrollbar is scrolled to
326         // the beginning or the end, depending on the button type.
327         int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
328         int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
329         if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType)) {
330           aState->disabled = true;
331         }
332 
333         // In order to simulate native GTK scrollbar click behavior,
334         // we set the active attribute on the element to true if it's
335         // pressed with any mouse button.
336         // This allows us to show that it's active without setting :active
337         else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
338           aState->active = true;
339 
340         if (aWidgetFlags) {
341           *aWidgetFlags = GetScrollbarButtonType(aFrame);
342           if (aWidgetType - NS_THEME_SCROLLBARBUTTON_UP < 2)
343             *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
344         }
345       }
346 
347       // menu item state is determined by the attribute "_moz-menuactive",
348       // and not by the mouse hovering (accessibility).  as a special case,
349       // menus which are children of a menu bar are only marked as prelight
350       // if they are open, not on normal hover.
351 
352       if (aWidgetType == NS_THEME_MENUITEM ||
353           aWidgetType == NS_THEME_CHECKMENUITEM ||
354           aWidgetType == NS_THEME_RADIOMENUITEM ||
355           aWidgetType == NS_THEME_MENUSEPARATOR ||
356           aWidgetType == NS_THEME_MENUARROW) {
357         bool isTopLevel = false;
358         nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
359         if (menuFrame) {
360           isTopLevel = menuFrame->IsOnMenuBar();
361         }
362 
363         if (isTopLevel) {
364           aState->inHover = menuFrame->IsOpen();
365         } else {
366           aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
367         }
368 
369         aState->active = FALSE;
370 
371         if (aWidgetType == NS_THEME_CHECKMENUITEM ||
372             aWidgetType == NS_THEME_RADIOMENUITEM) {
373           *aWidgetFlags = 0;
374           if (aFrame && aFrame->GetContent() &&
375               aFrame->GetContent()->IsElement()) {
376             *aWidgetFlags = aFrame->GetContent()->AsElement()->AttrValueIs(
377                 kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true,
378                 eIgnoreCase);
379           }
380         }
381       }
382 
383       // A button with drop down menu open or an activated toggle button
384       // should always appear depressed.
385       if (aWidgetType == NS_THEME_BUTTON ||
386           aWidgetType == NS_THEME_TOOLBARBUTTON ||
387           aWidgetType == NS_THEME_DUALBUTTON ||
388           aWidgetType == NS_THEME_TOOLBARBUTTON_DROPDOWN ||
389           aWidgetType == NS_THEME_MENULIST ||
390           aWidgetType == NS_THEME_MENULIST_BUTTON) {
391         bool menuOpen = IsOpenButton(aFrame);
392         aState->depressed = IsCheckedButton(aFrame) || menuOpen;
393         // we must not highlight buttons with open drop down menus on hover.
394         aState->inHover = aState->inHover && !menuOpen;
395       }
396 
397       // When the input field of the drop down button has focus, some themes
398       // should draw focus for the drop down button as well.
399       if (aWidgetType == NS_THEME_MENULIST_BUTTON && aWidgetFlags) {
400         *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
401       }
402     }
403   }
404 
405   switch (aWidgetType) {
406     case NS_THEME_BUTTON:
407       if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL;
408       aGtkWidgetType = MOZ_GTK_BUTTON;
409       break;
410     case NS_THEME_TOOLBARBUTTON:
411     case NS_THEME_DUALBUTTON:
412       if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE;
413       aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
414       break;
415     case NS_THEME_FOCUS_OUTLINE:
416       aGtkWidgetType = MOZ_GTK_ENTRY;
417       break;
418     case NS_THEME_CHECKBOX:
419     case NS_THEME_RADIO:
420       aGtkWidgetType = (aWidgetType == NS_THEME_RADIO) ? MOZ_GTK_RADIOBUTTON
421                                                        : MOZ_GTK_CHECKBUTTON;
422       break;
423     case NS_THEME_SCROLLBARBUTTON_UP:
424     case NS_THEME_SCROLLBARBUTTON_DOWN:
425     case NS_THEME_SCROLLBARBUTTON_LEFT:
426     case NS_THEME_SCROLLBARBUTTON_RIGHT:
427       aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
428       break;
429     case NS_THEME_SCROLLBAR_VERTICAL:
430       aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
431       if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
432         *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
433       else
434         *aWidgetFlags = 0;
435       break;
436     case NS_THEME_SCROLLBAR_HORIZONTAL:
437       aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
438       if (GetWidgetTransparency(aFrame, aWidgetType) == eOpaque)
439         *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
440       else
441         *aWidgetFlags = 0;
442       break;
443     case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
444       aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
445       break;
446     case NS_THEME_SCROLLBARTRACK_VERTICAL:
447       aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
448       break;
449     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
450       aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
451       break;
452     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
453       aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
454       break;
455     case NS_THEME_INNER_SPIN_BUTTON:
456       aGtkWidgetType = MOZ_GTK_INNER_SPIN_BUTTON;
457       break;
458     case NS_THEME_SPINNER:
459       aGtkWidgetType = MOZ_GTK_SPINBUTTON;
460       break;
461     case NS_THEME_SPINNER_UPBUTTON:
462       aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
463       break;
464     case NS_THEME_SPINNER_DOWNBUTTON:
465       aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
466       break;
467     case NS_THEME_SPINNER_TEXTFIELD:
468       aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
469       break;
470     case NS_THEME_RANGE: {
471       if (IsRangeHorizontal(aFrame)) {
472         if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
473         aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
474       } else {
475         if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
476         aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
477       }
478       break;
479     }
480     case NS_THEME_RANGE_THUMB: {
481       if (IsRangeHorizontal(aFrame)) {
482         if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
483         aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
484       } else {
485         if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
486         aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
487       }
488       break;
489     }
490     case NS_THEME_SCALE_HORIZONTAL:
491       if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
492       aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
493       break;
494     case NS_THEME_SCALETHUMB_HORIZONTAL:
495       if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
496       aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
497       break;
498     case NS_THEME_SCALE_VERTICAL:
499       if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
500       aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
501       break;
502     case NS_THEME_SEPARATOR:
503       aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
504       break;
505     case NS_THEME_SCALETHUMB_VERTICAL:
506       if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
507       aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
508       break;
509     case NS_THEME_TOOLBARGRIPPER:
510       aGtkWidgetType = MOZ_GTK_GRIPPER;
511       break;
512     case NS_THEME_RESIZER:
513       aGtkWidgetType = MOZ_GTK_RESIZER;
514       break;
515     case NS_THEME_NUMBER_INPUT:
516     case NS_THEME_TEXTFIELD:
517       aGtkWidgetType = MOZ_GTK_ENTRY;
518       break;
519     case NS_THEME_TEXTFIELD_MULTILINE:
520 #ifdef MOZ_WIDGET_GTK
521       aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
522 #else
523       aGtkWidgetType = MOZ_GTK_ENTRY;
524 #endif
525       break;
526     case NS_THEME_LISTBOX:
527     case NS_THEME_TREEVIEW:
528       aGtkWidgetType = MOZ_GTK_TREEVIEW;
529       break;
530     case NS_THEME_TREEHEADERCELL:
531       if (aWidgetFlags) {
532         // In this case, the flag denotes whether the header is the sorted one
533         // or not
534         if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
535           *aWidgetFlags = false;
536         else
537           *aWidgetFlags = true;
538       }
539       aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
540       break;
541     case NS_THEME_TREEHEADERSORTARROW:
542       if (aWidgetFlags) {
543         switch (GetTreeSortDirection(aFrame)) {
544           case eTreeSortDirection_Ascending:
545             *aWidgetFlags = GTK_ARROW_DOWN;
546             break;
547           case eTreeSortDirection_Descending:
548             *aWidgetFlags = GTK_ARROW_UP;
549             break;
550           case eTreeSortDirection_Natural:
551           default:
552             /* This prevents the treecolums from getting smaller
553              * and wider when switching sort direction off and on
554              * */
555             *aWidgetFlags = GTK_ARROW_NONE;
556             break;
557         }
558       }
559       aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
560       break;
561     case NS_THEME_TREETWISTY:
562       aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
563       if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
564       break;
565     case NS_THEME_TREETWISTYOPEN:
566       aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
567       if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED;
568       break;
569     case NS_THEME_MENULIST:
570       aGtkWidgetType = MOZ_GTK_DROPDOWN;
571       if (aWidgetFlags)
572         *aWidgetFlags =
573             IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
574       break;
575     case NS_THEME_MENULIST_TEXT:
576       return false;  // nothing to do, but prevents the bg from being drawn
577     case NS_THEME_MENULIST_TEXTFIELD:
578       aGtkWidgetType = MOZ_GTK_DROPDOWN_ENTRY;
579       break;
580     case NS_THEME_MENULIST_BUTTON:
581       aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
582       break;
583     case NS_THEME_TOOLBARBUTTON_DROPDOWN:
584     case NS_THEME_BUTTON_ARROW_DOWN:
585     case NS_THEME_BUTTON_ARROW_UP:
586     case NS_THEME_BUTTON_ARROW_NEXT:
587     case NS_THEME_BUTTON_ARROW_PREVIOUS:
588       aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
589       if (aWidgetFlags) {
590         *aWidgetFlags = GTK_ARROW_DOWN;
591 
592         if (aWidgetType == NS_THEME_BUTTON_ARROW_UP)
593           *aWidgetFlags = GTK_ARROW_UP;
594         else if (aWidgetType == NS_THEME_BUTTON_ARROW_NEXT)
595           *aWidgetFlags = GTK_ARROW_RIGHT;
596         else if (aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
597           *aWidgetFlags = GTK_ARROW_LEFT;
598       }
599       break;
600     case NS_THEME_CHECKBOX_CONTAINER:
601       aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
602       break;
603     case NS_THEME_RADIO_CONTAINER:
604       aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
605       break;
606     case NS_THEME_CHECKBOX_LABEL:
607       aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
608       break;
609     case NS_THEME_RADIO_LABEL:
610       aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
611       break;
612     case NS_THEME_TOOLBAR:
613       aGtkWidgetType = MOZ_GTK_TOOLBAR;
614       break;
615     case NS_THEME_TOOLTIP:
616       aGtkWidgetType = MOZ_GTK_TOOLTIP;
617       break;
618     case NS_THEME_STATUSBARPANEL:
619     case NS_THEME_RESIZERPANEL:
620       aGtkWidgetType = MOZ_GTK_FRAME;
621       break;
622     case NS_THEME_PROGRESSBAR:
623     case NS_THEME_PROGRESSBAR_VERTICAL:
624       aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
625       break;
626     case NS_THEME_PROGRESSCHUNK:
627     case NS_THEME_PROGRESSCHUNK_VERTICAL: {
628       nsIFrame* stateFrame = aFrame->GetParent();
629       EventStates eventStates = GetContentState(stateFrame, aWidgetType);
630 
631       aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
632                            ? IsVerticalProgress(stateFrame)
633                                  ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
634                                  : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
635                            : MOZ_GTK_PROGRESS_CHUNK;
636     } break;
637     case NS_THEME_TAB_SCROLL_ARROW_BACK:
638     case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
639       if (aWidgetFlags)
640         *aWidgetFlags = aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK
641                             ? GTK_ARROW_LEFT
642                             : GTK_ARROW_RIGHT;
643       aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
644       break;
645     case NS_THEME_TABPANELS:
646       aGtkWidgetType = MOZ_GTK_TABPANELS;
647       break;
648     case NS_THEME_TAB: {
649       if (IsBottomTab(aFrame)) {
650         aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
651       } else {
652         aGtkWidgetType = MOZ_GTK_TAB_TOP;
653       }
654 
655       if (aWidgetFlags) {
656         /* First bits will be used to store max(0,-bmargin) where bmargin
657          * is the bottom margin of the tab in pixels  (resp. top margin,
658          * for bottom tabs). */
659         *aWidgetFlags = GetTabMarginPixels(aFrame);
660 
661         if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
662 
663         if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
664       }
665     } break;
666     case NS_THEME_SPLITTER:
667       if (IsHorizontal(aFrame))
668         aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
669       else
670         aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
671       break;
672     case NS_THEME_MENUBAR:
673       aGtkWidgetType = MOZ_GTK_MENUBAR;
674       break;
675     case NS_THEME_MENUPOPUP:
676       aGtkWidgetType = MOZ_GTK_MENUPOPUP;
677       break;
678     case NS_THEME_MENUITEM: {
679       nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
680       if (menuFrame && menuFrame->IsOnMenuBar()) {
681         aGtkWidgetType = MOZ_GTK_MENUBARITEM;
682         break;
683       }
684     }
685       aGtkWidgetType = MOZ_GTK_MENUITEM;
686       break;
687     case NS_THEME_MENUSEPARATOR:
688       aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
689       break;
690     case NS_THEME_MENUARROW:
691       aGtkWidgetType = MOZ_GTK_MENUARROW;
692       break;
693     case NS_THEME_CHECKMENUITEM:
694       aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
695       break;
696     case NS_THEME_RADIOMENUITEM:
697       aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
698       break;
699     case NS_THEME_WINDOW:
700     case NS_THEME_DIALOG:
701       aGtkWidgetType = MOZ_GTK_WINDOW;
702       break;
703     case NS_THEME_GTK_INFO_BAR:
704       aGtkWidgetType = MOZ_GTK_INFO_BAR;
705       break;
706     case NS_THEME_WINDOW_TITLEBAR:
707       aGtkWidgetType = MOZ_GTK_HEADER_BAR;
708       break;
709     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
710       aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
711       break;
712     case NS_THEME_WINDOW_BUTTON_CLOSE:
713       aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
714       break;
715     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
716       aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
717       break;
718     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
719       aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
720       break;
721     case NS_THEME_WINDOW_BUTTON_RESTORE:
722       aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
723       break;
724     default:
725       return false;
726   }
727 
728   return true;
729 }
730 
731 class SystemCairoClipper : public ClipExporter {
732  public:
SystemCairoClipper(cairo_t * aContext)733   explicit SystemCairoClipper(cairo_t* aContext) : mContext(aContext) {}
734 
BeginClip(const Matrix & aTransform)735   void BeginClip(const Matrix& aTransform) override {
736     cairo_matrix_t mat;
737     GfxMatrixToCairoMatrix(aTransform, mat);
738     cairo_set_matrix(mContext, &mat);
739 
740     cairo_new_path(mContext);
741   }
742 
MoveTo(const Point & aPoint)743   void MoveTo(const Point& aPoint) override {
744     cairo_move_to(mContext, aPoint.x, aPoint.y);
745     mCurrentPoint = aPoint;
746   }
747 
LineTo(const Point & aPoint)748   void LineTo(const Point& aPoint) override {
749     cairo_line_to(mContext, aPoint.x, aPoint.y);
750     mCurrentPoint = aPoint;
751   }
752 
BezierTo(const Point & aCP1,const Point & aCP2,const Point & aCP3)753   void BezierTo(const Point& aCP1, const Point& aCP2,
754                 const Point& aCP3) override {
755     cairo_curve_to(mContext, aCP1.x, aCP1.y, aCP2.x, aCP2.y, aCP3.x, aCP3.y);
756     mCurrentPoint = aCP3;
757   }
758 
QuadraticBezierTo(const Point & aCP1,const Point & aCP2)759   void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override {
760     Point CP0 = CurrentPoint();
761     Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
762     Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
763     Point CP3 = aCP2;
764     cairo_curve_to(mContext, CP1.x, CP1.y, CP2.x, CP2.y, CP3.x, CP3.y);
765     mCurrentPoint = aCP2;
766   }
767 
Arc(const Point & aOrigin,float aRadius,float aStartAngle,float aEndAngle,bool aAntiClockwise)768   void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
769            float aEndAngle, bool aAntiClockwise) override {
770     ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
771                 aAntiClockwise);
772   }
773 
Close()774   void Close() override { cairo_close_path(mContext); }
775 
EndClip()776   void EndClip() override { cairo_clip(mContext); }
777 
CurrentPoint() const778   Point CurrentPoint() const override { return mCurrentPoint; }
779 
780  private:
781   cairo_t* mContext;
782   Point mCurrentPoint;
783 };
784 
DrawThemeWithCairo(gfxContext * aContext,DrawTarget * aDrawTarget,GtkWidgetState aState,WidgetNodeType aGTKWidgetType,gint aFlags,GtkTextDirection aDirection,gint aScaleFactor,bool aSnapped,const Point & aDrawOrigin,const nsIntSize & aDrawSize,GdkRectangle & aGDKRect,nsITheme::Transparency aTransparency)785 static void DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
786                                GtkWidgetState aState,
787                                WidgetNodeType aGTKWidgetType, gint aFlags,
788                                GtkTextDirection aDirection, gint aScaleFactor,
789                                bool aSnapped, const Point& aDrawOrigin,
790                                const nsIntSize& aDrawSize,
791                                GdkRectangle& aGDKRect,
792                                nsITheme::Transparency aTransparency) {
793   Point drawOffset;
794   Matrix transform;
795   if (!aSnapped) {
796     // If we are not snapped, we depend on the DT for translation.
797     drawOffset = aDrawOrigin;
798     transform = aDrawTarget->GetTransform().PreTranslate(aDrawOrigin);
799   } else {
800     // Otherwise, we only need to take the device offset into account.
801     drawOffset = aDrawOrigin - aContext->GetDeviceOffset();
802     transform = Matrix::Translation(drawOffset);
803   }
804 
805   if (aScaleFactor != 1) transform.PreScale(aScaleFactor, aScaleFactor);
806 
807   cairo_matrix_t mat;
808   GfxMatrixToCairoMatrix(transform, mat);
809 
810   nsIntSize clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
811                      (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
812 
813 #ifndef MOZ_TREE_CAIRO
814   // Directly use the Cairo draw target to render the widget if using system
815   // Cairo everywhere.
816   BorrowedCairoContext borrowCairo(aDrawTarget);
817   if (borrowCairo.mCairo) {
818     cairo_set_matrix(borrowCairo.mCairo, &mat);
819 
820     cairo_new_path(borrowCairo.mCairo);
821     cairo_rectangle(borrowCairo.mCairo, 0, 0, clipSize.width, clipSize.height);
822     cairo_clip(borrowCairo.mCairo);
823 
824     moz_gtk_widget_paint(aGTKWidgetType, borrowCairo.mCairo, &aGDKRect, &aState,
825                          aFlags, aDirection);
826 
827     borrowCairo.Finish();
828     return;
829   }
830 #endif
831 
832     // A direct Cairo draw target is not available, so we need to create a
833     // temporary one.
834 #if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
835   // If using a Cairo xlib surface, then try to reuse it.
836   BorrowedXlibDrawable borrow(aDrawTarget);
837   if (borrow.GetDrawable()) {
838     nsIntSize size = borrow.GetSize();
839     cairo_surface_t* surf = nullptr;
840     // Check if the surface is using XRender.
841 #ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
842     if (borrow.GetXRenderFormat()) {
843       surf = cairo_xlib_surface_create_with_xrender_format(
844           borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
845           borrow.GetXRenderFormat(), size.width, size.height);
846     } else {
847 #else
848     if (!borrow.GetXRenderFormat()) {
849 #endif
850       surf = cairo_xlib_surface_create(borrow.GetDisplay(),
851                                        borrow.GetDrawable(), borrow.GetVisual(),
852                                        size.width, size.height);
853     }
854     if (!NS_WARN_IF(!surf)) {
855       Point offset = borrow.GetOffset();
856       if (offset != Point()) {
857         cairo_surface_set_device_offset(surf, offset.x, offset.y);
858       }
859       cairo_t* cr = cairo_create(surf);
860       if (!NS_WARN_IF(!cr)) {
861         RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
862         aContext->ExportClip(*clipper);
863 
864         cairo_set_matrix(cr, &mat);
865 
866         cairo_new_path(cr);
867         cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
868         cairo_clip(cr);
869 
870         moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
871                              aDirection);
872 
873         cairo_destroy(cr);
874       }
875       cairo_surface_destroy(surf);
876     }
877     borrow.Finish();
878     return;
879   }
880 #endif
881 
882   // Check if the widget requires complex masking that must be composited.
883   // Try to directly write to the draw target's pixels if possible.
884   uint8_t* data;
885   nsIntSize size;
886   int32_t stride;
887   SurfaceFormat format;
888   IntPoint origin;
889   if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
890     // Create a Cairo image surface context the device rectangle.
891     cairo_surface_t* surf = cairo_image_surface_create_for_data(
892         data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
893     if (!NS_WARN_IF(!surf)) {
894       if (origin != IntPoint()) {
895         cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
896       }
897       cairo_t* cr = cairo_create(surf);
898       if (!NS_WARN_IF(!cr)) {
899         RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
900         aContext->ExportClip(*clipper);
901 
902         cairo_set_matrix(cr, &mat);
903 
904         cairo_new_path(cr);
905         cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
906         cairo_clip(cr);
907 
908         moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
909                              aDirection);
910 
911         cairo_destroy(cr);
912       }
913       cairo_surface_destroy(surf);
914     }
915     aDrawTarget->ReleaseBits(data);
916   } else {
917     // If the widget has any transparency, make sure to choose an alpha format.
918     format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8
919                                                 : aDrawTarget->GetFormat();
920     // Create a temporary data surface to render the widget into.
921     RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
922         aDrawSize, format, aTransparency != nsITheme::eOpaque);
923     DataSourceSurface::MappedSurface map;
924     if (!NS_WARN_IF(
925             !(dataSurface &&
926               dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
927       // Create a Cairo image surface wrapping the data surface.
928       cairo_surface_t* surf = cairo_image_surface_create_for_data(
929           map.mData, GfxFormatToCairoFormat(format), aDrawSize.width,
930           aDrawSize.height, map.mStride);
931       cairo_t* cr = nullptr;
932       if (!NS_WARN_IF(!surf)) {
933         cr = cairo_create(surf);
934         if (!NS_WARN_IF(!cr)) {
935           if (aScaleFactor != 1) {
936             cairo_scale(cr, aScaleFactor, aScaleFactor);
937           }
938 
939           moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
940                                aDirection);
941         }
942       }
943 
944       // Unmap the surface before using it as a source
945       dataSurface->Unmap();
946 
947       if (cr) {
948         // The widget either needs to be masked or has transparency, so use the
949         // slower drawing path.
950         aDrawTarget->DrawSurface(
951             dataSurface,
952             Rect(aSnapped
953                      ? drawOffset - aDrawTarget->GetTransform().GetTranslation()
954                      : drawOffset,
955                  Size(aDrawSize)),
956             Rect(0, 0, aDrawSize.width, aDrawSize.height));
957         cairo_destroy(cr);
958       }
959 
960       if (surf) {
961         cairo_surface_destroy(surf);
962       }
963     }
964   }
965 }
966 
967 bool nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame,
968                                              uint8_t aWidgetType,
969                                              nsIntMargin* aExtra) {
970   *aExtra = nsIntMargin(0, 0, 0, 0);
971   // Allow an extra one pixel above and below the thumb for certain
972   // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
973   // We modify the frame's overflow area.  See bug 297508.
974   switch (aWidgetType) {
975     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
976       aExtra->top = aExtra->bottom = 1;
977       break;
978     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
979       aExtra->left = aExtra->right = 1;
980       break;
981 
982     case NS_THEME_BUTTON: {
983       if (IsDefaultButton(aFrame)) {
984         // Some themes draw a default indicator outside the widget,
985         // include that in overflow
986         gint top, left, bottom, right;
987         moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
988         aExtra->top = top;
989         aExtra->right = right;
990         aExtra->bottom = bottom;
991         aExtra->left = left;
992         break;
993       }
994       return false;
995     }
996     case NS_THEME_FOCUS_OUTLINE: {
997       moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
998       aExtra->right = aExtra->left;
999       aExtra->bottom = aExtra->top;
1000       break;
1001     }
1002     case NS_THEME_TAB: {
1003       if (!IsSelectedTab(aFrame)) return false;
1004 
1005       gint gap_height = moz_gtk_get_tab_thickness(
1006           IsBottomTab(aFrame) ? MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
1007       if (!gap_height) return false;
1008 
1009       int32_t extra = gap_height - GetTabMarginPixels(aFrame);
1010       if (extra <= 0) return false;
1011 
1012       if (IsBottomTab(aFrame)) {
1013         aExtra->top = extra;
1014       } else {
1015         aExtra->bottom = extra;
1016       }
1017       return false;
1018     }
1019     default:
1020       return false;
1021   }
1022   gint scale = GetMonitorScaleFactor(aFrame);
1023   aExtra->top *= scale;
1024   aExtra->right *= scale;
1025   aExtra->bottom *= scale;
1026   aExtra->left *= scale;
1027   return true;
1028 }
1029 
1030 NS_IMETHODIMP
1031 nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1032                                        uint8_t aWidgetType, const nsRect& aRect,
1033                                        const nsRect& aDirtyRect) {
1034   GtkWidgetState state;
1035   WidgetNodeType gtkWidgetType;
1036   GtkTextDirection direction = GetTextDirection(aFrame);
1037   gint flags;
1038   if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, &state, &flags))
1039     return NS_OK;
1040 
1041   gfxContext* ctx = aContext;
1042   nsPresContext* presContext = aFrame->PresContext();
1043 
1044   gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
1045   gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
1046   gint scaleFactor = GetMonitorScaleFactor(aFrame);
1047 
1048   // Align to device pixels where sensible
1049   // to provide crisper and faster drawing.
1050   // Don't snap if it's a non-unit scale factor. We're going to have to take
1051   // slow paths then in any case.
1052   bool snapped = ctx->UserToDevicePixelSnapped(rect);
1053   if (snapped) {
1054     // Leave rect in device coords but make dirtyRect consistent.
1055     dirtyRect = ctx->UserToDevice(dirtyRect);
1056   }
1057 
1058   // Translate the dirty rect so that it is wrt the widget top-left.
1059   dirtyRect.MoveBy(-rect.TopLeft());
1060   // Round out the dirty rect to gdk pixels to ensure that gtk draws
1061   // enough pixels for interpolation to device pixels.
1062   dirtyRect.RoundOut();
1063 
1064   // GTK themes can only draw an integer number of pixels
1065   // (even when not snapped).
1066   nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
1067   nsIntRect overflowRect(widgetRect);
1068   nsIntMargin extraSize;
1069   if (GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) {
1070     overflowRect.Inflate(extraSize);
1071   }
1072 
1073   // This is the rectangle that will actually be drawn, in gdk pixels
1074   nsIntRect drawingRect(int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
1075                         int32_t(dirtyRect.Width()),
1076                         int32_t(dirtyRect.Height()));
1077   if (widgetRect.IsEmpty() ||
1078       !drawingRect.IntersectRect(overflowRect, drawingRect))
1079     return NS_OK;
1080 
1081   NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType),
1082                "Trying to render an unsafe widget!");
1083 
1084   bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
1085   if (!safeState) {
1086     gLastGdkError = 0;
1087     gdk_error_trap_push();
1088   }
1089 
1090   Transparency transparency = GetWidgetTransparency(aFrame, aWidgetType);
1091 
1092   // gdk rectangles are wrt the drawing rect.
1093   GdkRectangle gdk_rect = {
1094       -drawingRect.x / scaleFactor, -drawingRect.y / scaleFactor,
1095       widgetRect.width / scaleFactor, widgetRect.height / scaleFactor};
1096 
1097   // Save actual widget scale to GtkWidgetState as we don't provide
1098   // nsFrame to gtk3drawing routines.
1099   state.scale = scaleFactor;
1100 
1101   // translate everything so (0,0) is the top left of the drawingRect
1102   gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
1103 
1104   DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
1105                      flags, direction, scaleFactor, snapped, ToPoint(origin),
1106                      drawingRect.Size(), gdk_rect, transparency);
1107 
1108   if (!safeState) {
1109     // gdk_flush() call from expose event crashes Gtk+ on Wayland
1110     // (Gnome BZ #773307)
1111     if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
1112       gdk_flush();
1113     }
1114     gLastGdkError = gdk_error_trap_pop();
1115 
1116     if (gLastGdkError) {
1117 #ifdef DEBUG
1118       printf(
1119           "GTK theme failed for widget type %d, error was %d, state was "
1120           "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
1121           aWidgetType, gLastGdkError, state.active, state.focused,
1122           state.inHover, state.disabled);
1123 #endif
1124       NS_WARNING("GTK theme failed; disabling unsafe widget");
1125       SetWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType);
1126       // force refresh of the window, because the widget was not
1127       // successfully drawn it must be redrawn using the default look
1128       RefreshWidgetWindow(aFrame);
1129     } else {
1130       SetWidgetStateSafe(mSafeWidgetStates, aWidgetType, &state);
1131     }
1132   }
1133 
1134   // Indeterminate progress bar are animated.
1135   if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
1136       gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
1137     if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
1138       NS_WARNING("unable to animate widget!");
1139     }
1140   }
1141 
1142   return NS_OK;
1143 }
1144 
1145 bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
1146     mozilla::wr::DisplayListBuilder& aBuilder,
1147     mozilla::wr::IpcResourceUpdateQueue& aResources,
1148     const mozilla::layers::StackingContextHelper& aSc,
1149     mozilla::layers::WebRenderLayerManager* aManager, nsIFrame* aFrame,
1150     uint8_t aWidgetType, const nsRect& aRect) {
1151   nsPresContext* presContext = aFrame->PresContext();
1152   wr::LayoutRect bounds =
1153       aSc.ToRelativeLayoutRect(LayoutDeviceRect::FromAppUnits(
1154           aRect, presContext->AppUnitsPerDevPixel()));
1155 
1156   switch (aWidgetType) {
1157     case NS_THEME_WINDOW:
1158     case NS_THEME_DIALOG:
1159       aBuilder.PushRect(
1160           bounds, bounds, true,
1161           wr::ToColorF(Color::FromABGR(LookAndFeel::GetColor(
1162               LookAndFeel::eColorID_WindowBackground, NS_RGBA(0, 0, 0, 0)))));
1163       return true;
1164 
1165     default:
1166       return false;
1167   }
1168 }
1169 
1170 WidgetNodeType nsNativeThemeGTK::NativeThemeToGtkTheme(uint8_t aWidgetType,
1171                                                        nsIFrame* aFrame) {
1172   WidgetNodeType gtkWidgetType;
1173   gint unusedFlags;
1174 
1175   if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1176                             &unusedFlags)) {
1177     MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
1178     return MOZ_GTK_WINDOW;
1179   }
1180   return gtkWidgetType;
1181 }
1182 
1183 void nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame,
1184                                              uint8_t aWidgetType,
1185                                              GtkTextDirection aDirection,
1186                                              nsIntMargin* aResult) {
1187   aResult->SizeTo(0, 0, 0, 0);
1188 
1189   WidgetNodeType gtkWidgetType;
1190   gint unusedFlags;
1191   if (GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1192                            &unusedFlags)) {
1193     MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
1194     uint8_t cacheIndex = gtkWidgetType / 8;
1195     uint8_t cacheBit = 1u << (gtkWidgetType % 8);
1196 
1197     if (mBorderCacheValid[cacheIndex] & cacheBit) {
1198       *aResult = mBorderCache[gtkWidgetType];
1199     } else {
1200       moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
1201                                 &aResult->right, &aResult->bottom, aDirection);
1202       if (aWidgetType != MOZ_GTK_DROPDOWN) {  // depends on aDirection
1203         mBorderCacheValid[cacheIndex] |= cacheBit;
1204         mBorderCache[gtkWidgetType] = *aResult;
1205       }
1206     }
1207   }
1208 }
1209 
1210 NS_IMETHODIMP
1211 nsNativeThemeGTK::GetWidgetBorder(nsDeviceContext* aContext, nsIFrame* aFrame,
1212                                   uint8_t aWidgetType, nsIntMargin* aResult) {
1213   GtkTextDirection direction = GetTextDirection(aFrame);
1214   aResult->top = aResult->left = aResult->right = aResult->bottom = 0;
1215   switch (aWidgetType) {
1216     case NS_THEME_SCROLLBAR_HORIZONTAL:
1217     case NS_THEME_SCROLLBAR_VERTICAL: {
1218       GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
1219                                        ? GTK_ORIENTATION_HORIZONTAL
1220                                        : GTK_ORIENTATION_VERTICAL;
1221       const ScrollbarGTKMetrics* metrics =
1222           GetScrollbarMetrics(orientation, true);
1223 
1224       const GtkBorder& border = metrics->border.scrollbar;
1225       aResult->top = border.top;
1226       aResult->right = border.right;
1227       aResult->bottom = border.bottom;
1228       aResult->left = border.left;
1229     } break;
1230     case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
1231     case NS_THEME_SCROLLBARTRACK_VERTICAL: {
1232       GtkOrientation orientation =
1233           aWidgetType == NS_THEME_SCROLLBARTRACK_HORIZONTAL
1234               ? GTK_ORIENTATION_HORIZONTAL
1235               : GTK_ORIENTATION_VERTICAL;
1236       const ScrollbarGTKMetrics* metrics =
1237           GetScrollbarMetrics(orientation, true);
1238 
1239       const GtkBorder& border = metrics->border.track;
1240       aResult->top = border.top;
1241       aResult->right = border.right;
1242       aResult->bottom = border.bottom;
1243       aResult->left = border.left;
1244     } break;
1245     case NS_THEME_TOOLBOX:
1246       // gtk has no toolbox equivalent.  So, although we map toolbox to
1247       // gtk's 'toolbar' for purposes of painting the widget background,
1248       // we don't use the toolbar border for toolbox.
1249       break;
1250     case NS_THEME_DUALBUTTON:
1251       // TOOLBAR_DUAL_BUTTON is an interesting case.  We want a border to draw
1252       // around the entire button + dropdown, and also an inner border if you're
1253       // over the button part.  But, we want the inner button to be right up
1254       // against the edge of the outer button so that the borders overlap.
1255       // To make this happen, we draw a button border for the outer button,
1256       // but don't reserve any space for it.
1257       break;
1258     case NS_THEME_TAB: {
1259       WidgetNodeType gtkWidgetType;
1260       gint flags;
1261 
1262       if (!GetGtkWidgetAndState(aWidgetType, aFrame, gtkWidgetType, nullptr,
1263                                 &flags))
1264         return NS_OK;
1265 
1266       moz_gtk_get_tab_border(&aResult->left, &aResult->top, &aResult->right,
1267                              &aResult->bottom, direction, (GtkTabFlags)flags,
1268                              gtkWidgetType);
1269     } break;
1270     case NS_THEME_MENUITEM:
1271     case NS_THEME_CHECKMENUITEM:
1272     case NS_THEME_RADIOMENUITEM:
1273       // For regular menuitems, we will be using GetWidgetPadding instead of
1274       // GetWidgetBorder to pad up the widget's internals; other menuitems
1275       // will need to fall through and use the default case as before.
1276       if (IsRegularMenuItem(aFrame)) break;
1277       MOZ_FALLTHROUGH;
1278     default: { GetCachedWidgetBorder(aFrame, aWidgetType, direction, aResult); }
1279   }
1280 
1281   gint scale = GetMonitorScaleFactor(aFrame);
1282   aResult->top *= scale;
1283   aResult->right *= scale;
1284   aResult->bottom *= scale;
1285   aResult->left *= scale;
1286   return NS_OK;
1287 }
1288 
1289 bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
1290                                         nsIFrame* aFrame, uint8_t aWidgetType,
1291                                         nsIntMargin* aResult) {
1292   switch (aWidgetType) {
1293     case NS_THEME_BUTTON_FOCUS:
1294     case NS_THEME_TOOLBARBUTTON:
1295     case NS_THEME_WINDOW_BUTTON_CLOSE:
1296     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1297     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1298     case NS_THEME_WINDOW_BUTTON_RESTORE:
1299     case NS_THEME_DUALBUTTON:
1300     case NS_THEME_TAB_SCROLL_ARROW_BACK:
1301     case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
1302     case NS_THEME_MENULIST_BUTTON:
1303     case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1304     case NS_THEME_BUTTON_ARROW_UP:
1305     case NS_THEME_BUTTON_ARROW_DOWN:
1306     case NS_THEME_BUTTON_ARROW_NEXT:
1307     case NS_THEME_BUTTON_ARROW_PREVIOUS:
1308     case NS_THEME_RANGE_THUMB:
1309     // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1310     // and have a meaningful baseline, so they can't have
1311     // author-specified padding.
1312     case NS_THEME_CHECKBOX:
1313     case NS_THEME_RADIO:
1314       aResult->SizeTo(0, 0, 0, 0);
1315       return true;
1316     case NS_THEME_MENUITEM:
1317     case NS_THEME_CHECKMENUITEM:
1318     case NS_THEME_RADIOMENUITEM: {
1319       // Menubar and menulist have their padding specified in CSS.
1320       if (!IsRegularMenuItem(aFrame)) return false;
1321 
1322       GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
1323                             aResult);
1324 
1325       gint horizontal_padding;
1326 
1327       if (aWidgetType == NS_THEME_MENUITEM)
1328         moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
1329       else
1330         moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
1331 
1332       aResult->left += horizontal_padding;
1333       aResult->right += horizontal_padding;
1334 
1335       gint scale = GetMonitorScaleFactor(aFrame);
1336       aResult->top *= scale;
1337       aResult->right *= scale;
1338       aResult->bottom *= scale;
1339       aResult->left *= scale;
1340 
1341       return true;
1342     }
1343   }
1344 
1345   return false;
1346 }
1347 
1348 bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
1349                                          nsIFrame* aFrame, uint8_t aWidgetType,
1350                                          nsRect* aOverflowRect) {
1351   nsIntMargin extraSize;
1352   if (!GetExtraSizeForWidget(aFrame, aWidgetType, &extraSize)) return false;
1353 
1354   int32_t p2a = aContext->AppUnitsPerDevPixel();
1355   nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
1356              NSIntPixelsToAppUnits(extraSize.right, p2a),
1357              NSIntPixelsToAppUnits(extraSize.bottom, p2a),
1358              NSIntPixelsToAppUnits(extraSize.left, p2a));
1359 
1360   aOverflowRect->Inflate(m);
1361   return true;
1362 }
1363 
1364 NS_IMETHODIMP
1365 nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
1366                                        nsIFrame* aFrame, uint8_t aWidgetType,
1367                                        LayoutDeviceIntSize* aResult,
1368                                        bool* aIsOverridable) {
1369   aResult->width = aResult->height = 0;
1370   *aIsOverridable = true;
1371 
1372   switch (aWidgetType) {
1373     case NS_THEME_SCROLLBARBUTTON_UP:
1374     case NS_THEME_SCROLLBARBUTTON_DOWN: {
1375       const ScrollbarGTKMetrics* metrics =
1376           GetScrollbarMetrics(GTK_ORIENTATION_VERTICAL, true);
1377 
1378       aResult->width = metrics->size.button.width;
1379       aResult->height = metrics->size.button.height;
1380       *aIsOverridable = false;
1381     } break;
1382     case NS_THEME_SCROLLBARBUTTON_LEFT:
1383     case NS_THEME_SCROLLBARBUTTON_RIGHT: {
1384       const ScrollbarGTKMetrics* metrics =
1385           GetScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL, true);
1386 
1387       aResult->width = metrics->size.button.width;
1388       aResult->height = metrics->size.button.height;
1389       *aIsOverridable = false;
1390     } break;
1391     case NS_THEME_SPLITTER: {
1392       gint metrics;
1393       if (IsHorizontal(aFrame)) {
1394         moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
1395         aResult->width = metrics;
1396         aResult->height = 0;
1397       } else {
1398         moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
1399         aResult->width = 0;
1400         aResult->height = metrics;
1401       }
1402       *aIsOverridable = false;
1403     } break;
1404     case NS_THEME_SCROLLBAR_HORIZONTAL:
1405     case NS_THEME_SCROLLBAR_VERTICAL: {
1406       /* While we enforce a minimum size for the thumb, this is ignored
1407        * for the some scrollbars if buttons are hidden (bug 513006) because
1408        * the thumb isn't a direct child of the scrollbar, unlike the buttons
1409        * or track. So add a minimum size to the track as well to prevent a
1410        * 0-width scrollbar. */
1411       GtkOrientation orientation = aWidgetType == NS_THEME_SCROLLBAR_HORIZONTAL
1412                                        ? GTK_ORIENTATION_HORIZONTAL
1413                                        : GTK_ORIENTATION_VERTICAL;
1414       const ScrollbarGTKMetrics* metrics =
1415           GetScrollbarMetrics(orientation, true);
1416 
1417       aResult->width = metrics->size.scrollbar.width;
1418       aResult->height = metrics->size.scrollbar.height;
1419     } break;
1420     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
1421     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL: {
1422       GtkOrientation orientation =
1423           aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL
1424               ? GTK_ORIENTATION_HORIZONTAL
1425               : GTK_ORIENTATION_VERTICAL;
1426       const ScrollbarGTKMetrics* metrics =
1427           GetScrollbarMetrics(orientation, true);
1428 
1429       aResult->width = metrics->size.thumb.width;
1430       aResult->height = metrics->size.thumb.height;
1431       *aIsOverridable = false;
1432     } break;
1433     case NS_THEME_RANGE_THUMB: {
1434       gint thumb_length, thumb_height;
1435 
1436       if (IsRangeHorizontal(aFrame)) {
1437         moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
1438                                        &thumb_length, &thumb_height);
1439       } else {
1440         moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
1441                                        &thumb_length);
1442       }
1443       aResult->width = thumb_length;
1444       aResult->height = thumb_height;
1445 
1446       *aIsOverridable = false;
1447     } break;
1448     case NS_THEME_RANGE: {
1449       gint scale_width, scale_height;
1450 
1451       moz_gtk_get_scale_metrics(IsRangeHorizontal(aFrame)
1452                                     ? GTK_ORIENTATION_HORIZONTAL
1453                                     : GTK_ORIENTATION_VERTICAL,
1454                                 &scale_width, &scale_height);
1455       aResult->width = scale_width;
1456       aResult->height = scale_height;
1457 
1458       *aIsOverridable = true;
1459     } break;
1460     case NS_THEME_SCALETHUMB_HORIZONTAL:
1461     case NS_THEME_SCALETHUMB_VERTICAL: {
1462       gint thumb_length, thumb_height;
1463 
1464       if (aWidgetType == NS_THEME_SCALETHUMB_VERTICAL) {
1465         moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_length,
1466                                        &thumb_height);
1467         aResult->width = thumb_height;
1468         aResult->height = thumb_length;
1469       } else {
1470         moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
1471                                        &thumb_length, &thumb_height);
1472         aResult->width = thumb_length;
1473         aResult->height = thumb_height;
1474       }
1475 
1476       *aIsOverridable = false;
1477     } break;
1478     case NS_THEME_TAB_SCROLL_ARROW_BACK:
1479     case NS_THEME_TAB_SCROLL_ARROW_FORWARD: {
1480       moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
1481       *aIsOverridable = false;
1482     } break;
1483     case NS_THEME_MENULIST_BUTTON: {
1484       moz_gtk_get_combo_box_entry_button_size(&aResult->width,
1485                                               &aResult->height);
1486       *aIsOverridable = false;
1487     } break;
1488     case NS_THEME_MENUSEPARATOR: {
1489       gint separator_height;
1490 
1491       moz_gtk_get_menu_separator_height(&separator_height);
1492       aResult->height = separator_height;
1493 
1494       *aIsOverridable = false;
1495     } break;
1496     case NS_THEME_CHECKBOX:
1497     case NS_THEME_RADIO: {
1498       const ToggleGTKMetrics* metrics =
1499           GetToggleMetrics(aWidgetType == NS_THEME_RADIO);
1500       aResult->width = metrics->minSizeWithBorder.width;
1501       aResult->height = metrics->minSizeWithBorder.height;
1502     } break;
1503     case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1504     case NS_THEME_BUTTON_ARROW_UP:
1505     case NS_THEME_BUTTON_ARROW_DOWN:
1506     case NS_THEME_BUTTON_ARROW_NEXT:
1507     case NS_THEME_BUTTON_ARROW_PREVIOUS: {
1508       moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &aResult->width,
1509                              &aResult->height);
1510       *aIsOverridable = false;
1511     } break;
1512     case NS_THEME_WINDOW_BUTTON_CLOSE: {
1513       const ToolbarButtonGTKMetrics* metrics =
1514           GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
1515       aResult->width = metrics->minSizeWithBorderMargin.width;
1516       aResult->height = metrics->minSizeWithBorderMargin.height;
1517       break;
1518     }
1519     case NS_THEME_WINDOW_BUTTON_MINIMIZE: {
1520       const ToolbarButtonGTKMetrics* metrics =
1521           GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
1522       aResult->width = metrics->minSizeWithBorderMargin.width;
1523       aResult->height = metrics->minSizeWithBorderMargin.height;
1524       break;
1525     }
1526     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1527     case NS_THEME_WINDOW_BUTTON_RESTORE: {
1528       const ToolbarButtonGTKMetrics* metrics =
1529           GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
1530       aResult->width = metrics->minSizeWithBorderMargin.width;
1531       aResult->height = metrics->minSizeWithBorderMargin.height;
1532       break;
1533     }
1534     case NS_THEME_CHECKBOX_CONTAINER:
1535     case NS_THEME_RADIO_CONTAINER:
1536     case NS_THEME_CHECKBOX_LABEL:
1537     case NS_THEME_RADIO_LABEL:
1538     case NS_THEME_BUTTON:
1539     case NS_THEME_MENULIST:
1540     case NS_THEME_TOOLBARBUTTON:
1541     case NS_THEME_TREEHEADERCELL: {
1542       if (aWidgetType == NS_THEME_MENULIST) {
1543         // Include the arrow size.
1544         moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &aResult->width,
1545                                &aResult->height);
1546       }
1547       // else the minimum size is missing consideration of container
1548       // descendants; the value returned here will not be helpful, but the
1549       // box model may consider border and padding with child minimum sizes.
1550 
1551       nsIntMargin border;
1552       GetCachedWidgetBorder(aFrame, aWidgetType, GetTextDirection(aFrame),
1553                             &border);
1554       aResult->width += border.left + border.right;
1555       aResult->height += border.top + border.bottom;
1556     } break;
1557 #ifdef MOZ_WIDGET_GTK
1558     case NS_THEME_NUMBER_INPUT:
1559     case NS_THEME_TEXTFIELD: {
1560       moz_gtk_get_entry_min_height(&aResult->height);
1561     } break;
1562 #endif
1563     case NS_THEME_SEPARATOR: {
1564       gint separator_width;
1565 
1566       moz_gtk_get_toolbar_separator_width(&separator_width);
1567 
1568       aResult->width = separator_width;
1569     } break;
1570     case NS_THEME_INNER_SPIN_BUTTON:
1571     case NS_THEME_SPINNER:
1572       // hard code these sizes
1573       aResult->width = 14;
1574       aResult->height = 26;
1575       break;
1576     case NS_THEME_TREEHEADERSORTARROW:
1577     case NS_THEME_SPINNER_UPBUTTON:
1578     case NS_THEME_SPINNER_DOWNBUTTON:
1579       // hard code these sizes
1580       aResult->width = 14;
1581       aResult->height = 13;
1582       break;
1583     case NS_THEME_RESIZER:
1584       // same as Windows to make our lives easier
1585       aResult->width = aResult->height = 15;
1586       *aIsOverridable = false;
1587       break;
1588     case NS_THEME_TREETWISTY:
1589     case NS_THEME_TREETWISTYOPEN: {
1590       gint expander_size;
1591 
1592       moz_gtk_get_treeview_expander_size(&expander_size);
1593       aResult->width = aResult->height = expander_size;
1594       *aIsOverridable = false;
1595     } break;
1596   }
1597 
1598   *aResult = *aResult * GetMonitorScaleFactor(aFrame);
1599 
1600   return NS_OK;
1601 }
1602 
1603 NS_IMETHODIMP
1604 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame, uint8_t aWidgetType,
1605                                      nsAtom* aAttribute, bool* aShouldRepaint,
1606                                      const nsAttrValue* aOldValue) {
1607   // Some widget types just never change state.
1608   if (aWidgetType == NS_THEME_TOOLBOX || aWidgetType == NS_THEME_TOOLBAR ||
1609       aWidgetType == NS_THEME_STATUSBAR ||
1610       aWidgetType == NS_THEME_STATUSBARPANEL ||
1611       aWidgetType == NS_THEME_RESIZERPANEL ||
1612       aWidgetType == NS_THEME_PROGRESSCHUNK ||
1613       aWidgetType == NS_THEME_PROGRESSCHUNK_VERTICAL ||
1614       aWidgetType == NS_THEME_PROGRESSBAR ||
1615       aWidgetType == NS_THEME_PROGRESSBAR_VERTICAL ||
1616       aWidgetType == NS_THEME_MENUBAR || aWidgetType == NS_THEME_MENUPOPUP ||
1617       aWidgetType == NS_THEME_TOOLTIP ||
1618       aWidgetType == NS_THEME_MENUSEPARATOR || aWidgetType == NS_THEME_WINDOW ||
1619       aWidgetType == NS_THEME_DIALOG) {
1620     *aShouldRepaint = false;
1621     return NS_OK;
1622   }
1623 
1624   if ((aWidgetType == NS_THEME_SCROLLBARTHUMB_VERTICAL ||
1625        aWidgetType == NS_THEME_SCROLLBARTHUMB_HORIZONTAL) &&
1626       aAttribute == nsGkAtoms::active) {
1627     *aShouldRepaint = true;
1628     return NS_OK;
1629   }
1630 
1631   if ((aWidgetType == NS_THEME_SCROLLBARBUTTON_UP ||
1632        aWidgetType == NS_THEME_SCROLLBARBUTTON_DOWN ||
1633        aWidgetType == NS_THEME_SCROLLBARBUTTON_LEFT ||
1634        aWidgetType == NS_THEME_SCROLLBARBUTTON_RIGHT) &&
1635       (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos)) {
1636     // If 'curpos' has changed and we are passed its old value, we can
1637     // determine whether the button's enablement actually needs to change.
1638     if (aAttribute == nsGkAtoms::curpos && aOldValue) {
1639       int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
1640       int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
1641       nsAutoString str;
1642       aOldValue->ToString(str);
1643       nsresult err;
1644       int32_t oldCurpos = str.ToInteger(&err);
1645       if (str.IsEmpty() || NS_FAILED(err)) {
1646         *aShouldRepaint = true;
1647       } else {
1648         bool disabledBefore =
1649             ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aWidgetType);
1650         bool disabledNow =
1651             ShouldScrollbarButtonBeDisabled(curpos, maxpos, aWidgetType);
1652         *aShouldRepaint = (disabledBefore != disabledNow);
1653       }
1654     } else {
1655       *aShouldRepaint = true;
1656     }
1657     return NS_OK;
1658   }
1659 
1660   // XXXdwh Not sure what can really be done here.  Can at least guess for
1661   // specific widgets that they're highly unlikely to have certain states.
1662   // For example, a toolbar doesn't care about any states.
1663   if (!aAttribute) {
1664     // Hover/focus/active changed.  Always repaint.
1665     *aShouldRepaint = true;
1666   } else {
1667     // Check the attribute to see if it's relevant.
1668     // disabled, checked, dlgtype, default, etc.
1669     *aShouldRepaint = false;
1670     if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1671         aAttribute == nsGkAtoms::selected ||
1672         aAttribute == nsGkAtoms::visuallyselected ||
1673         aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::readonly ||
1674         aAttribute == nsGkAtoms::_default ||
1675         aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::open ||
1676         aAttribute == nsGkAtoms::parentfocused)
1677       *aShouldRepaint = true;
1678   }
1679 
1680   return NS_OK;
1681 }
1682 
1683 NS_IMETHODIMP
1684 nsNativeThemeGTK::ThemeChanged() {
1685   memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
1686   memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
1687   memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
1688   return NS_OK;
1689 }
1690 
1691 NS_IMETHODIMP_(bool)
1692 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
1693                                       nsIFrame* aFrame, uint8_t aWidgetType) {
1694   if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aWidgetType)) return false;
1695 
1696   switch (aWidgetType) {
1697     // Combobox dropdowns don't support native theming in vertical mode.
1698     case NS_THEME_MENULIST:
1699     case NS_THEME_MENULIST_TEXT:
1700     case NS_THEME_MENULIST_TEXTFIELD:
1701       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
1702         return false;
1703       }
1704       MOZ_FALLTHROUGH;
1705 
1706     case NS_THEME_BUTTON:
1707     case NS_THEME_BUTTON_FOCUS:
1708     case NS_THEME_RADIO:
1709     case NS_THEME_CHECKBOX:
1710     case NS_THEME_TOOLBOX:  // N/A
1711     case NS_THEME_TOOLBAR:
1712     case NS_THEME_TOOLBARBUTTON:
1713     case NS_THEME_DUALBUTTON:  // so we can override the border with 0
1714     case NS_THEME_TOOLBARBUTTON_DROPDOWN:
1715     case NS_THEME_BUTTON_ARROW_UP:
1716     case NS_THEME_BUTTON_ARROW_DOWN:
1717     case NS_THEME_BUTTON_ARROW_NEXT:
1718     case NS_THEME_BUTTON_ARROW_PREVIOUS:
1719     case NS_THEME_SEPARATOR:
1720     case NS_THEME_TOOLBARGRIPPER:
1721     case NS_THEME_STATUSBAR:
1722     case NS_THEME_STATUSBARPANEL:
1723     case NS_THEME_RESIZERPANEL:
1724     case NS_THEME_RESIZER:
1725     case NS_THEME_LISTBOX:
1726       // case NS_THEME_LISTITEM:
1727     case NS_THEME_TREEVIEW:
1728       // case NS_THEME_TREEITEM:
1729     case NS_THEME_TREETWISTY:
1730       // case NS_THEME_TREELINE:
1731       // case NS_THEME_TREEHEADER:
1732     case NS_THEME_TREEHEADERCELL:
1733     case NS_THEME_TREEHEADERSORTARROW:
1734     case NS_THEME_TREETWISTYOPEN:
1735     case NS_THEME_PROGRESSBAR:
1736     case NS_THEME_PROGRESSCHUNK:
1737     case NS_THEME_PROGRESSBAR_VERTICAL:
1738     case NS_THEME_PROGRESSCHUNK_VERTICAL:
1739     case NS_THEME_TAB:
1740     // case NS_THEME_TABPANEL:
1741     case NS_THEME_TABPANELS:
1742     case NS_THEME_TAB_SCROLL_ARROW_BACK:
1743     case NS_THEME_TAB_SCROLL_ARROW_FORWARD:
1744     case NS_THEME_TOOLTIP:
1745     case NS_THEME_INNER_SPIN_BUTTON:
1746     case NS_THEME_SPINNER:
1747     case NS_THEME_SPINNER_UPBUTTON:
1748     case NS_THEME_SPINNER_DOWNBUTTON:
1749     case NS_THEME_SPINNER_TEXTFIELD:
1750       // case NS_THEME_SCROLLBAR:  (n/a for gtk)
1751       // case NS_THEME_SCROLLBAR_SMALL: (n/a for gtk)
1752     case NS_THEME_SCROLLBARBUTTON_UP:
1753     case NS_THEME_SCROLLBARBUTTON_DOWN:
1754     case NS_THEME_SCROLLBARBUTTON_LEFT:
1755     case NS_THEME_SCROLLBARBUTTON_RIGHT:
1756     case NS_THEME_SCROLLBAR_HORIZONTAL:
1757     case NS_THEME_SCROLLBAR_VERTICAL:
1758     case NS_THEME_SCROLLBARTRACK_HORIZONTAL:
1759     case NS_THEME_SCROLLBARTRACK_VERTICAL:
1760     case NS_THEME_SCROLLBARTHUMB_HORIZONTAL:
1761     case NS_THEME_SCROLLBARTHUMB_VERTICAL:
1762     case NS_THEME_NUMBER_INPUT:
1763     case NS_THEME_TEXTFIELD:
1764     case NS_THEME_TEXTFIELD_MULTILINE:
1765     case NS_THEME_RANGE:
1766     case NS_THEME_RANGE_THUMB:
1767     case NS_THEME_SCALE_HORIZONTAL:
1768     case NS_THEME_SCALETHUMB_HORIZONTAL:
1769     case NS_THEME_SCALE_VERTICAL:
1770     case NS_THEME_SCALETHUMB_VERTICAL:
1771       // case NS_THEME_SCALETHUMBSTART:
1772       // case NS_THEME_SCALETHUMBEND:
1773       // case NS_THEME_SCALETHUMBTICK:
1774     case NS_THEME_CHECKBOX_CONTAINER:
1775     case NS_THEME_RADIO_CONTAINER:
1776     case NS_THEME_CHECKBOX_LABEL:
1777     case NS_THEME_RADIO_LABEL:
1778     case NS_THEME_MENUBAR:
1779     case NS_THEME_MENUPOPUP:
1780     case NS_THEME_MENUITEM:
1781     case NS_THEME_MENUARROW:
1782     case NS_THEME_MENUSEPARATOR:
1783     case NS_THEME_CHECKMENUITEM:
1784     case NS_THEME_RADIOMENUITEM:
1785     case NS_THEME_SPLITTER:
1786     case NS_THEME_WINDOW:
1787     case NS_THEME_DIALOG:
1788 #ifdef MOZ_WIDGET_GTK
1789     case NS_THEME_GTK_INFO_BAR:
1790 #endif
1791       return !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1792 
1793     case NS_THEME_WINDOW_BUTTON_CLOSE:
1794     case NS_THEME_WINDOW_BUTTON_MINIMIZE:
1795     case NS_THEME_WINDOW_BUTTON_MAXIMIZE:
1796     case NS_THEME_WINDOW_BUTTON_RESTORE:
1797     case NS_THEME_WINDOW_TITLEBAR:
1798     case NS_THEME_WINDOW_TITLEBAR_MAXIMIZED:
1799       // GtkHeaderBar is available on GTK 3.10+, which is used for styling
1800       // title bars and title buttons.
1801       return gtk_check_version(3, 10, 0) == nullptr &&
1802              !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1803 
1804     case NS_THEME_MENULIST_BUTTON:
1805       if (aFrame && aFrame->GetWritingMode().IsVertical()) {
1806         return false;
1807       }
1808       // "Native" dropdown buttons cause padding and margin problems, but only
1809       // in HTML so allow them in XUL.
1810       return (!aFrame ||
1811               IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
1812              !IsWidgetStyled(aPresContext, aFrame, aWidgetType);
1813 
1814     case NS_THEME_FOCUS_OUTLINE:
1815       return true;
1816   }
1817 
1818   return false;
1819 }
1820 
1821 NS_IMETHODIMP_(bool)
1822 nsNativeThemeGTK::WidgetIsContainer(uint8_t aWidgetType) {
1823   // XXXdwh At some point flesh all of this out.
1824   if (aWidgetType == NS_THEME_MENULIST_BUTTON ||
1825       aWidgetType == NS_THEME_RADIO || aWidgetType == NS_THEME_RANGE_THUMB ||
1826       aWidgetType == NS_THEME_CHECKBOX ||
1827       aWidgetType == NS_THEME_TAB_SCROLL_ARROW_BACK ||
1828       aWidgetType == NS_THEME_TAB_SCROLL_ARROW_FORWARD ||
1829       aWidgetType == NS_THEME_BUTTON_ARROW_UP ||
1830       aWidgetType == NS_THEME_BUTTON_ARROW_DOWN ||
1831       aWidgetType == NS_THEME_BUTTON_ARROW_NEXT ||
1832       aWidgetType == NS_THEME_BUTTON_ARROW_PREVIOUS)
1833     return false;
1834   return true;
1835 }
1836 
1837 bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(uint8_t aWidgetType) {
1838   if (aWidgetType == NS_THEME_MENULIST || aWidgetType == NS_THEME_BUTTON ||
1839       aWidgetType == NS_THEME_TREEHEADERCELL)
1840     return true;
1841 
1842   return false;
1843 }
1844 
1845 bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
1846 
1847 nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
1848     nsIFrame* aFrame, uint8_t aWidgetType) {
1849   switch (aWidgetType) {
1850     // These widgets always draw a default background.
1851     case NS_THEME_MENUPOPUP:
1852     case NS_THEME_WINDOW:
1853     case NS_THEME_DIALOG:
1854       return eOpaque;
1855     case NS_THEME_SCROLLBAR_VERTICAL:
1856     case NS_THEME_SCROLLBAR_HORIZONTAL:
1857 #ifdef MOZ_WIDGET_GTK
1858       // Make scrollbar tracks opaque on the window's scroll frame to prevent
1859       // leaf layers from overlapping. See bug 1179780.
1860       if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
1861             aFrame->PresContext()->IsRootContentDocument() &&
1862             IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)))
1863         return eTransparent;
1864 #endif
1865       return eOpaque;
1866     // Tooltips use gtk_paint_flat_box() on Gtk2
1867     // but are shaped on Gtk3
1868     case NS_THEME_TOOLTIP:
1869       return eTransparent;
1870   }
1871 
1872   return eUnknownTransparency;
1873 }
1874