Bug 1816917 - Re-enable video conversion, and text addition on desktop. r=perftest...
[gecko.git] / widget / Theme.cpp
blob5915f2fcaffcfb789edc1e6436b83780856d2a61
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 <utility>
8 #include "ThemeCocoa.h"
10 #include "ThemeDrawing.h"
11 #include "Units.h"
12 #include "mozilla/MathAlgorithms.h"
13 #include "mozilla/ClearOnShutdown.h"
14 #include "mozilla/dom/Document.h"
15 #include "mozilla/dom/HTMLMeterElement.h"
16 #include "mozilla/dom/HTMLProgressElement.h"
17 #include "mozilla/gfx/Rect.h"
18 #include "mozilla/gfx/Types.h"
19 #include "mozilla/gfx/Filters.h"
20 #include "mozilla/RelativeLuminanceUtils.h"
21 #include "mozilla/StaticPrefs_widget.h"
22 #include "mozilla/webrender/WebRenderAPI.h"
23 #include "nsCSSColorUtils.h"
24 #include "nsCSSRendering.h"
25 #include "nsScrollbarFrame.h"
26 #include "nsIScrollbarMediator.h"
27 #include "nsDeviceContext.h"
28 #include "nsLayoutUtils.h"
29 #include "nsRangeFrame.h"
30 #include "PathHelpers.h"
31 #include "ScrollbarDrawingAndroid.h"
32 #include "ScrollbarDrawingCocoa.h"
33 #include "ScrollbarDrawingGTK.h"
34 #include "ScrollbarDrawingWin.h"
35 #include "ScrollbarDrawingWin11.h"
37 #ifdef XP_WIN
38 # include "mozilla/WindowsVersion.h"
39 #endif
41 using namespace mozilla;
42 using namespace mozilla::dom;
43 using namespace mozilla::gfx;
44 using namespace mozilla::widget;
46 namespace {
48 static constexpr gfx::sRGBColor sColorGrey10(
49 gfx::sRGBColor::UnusualFromARGB(0xffe9e9ed));
50 static constexpr gfx::sRGBColor sColorGrey10Alpha50(
51 gfx::sRGBColor::UnusualFromARGB(0x7fe9e9ed));
52 static constexpr gfx::sRGBColor sColorGrey20(
53 gfx::sRGBColor::UnusualFromARGB(0xffd0d0d7));
54 static constexpr gfx::sRGBColor sColorGrey30(
55 gfx::sRGBColor::UnusualFromARGB(0xffb1b1b9));
56 static constexpr gfx::sRGBColor sColorGrey40(
57 gfx::sRGBColor::UnusualFromARGB(0xff8f8f9d));
58 static constexpr gfx::sRGBColor sColorGrey40Alpha50(
59 gfx::sRGBColor::UnusualFromARGB(0x7f8f8f9d));
60 static constexpr gfx::sRGBColor sColorGrey50(
61 gfx::sRGBColor::UnusualFromARGB(0xff676774));
62 static constexpr gfx::sRGBColor sColorGrey60(
63 gfx::sRGBColor::UnusualFromARGB(0xff484851));
65 static constexpr gfx::sRGBColor sColorMeterGreen10(
66 gfx::sRGBColor::UnusualFromARGB(0xff00ab60));
67 static constexpr gfx::sRGBColor sColorMeterGreen20(
68 gfx::sRGBColor::UnusualFromARGB(0xff056139));
69 static constexpr gfx::sRGBColor sColorMeterYellow10(
70 gfx::sRGBColor::UnusualFromARGB(0xffffbd4f));
71 static constexpr gfx::sRGBColor sColorMeterYellow20(
72 gfx::sRGBColor::UnusualFromARGB(0xffd2811e));
73 static constexpr gfx::sRGBColor sColorMeterRed10(
74 gfx::sRGBColor::UnusualFromARGB(0xffe22850));
75 static constexpr gfx::sRGBColor sColorMeterRed20(
76 gfx::sRGBColor::UnusualFromARGB(0xff810220));
78 static const CSSCoord kMinimumRangeThumbSize = 20.0f;
79 static const CSSCoord kMinimumDropdownArrowButtonWidth = 18.0f;
80 static const CSSCoord kMinimumSpinnerButtonWidth = 18.0f;
81 static const CSSCoord kMinimumSpinnerButtonHeight = 9.0f;
82 static const CSSCoord kButtonBorderWidth = 1.0f;
83 static const CSSCoord kMenulistBorderWidth = 1.0f;
84 static const CSSCoord kTextFieldBorderWidth = 1.0f;
85 static const CSSCoord kRangeHeight = 6.0f;
86 static const CSSCoord kProgressbarHeight = 6.0f;
87 static const CSSCoord kMeterHeight = 12.0f;
89 // nsCheckboxRadioFrame takes the bottom of the content box as the baseline.
90 // This border-width makes its baseline 2px under the bottom, which is nice.
91 static constexpr CSSCoord kCheckboxRadioBorderWidth = 2.0f;
93 static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
95 // This pushes and pops a clip rect to the draw target.
97 // This is done to reduce fuzz in places where we may have antialiasing,
98 // because skia is not clip-invariant: given different clips, it does not
99 // guarantee the same result, even if the painted content doesn't intersect
100 // the clips.
102 // This is a bit sad, overall, but...
103 struct MOZ_RAII AutoClipRect {
104 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
105 mDt.PushClipRect(aRect.ToUnknownRect());
108 ~AutoClipRect() { mDt.PopClip(); }
110 private:
111 DrawTarget& mDt;
114 static StaticRefPtr<Theme> gNativeInstance;
115 static StaticRefPtr<Theme> gNonNativeInstance;
116 static StaticRefPtr<Theme> gRDMInstance;
118 } // namespace
120 #ifdef ANDROID
121 already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
122 // Android doesn't have a native theme.
123 return do_AddRef(new Theme(Theme::ScrollbarStyle()));
125 #endif
127 already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
128 if (MOZ_UNLIKELY(!gNonNativeInstance)) {
129 UniquePtr<ScrollbarDrawing> scrollbarDrawing = Theme::ScrollbarStyle();
130 #ifdef MOZ_WIDGET_COCOA
131 gNonNativeInstance = new ThemeCocoa(std::move(scrollbarDrawing));
132 #else
133 gNonNativeInstance = new Theme(std::move(scrollbarDrawing));
134 #endif
135 ClearOnShutdown(&gNonNativeInstance);
137 return do_AddRef(gNonNativeInstance);
140 already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
141 if (MOZ_UNLIKELY(!gNativeInstance)) {
142 gNativeInstance = do_CreateNativeThemeDoNotUseDirectly();
143 ClearOnShutdown(&gNativeInstance);
145 return do_AddRef(gNativeInstance);
148 already_AddRefed<nsITheme> do_GetRDMThemeDoNotUseDirectly() {
149 if (MOZ_UNLIKELY(!gRDMInstance)) {
150 gRDMInstance = new Theme(MakeUnique<ScrollbarDrawingAndroid>());
151 ClearOnShutdown(&gRDMInstance);
153 return do_AddRef(gRDMInstance);
156 namespace mozilla::widget {
158 NS_IMPL_ISUPPORTS_INHERITED(Theme, nsNativeTheme, nsITheme)
160 static constexpr nsLiteralCString kPrefs[] = {
161 "widget.non-native-theme.use-theme-accent"_ns,
162 "widget.non-native-theme.win.scrollbar.use-system-size"_ns,
163 "widget.non-native-theme.scrollbar.size.override"_ns,
164 "widget.non-native-theme.scrollbar.style"_ns,
167 void Theme::Init() {
168 for (const auto& pref : kPrefs) {
169 Preferences::RegisterCallback(PrefChangedCallback, pref);
171 LookAndFeelChanged();
174 void Theme::Shutdown() {
175 for (const auto& pref : kPrefs) {
176 Preferences::UnregisterCallback(PrefChangedCallback, pref);
180 /* static */
181 void Theme::LookAndFeelChanged() {
182 ThemeColors::RecomputeAccentColors();
183 if (gNonNativeInstance) {
184 gNonNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
186 if (gNativeInstance) {
187 gNativeInstance->SetScrollbarDrawing(ScrollbarStyle());
191 auto Theme::GetDPIRatio(nsPresContext* aPc, StyleAppearance aAppearance)
192 -> DPIRatio {
193 // Widgets react to zoom, except scrollbars.
194 if (IsWidgetScrollbarPart(aAppearance)) {
195 return GetScrollbarDrawing().GetDPIRatioForScrollbarPart(aPc);
197 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
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::tuple<sRGBColor, sRGBColor, sRGBColor> Theme::ComputeCheckboxColors(
215 const ElementState& aState, StyleAppearance aAppearance,
216 const Colors& aColors) {
217 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
218 aAppearance == StyleAppearance::Radio);
220 bool isDisabled = aState.HasState(ElementState::DISABLED);
221 bool isChecked = aState.HasState(ElementState::CHECKED);
222 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
223 aState.HasState(ElementState::INDETERMINATE);
225 if (isChecked || isIndeterminate) {
226 if (isDisabled) {
227 auto bg = ComputeBorderColor(aState, aColors, OutlineCoversBorder::No);
228 auto fg = aColors.HighContrast()
229 ? aColors.System(StyleSystemColor::Graytext)
230 : sRGBColor::White(.8f);
231 return std::make_tuple(bg, bg, fg);
234 if (aColors.HighContrast()) {
235 auto bg = aColors.System(StyleSystemColor::Selecteditem);
236 auto fg = aColors.System(StyleSystemColor::Selecteditemtext);
237 return std::make_tuple(bg, bg, fg);
240 bool isActive =
241 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
242 bool isHovered = aState.HasState(ElementState::HOVER);
243 const auto& bg = isActive ? aColors.Accent().GetDarker()
244 : isHovered ? aColors.Accent().GetDark()
245 : aColors.Accent().Get();
246 const auto& fg = aColors.Accent().GetForeground();
247 return std::make_tuple(bg, bg, fg);
250 auto [bg, border] =
251 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::No);
252 // We don't paint a checkmark in this case so any color would do.
253 return std::make_tuple(bg, border, sTransparent);
256 sRGBColor Theme::ComputeBorderColor(const ElementState& aState,
257 const Colors& aColors,
258 OutlineCoversBorder aOutlineCoversBorder) {
259 bool isDisabled = aState.HasState(ElementState::DISABLED);
260 if (aColors.HighContrast()) {
261 return aColors.System(isDisabled ? StyleSystemColor::Graytext
262 : StyleSystemColor::Buttontext);
264 bool isActive =
265 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
266 bool isHovered = aState.HasState(ElementState::HOVER);
267 bool isFocused = aState.HasState(ElementState::FOCUSRING);
268 if (isDisabled) {
269 return sColorGrey40Alpha50;
271 if (isFocused && aOutlineCoversBorder == OutlineCoversBorder::Yes) {
272 // If we draw the outline over the border, prevent issues where the border
273 // shows underneath if it snaps in the wrong direction by using a
274 // transparent border. An alternative to this is ensuring that we snap the
275 // offset in PaintRoundedFocusRect the same was a we snap border widths, so
276 // that negative offsets are guaranteed to cover the border.
277 // But this looks harder to mess up.
278 return sTransparent;
280 bool dark = aColors.IsDark();
281 if (isActive) {
282 return dark ? sColorGrey20 : sColorGrey60;
284 if (isHovered) {
285 return dark ? sColorGrey30 : sColorGrey50;
287 return sColorGrey40;
290 std::pair<sRGBColor, sRGBColor> Theme::ComputeButtonColors(
291 const ElementState& aState, const Colors& aColors, nsIFrame* aFrame) {
292 bool isActive =
293 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
294 bool isDisabled = aState.HasState(ElementState::DISABLED);
295 bool isHovered = aState.HasState(ElementState::HOVER);
297 nscolor backgroundColor = [&] {
298 if (isDisabled) {
299 return aColors.SystemNs(StyleSystemColor::MozButtondisabledface);
301 if (isActive) {
302 return aColors.SystemNs(StyleSystemColor::MozButtonactiveface);
304 if (isHovered) {
305 return aColors.SystemNs(StyleSystemColor::MozButtonhoverface);
307 return aColors.SystemNs(StyleSystemColor::Buttonface);
308 }();
310 if (aState.HasState(ElementState::AUTOFILL)) {
311 backgroundColor = NS_ComposeColors(
312 backgroundColor,
313 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
316 const sRGBColor borderColor =
317 ComputeBorderColor(aState, aColors, OutlineCoversBorder::Yes);
318 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
321 std::pair<sRGBColor, sRGBColor> Theme::ComputeTextfieldColors(
322 const ElementState& aState, const Colors& aColors,
323 OutlineCoversBorder aOutlineCoversBorder) {
324 nscolor backgroundColor = [&] {
325 if (aState.HasState(ElementState::DISABLED)) {
326 return aColors.SystemNs(StyleSystemColor::MozDisabledfield);
328 return aColors.SystemNs(StyleSystemColor::Field);
329 }();
331 if (aState.HasState(ElementState::AUTOFILL)) {
332 backgroundColor = NS_ComposeColors(
333 backgroundColor,
334 aColors.SystemNs(StyleSystemColor::MozAutofillBackground));
337 const sRGBColor borderColor =
338 ComputeBorderColor(aState, aColors, aOutlineCoversBorder);
339 return std::make_pair(sRGBColor::FromABGR(backgroundColor), borderColor);
342 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeProgressColors(
343 const ElementState& aState, const Colors& aColors) {
344 if (aColors.HighContrast()) {
345 return aColors.SystemPair(StyleSystemColor::Selecteditem,
346 StyleSystemColor::Buttontext);
349 bool isActive =
350 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
351 bool isDisabled = aState.HasState(ElementState::DISABLED);
352 bool isHovered = aState.HasState(ElementState::HOVER);
354 if (isDisabled) {
355 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
357 if (isActive || isHovered) {
358 return std::make_pair(aColors.Accent().GetDark(),
359 aColors.Accent().GetDarker());
361 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
364 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeTrackColors(
365 const ElementState& aState, const Colors& aColors) {
366 if (aColors.HighContrast()) {
367 return aColors.SystemPair(StyleSystemColor::Window,
368 StyleSystemColor::Buttontext);
370 bool isActive =
371 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
372 bool isDisabled = aState.HasState(ElementState::DISABLED);
373 bool isHovered = aState.HasState(ElementState::HOVER);
375 if (isDisabled) {
376 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
378 if (isActive || isHovered) {
379 return std::make_pair(sColorGrey20, sColorGrey50);
381 return std::make_pair(sColorGrey10, sColorGrey40);
384 std::pair<sRGBColor, sRGBColor> Theme::ComputeRangeThumbColors(
385 const ElementState& aState, const Colors& aColors) {
386 if (aColors.HighContrast()) {
387 return aColors.SystemPair(StyleSystemColor::Selecteditemtext,
388 StyleSystemColor::Selecteditem);
391 bool isActive =
392 aState.HasAllStates(ElementState::HOVER | ElementState::ACTIVE);
393 bool isDisabled = aState.HasState(ElementState::DISABLED);
394 bool isHovered = aState.HasState(ElementState::HOVER);
396 const sRGBColor& backgroundColor = [&] {
397 if (isDisabled) {
398 return sColorGrey40;
400 if (isActive) {
401 return aColors.Accent().Get();
403 if (isHovered) {
404 return sColorGrey60;
406 return sColorGrey50;
407 }();
409 const sRGBColor borderColor = sRGBColor::OpaqueWhite();
410 return std::make_pair(backgroundColor, borderColor);
413 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressColors(
414 const Colors& aColors) {
415 if (aColors.HighContrast()) {
416 return aColors.SystemPair(StyleSystemColor::Selecteditem,
417 StyleSystemColor::Buttontext);
419 return std::make_pair(aColors.Accent().Get(), aColors.Accent().GetDark());
422 std::pair<sRGBColor, sRGBColor> Theme::ComputeProgressTrackColors(
423 const Colors& aColors) {
424 if (aColors.HighContrast()) {
425 return aColors.SystemPair(StyleSystemColor::Buttonface,
426 StyleSystemColor::Buttontext);
428 return std::make_pair(sColorGrey10, sColorGrey40);
431 std::pair<sRGBColor, sRGBColor> Theme::ComputeMeterchunkColors(
432 const ElementState& aMeterState, const Colors& aColors) {
433 if (aColors.HighContrast()) {
434 return ComputeProgressColors(aColors);
436 sRGBColor borderColor = sColorMeterGreen20;
437 sRGBColor chunkColor = sColorMeterGreen10;
439 if (aMeterState.HasState(ElementState::SUB_OPTIMUM)) {
440 borderColor = sColorMeterYellow20;
441 chunkColor = sColorMeterYellow10;
442 } else if (aMeterState.HasState(ElementState::SUB_SUB_OPTIMUM)) {
443 borderColor = sColorMeterRed20;
444 chunkColor = sColorMeterRed10;
447 return std::make_pair(chunkColor, borderColor);
450 std::array<sRGBColor, 3> Theme::ComputeFocusRectColors(const Colors& aColors) {
451 if (aColors.HighContrast()) {
452 return {aColors.System(StyleSystemColor::Selecteditem),
453 aColors.System(StyleSystemColor::Buttontext),
454 aColors.System(StyleSystemColor::Window)};
456 const auto& accent = aColors.Accent();
457 const sRGBColor middle =
458 aColors.IsDark() ? sRGBColor::Black(.3f) : sRGBColor::White(.3f);
459 return {accent.Get(), middle, accent.GetLight()};
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 = 2.0f;
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 ElementState& aState,
514 const Colors& aColors, DPIRatio aDpiRatio) {
515 auto [backgroundColor, borderColor, checkColor] =
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(ElementState::INDETERMINATE)) {
529 PaintIndeterminateMark(aDrawTarget, aRect, checkColor);
530 } else if (aState.HasState(ElementState::CHECKED)) {
531 PaintCheckMark(aDrawTarget, aRect, checkColor);
534 if (aState.HasState(ElementState::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 sRGBColor& aColor) {
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 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(aColor)));
569 void Theme::PaintIndeterminateMark(DrawTarget& aDrawTarget,
570 const LayoutDeviceRect& aRect,
571 const sRGBColor& aColor) {
572 const CSSCoord borderWidth = 2.0f;
573 const float scale =
574 ThemeDrawing::ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
576 Rect rect = aRect.ToUnknownRect();
577 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
578 rect.height = borderWidth * scale;
579 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
580 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
582 aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(aColor)));
585 template <typename PaintBackendData>
586 void Theme::PaintStrokedCircle(PaintBackendData& aPaintData,
587 const LayoutDeviceRect& aRect,
588 const sRGBColor& aBackgroundColor,
589 const sRGBColor& aBorderColor,
590 const CSSCoord aBorderWidth,
591 DPIRatio aDpiRatio) {
592 auto radius = LayoutDeviceCoord(aRect.Size().width) / aDpiRatio;
593 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, aBackgroundColor,
594 aBorderColor, aBorderWidth, radius,
595 aDpiRatio);
598 void Theme::PaintCircleShadow(WebRenderBackendData& aWrData,
599 const LayoutDeviceRect& aBoxRect,
600 const LayoutDeviceRect& aClipRect,
601 float aShadowAlpha, const CSSPoint& aShadowOffset,
602 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
603 const bool kBackfaceIsVisible = true;
604 const LayoutDeviceCoord stdDev = aShadowBlurStdDev * aDpiRatio;
605 const LayoutDevicePoint shadowOffset = aShadowOffset * aDpiRatio;
606 const IntSize inflation =
607 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
608 LayoutDeviceRect shadowRect = aBoxRect;
609 shadowRect.MoveBy(shadowOffset);
610 shadowRect.Inflate(inflation.width, inflation.height);
611 const auto boxRect = wr::ToLayoutRect(aBoxRect);
612 aWrData.mBuilder.PushBoxShadow(
613 wr::ToLayoutRect(shadowRect), wr::ToLayoutRect(aClipRect),
614 kBackfaceIsVisible, boxRect,
615 wr::ToLayoutVector2D(aShadowOffset * aDpiRatio),
616 wr::ToColorF(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)), stdDev,
617 /* aSpread = */ 0.0f,
618 wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect.Size().width)),
619 wr::BoxShadowClipMode::Outset);
622 void Theme::PaintCircleShadow(DrawTarget& aDrawTarget,
623 const LayoutDeviceRect& aBoxRect,
624 const LayoutDeviceRect& aClipRect,
625 float aShadowAlpha, const CSSPoint& aShadowOffset,
626 CSSCoord aShadowBlurStdDev, DPIRatio aDpiRatio) {
627 Float stdDev = aShadowBlurStdDev * aDpiRatio;
628 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
630 RefPtr<FilterNode> blurFilter =
631 aDrawTarget.CreateFilter(FilterType::GAUSSIAN_BLUR);
632 if (!blurFilter) {
633 return;
636 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
638 IntSize inflation =
639 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
640 Rect inflatedRect = aBoxRect.ToUnknownRect();
641 inflatedRect.Inflate(inflation.width, inflation.height);
642 Rect sourceRectInFilterSpace =
643 inflatedRect - aBoxRect.TopLeft().ToUnknownPoint();
644 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
646 IntSize dtSize = RoundedToInt(aBoxRect.Size().ToUnknownSize());
647 RefPtr<DrawTarget> ellipseDT = aDrawTarget.CreateSimilarDrawTargetForFilter(
648 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
649 sourceRectInFilterSpace, destinationPointOfSourceRect);
650 if (!ellipseDT) {
651 return;
654 AutoClipRect clipRect(aDrawTarget, aClipRect);
656 RefPtr<Path> ellipse = MakePathForEllipse(
657 *ellipseDT, (aBoxRect - aBoxRect.TopLeft()).Center().ToUnknownPoint(),
658 aBoxRect.Size().ToUnknownSize());
659 ellipseDT->Fill(ellipse,
660 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
661 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
663 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
664 aDrawTarget.DrawFilter(blurFilter, sourceRectInFilterSpace,
665 destinationPointOfSourceRect);
668 template <typename PaintBackendData>
669 void Theme::PaintRadioControl(PaintBackendData& aPaintData,
670 const LayoutDeviceRect& aRect,
671 const ElementState& aState, const Colors& aColors,
672 DPIRatio aDpiRatio) {
673 auto [backgroundColor, borderColor, checkColor] =
674 ComputeCheckboxColors(aState, StyleAppearance::Radio, aColors);
676 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
677 if (backgroundColor == borderColor) {
678 borderWidth = 0.0f;
680 PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
681 borderWidth, aDpiRatio);
684 if (aState.HasState(ElementState::CHECKED)) {
685 LayoutDeviceRect rect(aRect);
686 auto width = LayoutDeviceCoord(
687 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
688 rect.Deflate(width);
690 PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
691 kCheckboxRadioBorderWidth, aDpiRatio);
694 if (aState.HasState(ElementState::FOCUSRING)) {
695 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, 5.0f, 1.0f);
699 template <typename PaintBackendData>
700 void Theme::PaintTextField(PaintBackendData& aPaintData,
701 const LayoutDeviceRect& aRect,
702 const ElementState& aState, const Colors& aColors,
703 DPIRatio aDpiRatio) {
704 auto [backgroundColor, borderColor] =
705 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
707 const CSSCoord radius = 2.0f;
709 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
710 borderColor, kTextFieldBorderWidth,
711 radius, aDpiRatio);
713 if (aState.HasState(ElementState::FOCUSRING)) {
714 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
715 radius + kTextFieldBorderWidth,
716 -kTextFieldBorderWidth);
720 template <typename PaintBackendData>
721 void Theme::PaintListbox(PaintBackendData& aPaintData,
722 const LayoutDeviceRect& aRect,
723 const ElementState& aState, const Colors& aColors,
724 DPIRatio aDpiRatio) {
725 const CSSCoord radius = 2.0f;
726 auto [backgroundColor, borderColor] =
727 ComputeTextfieldColors(aState, aColors, OutlineCoversBorder::Yes);
729 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
730 borderColor, kMenulistBorderWidth,
731 radius, aDpiRatio);
733 if (aState.HasState(ElementState::FOCUSRING)) {
734 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
735 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
739 template <typename PaintBackendData>
740 void Theme::PaintMenulist(PaintBackendData& aDrawTarget,
741 const LayoutDeviceRect& aRect,
742 const ElementState& aState, const Colors& aColors,
743 DPIRatio aDpiRatio) {
744 const CSSCoord radius = 4.0f;
745 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
747 ThemeDrawing::PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor,
748 borderColor, kMenulistBorderWidth,
749 radius, aDpiRatio);
751 if (aState.HasState(ElementState::FOCUSRING)) {
752 PaintRoundedFocusRect(aDrawTarget, aRect, aColors, aDpiRatio,
753 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
757 void Theme::PaintMenulistArrowButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
758 const LayoutDeviceRect& aRect,
759 const ElementState& aState) {
760 // not const: these may be negated in-place below
761 float polygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
762 3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
763 float polygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
764 -2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
766 const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
768 auto const [xs, ys] = [&] {
769 using Pair = std::pair<const float*, const float*>;
770 switch (aFrame->GetWritingMode().GetBlockDir()) {
771 case WritingMode::BlockDir::eBlockRL:
772 // rotate 90°: [[0,1],[-1,0]]
773 for (float& f : polygonY) {
774 f = -f;
776 return Pair(polygonY, polygonX);
778 case WritingMode::BlockDir::eBlockLR:
779 // rotate 270°: [[0,-1],[1,0]]
780 for (float& f : polygonX) {
781 f = -f;
783 return Pair(polygonY, polygonX);
785 case WritingMode::BlockDir::eBlockTB:
786 // rotate 0°: [[1,0],[0,1]]
787 return Pair(polygonX, polygonY);
789 MOZ_ASSERT_UNREACHABLE("unhandled BlockDir");
790 return Pair(polygonX, polygonY);
791 }();
793 const auto arrowColor = sRGBColor::FromABGR(
794 nsLayoutUtils::GetColor(aFrame, &nsStyleText::mWebkitTextFillColor));
795 ThemeDrawing::PaintArrow(aDrawTarget, aRect, xs, ys, kPolygonSize,
796 ArrayLength(polygonX), arrowColor);
799 void Theme::PaintSpinnerButton(nsIFrame* aFrame, DrawTarget& aDrawTarget,
800 const LayoutDeviceRect& aRect,
801 const ElementState& aState,
802 StyleAppearance aAppearance,
803 const Colors& aColors, DPIRatio aDpiRatio) {
804 auto [backgroundColor, borderColor] = ComputeButtonColors(aState, aColors);
806 aDrawTarget.FillRect(aRect.ToUnknownRect(),
807 ColorPattern(ToDeviceColor(backgroundColor)));
809 const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
810 2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
811 float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
812 -2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
814 const float kPolygonSize = kMinimumSpinnerButtonHeight;
815 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
816 for (auto& coord : polygonY) {
817 coord = -coord;
821 ThemeDrawing::PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY,
822 kPolygonSize, ArrayLength(kPolygonX), borderColor);
825 template <typename PaintBackendData>
826 void Theme::PaintRange(nsIFrame* aFrame, PaintBackendData& aPaintData,
827 const LayoutDeviceRect& aRect,
828 const ElementState& aState, const Colors& aColors,
829 DPIRatio aDpiRatio, bool aHorizontal) {
830 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
831 if (!rangeFrame) {
832 return;
835 auto tickMarks = rangeFrame->TickMarks();
836 double progress = rangeFrame->GetValueAsFractionOfRange();
837 auto rect = aRect;
838 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
839 kMinimumRangeThumbSize * aDpiRatio);
840 LayoutDeviceRect progressClipRect(aRect);
841 LayoutDeviceRect trackClipRect(aRect);
842 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
843 const LayoutDeviceCoord tickMarkWidth(
844 ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
845 const LayoutDeviceCoord tickMarkHeight(
846 ThemeDrawing::SnapBorderWidth(5.0f, aDpiRatio));
847 LayoutDevicePoint tickMarkOrigin, tickMarkDirection;
848 LayoutDeviceSize tickMarkSize;
849 if (aHorizontal) {
850 rect.height = verticalSize;
851 rect.y = aRect.y + (aRect.height - rect.height) / 2;
852 tickMarkSize = LayoutDeviceSize(tickMarkWidth, tickMarkHeight);
853 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
855 if (IsFrameRTL(aFrame)) {
856 tickMarkOrigin =
857 LayoutDevicePoint(aRect.XMost() - thumbRect.width / 2, aRect.YMost());
858 tickMarkDirection = LayoutDevicePoint(-1.0f, 0.0f);
859 thumbRect.x =
860 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
861 float midPoint = thumbRect.Center().X();
862 trackClipRect.SetBoxX(aRect.X(), midPoint);
863 progressClipRect.SetBoxX(midPoint, aRect.XMost());
864 } else {
865 tickMarkOrigin =
866 LayoutDevicePoint(aRect.x + thumbRect.width / 2, aRect.YMost());
867 tickMarkDirection = LayoutDevicePoint(1.0, 0.0f);
868 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
869 float midPoint = thumbRect.Center().X();
870 progressClipRect.SetBoxX(aRect.X(), midPoint);
871 trackClipRect.SetBoxX(midPoint, aRect.XMost());
873 } else {
874 rect.width = verticalSize;
875 rect.x = aRect.x + (aRect.width - rect.width) / 2;
876 tickMarkOrigin = LayoutDevicePoint(aRect.XMost() - tickMarkHeight / 4,
877 aRect.YMost() - thumbRect.width / 2);
878 tickMarkDirection = LayoutDevicePoint(0.0f, -1.0f);
879 tickMarkSize = LayoutDeviceSize(tickMarkHeight, tickMarkWidth);
880 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
882 thumbRect.y =
883 aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
884 float midPoint = thumbRect.Center().Y();
885 trackClipRect.SetBoxY(aRect.Y(), midPoint);
886 progressClipRect.SetBoxY(midPoint, aRect.YMost());
889 const CSSCoord borderWidth = 1.0f;
890 const CSSCoord radius = 3.0f;
892 auto [progressColor, progressBorderColor] =
893 ComputeRangeProgressColors(aState, aColors);
894 auto [trackColor, trackBorderColor] =
895 ComputeRangeTrackColors(aState, aColors);
896 auto tickMarkColor = trackBorderColor;
898 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect,
899 progressColor, progressBorderColor,
900 borderWidth, radius, aDpiRatio);
902 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect,
903 trackColor, trackBorderColor,
904 borderWidth, radius, aDpiRatio);
906 if (!aState.HasState(ElementState::DISABLED)) {
907 // Ensure the shadow doesn't expand outside of our overflow rect declared in
908 // GetWidgetOverflow().
909 auto overflowRect = aRect;
910 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
911 // Thumb shadow
912 PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
913 CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
916 tickMarkDirection.x *= aRect.width - thumbRect.width;
917 tickMarkDirection.y *= aRect.height - thumbRect.height;
918 tickMarkOrigin -=
919 LayoutDevicePoint(tickMarkSize.width, tickMarkSize.height) / 2;
920 auto tickMarkRect = LayoutDeviceRect(tickMarkOrigin, tickMarkSize);
921 for (auto tickMark : tickMarks) {
922 auto tickMarkOffset =
923 tickMarkDirection *
924 float(rangeFrame->GetDoubleAsFractionOfRange(tickMark));
925 ThemeDrawing::FillRect(aPaintData, tickMarkRect + tickMarkOffset,
926 tickMarkColor);
929 // Draw the thumb on top.
930 const CSSCoord thumbBorderWidth = 2.0f;
931 auto [thumbColor, thumbBorderColor] =
932 ComputeRangeThumbColors(aState, aColors);
934 PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
935 thumbBorderWidth, aDpiRatio);
937 if (aState.HasState(ElementState::FOCUSRING)) {
938 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio, radius, 1.0f);
942 template <typename PaintBackendData>
943 void Theme::PaintProgress(nsIFrame* aFrame, PaintBackendData& aPaintData,
944 const LayoutDeviceRect& aRect,
945 const ElementState& aState, const Colors& aColors,
946 DPIRatio aDpiRatio, bool aIsMeter) {
947 const CSSCoord borderWidth = 1.0f;
948 const CSSCoord radius = aIsMeter ? 6.0f : 3.0f;
950 LayoutDeviceRect rect(aRect);
951 const LayoutDeviceCoord thickness =
952 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
954 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
955 if (isHorizontal) {
956 // Center it vertically.
957 rect.y += (rect.height - thickness) / 2;
958 rect.height = thickness;
959 } else {
960 // Center it horizontally.
961 rect.x += (rect.width - thickness) / 2;
962 rect.width = thickness;
966 // Paint the track, unclipped.
967 auto [backgroundColor, borderColor] = ComputeProgressTrackColors(aColors);
968 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, rect,
969 backgroundColor, borderColor,
970 borderWidth, radius, aDpiRatio);
973 // Now paint the chunk, clipped as needed.
974 LayoutDeviceRect clipRect = rect;
975 if (aState.HasState(ElementState::INDETERMINATE)) {
976 // For indeterminate progress, we paint an animated chunk of 1/3 of the
977 // progress size.
979 // Animation speed and math borrowed from GTK.
980 const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
981 const LayoutDeviceCoord barSize = size * 0.3333f;
982 const LayoutDeviceCoord travel = 2.0f * (size - barSize);
984 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
985 // equals progressSize / 1000.0. This is equivalent to 1600.
986 const unsigned kPeriod = 1600;
988 const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
989 const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
990 if (isHorizontal) {
991 rect.width = barSize;
992 rect.x += (dx < travel * .5f) ? dx : travel - dx;
993 } else {
994 rect.height = barSize;
995 rect.y += (dx < travel * .5f) ? dx : travel - dx;
997 clipRect = rect;
998 // Queue the next frame if needed.
999 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
1000 NS_WARNING("Couldn't refresh indeterminate <progress>");
1002 } else {
1003 // This is the progress chunk, clip it to the right amount.
1004 double position = [&] {
1005 if (aIsMeter) {
1006 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
1007 if (!meter) {
1008 return 0.0;
1010 return meter->Position();
1012 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
1013 if (!progress) {
1014 return 0.0;
1016 return progress->Position();
1017 }();
1018 if (isHorizontal) {
1019 double clipWidth = rect.width * position;
1020 clipRect.width = clipWidth;
1021 if (IsFrameRTL(aFrame)) {
1022 clipRect.x += rect.width - clipWidth;
1024 } else {
1025 double clipHeight = rect.height * position;
1026 clipRect.height = clipHeight;
1027 clipRect.y += rect.height - clipHeight;
1031 auto [backgroundColor, borderColor] =
1032 aIsMeter ? ComputeMeterchunkColors(aState, aColors)
1033 : ComputeProgressColors(aColors);
1034 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, rect, clipRect,
1035 backgroundColor, borderColor,
1036 borderWidth, radius, aDpiRatio);
1039 template <typename PaintBackendData>
1040 void Theme::PaintButton(nsIFrame* aFrame, PaintBackendData& aPaintData,
1041 const LayoutDeviceRect& aRect,
1042 const ElementState& aState, const Colors& aColors,
1043 DPIRatio aDpiRatio) {
1044 const CSSCoord radius = 4.0f;
1045 auto [backgroundColor, borderColor] =
1046 ComputeButtonColors(aState, aColors, aFrame);
1048 ThemeDrawing::PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor,
1049 borderColor, kButtonBorderWidth,
1050 radius, aDpiRatio);
1052 if (aState.HasState(ElementState::FOCUSRING)) {
1053 PaintRoundedFocusRect(aPaintData, aRect, aColors, aDpiRatio,
1054 radius + kButtonBorderWidth, -kButtonBorderWidth);
1058 NS_IMETHODIMP
1059 Theme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1060 StyleAppearance aAppearance, const nsRect& aRect,
1061 const nsRect& /* aDirtyRect */,
1062 DrawOverflow aDrawOverflow) {
1063 if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
1064 aRect, aDrawOverflow)) {
1065 return NS_ERROR_NOT_IMPLEMENTED;
1067 return NS_OK;
1070 bool Theme::CreateWebRenderCommandsForWidget(
1071 mozilla::wr::DisplayListBuilder& aBuilder,
1072 mozilla::wr::IpcResourceUpdateQueue& aResources,
1073 const mozilla::layers::StackingContextHelper& aSc,
1074 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1075 StyleAppearance aAppearance, const nsRect& aRect) {
1076 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1077 return false;
1079 WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
1080 return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect,
1081 DrawOverflow::Yes);
1084 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1085 nscoord aTwipsPerPixel, DrawTarget& aDt) {
1086 return LayoutDeviceRect::FromUnknownRect(
1087 NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
1090 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1091 nscoord aTwipsPerPixel,
1092 WebRenderBackendData& aDt) {
1093 // TODO: Do we need to do any more snapping here?
1094 return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
1097 static ScrollbarDrawing::ScrollbarKind ComputeScrollbarKind(
1098 nsIFrame* aFrame, bool aIsHorizontal) {
1099 if (aIsHorizontal) {
1100 return ScrollbarDrawing::ScrollbarKind::Horizontal;
1102 nsIFrame* scrollbar = ScrollbarDrawing::GetParentScrollbarFrame(aFrame);
1103 if (NS_WARN_IF(!scrollbar)) {
1104 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1106 MOZ_ASSERT(scrollbar->IsScrollbarFrame());
1107 nsIScrollbarMediator* sm =
1108 static_cast<nsScrollbarFrame*>(scrollbar)->GetScrollbarMediator();
1109 if (NS_WARN_IF(!sm)) {
1110 return ScrollbarDrawing::ScrollbarKind::VerticalRight;
1112 return sm->IsScrollbarOnRight()
1113 ? ScrollbarDrawing::ScrollbarKind::VerticalRight
1114 : ScrollbarDrawing::ScrollbarKind::VerticalLeft;
1117 template <typename PaintBackendData>
1118 bool Theme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
1119 nsIFrame* aFrame,
1120 StyleAppearance aAppearance,
1121 const nsRect& aRect,
1122 DrawOverflow aDrawOverflow) {
1123 static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
1124 std::is_same_v<PaintBackendData, WebRenderBackendData>);
1126 const nsPresContext* pc = aFrame->PresContext();
1127 const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
1128 const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
1130 const DocumentState docState = pc->Document()->GetDocumentState();
1131 ElementState elementState = GetContentState(aFrame, aAppearance);
1132 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1133 // HTML select and XUL menulist dropdown buttons get state from the
1134 // parent.
1135 nsIFrame* parentFrame = aFrame->GetParent();
1136 aFrame = parentFrame;
1137 elementState = GetContentState(parentFrame, aAppearance);
1140 // Paint the outline iff we're asked to draw overflow and we have
1141 // outline-style: auto.
1142 if (aDrawOverflow == DrawOverflow::Yes &&
1143 aFrame->StyleOutline()->mOutlineStyle.IsAuto()) {
1144 elementState |= ElementState::FOCUSRING;
1145 } else {
1146 elementState &= ~ElementState::FOCUSRING;
1149 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1150 // overflow devPxRect.
1151 Maybe<AutoClipRect> maybeClipRect;
1152 if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
1153 if (aAppearance != StyleAppearance::FocusOutline &&
1154 aAppearance != StyleAppearance::Range &&
1155 !elementState.HasState(ElementState::FOCUSRING)) {
1156 maybeClipRect.emplace(aPaintData, devPxRect);
1160 const Colors colors(aFrame, aAppearance);
1161 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1163 switch (aAppearance) {
1164 case StyleAppearance::Radio: {
1165 auto rect = CheckBoxRadioRect(devPxRect);
1166 PaintRadioControl(aPaintData, rect, elementState, colors, dpiRatio);
1167 break;
1169 case StyleAppearance::Checkbox: {
1170 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1171 // TODO: Need to figure out how to best draw this using WR.
1172 return false;
1173 } else {
1174 auto rect = CheckBoxRadioRect(devPxRect);
1175 PaintCheckboxControl(aPaintData, rect, elementState, colors, dpiRatio);
1177 break;
1179 case StyleAppearance::Textarea:
1180 case StyleAppearance::Textfield:
1181 case StyleAppearance::NumberInput:
1182 PaintTextField(aPaintData, devPxRect, elementState, colors, dpiRatio);
1183 break;
1184 case StyleAppearance::Listbox:
1185 PaintListbox(aPaintData, devPxRect, elementState, colors, dpiRatio);
1186 break;
1187 case StyleAppearance::MenulistButton:
1188 case StyleAppearance::Menulist:
1189 PaintMenulist(aPaintData, devPxRect, elementState, colors, dpiRatio);
1190 break;
1191 case StyleAppearance::MozMenulistArrowButton:
1192 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1193 // TODO: Need to figure out how to best draw this using WR.
1194 return false;
1195 } else {
1196 PaintMenulistArrowButton(aFrame, aPaintData, devPxRect, elementState);
1198 break;
1199 case StyleAppearance::Tooltip: {
1200 const CSSCoord strokeWidth(1.0f);
1201 const CSSCoord strokeRadius(2.0f);
1202 ThemeDrawing::PaintRoundedRectWithRadius(
1203 aPaintData, devPxRect,
1204 colors.System(StyleSystemColor::Infobackground),
1205 colors.System(StyleSystemColor::Infotext), strokeWidth, strokeRadius,
1206 dpiRatio);
1207 break;
1209 case StyleAppearance::Menuitem: {
1210 ThemeDrawing::FillRect(aPaintData, devPxRect, [&] {
1211 if (CheckBooleanAttr(aFrame, nsGkAtoms::menuactive)) {
1212 if (elementState.HasState(ElementState::DISABLED)) {
1213 return colors.System(StyleSystemColor::MozMenuhoverdisabled);
1215 return colors.System(StyleSystemColor::MozMenuhover);
1217 return sTransparent;
1218 }());
1219 break;
1221 case StyleAppearance::SpinnerUpbutton:
1222 case StyleAppearance::SpinnerDownbutton:
1223 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1224 // TODO: Need to figure out how to best draw this using WR.
1225 return false;
1226 } else {
1227 PaintSpinnerButton(aFrame, aPaintData, devPxRect, elementState,
1228 aAppearance, colors, dpiRatio);
1230 break;
1231 case StyleAppearance::Range:
1232 PaintRange(aFrame, aPaintData, devPxRect, elementState, colors, dpiRatio,
1233 IsRangeHorizontal(aFrame));
1234 break;
1235 case StyleAppearance::RangeThumb:
1236 // Painted as part of StyleAppearance::Range.
1237 break;
1238 case StyleAppearance::ProgressBar:
1239 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1240 dpiRatio,
1241 /* aIsMeter = */ false);
1242 break;
1243 case StyleAppearance::Progresschunk:
1244 /* Painted as part of the progress bar */
1245 break;
1246 case StyleAppearance::Meter:
1247 PaintProgress(aFrame, aPaintData, devPxRect, elementState, colors,
1248 dpiRatio,
1249 /* aIsMeter = */ true);
1250 break;
1251 case StyleAppearance::Meterchunk:
1252 /* Painted as part of the meter bar */
1253 break;
1254 case StyleAppearance::ScrollbarthumbHorizontal:
1255 case StyleAppearance::ScrollbarthumbVertical: {
1256 bool isHorizontal =
1257 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1258 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1259 return GetScrollbarDrawing().PaintScrollbarThumb(
1260 aPaintData, devPxRect, kind, aFrame,
1261 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1262 colors, dpiRatio);
1264 case StyleAppearance::ScrollbartrackHorizontal:
1265 case StyleAppearance::ScrollbartrackVertical: {
1266 bool isHorizontal =
1267 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1268 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1269 return GetScrollbarDrawing().PaintScrollbarTrack(
1270 aPaintData, devPxRect, kind, aFrame,
1271 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1272 dpiRatio);
1274 case StyleAppearance::ScrollbarHorizontal:
1275 case StyleAppearance::ScrollbarVertical: {
1276 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1277 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1278 return GetScrollbarDrawing().PaintScrollbar(
1279 aPaintData, devPxRect, kind, aFrame,
1280 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1281 colors, dpiRatio);
1283 case StyleAppearance::Scrollcorner: {
1284 auto kind = ComputeScrollbarKind(aFrame, false);
1285 return GetScrollbarDrawing().PaintScrollCorner(
1286 aPaintData, devPxRect, kind, aFrame,
1287 *nsLayoutUtils::StyleForScrollbar(aFrame), docState, colors,
1288 dpiRatio);
1290 case StyleAppearance::ScrollbarbuttonUp:
1291 case StyleAppearance::ScrollbarbuttonDown:
1292 case StyleAppearance::ScrollbarbuttonLeft:
1293 case StyleAppearance::ScrollbarbuttonRight: {
1294 // For scrollbar-width:thin, we don't display the buttons.
1295 if (!ScrollbarDrawing::IsScrollbarWidthThin(aFrame)) {
1296 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1297 // TODO: Need to figure out how to best draw this using WR.
1298 return false;
1299 } else {
1300 bool isHorizontal =
1301 aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
1302 aAppearance == StyleAppearance::ScrollbarbuttonRight;
1303 auto kind = ComputeScrollbarKind(aFrame, isHorizontal);
1304 GetScrollbarDrawing().PaintScrollbarButton(
1305 aPaintData, aAppearance, devPxRect, kind, aFrame,
1306 *nsLayoutUtils::StyleForScrollbar(aFrame), elementState, docState,
1307 colors, dpiRatio);
1310 break;
1312 case StyleAppearance::Button:
1313 PaintButton(aFrame, aPaintData, devPxRect, elementState, colors,
1314 dpiRatio);
1315 break;
1316 case StyleAppearance::FocusOutline:
1317 PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, colors, dpiRatio);
1318 break;
1319 default:
1320 // Various appearance values are used for XUL elements. Normally these
1321 // will not be available in content documents (and thus in the content
1322 // processes where the native basic theme can be used), but tests are
1323 // run with the remote XUL pref enabled and so we can get in here. So
1324 // we just return an error rather than assert.
1325 return false;
1328 return true;
1331 template <typename PaintBackendData>
1332 void Theme::PaintAutoStyleOutline(nsIFrame* aFrame,
1333 PaintBackendData& aPaintData,
1334 const LayoutDeviceRect& aRect,
1335 const Colors& aColors, DPIRatio aDpiRatio) {
1336 const auto& accentColor = aColors.Accent();
1337 const bool solid = StaticPrefs::widget_non_native_theme_solid_outline_style();
1338 LayoutDeviceCoord strokeWidth(ThemeDrawing::SnapBorderWidth(2.0f, aDpiRatio));
1340 LayoutDeviceRect rect(aRect);
1341 rect.Inflate(strokeWidth);
1343 const nscoord a2d = aFrame->PresContext()->AppUnitsPerDevPixel();
1344 nscoord cssOffset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1345 nscoord cssRadii[8] = {0};
1346 if (!aFrame->GetBorderRadii(cssRadii)) {
1347 const auto twoPixels = 2 * AppUnitsPerCSSPixel();
1348 const nscoord radius =
1349 cssOffset >= 0 ? twoPixels : std::max(twoPixels + cssOffset, 0);
1350 cssOffset = -twoPixels;
1351 for (auto& r : cssRadii) {
1352 r = radius;
1356 auto offset = LayoutDevicePixel::FromAppUnits(cssOffset, a2d);
1357 RectCornerRadii innerRadii;
1358 nsCSSRendering::ComputePixelRadii(cssRadii, a2d, &innerRadii);
1360 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1361 // to support arbitrary radii.
1362 auto DrawRect = [&](const sRGBColor& aColor) {
1363 RectCornerRadii outerRadii;
1364 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1365 const Float widths[4] = {strokeWidth + offset, strokeWidth + offset,
1366 strokeWidth + offset, strokeWidth + offset};
1367 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1368 const auto dest = wr::ToLayoutRect(rect);
1369 const auto side =
1370 wr::ToBorderSide(ToDeviceColor(aColor), StyleBorderStyle::Solid);
1371 const wr::BorderSide sides[4] = {side, side, side, side};
1372 const bool kBackfaceIsVisible = true;
1373 const auto wrWidths = wr::ToBorderWidths(strokeWidth, strokeWidth,
1374 strokeWidth, strokeWidth);
1375 const auto wrRadius = wr::ToBorderRadius(outerRadii);
1376 aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
1377 {sides, 4}, wrRadius);
1378 } else {
1379 const LayoutDeviceCoord halfWidth = strokeWidth * 0.5f;
1380 const Float widths[4] = {halfWidth + offset, halfWidth + offset,
1381 halfWidth + offset, halfWidth + offset};
1382 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1383 LayoutDeviceRect dest(rect);
1384 dest.Deflate(halfWidth);
1385 RefPtr<Path> path =
1386 MakePathForRoundedRect(aPaintData, dest.ToUnknownRect(), outerRadii);
1387 aPaintData.Stroke(path, ColorPattern(ToDeviceColor(aColor)),
1388 StrokeOptions(strokeWidth));
1392 DrawRect(accentColor.Get());
1394 if (solid) {
1395 return;
1398 offset += strokeWidth;
1400 strokeWidth =
1401 LayoutDeviceCoord(ThemeDrawing::SnapBorderWidth(1.0f, aDpiRatio));
1402 rect.Inflate(strokeWidth);
1404 DrawRect(accentColor.GetForeground());
1407 LayoutDeviceIntMargin Theme::GetWidgetBorder(nsDeviceContext* aContext,
1408 nsIFrame* aFrame,
1409 StyleAppearance aAppearance) {
1410 switch (aAppearance) {
1411 case StyleAppearance::Textfield:
1412 case StyleAppearance::Textarea:
1413 case StyleAppearance::NumberInput:
1414 case StyleAppearance::Listbox:
1415 case StyleAppearance::Menulist:
1416 case StyleAppearance::MenulistButton:
1417 case StyleAppearance::Button:
1418 // Return the border size from the UA sheet, even though what we paint
1419 // doesn't actually match that. We know this is the UA sheet border
1420 // because we disable native theming when different border widths are
1421 // specified by authors, see Theme::IsWidgetStyled.
1423 // The Rounded() bit is technically redundant, but needed to appease the
1424 // type system, we should always end up with full device pixels due to
1425 // round_border_to_device_pixels at style time.
1426 return LayoutDeviceIntMargin::FromAppUnits(
1427 aFrame->StyleBorder()->GetComputedBorder(),
1428 aFrame->PresContext()->AppUnitsPerDevPixel())
1429 .Rounded();
1430 case StyleAppearance::Checkbox:
1431 case StyleAppearance::Radio: {
1432 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1433 LayoutDeviceIntCoord w =
1434 ThemeDrawing::SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1435 return LayoutDeviceIntMargin(w, w, w, w);
1437 default:
1438 return LayoutDeviceIntMargin();
1442 bool Theme::GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
1443 StyleAppearance aAppearance,
1444 LayoutDeviceIntMargin* aResult) {
1445 switch (aAppearance) {
1446 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1447 // and have a meaningful baseline, so they can't have
1448 // author-specified padding.
1449 case StyleAppearance::Radio:
1450 case StyleAppearance::Checkbox:
1451 aResult->SizeTo(0, 0, 0, 0);
1452 return true;
1453 default:
1454 break;
1456 return false;
1459 bool Theme::GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
1460 StyleAppearance aAppearance,
1461 nsRect* aOverflowRect) {
1462 CSSIntMargin overflow;
1463 switch (aAppearance) {
1464 case StyleAppearance::FocusOutline: {
1465 // 2px * one segment, or 2px + 1px
1466 const auto width =
1467 StaticPrefs::widget_non_native_theme_solid_outline_style() ? 2 : 3;
1468 overflow.SizeTo(width, width, width, width);
1469 break;
1471 case StyleAppearance::Radio:
1472 case StyleAppearance::Checkbox:
1473 case StyleAppearance::Range:
1474 // 2px for each outline segment, plus 1px separation, plus we paint with a
1475 // 1px extra offset, so 6px.
1476 overflow.SizeTo(6, 6, 6, 6);
1477 break;
1478 case StyleAppearance::Textarea:
1479 case StyleAppearance::Textfield:
1480 case StyleAppearance::NumberInput:
1481 case StyleAppearance::Listbox:
1482 case StyleAppearance::MenulistButton:
1483 case StyleAppearance::Menulist:
1484 case StyleAppearance::Button:
1485 // 2px for each segment, plus 1px separation, but we paint 1px inside
1486 // the border area so 4px overflow.
1487 overflow.SizeTo(4, 4, 4, 4);
1488 break;
1489 default:
1490 return false;
1493 // TODO: This should convert from device pixels to app units, not from CSS
1494 // pixels. And it should take the dpi ratio into account.
1495 // Using CSS pixels can cause the overflow to be too small if the page is
1496 // zoomed out.
1497 aOverflowRect->Inflate(CSSPixel::ToAppUnits(overflow));
1498 return true;
1501 LayoutDeviceIntCoord Theme::GetScrollbarSize(const nsPresContext* aPresContext,
1502 StyleScrollbarWidth aWidth,
1503 Overlay aOverlay) {
1504 return GetScrollbarDrawing().GetScrollbarSize(aPresContext, aWidth, aOverlay);
1507 nscoord Theme::GetCheckboxRadioPrefSize() {
1508 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
1511 /* static */
1512 UniquePtr<ScrollbarDrawing> Theme::ScrollbarStyle() {
1513 switch (StaticPrefs::widget_non_native_theme_scrollbar_style()) {
1514 case 1:
1515 return MakeUnique<ScrollbarDrawingCocoa>();
1516 case 2:
1517 return MakeUnique<ScrollbarDrawingGTK>();
1518 case 3:
1519 return MakeUnique<ScrollbarDrawingAndroid>();
1520 case 4:
1521 return MakeUnique<ScrollbarDrawingWin>();
1522 case 5:
1523 return MakeUnique<ScrollbarDrawingWin11>();
1524 default:
1525 break;
1527 // Default to native scrollbar style for each platform.
1528 #ifdef XP_WIN
1529 if (IsWin11OrLater()) {
1530 return MakeUnique<ScrollbarDrawingWin11>();
1532 return MakeUnique<ScrollbarDrawingWin>();
1533 #elif MOZ_WIDGET_COCOA
1534 return MakeUnique<ScrollbarDrawingCocoa>();
1535 #elif MOZ_WIDGET_GTK
1536 return MakeUnique<ScrollbarDrawingGTK>();
1537 #elif ANDROID
1538 return MakeUnique<ScrollbarDrawingAndroid>();
1539 #else
1540 # error "Unknown platform, need scrollbar implementation."
1541 #endif
1544 LayoutDeviceIntSize Theme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1545 nsIFrame* aFrame,
1546 StyleAppearance aAppearance) {
1547 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1549 if (IsWidgetScrollbarPart(aAppearance)) {
1550 return GetScrollbarDrawing().GetMinimumWidgetSize(aPresContext, aAppearance,
1551 aFrame);
1554 LayoutDeviceIntSize result;
1555 switch (aAppearance) {
1556 case StyleAppearance::RangeThumb:
1557 result.SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1558 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1559 break;
1560 case StyleAppearance::MozMenulistArrowButton:
1561 result.width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1562 break;
1563 case StyleAppearance::SpinnerUpbutton:
1564 case StyleAppearance::SpinnerDownbutton:
1565 result.width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1566 result.height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1567 break;
1568 default:
1569 break;
1571 return result;
1574 nsITheme::Transparency Theme::GetWidgetTransparency(
1575 nsIFrame* aFrame, StyleAppearance aAppearance) {
1576 if (auto scrollbar = GetScrollbarDrawing().GetScrollbarPartTransparency(
1577 aFrame, aAppearance)) {
1578 return *scrollbar;
1580 if (aAppearance == StyleAppearance::Tooltip) {
1581 // We draw a rounded rect, so we need transparency.
1582 return eTransparent;
1584 return eUnknownTransparency;
1587 NS_IMETHODIMP
1588 Theme::WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
1589 nsAtom* aAttribute, bool* aShouldRepaint,
1590 const nsAttrValue* aOldValue) {
1591 if (!aAttribute) {
1592 // Hover/focus/active changed. Always repaint.
1593 *aShouldRepaint = true;
1594 } else {
1595 // Check the attribute to see if it's relevant.
1596 // disabled, checked, dlgtype, default, etc.
1597 *aShouldRepaint = false;
1598 if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
1599 aAttribute == nsGkAtoms::selected ||
1600 aAttribute == nsGkAtoms::visuallyselected ||
1601 aAttribute == nsGkAtoms::menuactive ||
1602 aAttribute == nsGkAtoms::sortDirection ||
1603 aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::_default ||
1604 aAttribute == nsGkAtoms::open || aAttribute == nsGkAtoms::hover) {
1605 *aShouldRepaint = true;
1609 return NS_OK;
1612 NS_IMETHODIMP
1613 Theme::ThemeChanged() { return NS_OK; }
1615 bool Theme::WidgetAppearanceDependsOnWindowFocus(StyleAppearance aAppearance) {
1616 return IsWidgetScrollbarPart(aAppearance);
1619 nsITheme::ThemeGeometryType Theme::ThemeGeometryTypeForWidget(
1620 nsIFrame* aFrame, StyleAppearance aAppearance) {
1621 return eThemeGeometryTypeUnknown;
1624 bool Theme::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
1625 StyleAppearance aAppearance) {
1626 switch (aAppearance) {
1627 case StyleAppearance::Radio:
1628 case StyleAppearance::Checkbox:
1629 case StyleAppearance::FocusOutline:
1630 case StyleAppearance::Textarea:
1631 case StyleAppearance::Textfield:
1632 case StyleAppearance::Range:
1633 case StyleAppearance::RangeThumb:
1634 case StyleAppearance::ProgressBar:
1635 case StyleAppearance::Progresschunk:
1636 case StyleAppearance::Meter:
1637 case StyleAppearance::Meterchunk:
1638 case StyleAppearance::ScrollbarbuttonUp:
1639 case StyleAppearance::ScrollbarbuttonDown:
1640 case StyleAppearance::ScrollbarbuttonLeft:
1641 case StyleAppearance::ScrollbarbuttonRight:
1642 case StyleAppearance::ScrollbarthumbHorizontal:
1643 case StyleAppearance::ScrollbarthumbVertical:
1644 case StyleAppearance::ScrollbartrackHorizontal:
1645 case StyleAppearance::ScrollbartrackVertical:
1646 case StyleAppearance::ScrollbarHorizontal:
1647 case StyleAppearance::ScrollbarVertical:
1648 case StyleAppearance::Scrollcorner:
1649 case StyleAppearance::Button:
1650 case StyleAppearance::Listbox:
1651 case StyleAppearance::Menulist:
1652 case StyleAppearance::MenulistButton:
1653 case StyleAppearance::NumberInput:
1654 case StyleAppearance::MozMenulistArrowButton:
1655 case StyleAppearance::SpinnerUpbutton:
1656 case StyleAppearance::SpinnerDownbutton:
1657 case StyleAppearance::Menuitem:
1658 case StyleAppearance::Tooltip:
1659 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
1660 default:
1661 return false;
1665 bool Theme::WidgetIsContainer(StyleAppearance aAppearance) {
1666 switch (aAppearance) {
1667 case StyleAppearance::MozMenulistArrowButton:
1668 case StyleAppearance::Radio:
1669 case StyleAppearance::Checkbox:
1670 return false;
1671 default:
1672 return true;
1676 bool Theme::ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) {
1677 return true;
1680 bool Theme::ThemeNeedsComboboxDropmarker() { return true; }
1682 bool Theme::ThemeSupportsScrollbarButtons() {
1683 return GetScrollbarDrawing().ShouldDrawScrollbarButtons();
1686 } // namespace mozilla::widget