Bug 1869647 - Mark hasStorageAccess.sub.https.window.html as intermittent after wpt...
[gecko.git] / widget / gtk / nsNativeThemeGTK.cpp
blob06d9b480070ada77808560a77e24c4d8d73833eb
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/. */
6 #include "nsNativeThemeGTK.h"
7 #include "nsPresContext.h"
8 #include "nsStyleConsts.h"
9 #include "gtkdrawing.h"
10 #include "ScreenHelperGTK.h"
11 #include "WidgetUtilsGtk.h"
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 "nsXULPopupManager.h"
22 #include "tree/nsTreeBodyFrame.h"
23 #include "prlink.h"
24 #include "nsGkAtoms.h"
25 #include "nsAttrValueInlines.h"
27 #include "mozilla/dom/HTMLInputElement.h"
28 #include "mozilla/ClearOnShutdown.h"
29 #include "mozilla/Services.h"
31 #include <gdk/gdkprivate.h>
32 #include <gtk/gtk.h>
34 #include "gfxContext.h"
35 #include "mozilla/dom/XULButtonElement.h"
36 #include "mozilla/gfx/BorrowedContext.h"
37 #include "mozilla/gfx/HelpersCairo.h"
38 #include "mozilla/gfx/PathHelpers.h"
39 #include "mozilla/Preferences.h"
40 #include "mozilla/PresShell.h"
41 #include "mozilla/layers/StackingContextHelper.h"
42 #include "mozilla/StaticPrefs_layout.h"
43 #include "mozilla/StaticPrefs_widget.h"
44 #include "nsWindow.h"
45 #include "nsLayoutUtils.h"
46 #include "Theme.h"
48 #ifdef MOZ_X11
49 # ifdef CAIRO_HAS_XLIB_SURFACE
50 # include "cairo-xlib.h"
51 # endif
52 #endif
54 #include <algorithm>
55 #include <dlfcn.h>
57 using namespace mozilla;
58 using namespace mozilla::gfx;
59 using namespace mozilla::widget;
61 static int gLastGdkError;
63 // Return widget scale factor of the monitor where the window is located by the
64 // most part. We intentionally honor the text scale factor here in order to
65 // have consistent scaling with other UI elements.
66 static inline CSSToLayoutDeviceScale GetWidgetScaleFactor(nsIFrame* aFrame) {
67 return aFrame->PresContext()->CSSToDevPixelScale();
70 nsNativeThemeGTK::nsNativeThemeGTK() : Theme(ScrollbarStyle()) {
71 if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
72 memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
73 return;
76 ThemeChanged();
79 nsNativeThemeGTK::~nsNativeThemeGTK() { moz_gtk_shutdown(); }
81 void nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) {
82 MOZ_ASSERT(aFrame);
83 MOZ_ASSERT(aFrame->PresShell());
85 nsViewManager* vm = aFrame->PresShell()->GetViewManager();
86 if (!vm) {
87 return;
89 vm->InvalidateAllViews();
92 static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
93 uint32_t aNamespace) {
94 nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
95 if (!content) return false;
96 return content->IsInNamespace(aNamespace);
99 static bool IsWidgetTypeDisabled(const uint8_t* aDisabledVector,
100 StyleAppearance aAppearance) {
101 auto type = static_cast<size_t>(aAppearance);
102 MOZ_ASSERT(type < static_cast<size_t>(StyleAppearance::Count));
103 return (aDisabledVector[type >> 3] & (1 << (type & 7))) != 0;
106 static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
107 StyleAppearance aAppearance) {
108 auto type = static_cast<size_t>(aAppearance);
109 MOZ_ASSERT(type < static_cast<size_t>(mozilla::StyleAppearance::Count));
110 aDisabledVector[type >> 3] |= (1 << (type & 7));
113 static inline uint16_t GetWidgetStateKey(StyleAppearance aAppearance,
114 GtkWidgetState* aWidgetState) {
115 return (aWidgetState->active | aWidgetState->focused << 1 |
116 aWidgetState->inHover << 2 | aWidgetState->disabled << 3 |
117 aWidgetState->isDefault << 4 |
118 static_cast<uint16_t>(aAppearance) << 5);
121 static bool IsWidgetStateSafe(uint8_t* aSafeVector, StyleAppearance aAppearance,
122 GtkWidgetState* aWidgetState) {
123 MOZ_ASSERT(static_cast<size_t>(aAppearance) <
124 static_cast<size_t>(mozilla::StyleAppearance::Count));
125 uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
126 return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
129 static void SetWidgetStateSafe(uint8_t* aSafeVector,
130 StyleAppearance aAppearance,
131 GtkWidgetState* aWidgetState) {
132 MOZ_ASSERT(static_cast<size_t>(aAppearance) <
133 static_cast<size_t>(mozilla::StyleAppearance::Count));
134 uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
135 aSafeVector[key >> 3] |= (1 << (key & 7));
138 /* static */
139 GtkTextDirection nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame) {
140 // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
141 // horizontal text with direction=RTL), rather than just considering the
142 // text direction. GtkTextDirection does not have distinct values for
143 // vertical writing modes, but considering the block flow direction is
144 // important for resizers and scrollbar elements, at least.
145 return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
148 // Returns positive for negative margins (otherwise 0).
149 gint nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) {
150 nscoord margin = IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
151 : aFrame->GetUsedMargin().bottom;
153 return std::min<gint>(
154 MOZ_GTK_TAB_MARGIN_MASK,
155 std::max(0, aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
158 bool nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aAppearance,
159 nsIFrame* aFrame,
160 WidgetNodeType& aGtkWidgetType,
161 GtkWidgetState* aState,
162 gint* aWidgetFlags) {
163 if (aWidgetFlags) {
164 *aWidgetFlags = 0;
167 ElementState elementState = GetContentState(aFrame, aAppearance);
168 if (aState) {
169 memset(aState, 0, sizeof(GtkWidgetState));
171 // For XUL checkboxes and radio buttons, the state of the parent
172 // determines our state.
173 if (aWidgetFlags) {
174 if (elementState.HasState(ElementState::CHECKED)) {
175 *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
177 if (elementState.HasState(ElementState::INDETERMINATE)) {
178 *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
182 aState->disabled =
183 elementState.HasState(ElementState::DISABLED) || IsReadOnly(aFrame);
184 aState->active = elementState.HasState(ElementState::ACTIVE);
185 aState->focused = elementState.HasState(ElementState::FOCUS);
186 aState->inHover = elementState.HasState(ElementState::HOVER);
187 aState->isDefault = IsDefaultButton(aFrame);
188 aState->canDefault = FALSE; // XXX fix me
190 if (aAppearance == StyleAppearance::Button ||
191 aAppearance == StyleAppearance::Toolbarbutton ||
192 aAppearance == StyleAppearance::Dualbutton ||
193 aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
194 aAppearance == StyleAppearance::Menulist ||
195 aAppearance == StyleAppearance::MenulistButton) {
196 aState->active &= aState->inHover;
197 } else if (aAppearance == StyleAppearance::Treetwisty ||
198 aAppearance == StyleAppearance::Treetwistyopen) {
199 if (nsTreeBodyFrame* treeBodyFrame = do_QueryFrame(aFrame)) {
200 const mozilla::AtomArray& atoms =
201 treeBodyFrame->GetPropertyArrayForCurrentDrawingItem();
202 aState->selected = atoms.Contains(nsGkAtoms::selected);
203 aState->inHover = atoms.Contains(nsGkAtoms::hover);
207 if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
208 // For these widget types, some element (either a child or parent)
209 // actually has element focus, so we check the focused attribute
210 // to see whether to draw in the focused state.
211 aState->focused = elementState.HasState(ElementState::FOCUSRING);
212 if (aAppearance == StyleAppearance::Radio ||
213 aAppearance == StyleAppearance::Checkbox) {
214 // In XUL, checkboxes and radios shouldn't have focus rings, their
215 // labels do
216 aState->focused = FALSE;
219 // A button with drop down menu open or an activated toggle button
220 // should always appear depressed.
221 if (aAppearance == StyleAppearance::Button ||
222 aAppearance == StyleAppearance::Toolbarbutton ||
223 aAppearance == StyleAppearance::Dualbutton ||
224 aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
225 aAppearance == StyleAppearance::Menulist ||
226 aAppearance == StyleAppearance::MenulistButton) {
227 bool menuOpen = IsOpenButton(aFrame);
228 aState->depressed = IsCheckedButton(aFrame) || menuOpen;
229 // we must not highlight buttons with open drop down menus on hover.
230 aState->inHover = aState->inHover && !menuOpen;
234 if (aAppearance == StyleAppearance::MozWindowTitlebar ||
235 aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
236 aAppearance == StyleAppearance::MozWindowButtonClose ||
237 aAppearance == StyleAppearance::MozWindowButtonMinimize ||
238 aAppearance == StyleAppearance::MozWindowButtonMaximize ||
239 aAppearance == StyleAppearance::MozWindowButtonRestore) {
240 aState->backdrop = !nsWindow::GetTopLevelWindowActiveState(aFrame);
244 switch (aAppearance) {
245 case StyleAppearance::Button:
246 if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL;
247 aGtkWidgetType = MOZ_GTK_BUTTON;
248 break;
249 case StyleAppearance::Toolbarbutton:
250 case StyleAppearance::Dualbutton:
251 if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE;
252 aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
253 break;
254 case StyleAppearance::Checkbox:
255 aGtkWidgetType = MOZ_GTK_CHECKBUTTON;
256 break;
257 case StyleAppearance::Radio:
258 aGtkWidgetType = MOZ_GTK_RADIOBUTTON;
259 break;
260 case StyleAppearance::Spinner:
261 aGtkWidgetType = MOZ_GTK_SPINBUTTON;
262 break;
263 case StyleAppearance::SpinnerUpbutton:
264 aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
265 break;
266 case StyleAppearance::SpinnerDownbutton:
267 aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
268 break;
269 case StyleAppearance::SpinnerTextfield:
270 aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
271 break;
272 case StyleAppearance::Range: {
273 if (IsRangeHorizontal(aFrame)) {
274 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
275 aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
276 } else {
277 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
278 aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
280 break;
282 case StyleAppearance::RangeThumb: {
283 if (IsRangeHorizontal(aFrame)) {
284 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
285 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
286 } else {
287 if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
288 aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
290 break;
292 case StyleAppearance::NumberInput:
293 case StyleAppearance::Textfield:
294 aGtkWidgetType = MOZ_GTK_ENTRY;
295 break;
296 case StyleAppearance::Textarea:
297 aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
298 break;
299 case StyleAppearance::Listbox:
300 case StyleAppearance::Treeview:
301 aGtkWidgetType = MOZ_GTK_TREEVIEW;
302 break;
303 case StyleAppearance::Treetwisty:
304 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
305 if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
306 break;
307 case StyleAppearance::Treetwistyopen:
308 aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
309 if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED;
310 break;
311 case StyleAppearance::MenulistButton:
312 case StyleAppearance::Menulist:
313 aGtkWidgetType = MOZ_GTK_DROPDOWN;
314 if (aWidgetFlags)
315 *aWidgetFlags =
316 IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
317 break;
318 case StyleAppearance::ToolbarbuttonDropdown:
319 case StyleAppearance::ButtonArrowDown:
320 case StyleAppearance::ButtonArrowUp:
321 case StyleAppearance::ButtonArrowNext:
322 case StyleAppearance::ButtonArrowPrevious:
323 aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
324 if (aWidgetFlags) {
325 *aWidgetFlags = GTK_ARROW_DOWN;
327 if (aAppearance == StyleAppearance::ButtonArrowUp)
328 *aWidgetFlags = GTK_ARROW_UP;
329 else if (aAppearance == StyleAppearance::ButtonArrowNext)
330 *aWidgetFlags = GTK_ARROW_RIGHT;
331 else if (aAppearance == StyleAppearance::ButtonArrowPrevious)
332 *aWidgetFlags = GTK_ARROW_LEFT;
334 break;
335 case StyleAppearance::Tooltip:
336 aGtkWidgetType = MOZ_GTK_TOOLTIP;
337 break;
338 case StyleAppearance::ProgressBar:
339 aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
340 break;
341 case StyleAppearance::Progresschunk: {
342 nsIFrame* stateFrame = aFrame->GetParent();
343 ElementState elementState = GetContentState(stateFrame, aAppearance);
345 aGtkWidgetType = elementState.HasState(ElementState::INDETERMINATE)
346 ? IsVerticalProgress(stateFrame)
347 ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
348 : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
349 : MOZ_GTK_PROGRESS_CHUNK;
350 } break;
351 case StyleAppearance::TabScrollArrowBack:
352 case StyleAppearance::TabScrollArrowForward:
353 if (aWidgetFlags)
354 *aWidgetFlags = aAppearance == StyleAppearance::TabScrollArrowBack
355 ? GTK_ARROW_LEFT
356 : GTK_ARROW_RIGHT;
357 aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
358 break;
359 case StyleAppearance::Tabpanels:
360 aGtkWidgetType = MOZ_GTK_TABPANELS;
361 break;
362 case StyleAppearance::Tab: {
363 if (IsBottomTab(aFrame)) {
364 aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
365 } else {
366 aGtkWidgetType = MOZ_GTK_TAB_TOP;
369 if (aWidgetFlags) {
370 /* First bits will be used to store max(0,-bmargin) where bmargin
371 * is the bottom margin of the tab in pixels (resp. top margin,
372 * for bottom tabs). */
373 *aWidgetFlags = GetTabMarginPixels(aFrame);
375 if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
377 if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
379 } break;
380 case StyleAppearance::Splitter:
381 if (IsHorizontal(aFrame))
382 aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
383 else
384 aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
385 break;
386 case StyleAppearance::MozWindowTitlebar:
387 aGtkWidgetType = MOZ_GTK_HEADER_BAR;
388 break;
389 case StyleAppearance::MozWindowDecorations:
390 aGtkWidgetType = MOZ_GTK_WINDOW_DECORATION;
391 break;
392 case StyleAppearance::MozWindowTitlebarMaximized:
393 aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
394 break;
395 case StyleAppearance::MozWindowButtonBox:
396 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_BOX;
397 break;
398 case StyleAppearance::MozWindowButtonClose:
399 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
400 break;
401 case StyleAppearance::MozWindowButtonMinimize:
402 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
403 break;
404 case StyleAppearance::MozWindowButtonMaximize:
405 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
406 break;
407 case StyleAppearance::MozWindowButtonRestore:
408 aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
409 break;
410 default:
411 return false;
414 return true;
417 class SystemCairoClipper : public ClipExporter {
418 public:
419 explicit SystemCairoClipper(cairo_t* aContext, gint aScaleFactor = 1)
420 : mContext(aContext), mScaleFactor(aScaleFactor) {}
422 void BeginClip(const Matrix& aTransform) override {
423 cairo_matrix_t mat;
424 GfxMatrixToCairoMatrix(aTransform, mat);
425 // We also need to remove the scale factor effect from the matrix
426 mat.y0 = mat.y0 / mScaleFactor;
427 mat.x0 = mat.x0 / mScaleFactor;
428 cairo_set_matrix(mContext, &mat);
430 cairo_new_path(mContext);
433 void MoveTo(const Point& aPoint) override {
434 cairo_move_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
435 mBeginPoint = aPoint;
436 mCurrentPoint = aPoint;
439 void LineTo(const Point& aPoint) override {
440 cairo_line_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
441 mCurrentPoint = aPoint;
444 void BezierTo(const Point& aCP1, const Point& aCP2,
445 const Point& aCP3) override {
446 cairo_curve_to(mContext, aCP1.x / mScaleFactor, aCP1.y / mScaleFactor,
447 aCP2.x / mScaleFactor, aCP2.y / mScaleFactor,
448 aCP3.x / mScaleFactor, aCP3.y / mScaleFactor);
449 mCurrentPoint = aCP3;
452 void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override {
453 Point CP0 = CurrentPoint();
454 Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
455 Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
456 Point CP3 = aCP2;
457 cairo_curve_to(mContext, CP1.x / mScaleFactor, CP1.y / mScaleFactor,
458 CP2.x / mScaleFactor, CP2.y / mScaleFactor,
459 CP3.x / mScaleFactor, CP3.y / mScaleFactor);
460 mCurrentPoint = aCP2;
463 void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
464 float aEndAngle, bool aAntiClockwise) override {
465 ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
466 aAntiClockwise);
469 void Close() override {
470 cairo_close_path(mContext);
471 mCurrentPoint = mBeginPoint;
474 void EndClip() override { cairo_clip(mContext); }
476 private:
477 cairo_t* mContext;
478 gint mScaleFactor;
481 static void DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
482 GtkWidgetState aState,
483 WidgetNodeType aGTKWidgetType, gint aFlags,
484 GtkTextDirection aDirection, double aScaleFactor,
485 bool aSnapped, const Point& aDrawOrigin,
486 const nsIntSize& aDrawSize,
487 GdkRectangle& aGDKRect,
488 nsITheme::Transparency aTransparency) {
489 static auto sCairoSurfaceSetDeviceScalePtr =
490 (void (*)(cairo_surface_t*, double, double))dlsym(
491 RTLD_DEFAULT, "cairo_surface_set_device_scale");
492 const bool useHiDPIWidgets =
493 aScaleFactor != 1.0 && sCairoSurfaceSetDeviceScalePtr;
495 Point drawOffsetScaled;
496 Point drawOffsetOriginal;
497 Matrix transform;
498 if (!aSnapped) {
499 // If we are not snapped, we depend on the DT for translation.
500 drawOffsetOriginal = aDrawOrigin;
501 drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
502 : drawOffsetOriginal;
503 transform = aDrawTarget->GetTransform().PreTranslate(drawOffsetScaled);
504 } else {
505 // Otherwise, we only need to take the device offset into account.
506 drawOffsetOriginal = aDrawOrigin - aContext->GetDeviceOffset();
507 drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
508 : drawOffsetOriginal;
509 transform = Matrix::Translation(drawOffsetScaled);
512 if (!useHiDPIWidgets && aScaleFactor != 1) {
513 transform.PreScale(aScaleFactor, aScaleFactor);
516 cairo_matrix_t mat;
517 GfxMatrixToCairoMatrix(transform, mat);
519 Size clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
520 (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
522 // A direct Cairo draw target is not available, so we need to create a
523 // temporary one.
524 #if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
525 if (GdkIsX11Display()) {
526 // If using a Cairo xlib surface, then try to reuse it.
527 BorrowedXlibDrawable borrow(aDrawTarget);
528 if (Drawable drawable = borrow.GetDrawable()) {
529 nsIntSize size = borrow.GetSize();
530 cairo_surface_t* surf = cairo_xlib_surface_create(
531 borrow.GetDisplay(), drawable, borrow.GetVisual(), size.width,
532 size.height);
533 if (!NS_WARN_IF(!surf)) {
534 Point offset = borrow.GetOffset();
535 if (offset != Point()) {
536 cairo_surface_set_device_offset(surf, offset.x, offset.y);
538 cairo_t* cr = cairo_create(surf);
539 if (!NS_WARN_IF(!cr)) {
540 RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
541 aContext->ExportClip(*clipper);
543 cairo_set_matrix(cr, &mat);
545 cairo_new_path(cr);
546 cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
547 cairo_clip(cr);
549 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
550 aDirection);
552 cairo_destroy(cr);
554 cairo_surface_destroy(surf);
556 borrow.Finish();
557 return;
560 #endif
562 // Check if the widget requires complex masking that must be composited.
563 // Try to directly write to the draw target's pixels if possible.
564 uint8_t* data;
565 nsIntSize size;
566 int32_t stride;
567 SurfaceFormat format;
568 IntPoint origin;
569 if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
570 // Create a Cairo image surface context the device rectangle.
571 cairo_surface_t* surf = cairo_image_surface_create_for_data(
572 data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
573 if (!NS_WARN_IF(!surf)) {
574 if (useHiDPIWidgets) {
575 sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
577 if (origin != IntPoint()) {
578 cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
580 cairo_t* cr = cairo_create(surf);
581 if (!NS_WARN_IF(!cr)) {
582 RefPtr<SystemCairoClipper> clipper =
583 new SystemCairoClipper(cr, useHiDPIWidgets ? aScaleFactor : 1);
584 aContext->ExportClip(*clipper);
586 cairo_set_matrix(cr, &mat);
588 cairo_new_path(cr);
589 cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
590 cairo_clip(cr);
592 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
593 aDirection);
595 cairo_destroy(cr);
597 cairo_surface_destroy(surf);
599 aDrawTarget->ReleaseBits(data);
600 } else {
601 // If the widget has any transparency, make sure to choose an alpha format.
602 format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8
603 : aDrawTarget->GetFormat();
604 // Create a temporary data surface to render the widget into.
605 RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
606 aDrawSize, format, aTransparency != nsITheme::eOpaque);
607 DataSourceSurface::MappedSurface map;
608 if (!NS_WARN_IF(
609 !(dataSurface &&
610 dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
611 // Create a Cairo image surface wrapping the data surface.
612 cairo_surface_t* surf = cairo_image_surface_create_for_data(
613 map.mData, GfxFormatToCairoFormat(format), aDrawSize.width,
614 aDrawSize.height, map.mStride);
615 cairo_t* cr = nullptr;
616 if (!NS_WARN_IF(!surf)) {
617 cr = cairo_create(surf);
618 if (!NS_WARN_IF(!cr)) {
619 if (aScaleFactor != 1) {
620 if (useHiDPIWidgets) {
621 sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
622 } else {
623 cairo_scale(cr, aScaleFactor, aScaleFactor);
627 moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
628 aDirection);
632 // Unmap the surface before using it as a source
633 dataSurface->Unmap();
635 if (cr) {
636 // The widget either needs to be masked or has transparency, so use the
637 // slower drawing path.
638 aDrawTarget->DrawSurface(
639 dataSurface,
640 Rect(aSnapped ? drawOffsetOriginal -
641 aDrawTarget->GetTransform().GetTranslation()
642 : drawOffsetOriginal,
643 Size(aDrawSize)),
644 Rect(0, 0, aDrawSize.width, aDrawSize.height));
645 cairo_destroy(cr);
648 if (surf) {
649 cairo_surface_destroy(surf);
655 CSSIntMargin nsNativeThemeGTK::GetExtraSizeForWidget(
656 nsIFrame* aFrame, StyleAppearance aAppearance) {
657 CSSIntMargin extra;
658 // Allow an extra one pixel above and below the thumb for certain
659 // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
660 // We modify the frame's overflow area. See bug 297508.
661 switch (aAppearance) {
662 case StyleAppearance::Button: {
663 if (IsDefaultButton(aFrame)) {
664 // Some themes draw a default indicator outside the widget,
665 // include that in overflow
666 moz_gtk_button_get_default_overflow(&extra.top.value, &extra.left.value,
667 &extra.bottom.value,
668 &extra.right.value);
669 break;
671 return {};
673 default:
674 return {};
676 return extra;
679 bool nsNativeThemeGTK::IsWidgetVisible(StyleAppearance aAppearance) {
680 switch (aAppearance) {
681 case StyleAppearance::MozWindowButtonBox:
682 return false;
683 default:
684 break;
686 return true;
689 NS_IMETHODIMP
690 nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
691 StyleAppearance aAppearance,
692 const nsRect& aRect,
693 const nsRect& aDirtyRect,
694 DrawOverflow aDrawOverflow) {
695 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
696 return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
697 aDirtyRect, aDrawOverflow);
700 GtkWidgetState state;
701 WidgetNodeType gtkWidgetType;
702 GtkTextDirection direction = GetTextDirection(aFrame);
703 gint flags;
705 if (!IsWidgetVisible(aAppearance) ||
706 !GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, &state,
707 &flags)) {
708 return NS_OK;
711 gfxContext* ctx = aContext;
712 nsPresContext* presContext = aFrame->PresContext();
714 gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
715 gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
717 // Align to device pixels where sensible
718 // to provide crisper and faster drawing.
719 // Don't snap if it's a non-unit scale factor. We're going to have to take
720 // slow paths then in any case.
721 // We prioritize the size when snapping in order to avoid distorting widgets
722 // that should be square, which can occur if edges are snapped independently.
723 bool snapped = ctx->UserToDevicePixelSnapped(
724 rect, gfxContext::SnapOption::PrioritizeSize);
725 if (snapped) {
726 // Leave rect in device coords but make dirtyRect consistent.
727 dirtyRect = ctx->UserToDevice(dirtyRect);
730 // Translate the dirty rect so that it is wrt the widget top-left.
731 dirtyRect.MoveBy(-rect.TopLeft());
732 // Round out the dirty rect to gdk pixels to ensure that gtk draws
733 // enough pixels for interpolation to device pixels.
734 dirtyRect.RoundOut();
736 // GTK themes can only draw an integer number of pixels
737 // (even when not snapped).
738 LayoutDeviceIntRect widgetRect(0, 0, NS_lround(rect.Width()),
739 NS_lround(rect.Height()));
741 // This is the rectangle that will actually be drawn, in gdk pixels
742 LayoutDeviceIntRect drawingRect(
743 int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
744 int32_t(dirtyRect.Width()), int32_t(dirtyRect.Height()));
745 if (widgetRect.IsEmpty() ||
746 !drawingRect.IntersectRect(widgetRect, drawingRect)) {
747 return NS_OK;
750 NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance),
751 "Trying to render an unsafe widget!");
753 bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
754 if (!safeState) {
755 gLastGdkError = 0;
756 gdk_error_trap_push();
759 Transparency transparency = GetWidgetTransparency(aFrame, aAppearance);
761 // gdk rectangles are wrt the drawing rect.
762 auto scaleFactor = GetWidgetScaleFactor(aFrame);
763 LayoutDeviceIntRect gdkDevRect(-drawingRect.TopLeft(), widgetRect.Size());
765 auto gdkCssRect = CSSIntRect::RoundIn(gdkDevRect / scaleFactor);
766 GdkRectangle gdk_rect = {gdkCssRect.x, gdkCssRect.y, gdkCssRect.width,
767 gdkCssRect.height};
769 // Save actual widget scale to GtkWidgetState as we don't provide
770 // the frame to gtk3drawing routines.
771 state.image_scale = std::ceil(scaleFactor.scale);
773 // translate everything so (0,0) is the top left of the drawingRect
774 gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft().ToUnknownPoint();
776 DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
777 flags, direction, scaleFactor.scale, snapped,
778 ToPoint(origin), drawingRect.Size().ToUnknownSize(),
779 gdk_rect, transparency);
781 if (!safeState) {
782 // gdk_flush() call from expose event crashes Gtk+ on Wayland
783 // (Gnome BZ #773307)
784 if (GdkIsX11Display()) {
785 gdk_flush();
787 gLastGdkError = gdk_error_trap_pop();
789 if (gLastGdkError) {
790 #ifdef DEBUG
791 printf(
792 "GTK theme failed for widget type %d, error was %d, state was "
793 "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
794 static_cast<int>(aAppearance), gLastGdkError, state.active,
795 state.focused, state.inHover, state.disabled);
796 #endif
797 NS_WARNING("GTK theme failed; disabling unsafe widget");
798 SetWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance);
799 // force refresh of the window, because the widget was not
800 // successfully drawn it must be redrawn using the default look
801 RefreshWidgetWindow(aFrame);
802 } else {
803 SetWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
807 // Indeterminate progress bar are animated.
808 if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
809 gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
810 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
811 NS_WARNING("unable to animate widget!");
815 return NS_OK;
818 bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
819 mozilla::wr::DisplayListBuilder& aBuilder,
820 mozilla::wr::IpcResourceUpdateQueue& aResources,
821 const mozilla::layers::StackingContextHelper& aSc,
822 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
823 StyleAppearance aAppearance, const nsRect& aRect) {
824 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
825 return Theme::CreateWebRenderCommandsForWidget(
826 aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
828 return false;
831 WidgetNodeType nsNativeThemeGTK::NativeThemeToGtkTheme(
832 StyleAppearance aAppearance, nsIFrame* aFrame) {
833 WidgetNodeType gtkWidgetType;
834 gint unusedFlags;
836 if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
837 &unusedFlags)) {
838 MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
839 return MOZ_GTK_WINDOW;
841 return gtkWidgetType;
844 static void FixupForVerticalWritingMode(WritingMode aWritingMode,
845 CSSIntMargin* aResult) {
846 if (aWritingMode.IsVertical()) {
847 bool rtl = aWritingMode.IsBidiRTL();
848 LogicalMargin logical(aWritingMode, aResult->top,
849 rtl ? aResult->left : aResult->right, aResult->bottom,
850 rtl ? aResult->right : aResult->left);
851 nsMargin physical = logical.GetPhysicalMargin(aWritingMode);
852 aResult->top = physical.top;
853 aResult->right = physical.right;
854 aResult->bottom = physical.bottom;
855 aResult->left = physical.left;
859 CSSIntMargin nsNativeThemeGTK::GetCachedWidgetBorder(
860 nsIFrame* aFrame, StyleAppearance aAppearance,
861 GtkTextDirection aDirection) {
862 CSSIntMargin result;
864 WidgetNodeType gtkWidgetType;
865 gint unusedFlags;
866 if (GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
867 &unusedFlags)) {
868 MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
869 uint8_t cacheIndex = gtkWidgetType / 8;
870 uint8_t cacheBit = 1u << (gtkWidgetType % 8);
872 if (mBorderCacheValid[cacheIndex] & cacheBit) {
873 result = mBorderCache[gtkWidgetType];
874 } else {
875 moz_gtk_get_widget_border(gtkWidgetType, &result.left.value,
876 &result.top.value, &result.right.value,
877 &result.bottom.value, aDirection);
878 if (gtkWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection
879 mBorderCacheValid[cacheIndex] |= cacheBit;
880 mBorderCache[gtkWidgetType] = result;
884 FixupForVerticalWritingMode(aFrame->GetWritingMode(), &result);
885 return result;
888 LayoutDeviceIntMargin nsNativeThemeGTK::GetWidgetBorder(
889 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
890 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
891 return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
894 CSSIntMargin result;
895 GtkTextDirection direction = GetTextDirection(aFrame);
896 switch (aAppearance) {
897 case StyleAppearance::Toolbox:
898 // gtk has no toolbox equivalent. So, although we map toolbox to
899 // gtk's 'toolbar' for purposes of painting the widget background,
900 // we don't use the toolbar border for toolbox.
901 break;
902 case StyleAppearance::Dualbutton:
903 // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
904 // around the entire button + dropdown, and also an inner border if you're
905 // over the button part. But, we want the inner button to be right up
906 // against the edge of the outer button so that the borders overlap.
907 // To make this happen, we draw a button border for the outer button,
908 // but don't reserve any space for it.
909 break;
910 case StyleAppearance::Tab: {
911 WidgetNodeType gtkWidgetType;
912 gint flags;
914 if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
915 &flags)) {
916 return {};
918 moz_gtk_get_tab_border(&result.left.value, &result.top.value,
919 &result.right.value, &result.bottom.value,
920 direction, (GtkTabFlags)flags, gtkWidgetType);
921 } break;
922 default: {
923 result = GetCachedWidgetBorder(aFrame, aAppearance, direction);
927 return (CSSMargin(result) * GetWidgetScaleFactor(aFrame)).Rounded();
930 bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
931 nsIFrame* aFrame,
932 StyleAppearance aAppearance,
933 LayoutDeviceIntMargin* aResult) {
934 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
935 return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
937 switch (aAppearance) {
938 case StyleAppearance::Toolbarbutton:
939 case StyleAppearance::Tooltip:
940 case StyleAppearance::MozWindowButtonBox:
941 case StyleAppearance::MozWindowButtonClose:
942 case StyleAppearance::MozWindowButtonMinimize:
943 case StyleAppearance::MozWindowButtonMaximize:
944 case StyleAppearance::MozWindowButtonRestore:
945 case StyleAppearance::Dualbutton:
946 case StyleAppearance::TabScrollArrowBack:
947 case StyleAppearance::TabScrollArrowForward:
948 case StyleAppearance::ToolbarbuttonDropdown:
949 case StyleAppearance::ButtonArrowUp:
950 case StyleAppearance::ButtonArrowDown:
951 case StyleAppearance::ButtonArrowNext:
952 case StyleAppearance::ButtonArrowPrevious:
953 case StyleAppearance::RangeThumb:
954 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
955 // and have a meaningful baseline, so they can't have
956 // author-specified padding.
957 case StyleAppearance::Checkbox:
958 case StyleAppearance::Radio:
959 aResult->SizeTo(0, 0, 0, 0);
960 return true;
961 default:
962 break;
965 return false;
968 bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
969 nsIFrame* aFrame,
970 StyleAppearance aAppearance,
971 nsRect* aOverflowRect) {
972 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
973 return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
974 aOverflowRect);
976 auto overflow = GetExtraSizeForWidget(aFrame, aAppearance);
977 if (overflow == CSSIntMargin()) {
978 return false;
980 aOverflowRect->Inflate(CSSIntMargin::ToAppUnits(overflow));
981 return true;
984 auto nsNativeThemeGTK::IsWidgetNonNative(nsIFrame* aFrame,
985 StyleAppearance aAppearance)
986 -> NonNative {
987 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
988 return NonNative::Always;
991 // If the current GTK theme color scheme matches our color-scheme, then we
992 // can draw a native widget.
993 if (LookAndFeel::ColorSchemeForFrame(aFrame) ==
994 PreferenceSheet::ColorSchemeForChrome()) {
995 return NonNative::No;
998 // As an special-case, for tooltips, we check if the tooltip color is the
999 // same between the light and dark themes. If so we can get away with drawing
1000 // the native widget, see bug 1817396.
1001 if (aAppearance == StyleAppearance::Tooltip) {
1002 auto darkColor =
1003 LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Dark,
1004 LookAndFeel::UseStandins::No);
1005 auto lightColor =
1006 LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Light,
1007 LookAndFeel::UseStandins::No);
1008 if (darkColor == lightColor) {
1009 return NonNative::No;
1013 // If the non-native theme doesn't support the widget then oh well...
1014 if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) {
1015 return NonNative::No;
1018 return NonNative::BecauseColorMismatch;
1021 bool nsNativeThemeGTK::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
1022 StyleAppearance aAppearance) {
1023 return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
1024 aAppearance == StyleAppearance::MozMenulistArrowButton;
1027 LayoutDeviceIntSize nsNativeThemeGTK::GetMinimumWidgetSize(
1028 nsPresContext* aPresContext, nsIFrame* aFrame,
1029 StyleAppearance aAppearance) {
1030 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1031 return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
1034 CSSIntSize result;
1035 switch (aAppearance) {
1036 case StyleAppearance::Splitter: {
1037 if (IsHorizontal(aFrame)) {
1038 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &result.width);
1039 } else {
1040 moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &result.height);
1042 } break;
1043 case StyleAppearance::RangeThumb: {
1044 if (IsRangeHorizontal(aFrame)) {
1045 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
1046 &result.width, &result.height);
1047 } else {
1048 moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &result.width,
1049 &result.width);
1051 } break;
1052 case StyleAppearance::TabScrollArrowBack:
1053 case StyleAppearance::TabScrollArrowForward: {
1054 moz_gtk_get_tab_scroll_arrow_size(&result.width, &result.height);
1055 } break;
1056 case StyleAppearance::Checkbox:
1057 case StyleAppearance::Radio: {
1058 const ToggleGTKMetrics* metrics = GetToggleMetrics(
1059 aAppearance == StyleAppearance::Radio ? MOZ_GTK_RADIOBUTTON
1060 : MOZ_GTK_CHECKBUTTON);
1061 result.width = metrics->minSizeWithBorder.width;
1062 result.height = metrics->minSizeWithBorder.height;
1063 } break;
1064 case StyleAppearance::ToolbarbuttonDropdown:
1065 case StyleAppearance::ButtonArrowUp:
1066 case StyleAppearance::ButtonArrowDown:
1067 case StyleAppearance::ButtonArrowNext:
1068 case StyleAppearance::ButtonArrowPrevious: {
1069 moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &result.width,
1070 &result.height);
1071 } break;
1072 case StyleAppearance::MozWindowButtonClose: {
1073 const ToolbarButtonGTKMetrics* metrics =
1074 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
1075 result.width = metrics->minSizeWithBorderMargin.width;
1076 result.height = metrics->minSizeWithBorderMargin.height;
1077 break;
1079 case StyleAppearance::MozWindowButtonMinimize: {
1080 const ToolbarButtonGTKMetrics* metrics =
1081 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
1082 result.width = metrics->minSizeWithBorderMargin.width;
1083 result.height = metrics->minSizeWithBorderMargin.height;
1084 break;
1086 case StyleAppearance::MozWindowButtonMaximize:
1087 case StyleAppearance::MozWindowButtonRestore: {
1088 const ToolbarButtonGTKMetrics* metrics =
1089 GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
1090 result.width = metrics->minSizeWithBorderMargin.width;
1091 result.height = metrics->minSizeWithBorderMargin.height;
1092 break;
1094 case StyleAppearance::Button:
1095 case StyleAppearance::Menulist:
1096 case StyleAppearance::MenulistButton: {
1097 if (aAppearance == StyleAppearance::Menulist ||
1098 aAppearance == StyleAppearance::MenulistButton) {
1099 // Include the arrow size.
1100 moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &result.width, &result.height);
1102 // else the minimum size is missing consideration of container
1103 // descendants; the value returned here will not be helpful, but the
1104 // box model may consider border and padding with child minimum sizes.
1106 CSSIntMargin border =
1107 GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame));
1108 result.width += border.LeftRight();
1109 result.height += border.TopBottom();
1110 } break;
1111 case StyleAppearance::NumberInput:
1112 case StyleAppearance::Textfield: {
1113 gint contentHeight = 0;
1114 gint borderPaddingHeight = 0;
1115 moz_gtk_get_entry_min_height(&contentHeight, &borderPaddingHeight);
1117 // Scale the min content height proportionately with the font-size if it's
1118 // smaller than the default one. This prevents <input type=text
1119 // style="font-size: .5em"> from keeping a ridiculously large size, for
1120 // example.
1121 const gfxFloat fieldFontSizeInCSSPixels = [] {
1122 gfxFontStyle fieldFontStyle;
1123 nsAutoString unusedFontName;
1124 DebugOnly<bool> result = LookAndFeel::GetFont(
1125 LookAndFeel::FontID::MozField, unusedFontName, fieldFontStyle);
1126 MOZ_ASSERT(result, "GTK look and feel supports the field font");
1127 // NOTE: GetFont returns font sizes in CSS pixels, and we want just
1128 // that.
1129 return fieldFontStyle.size;
1130 }();
1132 const gfxFloat fontSize = aFrame->StyleFont()->mFont.size.ToCSSPixels();
1133 if (fieldFontSizeInCSSPixels > fontSize) {
1134 contentHeight =
1135 std::round(contentHeight * fontSize / fieldFontSizeInCSSPixels);
1138 gint height = contentHeight + borderPaddingHeight;
1139 if (aFrame->GetWritingMode().IsVertical()) {
1140 result.width = height;
1141 } else {
1142 result.height = height;
1144 } break;
1145 case StyleAppearance::Spinner:
1146 // hard code these sizes
1147 result.width = 14;
1148 result.height = 26;
1149 break;
1150 case StyleAppearance::SpinnerUpbutton:
1151 case StyleAppearance::SpinnerDownbutton:
1152 // hard code these sizes
1153 result.width = 14;
1154 result.height = 13;
1155 break;
1156 case StyleAppearance::Treetwisty:
1157 case StyleAppearance::Treetwistyopen: {
1158 gint expander_size;
1159 moz_gtk_get_treeview_expander_size(&expander_size);
1160 result.width = result.height = expander_size;
1161 } break;
1162 default:
1163 break;
1166 return LayoutDeviceIntSize::Round(CSSSize(result) *
1167 GetWidgetScaleFactor(aFrame));
1170 NS_IMETHODIMP
1171 nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame,
1172 StyleAppearance aAppearance,
1173 nsAtom* aAttribute, bool* aShouldRepaint,
1174 const nsAttrValue* aOldValue) {
1175 *aShouldRepaint = false;
1177 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1178 return Theme::WidgetStateChanged(aFrame, aAppearance, aAttribute,
1179 aShouldRepaint, aOldValue);
1182 // Some widget types just never change state.
1183 if (aAppearance == StyleAppearance::Toolbox ||
1184 aAppearance == StyleAppearance::Toolbar ||
1185 aAppearance == StyleAppearance::Progresschunk ||
1186 aAppearance == StyleAppearance::ProgressBar ||
1187 aAppearance == StyleAppearance::Tooltip ||
1188 aAppearance == StyleAppearance::MozWindowDecorations) {
1189 return NS_OK;
1192 if (aAppearance == StyleAppearance::MozWindowTitlebar ||
1193 aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
1194 aAppearance == StyleAppearance::MozWindowButtonClose ||
1195 aAppearance == StyleAppearance::MozWindowButtonMinimize ||
1196 aAppearance == StyleAppearance::MozWindowButtonMaximize ||
1197 aAppearance == StyleAppearance::MozWindowButtonRestore) {
1198 *aShouldRepaint = true;
1199 return NS_OK;
1202 // XXXdwh Not sure what can really be done here. Can at least guess for
1203 // specific widgets that they're highly unlikely to have certain states.
1204 // For example, a toolbar doesn't care about any states.
1205 if (!aAttribute) {
1206 // Hover/focus/active changed. Always repaint.
1207 *aShouldRepaint = true;
1208 return NS_OK;
1211 // Check the attribute to see if it's relevant.
1212 // disabled, checked, dlgtype, default, etc.
1213 *aShouldRepaint = false;
1214 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1215 aAttribute == nsGkAtoms::selected ||
1216 aAttribute == nsGkAtoms::visuallyselected ||
1217 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::readonly ||
1218 aAttribute == nsGkAtoms::_default ||
1219 aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::open) {
1220 *aShouldRepaint = true;
1221 return NS_OK;
1223 return NS_OK;
1226 NS_IMETHODIMP
1227 nsNativeThemeGTK::ThemeChanged() {
1228 memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
1229 memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
1230 memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
1231 return NS_OK;
1234 NS_IMETHODIMP_(bool)
1235 nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
1236 nsIFrame* aFrame,
1237 StyleAppearance aAppearance) {
1238 if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance)) {
1239 return false;
1242 if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
1243 return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
1246 switch (aAppearance) {
1247 // Combobox dropdowns don't support native theming in vertical mode.
1248 case StyleAppearance::Menulist:
1249 case StyleAppearance::MenulistButton:
1250 if (aFrame && aFrame->GetWritingMode().IsVertical()) {
1251 return false;
1253 [[fallthrough]];
1255 case StyleAppearance::Button:
1256 case StyleAppearance::Radio:
1257 case StyleAppearance::Checkbox:
1258 case StyleAppearance::Toolbox: // N/A
1259 case StyleAppearance::Toolbarbutton:
1260 case StyleAppearance::Dualbutton: // so we can override the border with 0
1261 case StyleAppearance::ToolbarbuttonDropdown:
1262 case StyleAppearance::ButtonArrowUp:
1263 case StyleAppearance::ButtonArrowDown:
1264 case StyleAppearance::ButtonArrowNext:
1265 case StyleAppearance::ButtonArrowPrevious:
1266 case StyleAppearance::Listbox:
1267 case StyleAppearance::Treeview:
1268 // case StyleAppearance::Treeitem:
1269 case StyleAppearance::Treetwisty:
1270 // case StyleAppearance::Treeline:
1271 // case StyleAppearance::Treeheader:
1272 case StyleAppearance::Treetwistyopen:
1273 case StyleAppearance::ProgressBar:
1274 case StyleAppearance::Progresschunk:
1275 case StyleAppearance::Tab:
1276 // case StyleAppearance::Tabpanel:
1277 case StyleAppearance::Tabpanels:
1278 case StyleAppearance::TabScrollArrowBack:
1279 case StyleAppearance::TabScrollArrowForward:
1280 case StyleAppearance::Tooltip:
1281 case StyleAppearance::Spinner:
1282 case StyleAppearance::SpinnerUpbutton:
1283 case StyleAppearance::SpinnerDownbutton:
1284 case StyleAppearance::SpinnerTextfield:
1285 case StyleAppearance::NumberInput:
1286 case StyleAppearance::Textfield:
1287 case StyleAppearance::Textarea:
1288 case StyleAppearance::Range:
1289 case StyleAppearance::RangeThumb:
1290 case StyleAppearance::Splitter:
1291 case StyleAppearance::MozWindowButtonBox:
1292 case StyleAppearance::MozWindowButtonClose:
1293 case StyleAppearance::MozWindowButtonMinimize:
1294 case StyleAppearance::MozWindowButtonMaximize:
1295 case StyleAppearance::MozWindowButtonRestore:
1296 case StyleAppearance::MozWindowTitlebar:
1297 case StyleAppearance::MozWindowTitlebarMaximized:
1298 case StyleAppearance::MozWindowDecorations:
1299 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1300 default:
1301 break;
1304 return false;
1307 NS_IMETHODIMP_(bool)
1308 nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) {
1309 // XXXdwh At some point flesh all of this out.
1310 if (aAppearance == StyleAppearance::Radio ||
1311 aAppearance == StyleAppearance::RangeThumb ||
1312 aAppearance == StyleAppearance::Checkbox ||
1313 aAppearance == StyleAppearance::TabScrollArrowBack ||
1314 aAppearance == StyleAppearance::TabScrollArrowForward ||
1315 aAppearance == StyleAppearance::ButtonArrowUp ||
1316 aAppearance == StyleAppearance::ButtonArrowDown ||
1317 aAppearance == StyleAppearance::ButtonArrowNext ||
1318 aAppearance == StyleAppearance::ButtonArrowPrevious)
1319 return false;
1320 return true;
1323 bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
1324 StyleAppearance aAppearance) {
1325 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1326 return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
1328 switch (aAppearance) {
1329 case StyleAppearance::Checkbox:
1330 case StyleAppearance::Radio:
1331 // These are drawn only for non-XUL elements, but in XUL the label has
1332 // the focus ring.
1333 return true;
1334 case StyleAppearance::Button:
1335 case StyleAppearance::Menulist:
1336 case StyleAppearance::MenulistButton:
1337 case StyleAppearance::Textarea:
1338 case StyleAppearance::Textfield:
1339 case StyleAppearance::NumberInput:
1340 return true;
1341 default:
1342 return false;
1346 bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
1348 nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
1349 nsIFrame* aFrame, StyleAppearance aAppearance) {
1350 if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
1351 return Theme::GetWidgetTransparency(aFrame, aAppearance);
1354 switch (aAppearance) {
1355 // Tooltips use gtk_paint_flat_box() on Gtk2
1356 // but are shaped on Gtk3
1357 case StyleAppearance::Tooltip:
1358 return eTransparent;
1359 default:
1360 return eUnknownTransparency;
1364 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
1365 if (gfxPlatform::IsHeadless()) {
1366 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
1368 return do_AddRef(new nsNativeThemeGTK());