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