Bug 1766413 [wpt PR 33787] - Fix some cases of "ref test" to be "reftest", a=testonly
[gecko.git] / widget / Theme.cpp
blobd4f16e3e332830cf7f2b4e7601be25d80c984985
1 /* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "Theme.h"
7 #include "ThemeCocoa.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/dom/Document.h"
12 #include "mozilla/dom/HTMLMeterElement.h"
13 #include "mozilla/dom/HTMLProgressElement.h"
14 #include "mozilla/gfx/Rect.h"
15 #include "mozilla/gfx/Types.h"
16 #include "mozilla/gfx/Filters.h"
17 #include "mozilla/RelativeLuminanceUtils.h"
18 #include "mozilla/StaticPrefs_widget.h"
19 #include "mozilla/webrender/WebRenderAPI.h"
20 #include "nsCSSColorUtils.h"
21 #include "nsCSSRendering.h"
22 #include "nsScrollbarFrame.h"
23 #include "nsIScrollbarMediator.h"
24 #include "nsDeviceContext.h"
25 #include "nsLayoutUtils.h"
26 #include "nsRangeFrame.h"
27 #include "PathHelpers.h"
28 #include "ScrollbarDrawingAndroid.h"
29 #include "ScrollbarDrawingCocoa.h"
30 #include "ScrollbarDrawingGTK.h"
31 #include "ScrollbarDrawingWin.h"
32 #include "ScrollbarDrawingWin11.h"
34 #ifdef XP_WIN
35 # include "mozilla/WindowsVersion.h"
36 #endif
38 using namespace mozilla;
39 using namespace mozilla::dom;
40 using namespace mozilla::gfx;
41 using namespace mozilla::widget;
43 namespace {
45 static constexpr gfx::sRGBColor sColorGrey10(
46 gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
47 static constexpr gfx::sRGBColor sColorGrey10Alpha50(
48 gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
49 static constexpr gfx::sRGBColor sColorGrey20(
50 gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
51 static constexpr gfx::sRGBColor sColorGrey30(
52 gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
53 static constexpr gfx::sRGBColor sColorGrey40(
54 gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
55 static constexpr gfx::sRGBColor sColorGrey40Alpha50(
56 gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
57 static constexpr gfx::sRGBColor sColorGrey50(
58 gfx::sRGBColor::UnusualFromARGB(0xff676774));
59 static constexpr gfx::sRGBColor sColorGrey60(
60 gfx::sRGBColor::UnusualFromARGB(0xff484851));
62 static constexpr gfx::sRGBColor sColorMeterGreen10(
63 gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
64 static constexpr gfx::sRGBColor sColorMeterGreen20(
65 gfx::sRGBColor::UnusualFromARGB(0xff056139));
66 static constexpr gfx::sRGBColor sColorMeterYellow10(
67 gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
68 static constexpr gfx::sRGBColor sColorMeterYellow20(
69 gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
70 static constexpr gfx::sRGBColor sColorMeterRed10(
71 gfx::sRGBColor::UnusualFromARGB(0xffe22850));
72 static constexpr gfx::sRGBColor sColorMeterRed20(
73 gfx::sRGBColor::UnusualFromARGB(0xff810220));
75 static const CSSCoord kMinimumColorPickerHeight = 32.0f;
76 static const CSSCoord kMinimumRangeThumbSize = 20.0f;
77 static const CSSCoord kMinimumDropdownArrowButtonWidth = 18.0f;
78 static const CSSCoord kMinimumSpinnerButtonWidth = 18.0f;
79 static const CSSCoord kMinimumSpinnerButtonHeight = 9.0f;
80 static const CSSCoord kButtonBorderWidth = 1.0f;
81 static const CSSCoord kMenulistBorderWidth = 1.0f;
82 static const CSSCoord kTextFieldBorderWidth = 1.0f;
83 static const CSSCoord kRangeHeight = 6.0f;
84 static const CSSCoord kProgressbarHeight = 6.0f;
85 static const CSSCoord kMeterHeight = 12.0f;
87 // nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
88 // This border-width makes its baseline 2px under the bottom, which is nice.
89 static constexpr CSSCoord kCheckboxRadioBorderWidth = 2.0f;
91 static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
93 // This pushes and pops a clip rect to the draw target.
95 // This is done to reduce fuzz in places where we may have antialiasing,
96 // because skia is not clip-invariant: given different clips, it does not
97 // guarantee the same result, even if the painted content doesn't intersect
98 // the clips.
100 // This is a bit sad, overall, but...
101 struct MOZ_RAII AutoClipRect {
102 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
103 mDt.PushClipRect(aRect.ToUnknownRect());
106 ~AutoClipRect() { mDt.PopClip(); }
108 private:
109 DrawTarget& mDt;
112 static StaticRefPtr<Theme> gNativeInstance;
113 static StaticRefPtr<Theme> gNonNativeInstance;
114 static StaticRefPtr<Theme> gRDMInstance;
116 } // namespace
118 #ifdef ANDROID
119 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
120 // Android doesn't have a native theme.
121 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
123 #endif
125 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
126 if (MOZ_UNLIKELY(!gNonNativeInstance)) {
127 UniquePtr<ScrollbarDrawing> scrollbarDrawing = Theme::ScrollbarStyle();
128 #ifdef MOZ_WIDGET_COCOA
129 gNonNativeInstance = new ThemeCocoa(std::move(scrollbarDrawing));
130 #else
131 gNonNativeInstance = new Theme(std::move(scrollbarDrawing));
132 #endif
133 ClearOnShutdown(&gNonNativeInstance);
135 return do_AddRef(gNonNativeInstance);
138 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
139 if (MOZ_UNLIKELY(!gNativeInstance)) {
140 gNativeInstance = do_CreateNativeThemeDoNotUseDirectly();
141 ClearOnShutdown(&gNativeInstance);
143 return do_AddRef(gNativeInstance);
146 already_AddRefed<nsITheme> do_GetRDMThemeDoNotUseDirectly() {
147 if (MOZ_UNLIKELY(!gRDMInstance)) {
148 gRDMInstance = new Theme(MakeUnique<ScrollbarDrawingAndroid>());
149 ClearOnShutdown(&gRDMInstance);
151 return do_AddRef(gRDMInstance);
154 namespace mozilla::widget {
156 NS_IMPL_ISUPPORTS_INHERITED(Theme, nsNativeTheme, nsITheme)
158 static constexpr nsLiteralCString kPrefs[] = {
159 "widget.non-native-theme.use-theme-accent"_ns,
160 "widget.non-native-theme.win.scrollbar.use-system-size"_ns,
161 "widget.non-native-theme.scrollbar.size.override"_ns,
162 "widget.non-native-theme.scrollbar.style"_ns,
165 void Theme::Init() {
166 for (const auto& pref : kPrefs) {
167 Preferences::RegisterCallback(PrefChangedCallback, pref);
169 LookAndFeelChanged();
172 void Theme::Shutdown() {
173 for (const auto& pref : kPrefs) {
174 Preferences::UnregisterCallback(PrefChangedCallback, pref);
178 /* static */
179 void Theme::LookAndFeelChanged() {
180 ThemeColors::RecomputeAccentColors();
181 if (gNonNativeInstance) {
182 gNonNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
184 if (gNativeInstance) {
185 gNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
189 /* static */
190 auto Theme::GetDPIRatio(nsPresContext* aPc, StyleAppearance aAppearance)
191 -> DPIRatio {
192 // Widgets react to zoom, except scrollbars.
193 if (IsWidgetScrollbarPart(aAppearance)) {
194 return ScrollbarDrawing::GetDPIRatioForScrollbarPart(aPc);
196 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
199 /* static */
200 auto Theme::GetDPIRatio(nsIFrame* aFrame, StyleAppearance aAppearance)
201 -> DPIRatio {
202 return GetDPIRatio(aFrame->PresContext(), aAppearance);
205 // Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
206 // size to exact device pixels to avoid snapping disorting the circles.
207 static LayoutDeviceRect CheckBoxRadioRect(const LayoutDeviceRect& aRect) {
208 // Place a square rect in the center of aRect.
209 auto size = std::trunc(std::min(aRect.width, aRect.height));
210 auto position = aRect.Center() - LayoutDevicePoint(size * 0.5, size * 0.5);
211 return LayoutDeviceRect(position, LayoutDeviceSize(size, size));
214 std::pair<sRGBColor, sRGBColor> Theme::ComputeCheckboxColors(
215 const EventStates& aState, StyleAppearance aAppearance,
216 const Colors& aColors) {
217 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
218 aAppearance == StyleAppearance::Radio);
220 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
221 bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
222 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
223 aState.HasState(NS_EVENT_STATE_INDETERMINATE);
225 if (isChecked || isIndeterminate) {
226 if (isDisabled) {
227 auto color = ComputeBorderColor(aState, aColors, OutlineCoversBorder::No);
228 return std::make_pair(color, color);
231 bool isActive =
232 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
233 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
234 const auto& color = isActive ? aColors.Accent().GetDarker()
235 : isHovered ? aColors.Accent().GetDark()
236 : aColors.Accent().Get();
237 return std::make_pair(color, color);
240 return ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::No);
243 sRGBColor Theme::ComputeCheckmarkColor(const EventStates& aState,
244 const Colors& aColors) {
245 if (aColors.HighContrast()) {
246 return aColors.System(StyleSystemColor::Selecteditemtext);
248 if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
249 return sRGBColor::White(.8f);
251 return aColors.Accent().GetForeground();
254 sRGBColor Theme::ComputeBorderColor(const EventStates& aState,
255 const Colors& aColors,
256 OutlineCoversBorder aOutlineCoversBorder) {
257 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
258 if (aColors.HighContrast()) {
259 return aColors.System(isDisabled ? StyleSystemColor::Graytext
260 : StyleSystemColor::Buttontext);
262 bool isActive =
263 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
264 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
265 bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
266 if (isDisabled) {
267 return sColorGrey40Alpha50;
269 if (isFocused && aOutlineCoversBorder == OutlineCoversBorder::Yes) {
270 // If we draw the outline over the border, prevent issues where the border
271 // shows underneath if it snaps in the wrong direction by using a
272 // transparent border. An alternative to this is ensuring that we snap the
273 // offset in PaintRoundedFocusRect the same was a we snap border widths, so
274 // that negative offsets are guaranteed to cover the border.
275 // But this looks harder to mess up.
276 return sTransparent;
278 bool dark = aColors.IsDark();
279 if (isActive) {
280 return dark ? sColorGrey20 : sColorGrey60;
282 if (isHovered) {
283 return dark ? sColorGrey30 : sColorGrey50;
285 return sColorGrey40;
288 std::pair<sRGBColor, sRGBColor> Theme::ComputeButtonColors(
289 const EventStates& aState, const Colors& aColors, nsIFrame* aFrame) {
290 bool isActive =
291 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
292 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
293 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
295 nscolor backgroundColor = [&] {
296 if (isDisabled) {
297 return aColors.SystemNs(StyleSystemColor::MozButtondisabledface);
299 if (isActive) {
300 return aColors.SystemNs(StyleSystemColor::MozButtonactiveface);
302 if (isHovered) {
303 return aColors.SystemNs(StyleSystemColor::MozButtonhoverface);
305 return aColors.SystemNs(StyleSystemColor::Buttonface);
306 }();
308 if (aState.HasState(NS_EVENT_STATE_AUTOFILL)) {
309 backgroundColor = NS_ComposeColors(
310 backgroundColor,
311 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
314 const sRGBColor borderColor =
315 ComputeBorderColor(aState, aColors, OutlineCoversBorder::Yes);
316 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
319 std::pair<sRGBColor, sRGBColor> Theme::ComputeTextfieldColors(
320 const EventStates& aState, const Colors& aColors,
321 OutlineCoversBorder aOutlineCoversBorder) {
322 nscolor backgroundColor = [&] {
323 if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
324 return aColors.SystemNs(StyleSystemColor::MozDisabledfield);
326 return aColors.SystemNs(StyleSystemColor::Field);
327 }();
329 if (aState.HasState(NS_EVENT_STATE_AUTOFILL)) {
330 backgroundColor = NS_ComposeColors(
331 backgroundColor,
332 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
335 const sRGBColor borderColor =
336 ComputeBorderColor(aState, aColors, aOutlineCoversBorder);
337 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
340 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeProgressColors(
341 const EventStates& aState, const Colors& aColors) {
342 if (aColors.HighContrast()) {
343 return aColors.SystemPair(StyleSystemColor::Selecteditem,
344 StyleSystemColor::Buttontext);
347 bool isActive =
348 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
349 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
350 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
352 if (isDisabled) {
353 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
355 if (isActive || isHovered) {
356 return std::make_pair(aColors.Accent().GetDark(),
357 aColors.Accent().GetDarker());
359 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
362 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeTrackColors(
363 const EventStates& aState, const Colors& aColors) {
364 if (aColors.HighContrast()) {
365 return aColors.SystemPair(StyleSystemColor::Window,
366 StyleSystemColor::Buttontext);
368 bool isActive =
369 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
370 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
371 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
373 if (isDisabled) {
374 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
376 if (isActive || isHovered) {
377 return std::make_pair(sColorGrey20, sColorGrey50);
379 return std::make_pair(sColorGrey10, sColorGrey40);
382 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeThumbColors(
383 const EventStates& aState, const Colors& aColors) {
384 if (aColors.HighContrast()) {
385 return aColors.SystemPair(StyleSystemColor::Selecteditemtext,
386 StyleSystemColor::Selecteditem);
389 bool isActive =
390 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
391 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
392 bool isHovered = aState.HasState(NS_EVENT_STATE_HOVER);
394 const sRGBColor& backgroundColor = [&] {
395 if (isDisabled) {
396 return sColorGrey40;
398 if (isActive) {
399 return aColors.Accent().Get();
401 if (isHovered) {
402 return sColorGrey60;
404 return sColorGrey50;
405 }();
407 const sRGBColor borderColor = sRGBColor::OpaqueWhite();
408 return std::make_pair(backgroundColor, borderColor);
411 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressColors(
412 const Colors& aColors) {
413 if (aColors.HighContrast()) {
414 return aColors.SystemPair(StyleSystemColor::Selecteditem,
415 StyleSystemColor::Buttontext);
417 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
420 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressTrackColors(
421 const Colors& aColors) {
422 if (aColors.HighContrast()) {
423 return aColors.SystemPair(StyleSystemColor::Buttonface,
424 StyleSystemColor::Buttontext);
426 return std::make_pair(sColorGrey10, sColorGrey40);
429 std::pair<sRGBColor, sRGBColor> Theme::ComputeMeterchunkColors(
430 const EventStates& aMeterState, const Colors& aColors) {
431 if (aColors.HighContrast()) {
432 return ComputeProgressColors(aColors);
434 sRGBColor borderColor = sColorMeterGreen20;
435 sRGBColor chunkColor = sColorMeterGreen10;
437 if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
438 borderColor = sColorMeterYellow20;
439 chunkColor = sColorMeterYellow10;
440 } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
441 borderColor = sColorMeterRed20;
442 chunkColor = sColorMeterRed10;
445 return std::make_pair(chunkColor, borderColor);
448 std::array<sRGBColor, 3> Theme::ComputeFocusRectColors(const Colors& aColors) {
449 if (aColors.HighContrast()) {
450 return {aColors.System(StyleSystemColor::Selecteditem),
451 aColors.System(StyleSystemColor::Buttontext),
452 aColors.System(StyleSystemColor::Window)};
454 const auto& accent = aColors.Accent();
455 const sRGBColor middle =
456 aColors.IsDark() ? sRGBColor::Black(.3f) : sRGBColor::White(.3f);
457 return {accent.Get(), middle, accent.GetLight()};
460 static const CSSCoord kInnerFocusOutlineWidth = 2.0f;
462 template <typename PaintBackendData>
463 void Theme::PaintRoundedFocusRect(PaintBackendData& aBackendData,
464 const LayoutDeviceRect& aRect,
465 const Colors& aColors, DPIRatio aDpiRatio,
466 CSSCoord aRadius, CSSCoord aOffset) {
467 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
468 // the GetWidgetOverflow path for FocusOutline.
469 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
471 LayoutDeviceRect focusRect(aRect);
473 // The focus rect is painted outside of the border area (aRect), see:
475 // data:text/html,<div style="border: 1px solid; outline: 2px solid
476 // red">Foobar</div>
478 // But some controls might provide a negative offset to cover the border, if
479 // necessary.
480 CSSCoord strokeWidth = kInnerFocusOutlineWidth;
481 auto strokeWidthDevPx =
482 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
483 CSSCoord strokeRadius = aRadius;
484 focusRect.Inflate(aOffset * aDpiRatio + strokeWidthDevPx);
486 ThemeDrawing::PaintRoundedRectWithRadius(
487 aBackendData, focusRect, sTransparent, innerColor, strokeWidth,
488 strokeRadius, aDpiRatio);
490 strokeWidth = CSSCoord(1.0f);
491 strokeWidthDevPx =
492 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
493 strokeRadius += strokeWidth;
494 focusRect.Inflate(strokeWidthDevPx);
496 ThemeDrawing::PaintRoundedRectWithRadius(
497 aBackendData, focusRect, sTransparent, middleColor, strokeWidth,
498 strokeRadius, aDpiRatio);
500 strokeWidth = CSSCoord(2.0f);
501 strokeWidthDevPx =
502 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(strokeWidth, aDpiRatio));
503 strokeRadius += strokeWidth;
504 focusRect.Inflate(strokeWidthDevPx);
506 ThemeDrawing::PaintRoundedRectWithRadius(
507 aBackendData, focusRect, sTransparent, outerColor, strokeWidth,
508 strokeRadius, aDpiRatio);
511 void Theme::PaintCheckboxControl(DrawTarget& aDrawTarget,
512 const LayoutDeviceRect& aRect,
513 const EventStates& aState,
514 const Colors& aColors, DPIRatio aDpiRatio) {
515 auto [backgroundColor, borderColor] =
516 ComputeCheckboxColors(aState, StyleAppearance::Checkbox, aColors);
518 const CSSCoord radius = 2.0f;
519 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
520 if (backgroundColor == borderColor) {
521 borderWidth = 0.0f;
523 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect,
524 backgroundColor, borderColor,
525 borderWidth, radius, aDpiRatio);
528 if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
529 PaintIndeterminateMark(aDrawTarget, aRect, aState, aColors);
530 } else if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
531 PaintCheckMark(aDrawTarget, aRect, aState, aColors);
534 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
535 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
539 constexpr CSSCoord kCheckboxRadioContentBoxSize = 10.0f;
540 constexpr CSSCoord kCheckboxRadioBorderBoxSize =
541 kCheckboxRadioContentBoxSize + kCheckboxRadioBorderWidth * 2.0f;
543 void Theme::PaintCheckMark(DrawTarget& aDrawTarget,
544 const LayoutDeviceRect& aRect,
545 const EventStates& aState, const Colors& aColors) {
546 // Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
547 // unit box centered at 0,0
548 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
549 3.5f, -0.5f, -1.5f, -3.5f};
550 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
551 -4.0f, 1.0f, 1.25f, -1.0f};
552 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
553 const float scale =
554 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
555 auto center = aRect.Center().ToUnknownPoint();
557 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
558 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
559 builder->MoveTo(p);
560 for (int32_t i = 1; i < checkNumPoints; i++) {
561 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
562 builder->LineTo(p);
564 RefPtr<Path> path = builder->Finish();
566 sRGBColor fillColor = ComputeCheckmarkColor(aState, aColors);
567 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(fillColor)));
570 void Theme::PaintIndeterminateMark(DrawTarget& aDrawTarget,
571 const LayoutDeviceRect& aRect,
572 const EventStates& aState,
573 const Colors& aColors) {
574 const CSSCoord borderWidth = 2.0f;
575 const float scale =
576 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
578 Rect rect = aRect.ToUnknownRect();
579 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
580 rect.height = borderWidth * scale;
581 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
582 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
584 sRGBColor fillColor = ComputeCheckmarkColor(aState, aColors);
585 aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
588 template <typename PaintBackendData>
589 void Theme::PaintStrokedCircle(PaintBackendData& aPaintData,
590 const LayoutDeviceRect& aRect,
591 const sRGBColor& aBackgroundColor,
592 const sRGBColor& aBorderColor,
593 const CSSCoord aBorderWidth,
594 DPIRatio aDpiRatio) {
595 auto radius = LayoutDeviceCoord(aRect.Size().width) / aDpiRatio;
596 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, aBackgroundColor,
597 aBorderColor, aBorderWidth, radius,
598 aDpiRatio);
601 void Theme::PaintCircleShadow(WebRenderBackendData& aWrData,
602 const LayoutDeviceRect& aBoxRect,
603 const LayoutDeviceRect& aClipRect,
604 float aShadowAlpha, const CSSPoint& aShadowOffset,
605 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
606 const bool kBackfaceIsVisible = true;
607 const LayoutDeviceCoord stdDev = aShadowBlurStdDev * aDpiRatio;
608 const LayoutDevicePoint shadowOffset = aShadowOffset * aDpiRatio;
609 const IntSize inflation =
610 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
611 LayoutDeviceRect shadowRect = aBoxRect;
612 shadowRect.MoveBy(shadowOffset);
613 shadowRect.Inflate(inflation.width, inflation.height);
614 const auto boxRect = wr::ToLayoutRect(aBoxRect);
615 aWrData.mBuilder.PushBoxShadow(
616 wr::ToLayoutRect(shadowRect), wr::ToLayoutRect(aClipRect),
617 kBackfaceIsVisible, boxRect,
618 wr::ToLayoutVector2D(aShadowOffset * aDpiRatio),
619 wr::ToColorF(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)), stdDev,
620 /* aSpread = */ 0.0f,
621 wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect.Size().width)),
622 wr::BoxShadowClipMode::Outset);
625 void Theme::PaintCircleShadow(DrawTarget& aDrawTarget,
626 const LayoutDeviceRect& aBoxRect,
627 const LayoutDeviceRect& aClipRect,
628 float aShadowAlpha, const CSSPoint& aShadowOffset,
629 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
630 Float stdDev = aShadowBlurStdDev * aDpiRatio;
631 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
633 RefPtr<FilterNode> blurFilter =
634 aDrawTarget.CreateFilter(FilterType::GAUSSIAN_BLUR);
635 if (!blurFilter) {
636 return;
639 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
641 IntSize inflation =
642 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
643 Rect inflatedRect = aBoxRect.ToUnknownRect();
644 inflatedRect.Inflate(inflation.width, inflation.height);
645 Rect sourceRectInFilterSpace =
646 inflatedRect - aBoxRect.TopLeft().ToUnknownPoint();
647 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
649 IntSize dtSize = RoundedToInt(aBoxRect.Size().ToUnknownSize());
650 RefPtr<DrawTarget> ellipseDT = aDrawTarget.CreateSimilarDrawTargetForFilter(
651 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
652 sourceRectInFilterSpace, destinationPointOfSourceRect);
653 if (!ellipseDT) {
654 return;
657 AutoClipRect clipRect(aDrawTarget, aClipRect);
659 RefPtr<Path> ellipse = MakePathForEllipse(
660 *ellipseDT, (aBoxRect - aBoxRect.TopLeft()).Center().ToUnknownPoint(),
661 aBoxRect.Size().ToUnknownSize());
662 ellipseDT->Fill(ellipse,
663 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
664 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
666 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
667 aDrawTarget.DrawFilter(blurFilter, sourceRectInFilterSpace,
668 destinationPointOfSourceRect);
671 template <typename PaintBackendData>
672 void Theme::PaintRadioControl(PaintBackendData& aPaintData,
673 const LayoutDeviceRect& aRect,
674 const EventStates& aState, const Colors& aColors,
675 DPIRatio aDpiRatio) {
676 auto [backgroundColor, borderColor] =
677 ComputeCheckboxColors(aState, StyleAppearance::Radio, aColors);
679 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
680 if (backgroundColor == borderColor) {
681 borderWidth = 0.0f;
683 PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
684 borderWidth, aDpiRatio);
687 if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
688 LayoutDeviceRect rect(aRect);
689 auto width = LayoutDeviceCoord(
690 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
691 rect.Deflate(width);
693 auto checkColor = ComputeCheckmarkColor(aState, aColors);
694 PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
695 kCheckboxRadioBorderWidth, aDpiRatio);
698 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
699 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
703 template <typename PaintBackendData>
704 void Theme::PaintTextField(PaintBackendData& aPaintData,
705 const LayoutDeviceRect& aRect,
706 const EventStates& aState, const Colors& aColors,
707 DPIRatio aDpiRatio) {
708 auto [backgroundColor, borderColor] =
709 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
711 const CSSCoord radius = 2.0f;
713 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
714 borderColor, kTextFieldBorderWidth,
715 radius, aDpiRatio);
717 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
718 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
719 radius + kTextFieldBorderWidth,
720 -kTextFieldBorderWidth);
724 template <typename PaintBackendData>
725 void Theme::PaintListbox(PaintBackendData& aPaintData,
726 const LayoutDeviceRect& aRect,
727 const EventStates& aState, const Colors& aColors,
728 DPIRatio aDpiRatio) {
729 const CSSCoord radius = 2.0f;
730 auto [backgroundColor, borderColor] =
731 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
733 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
734 borderColor, kMenulistBorderWidth,
735 radius, aDpiRatio);
737 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
738 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
739 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
743 template <typename PaintBackendData>
744 void Theme::PaintMenulist(PaintBackendData& aDrawTarget,
745 const LayoutDeviceRect& aRect,
746 const EventStates& aState, const Colors& aColors,
747 DPIRatio aDpiRatio) {
748 const CSSCoord radius = 4.0f;
749 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
751 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor,
752 borderColor, kMenulistBorderWidth,
753 radius, aDpiRatio);
755 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
756 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio,
757 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
761 void Theme::PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
762 const LayoutDeviceRect& aRect,
763 const EventStates& aState) {
764 const float kPolygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
765 3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
766 const float kPolygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
767 -2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
769 const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
771 const auto arrowColor = sRGBColor::FromABGR(
772 nsLayoutUtils::GetColor(aFrame, &nsStyleText::mWebkitTextFillColor));
773 ThemeDrawing::PaintArrow(aDrawTarget, aRect, kPolygonX, kPolygonY,
774 kPolygonSize, ArrayLength(kPolygonX), arrowColor);
777 void Theme::PaintSpinnerButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
778 const LayoutDeviceRect& aRect,
779 const EventStates& aState,
780 StyleAppearance aAppearance,
781 const Colors& aColors, DPIRatio aDpiRatio) {
782 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
784 aDrawTarget.FillRect(aRect.ToUnknownRect(),
785 ColorPattern(ToDeviceColor(backgroundColor)));
787 const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
788 2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
789 float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
790 -2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
792 const float kPolygonSize = kMinimumSpinnerButtonHeight;
793 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
794 for (auto& coord : polygonY) {
795 coord = -coord;
799 ThemeDrawing::PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY,
800 kPolygonSize, ArrayLength(kPolygonX), borderColor);
803 template <typename PaintBackendData>
804 void Theme::PaintRange(nsIFrame* aFrame, PaintBackendData& aPaintData,
805 const LayoutDeviceRect& aRect, const EventStates& aState,
806 const Colors& aColors, DPIRatio aDpiRatio,
807 bool aHorizontal) {
808 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
809 if (!rangeFrame) {
810 return;
813 double progress = rangeFrame->GetValueAsFractionOfRange();
814 auto rect = aRect;
815 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
816 kMinimumRangeThumbSize * aDpiRatio);
817 LayoutDeviceRect progressClipRect(aRect);
818 LayoutDeviceRect trackClipRect(aRect);
819 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
820 if (aHorizontal) {
821 rect.height = verticalSize;
822 rect.y = aRect.y + (aRect.height - rect.height) / 2;
823 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
825 if (IsFrameRTL(aFrame)) {
826 thumbRect.x =
827 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
828 float midPoint = thumbRect.Center().X();
829 trackClipRect.SetBoxX(aRect.X(), midPoint);
830 progressClipRect.SetBoxX(midPoint, aRect.XMost());
831 } else {
832 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
833 float midPoint = thumbRect.Center().X();
834 progressClipRect.SetBoxX(aRect.X(), midPoint);
835 trackClipRect.SetBoxX(midPoint, aRect.XMost());
837 } else {
838 rect.width = verticalSize;
839 rect.x = aRect.x + (aRect.width - rect.width) / 2;
840 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
842 thumbRect.y =
843 aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
844 float midPoint = thumbRect.Center().Y();
845 trackClipRect.SetBoxY(aRect.Y(), midPoint);
846 progressClipRect.SetBoxY(midPoint, aRect.YMost());
849 const CSSCoord borderWidth = 1.0f;
850 const CSSCoord radius = 3.0f;
852 auto [progressColor, progressBorderColor] =
853 ComputeRangeProgressColors(aState, aColors);
854 auto [trackColor, trackBorderColor] =
855 ComputeRangeTrackColors(aState, aColors);
857 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect,
858 progressColor, progressBorderColor,
859 borderWidth, radius, aDpiRatio);
861 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect,
862 trackColor, trackBorderColor,
863 borderWidth, radius, aDpiRatio);
865 if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
866 // Ensure the shadow doesn't expand outside of our overflow rect declared in
867 // GetWidgetOverflow().
868 auto overflowRect = aRect;
869 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
870 // Thumb shadow
871 PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
872 CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
875 // Draw the thumb on top.
876 const CSSCoord thumbBorderWidth = 2.0f;
877 auto [thumbColor, thumbBorderColor] =
878 ComputeRangeThumbColors(aState, aColors);
880 PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
881 thumbBorderWidth, aDpiRatio);
883 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
884 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, radius, 1.0f);
888 template <typename PaintBackendData>
889 void Theme::PaintProgress(nsIFrame* aFrame, PaintBackendData& aPaintData,
890 const LayoutDeviceRect& aRect,
891 const EventStates& aState, const Colors& aColors,
892 DPIRatio aDpiRatio, bool aIsMeter) {
893 const CSSCoord borderWidth = 1.0f;
894 const CSSCoord radius = aIsMeter ? 6.0f : 3.0f;
896 LayoutDeviceRect rect(aRect);
897 const LayoutDeviceCoord thickness =
898 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
900 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
901 if (isHorizontal) {
902 // Center it vertically.
903 rect.y += (rect.height - thickness) / 2;
904 rect.height = thickness;
905 } else {
906 // Center it horizontally.
907 rect.x += (rect.width - thickness) / 2;
908 rect.width = thickness;
912 // Paint the track, unclipped.
913 auto [backgroundColor, borderColor] = ComputeProgressTrackColors(aColors);
914 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, rect,
915 backgroundColor, borderColor,
916 borderWidth, radius, aDpiRatio);
919 // Now paint the chunk, clipped as needed.
920 LayoutDeviceRect clipRect = rect;
921 if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
922 // For indeterminate progress, we paint an animated chunk of 1/3 of the
923 // progress size.
925 // Animation speed and math borrowed from GTK.
926 const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
927 const LayoutDeviceCoord barSize = size * 0.3333f;
928 const LayoutDeviceCoord travel = 2.0f * (size - barSize);
930 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
931 // equals progressSize / 1000.0. This is equivalent to 1600.
932 const unsigned kPeriod = 1600;
934 const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
935 const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
936 if (isHorizontal) {
937 rect.width = barSize;
938 rect.x += (dx < travel * .5f) ? dx : travel - dx;
939 } else {
940 rect.height = barSize;
941 rect.y += (dx < travel * .5f) ? dx : travel - dx;
943 clipRect = rect;
944 // Queue the next frame if needed.
945 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
946 NS_WARNING("Couldn't refresh indeterminate <progress>");
948 } else {
949 // This is the progress chunk, clip it to the right amount.
950 double position = [&] {
951 if (aIsMeter) {
952 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
953 if (!meter) {
954 return 0.0;
956 return meter->Position();
958 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
959 if (!progress) {
960 return 0.0;
962 return progress->Position();
963 }();
964 if (isHorizontal) {
965 double clipWidth = rect.width * position;
966 clipRect.width = clipWidth;
967 if (IsFrameRTL(aFrame)) {
968 clipRect.x += rect.width - clipWidth;
970 } else {
971 double clipHeight = rect.height * position;
972 clipRect.height = clipHeight;
973 clipRect.y += rect.height - clipHeight;
977 auto [backgroundColor, borderColor] =
978 aIsMeter ? ComputeMeterchunkColors(aState, aColors)
979 : ComputeProgressColors(aColors);
980 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, clipRect,
981 backgroundColor, borderColor,
982 borderWidth, radius, aDpiRatio);
985 template <typename PaintBackendData>
986 void Theme::PaintButton(nsIFrame* aFrame, PaintBackendData& aPaintData,
987 const LayoutDeviceRect& aRect,
988 const EventStates& aState, const Colors& aColors,
989 DPIRatio aDpiRatio) {
990 const CSSCoord radius = 4.0f;
991 auto [backgroundColor, borderColor] =
992 ComputeButtonColors(aState, aColors, aFrame);
994 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
995 borderColor, kButtonBorderWidth,
996 radius, aDpiRatio);
998 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
999 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
1000 radius + kButtonBorderWidth, -kButtonBorderWidth);
1004 NS_IMETHODIMP
1005 Theme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1006 StyleAppearance aAppearance, const nsRect& aRect,
1007 const nsRect& /* aDirtyRect */,
1008 DrawOverflow aDrawOverflow) {
1009 if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
1010 aRect, aDrawOverflow)) {
1011 return NS_ERROR_NOT_IMPLEMENTED;
1013 return NS_OK;
1016 bool Theme::CreateWebRenderCommandsForWidget(
1017 mozilla::wr::DisplayListBuilder& aBuilder,
1018 mozilla::wr::IpcResourceUpdateQueue& aResources,
1019 const mozilla::layers::StackingContextHelper& aSc,
1020 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1021 StyleAppearance aAppearance, const nsRect& aRect) {
1022 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1023 return false;
1025 WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
1026 return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect,
1027 DrawOverflow::Yes);
1030 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1031 nscoord aTwipsPerPixel, DrawTarget& aDt) {
1032 return LayoutDeviceRect::FromUnknownRect(
1033 NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
1036 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1037 nscoord aTwipsPerPixel,
1038 WebRenderBackendData& aDt) {
1039 // TODO: Do we need to do any more snapping here?
1040 return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
1043 static ScrollbarDrawing::ScrollbarKind ComputeScrollbarKind(
1044 nsIFrame* aFrame, bool aIsHorizontal) {
1045 if (aIsHorizontal) {
1046 return ScrollbarDrawing::ScrollbarKind::Horizontal;
1048 nsIFrame* scrollbar = ScrollbarDrawing::GetParentScrollbarFrame(aFrame);
1049 if (NS_WARN_IF(!scrollbar)) {
1050 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1052 MOZ_ASSERT(scrollbar->IsScrollbarFrame());
1053 nsIScrollbarMediator* sm =
1054 static_cast<nsScrollbarFrame*>(scrollbar)->GetScrollbarMediator();
1055 if (NS_WARN_IF(!sm)) {
1056 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1058 return sm->IsScrollbarOnRight()
1059 ? ScrollbarDrawing::ScrollbarKind::VerticalRight
1060 : ScrollbarDrawing::ScrollbarKind::VerticalLeft;
1063 template <typename PaintBackendData>
1064 bool Theme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
1065 nsIFrame* aFrame,
1066 StyleAppearance aAppearance,
1067 const nsRect& aRect,
1068 DrawOverflow aDrawOverflow) {
1069 static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
1070 std::is_same_v<PaintBackendData, WebRenderBackendData>);
1072 const nsPresContext* pc = aFrame->PresContext();
1073 const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
1074 const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
1076 const EventStates docState = pc->Document()->GetDocumentState();
1077 EventStates eventState = GetContentState(aFrame, aAppearance);
1078 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1079 bool isHTML = IsHTMLContent(aFrame);
1080 nsIFrame* parentFrame = aFrame->GetParent();
1081 bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1082 // HTML select and XUL menulist dropdown buttons get state from the
1083 // parent.
1084 if (isHTML || isMenulist) {
1085 aFrame = parentFrame;
1086 eventState = GetContentState(parentFrame, aAppearance);
1090 // Paint the outline iff we're asked to draw overflow and we have
1091 // outline-style: auto.
1092 if (aDrawOverflow == DrawOverflow::Yes &&
1093 aFrame->StyleOutline()->mOutlineStyle.IsAuto()) {
1094 eventState |= NS_EVENT_STATE_FOCUSRING;
1095 } else {
1096 eventState &= ~NS_EVENT_STATE_FOCUSRING;
1099 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1100 // overflow devPxRect.
1101 Maybe<AutoClipRect> maybeClipRect;
1102 if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
1103 if (aAppearance != StyleAppearance::FocusOutline &&
1104 aAppearance != StyleAppearance::Range &&
1105 !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1106 maybeClipRect.emplace(aPaintData, devPxRect);
1110 const Colors colors(aFrame, aAppearance);
1111 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1113 switch (aAppearance) {
1114 case StyleAppearance::Radio: {
1115 auto rect = CheckBoxRadioRect(devPxRect);
1116 PaintRadioControl(aPaintData, rect, eventState, colors, dpiRatio);
1117 break;
1119 case StyleAppearance::Checkbox: {
1120 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1121 // TODO: Need to figure out how to best draw this using WR.
1122 return false;
1123 } else {
1124 auto rect = CheckBoxRadioRect(devPxRect);
1125 PaintCheckboxControl(aPaintData, rect, eventState, colors, dpiRatio);
1127 break;
1129 case StyleAppearance::Textarea:
1130 case StyleAppearance::Textfield:
1131 case StyleAppearance::NumberInput:
1132 PaintTextField(aPaintData, devPxRect, eventState, colors, dpiRatio);
1133 break;
1134 case StyleAppearance::Listbox:
1135 PaintListbox(aPaintData, devPxRect, eventState, colors, dpiRatio);
1136 break;
1137 case StyleAppearance::MenulistButton:
1138 case StyleAppearance::Menulist:
1139 PaintMenulist(aPaintData, devPxRect, eventState, colors, dpiRatio);
1140 break;
1141 case StyleAppearance::MozMenulistArrowButton:
1142 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1143 // TODO: Need to figure out how to best draw this using WR.
1144 return false;
1145 } else {
1146 PaintMenulistArrowButton(aFrame, aPaintData, devPxRect, eventState);
1148 break;
1149 case StyleAppearance::SpinnerUpbutton:
1150 case StyleAppearance::SpinnerDownbutton:
1151 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1152 // TODO: Need to figure out how to best draw this using WR.
1153 return false;
1154 } else {
1155 PaintSpinnerButton(aFrame, aPaintData, devPxRect, eventState,
1156 aAppearance, colors, dpiRatio);
1158 break;
1159 case StyleAppearance::Range:
1160 PaintRange(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
1161 IsRangeHorizontal(aFrame));
1162 break;
1163 case StyleAppearance::RangeThumb:
1164 // Painted as part of StyleAppearance::Range.
1165 break;
1166 case StyleAppearance::ProgressBar:
1167 PaintProgress(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
1168 /* aIsMeter = */ false);
1169 break;
1170 case StyleAppearance::Progresschunk:
1171 /* Painted as part of the progress bar */
1172 break;
1173 case StyleAppearance::Meter:
1174 PaintProgress(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio,
1175 /* aIsMeter = */ true);
1176 break;
1177 case StyleAppearance::Meterchunk:
1178 /* Painted as part of the meter bar */
1179 break;
1180 case StyleAppearance::ScrollbarthumbHorizontal:
1181 case StyleAppearance::ScrollbarthumbVertical: {
1182 bool isHorizontal =
1183 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1184 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1185 return GetScrollbarDrawing().PaintScrollbarThumb(
1186 aPaintData, devPxRect, kind, aFrame,
1187 *nsLayoutUtils::StyleForScrollbar(aFrame), eventState, docState,
1188 colors, dpiRatio);
1190 case StyleAppearance::ScrollbartrackHorizontal:
1191 case StyleAppearance::ScrollbartrackVertical: {
1192 bool isHorizontal =
1193 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1194 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1195 return GetScrollbarDrawing().PaintScrollbarTrack(
1196 aPaintData, devPxRect, kind, aFrame,
1197 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1198 dpiRatio);
1200 case StyleAppearance::ScrollbarHorizontal:
1201 case StyleAppearance::ScrollbarVertical: {
1202 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1203 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1204 return GetScrollbarDrawing().PaintScrollbar(
1205 aPaintData, devPxRect, kind, aFrame,
1206 *nsLayoutUtils::StyleForScrollbar(aFrame), eventState, docState,
1207 colors, dpiRatio);
1209 case StyleAppearance::Scrollcorner: {
1210 auto kind = ComputeScrollbarKind(aFrame, false);
1211 return GetScrollbarDrawing().PaintScrollCorner(
1212 aPaintData, devPxRect, kind, aFrame,
1213 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1214 dpiRatio);
1216 case StyleAppearance::ScrollbarbuttonUp:
1217 case StyleAppearance::ScrollbarbuttonDown:
1218 case StyleAppearance::ScrollbarbuttonLeft:
1219 case StyleAppearance::ScrollbarbuttonRight: {
1220 // For scrollbar-width:thin, we don't display the buttons.
1221 if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) {
1222 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1223 // TODO: Need to figure out how to best draw this using WR.
1224 return false;
1225 } else {
1226 bool isHorizontal =
1227 aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
1228 aAppearance == StyleAppearance::ScrollbarbuttonRight;
1229 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1230 GetScrollbarDrawing().PaintScrollbarButton(
1231 aPaintData, aAppearance, devPxRect, kind, aFrame,
1232 *nsLayoutUtils::StyleForScrollbar(aFrame), eventState, docState,
1233 colors, dpiRatio);
1236 break;
1238 case StyleAppearance::Button:
1239 PaintButton(aFrame, aPaintData, devPxRect, eventState, colors, dpiRatio);
1240 break;
1241 case StyleAppearance::FocusOutline:
1242 PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, colors, dpiRatio);
1243 break;
1244 default:
1245 // Various appearance values are used for XUL elements. Normally these
1246 // will not be available in content documents (and thus in the content
1247 // processes where the native basic theme can be used), but tests are
1248 // run with the remote XUL pref enabled and so we can get in here. So
1249 // we just return an error rather than assert.
1250 return false;
1253 return true;
1256 template <typename PaintBackendData>
1257 void Theme::PaintAutoStyleOutline(nsIFrame* aFrame,
1258 PaintBackendData& aPaintData,
1259 const LayoutDeviceRect& aRect,
1260 const Colors& aColors, DPIRatio aDpiRatio) {
1261 auto [innerColor, middleColor, outerColor] = ComputeFocusRectColors(aColors);
1262 Unused << middleColor;
1263 Unused << outerColor;
1265 LayoutDeviceRect rect(aRect);
1266 auto width = LayoutDeviceCoord(
1267 ThemeDrawing::SnapBorderWidth(kInnerFocusOutlineWidth, aDpiRatio));
1268 rect.Inflate(width);
1270 const nscoord offset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1271 nscoord cssRadii[8];
1272 if (!aFrame->GetBorderRadii(cssRadii)) {
1273 const CSSCoord cssOffset = CSSCoord::FromAppUnits(offset);
1274 const CSSCoord radius =
1275 cssOffset >= 0.0f
1276 ? kInnerFocusOutlineWidth
1277 : std::max(kInnerFocusOutlineWidth + cssOffset, CSSCoord(0.0f));
1278 return ThemeDrawing::PaintRoundedRectWithRadius(
1279 aPaintData, rect, sRGBColor::White(0.0f), innerColor,
1280 kInnerFocusOutlineWidth, radius, aDpiRatio);
1283 nsPresContext* pc = aFrame->PresContext();
1284 const Float devPixelOffset = pc->AppUnitsToFloatDevPixels(offset);
1286 RectCornerRadii innerRadii;
1287 nsCSSRendering::ComputePixelRadii(cssRadii, pc->AppUnitsPerDevPixel(),
1288 &innerRadii);
1290 const auto borderColor = ToDeviceColor(innerColor);
1291 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1292 // to support arbitrary radii.
1293 RectCornerRadii outerRadii;
1294 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1295 const Float widths[4] = {width + devPixelOffset, width + devPixelOffset,
1296 width + devPixelOffset, width + devPixelOffset};
1297 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1299 const auto dest = wr::ToLayoutRect(rect);
1300 const auto side = wr::ToBorderSide(borderColor, StyleBorderStyle::Solid);
1301 const wr::BorderSide sides[4] = {side, side, side, side};
1302 const bool kBackfaceIsVisible = true;
1303 const auto wrWidths = wr::ToBorderWidths(width, width, width, width);
1304 const auto wrRadius = wr::ToBorderRadius(outerRadii);
1305 aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
1306 {sides, 4}, wrRadius);
1307 } else {
1308 const LayoutDeviceCoord halfWidth = width * 0.5f;
1309 rect.Deflate(halfWidth);
1310 const Float widths[4] = {
1311 halfWidth + devPixelOffset, halfWidth + devPixelOffset,
1312 halfWidth + devPixelOffset, halfWidth + devPixelOffset};
1313 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1314 RefPtr<Path> path =
1315 MakePathForRoundedRect(aPaintData, rect.ToUnknownRect(), outerRadii);
1316 aPaintData.Stroke(path, ColorPattern(borderColor), StrokeOptions(width));
1320 LayoutDeviceIntMargin Theme::GetWidgetBorder(nsDeviceContext* aContext,
1321 nsIFrame* aFrame,
1322 StyleAppearance aAppearance) {
1323 switch (aAppearance) {
1324 case StyleAppearance::Textfield:
1325 case StyleAppearance::Textarea:
1326 case StyleAppearance::NumberInput:
1327 case StyleAppearance::Listbox:
1328 case StyleAppearance::Menulist:
1329 case StyleAppearance::MenulistButton:
1330 case StyleAppearance::Button:
1331 // Return the border size from the UA sheet, even though what we paint
1332 // doesn't actually match that. We know this is the UA sheet border
1333 // because we disable native theming when different border widths are
1334 // specified by authors, see Theme::IsWidgetStyled.
1336 // The Rounded() bit is technically redundant, but needed to appease the
1337 // type system, we should always end up with full device pixels due to
1338 // round_border_to_device_pixels at style time.
1339 return LayoutDeviceIntMargin::FromAppUnits(
1340 aFrame->StyleBorder()->GetComputedBorder(),
1341 aFrame->PresContext()->AppUnitsPerDevPixel())
1342 .Rounded();
1343 case StyleAppearance::Checkbox:
1344 case StyleAppearance::Radio: {
1345 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1346 LayoutDeviceIntCoord w =
1347 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1348 return LayoutDeviceIntMargin(w, w, w, w);
1350 default:
1351 return LayoutDeviceIntMargin();
1355 bool Theme::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
1356 StyleAppearance aAppearance,
1357 LayoutDeviceIntMargin* aResult) {
1358 switch (aAppearance) {
1359 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1360 // and have a meaningful baseline, so they can't have
1361 // author-specified padding.
1362 case StyleAppearance::Radio:
1363 case StyleAppearance::Checkbox:
1364 aResult->SizeTo(0, 0, 0, 0);
1365 return true;
1366 default:
1367 break;
1369 return false;
1372 bool Theme::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
1373 StyleAppearance aAppearance,
1374 nsRect* aOverflowRect) {
1375 nsIntMargin overflow;
1376 switch (aAppearance) {
1377 case StyleAppearance::FocusOutline:
1378 // 2px * one segment
1379 overflow.SizeTo(2, 2, 2, 2);
1380 break;
1381 case StyleAppearance::Radio:
1382 case StyleAppearance::Checkbox:
1383 case StyleAppearance::Range:
1384 // 2px for each outline segment, plus 1px separation, plus we paint with a
1385 // 1px extra offset, so 6px.
1386 overflow.SizeTo(6, 6, 6, 6);
1387 break;
1388 case StyleAppearance::Textarea:
1389 case StyleAppearance::Textfield:
1390 case StyleAppearance::NumberInput:
1391 case StyleAppearance::Listbox:
1392 case StyleAppearance::MenulistButton:
1393 case StyleAppearance::Menulist:
1394 case StyleAppearance::Button:
1395 // 2px for each segment, plus 1px separation, but we paint 1px inside
1396 // the border area so 4px overflow.
1397 overflow.SizeTo(4, 4, 4, 4);
1398 break;
1399 default:
1400 return false;
1403 // TODO: This should convert from device pixels to app units, not from CSS
1404 // pixels. And it should take the dpi ratio into account.
1405 // Using CSS pixels can cause the overflow to be too small if the page is
1406 // zoomed out.
1407 aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
1408 CSSPixel::ToAppUnits(overflow.right),
1409 CSSPixel::ToAppUnits(overflow.bottom),
1410 CSSPixel::ToAppUnits(overflow.left)));
1412 return true;
1415 auto Theme::GetScrollbarSizes(nsPresContext* aPresContext,
1416 StyleScrollbarWidth aWidth, Overlay aOverlay)
1417 -> ScrollbarSizes {
1418 return GetScrollbarDrawing().GetScrollbarSizes(aPresContext, aWidth,
1419 aOverlay);
1422 nscoord Theme::GetCheckboxRadioPrefSize() {
1423 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
1426 /* static */
1427 UniquePtr<ScrollbarDrawing> Theme::ScrollbarStyle() {
1428 switch (StaticPrefs::widget_non_native_theme_scrollbar_style()) {
1429 case 1:
1430 return MakeUnique<ScrollbarDrawingCocoa>();
1431 case 2:
1432 return MakeUnique<ScrollbarDrawingGTK>();
1433 case 3:
1434 return MakeUnique<ScrollbarDrawingAndroid>();
1435 case 4:
1436 return MakeUnique<ScrollbarDrawingWin>();
1437 case 5:
1438 return MakeUnique<ScrollbarDrawingWin11>();
1439 default:
1440 break;
1442 // Default to native scrollbar style for each platform.
1443 #ifdef XP_WIN
1444 if (IsWin11OrLater()) {
1445 return MakeUnique<ScrollbarDrawingWin11>();
1447 return MakeUnique<ScrollbarDrawingWin>();
1448 #elif MOZ_WIDGET_COCOA
1449 return MakeUnique<ScrollbarDrawingCocoa>();
1450 #elif MOZ_WIDGET_GTK
1451 return MakeUnique<ScrollbarDrawingGTK>();
1452 #elif ANDROID
1453 return MakeUnique<ScrollbarDrawingAndroid>();
1454 #else
1455 # error "Unknown platform, need scrollbar implementation."
1456 #endif
1459 NS_IMETHODIMP
1460 Theme::GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
1461 StyleAppearance aAppearance,
1462 LayoutDeviceIntSize* aResult,
1463 bool* aIsOverridable) {
1464 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1466 aResult->width = aResult->height = 0;
1467 *aIsOverridable = true;
1469 if (IsWidgetScrollbarPart(aAppearance)) {
1470 *aResult = GetScrollbarDrawing().GetMinimumWidgetSize(aPresContext,
1471 aAppearance, aFrame);
1472 return NS_OK;
1475 switch (aAppearance) {
1476 case StyleAppearance::Button:
1477 if (aFrame->IsColorControlFrame()) {
1478 aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
1480 break;
1481 case StyleAppearance::RangeThumb:
1482 aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1483 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1484 break;
1485 case StyleAppearance::MozMenulistArrowButton:
1486 aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1487 break;
1488 case StyleAppearance::SpinnerUpbutton:
1489 case StyleAppearance::SpinnerDownbutton:
1490 aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1491 aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1492 break;
1493 default:
1494 break;
1497 return NS_OK;
1500 nsITheme::Transparency Theme::GetWidgetTransparency(
1501 nsIFrame* aFrame, StyleAppearance aAppearance) {
1502 return GetScrollbarDrawing()
1503 .GetScrollbarPartTransparency(aFrame, aAppearance)
1504 .valueOr(eUnknownTransparency);
1507 NS_IMETHODIMP
1508 Theme::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
1509 nsAtom* aAttribute, bool* aShouldRepaint,
1510 const nsAttrValue* aOldValue) {
1511 if (!aAttribute) {
1512 // Hover/focus/active changed. Always repaint.
1513 *aShouldRepaint = true;
1514 } else {
1515 // Check the attribute to see if it's relevant.
1516 // disabled, checked, dlgtype, default, etc.
1517 *aShouldRepaint = false;
1518 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1519 aAttribute == nsGkAtoms::selected ||
1520 aAttribute == nsGkAtoms::visuallyselected ||
1521 aAttribute == nsGkAtoms::menuactive ||
1522 aAttribute == nsGkAtoms::sortDirection ||
1523 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
1524 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) {
1525 *aShouldRepaint = true;
1529 return NS_OK;
1532 NS_IMETHODIMP
1533 Theme::ThemeChanged() { return NS_OK; }
1535 bool Theme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
1536 return IsWidgetScrollbarPart(aAppearance);
1539 nsITheme::ThemeGeometryType Theme::ThemeGeometryTypeForWidget(
1540 nsIFrame* aFrame, StyleAppearance aAppearance) {
1541 return eThemeGeometryTypeUnknown;
1544 bool Theme::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
1545 StyleAppearance aAppearance) {
1546 switch (aAppearance) {
1547 case StyleAppearance::Radio:
1548 case StyleAppearance::Checkbox:
1549 case StyleAppearance::FocusOutline:
1550 case StyleAppearance::Textarea:
1551 case StyleAppearance::Textfield:
1552 case StyleAppearance::Range:
1553 case StyleAppearance::RangeThumb:
1554 case StyleAppearance::ProgressBar:
1555 case StyleAppearance::Progresschunk:
1556 case StyleAppearance::Meter:
1557 case StyleAppearance::Meterchunk:
1558 case StyleAppearance::ScrollbarbuttonUp:
1559 case StyleAppearance::ScrollbarbuttonDown:
1560 case StyleAppearance::ScrollbarbuttonLeft:
1561 case StyleAppearance::ScrollbarbuttonRight:
1562 case StyleAppearance::ScrollbarthumbHorizontal:
1563 case StyleAppearance::ScrollbarthumbVertical:
1564 case StyleAppearance::ScrollbartrackHorizontal:
1565 case StyleAppearance::ScrollbartrackVertical:
1566 case StyleAppearance::ScrollbarHorizontal:
1567 case StyleAppearance::ScrollbarVertical:
1568 case StyleAppearance::Scrollcorner:
1569 case StyleAppearance::Button:
1570 case StyleAppearance::Listbox:
1571 case StyleAppearance::Menulist:
1572 case StyleAppearance::MenulistButton:
1573 case StyleAppearance::NumberInput:
1574 case StyleAppearance::MozMenulistArrowButton:
1575 case StyleAppearance::SpinnerUpbutton:
1576 case StyleAppearance::SpinnerDownbutton:
1577 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1578 default:
1579 return false;
1583 bool Theme::WidgetIsContainer(StyleAppearance aAppearance) {
1584 switch (aAppearance) {
1585 case StyleAppearance::MozMenulistArrowButton:
1586 case StyleAppearance::Radio:
1587 case StyleAppearance::Checkbox:
1588 return false;
1589 default:
1590 return true;
1594 bool Theme::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) {
1595 return true;
1598 bool Theme::ThemeNeedsComboboxDropmarker() { return true; }
1600 bool Theme::ThemeSupportsScrollbarButtons() {
1601 return GetScrollbarDrawing().ShouldDrawScrollbarButtons();
1604 } // namespace mozilla::widget