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