Bug 1698786: part 1) Add some logging to `mozInlineSpellChecker`. r=masayuki
[gecko.git] / widget / nsNativeBasicTheme.cpp
blob374103666a39216df1fab8023fe74f763f891053
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 "nsNativeBasicTheme.h"
8 #include "gfxBlur.h"
9 #include "mozilla/MathAlgorithms.h"
10 #include "mozilla/dom/Document.h"
11 #include "mozilla/gfx/Rect.h"
12 #include "mozilla/gfx/Types.h"
13 #include "mozilla/gfx/Filters.h"
14 #include "mozilla/RelativeLuminanceUtils.h"
15 #include "mozilla/StaticPrefs_widget.h"
16 #include "mozilla/webrender/WebRenderAPI.h"
17 #include "nsCSSColorUtils.h"
18 #include "nsCSSRendering.h"
19 #include "nsLayoutUtils.h"
20 #include "PathHelpers.h"
22 #include "nsDeviceContext.h"
24 #include "nsColorControlFrame.h"
25 #include "nsDateTimeControlFrame.h"
26 #include "nsMeterFrame.h"
27 #include "nsProgressFrame.h"
28 #include "nsRangeFrame.h"
29 #include "mozilla/dom/HTMLMeterElement.h"
30 #include "mozilla/dom/HTMLProgressElement.h"
32 using namespace mozilla;
33 using namespace mozilla::widget;
34 using namespace mozilla::gfx;
36 NS_IMPL_ISUPPORTS_INHERITED(nsNativeBasicTheme, nsNativeTheme, nsITheme)
38 namespace {
40 static constexpr sRGBColor sTransparent = sRGBColor::White(0.0);
42 // This pushes and pops a clip rect to the draw target.
44 // This is done to reduce fuzz in places where we may have antialiasing,
45 // because skia is not clip-invariant: given different clips, it does not
46 // guarantee the same result, even if the painted content doesn't intersect
47 // the clips.
49 // This is a bit sad, overall, but...
50 struct MOZ_RAII AutoClipRect {
51 AutoClipRect(DrawTarget& aDt, const LayoutDeviceRect& aRect) : mDt(aDt) {
52 mDt.PushClipRect(aRect.ToUnknownRect());
55 ~AutoClipRect() { mDt.PopClip(); }
57 private:
58 DrawTarget& mDt;
61 static LayoutDeviceIntCoord SnapBorderWidth(
62 CSSCoord aCssWidth, nsNativeBasicTheme::DPIRatio aDpiRatio) {
63 if (aCssWidth == 0.0f) {
64 return 0;
66 return std::max(LayoutDeviceIntCoord(1), (aCssWidth * aDpiRatio).Truncated());
69 [[nodiscard]] static float ScaleLuminanceBy(float aLuminance, float aFactor) {
70 return aLuminance >= 0.18f ? aLuminance * aFactor : aLuminance / aFactor;
73 static nscolor ThemedAccentColor(bool aBackground) {
74 MOZ_ASSERT(StaticPrefs::widget_non_native_theme_use_theme_accent());
75 nscolor color = LookAndFeel::GetColor(
76 aBackground ? LookAndFeel::ColorID::MozAccentColor
77 : LookAndFeel::ColorID::MozAccentColorForeground);
78 if (NS_GET_A(color) != 0xff) {
79 // Blend with white, ensuring the color is opaque to avoid surprises if we
80 // overdraw.
81 color = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), color);
83 return color;
86 } // namespace
88 sRGBColor nsNativeBasicTheme::sAccentColor = sRGBColor::OpaqueWhite();
89 sRGBColor nsNativeBasicTheme::sAccentColorForeground = sRGBColor::OpaqueWhite();
90 sRGBColor nsNativeBasicTheme::sAccentColorLight = sRGBColor::OpaqueWhite();
91 sRGBColor nsNativeBasicTheme::sAccentColorDark = sRGBColor::OpaqueWhite();
92 sRGBColor nsNativeBasicTheme::sAccentColorDarker = sRGBColor::OpaqueWhite();
94 void nsNativeBasicTheme::Init() {
95 Preferences::RegisterCallbackAndCall(PrefChangedCallback,
96 "widget.non-native.use-theme-accent");
99 void nsNativeBasicTheme::Shutdown() {
100 Preferences::UnregisterCallback(PrefChangedCallback,
101 "widget.non-native.use-theme-accent");
104 void nsNativeBasicTheme::LookAndFeelChanged() { RecomputeAccentColors(); }
106 void nsNativeBasicTheme::RecomputeAccentColors() {
107 MOZ_RELEASE_ASSERT(NS_IsMainThread());
109 if (!StaticPrefs::widget_non_native_theme_use_theme_accent()) {
110 sAccentColorForeground = sColorWhite;
111 sAccentColor =
112 sRGBColor::UnusualFromARGB(0xff0060df); // Luminance: 13.69346%
113 sAccentColorLight =
114 sRGBColor::UnusualFromARGB(0x4d008deb); // Luminance: 25.04791%
115 sAccentColorDark =
116 sRGBColor::UnusualFromARGB(0xff0250bb); // Luminance: 9.33808%
117 sAccentColorDarker =
118 sRGBColor::UnusualFromARGB(0xff054096); // Luminance: 5.90106%
119 return;
122 sAccentColorForeground = sRGBColor::FromABGR(ThemedAccentColor(false));
123 const nscolor accent = ThemedAccentColor(true);
124 const float luminance = RelativeLuminanceUtils::Compute(accent);
126 constexpr float kLightLuminanceScale = 25.048f / 13.693f;
127 constexpr float kDarkLuminanceScale = 9.338f / 13.693f;
128 constexpr float kDarkerLuminanceScale = 5.901f / 13.693f;
130 const float lightLuminanceAdjust =
131 ScaleLuminanceBy(luminance, kLightLuminanceScale);
132 const float darkLuminanceAdjust =
133 ScaleLuminanceBy(luminance, kDarkLuminanceScale);
134 const float darkerLuminanceAdjust =
135 ScaleLuminanceBy(luminance, kDarkerLuminanceScale);
137 sAccentColor = sRGBColor::FromABGR(accent);
140 nscolor lightColor =
141 RelativeLuminanceUtils::Adjust(accent, lightLuminanceAdjust);
142 lightColor = NS_RGBA(NS_GET_R(lightColor), NS_GET_G(lightColor),
143 NS_GET_B(lightColor), 0x4d);
144 sAccentColorLight = sRGBColor::FromABGR(lightColor);
147 sAccentColorDark = sRGBColor::FromABGR(
148 RelativeLuminanceUtils::Adjust(accent, darkLuminanceAdjust));
149 sAccentColorDarker = sRGBColor::FromABGR(
150 RelativeLuminanceUtils::Adjust(accent, darkerLuminanceAdjust));
153 static bool IsScrollbarWidthThin(nsIFrame* aFrame) {
154 ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
155 auto scrollbarWidth = style->StyleUIReset()->mScrollbarWidth;
156 return scrollbarWidth == StyleScrollbarWidth::Thin;
159 static sRGBColor SystemColor(StyleSystemColor aColor) {
160 return sRGBColor::FromABGR(LookAndFeel::GetColor(aColor));
163 static std::pair<sRGBColor, sRGBColor> SystemColorPair(
164 StyleSystemColor aFirst, StyleSystemColor aSecond) {
165 return std::make_pair(SystemColor(aFirst), SystemColor(aSecond));
168 /* static */
169 auto nsNativeBasicTheme::GetDPIRatioForScrollbarPart(nsPresContext* aPc)
170 -> DPIRatio {
171 return DPIRatio(float(AppUnitsPerCSSPixel()) /
172 aPc->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
175 /* static */
176 auto nsNativeBasicTheme::GetDPIRatio(nsPresContext* aPc,
177 StyleAppearance aAppearance) -> DPIRatio {
178 // Widgets react to zoom, except scrollbars.
179 if (IsWidgetScrollbarPart(aAppearance)) {
180 return GetDPIRatioForScrollbarPart(aPc);
182 return DPIRatio(float(AppUnitsPerCSSPixel()) / aPc->AppUnitsPerDevPixel());
185 /* static */
186 auto nsNativeBasicTheme::GetDPIRatio(nsIFrame* aFrame,
187 StyleAppearance aAppearance) -> DPIRatio {
188 return GetDPIRatio(aFrame->PresContext(), aAppearance);
191 /* static */
192 bool nsNativeBasicTheme::IsDateTimeResetButton(nsIFrame* aFrame) {
193 if (!aFrame) {
194 return false;
197 nsIFrame* parent = aFrame->GetParent();
198 if (parent && (parent = parent->GetParent()) &&
199 (parent = parent->GetParent())) {
200 nsDateTimeControlFrame* dateTimeFrame = do_QueryFrame(parent);
201 if (dateTimeFrame) {
202 return true;
205 return false;
208 /* static */
209 bool nsNativeBasicTheme::IsColorPickerButton(nsIFrame* aFrame) {
210 nsColorControlFrame* colorPickerButton = do_QueryFrame(aFrame);
211 return colorPickerButton;
214 // Checkbox and radio need to preserve aspect-ratio for compat. We also snap the
215 // size to exact device pixels to avoid snapping disorting the circles.
216 static LayoutDeviceRect CheckBoxRadioRect(const LayoutDeviceRect& aRect) {
217 // Place a square rect in the center of aRect.
218 auto size = std::trunc(std::min(aRect.width, aRect.height));
219 auto position = aRect.Center() - LayoutDevicePoint(size * 0.5, size * 0.5);
220 return LayoutDeviceRect(position, LayoutDeviceSize(size, size));
223 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeCheckboxColors(
224 const EventStates& aState, StyleAppearance aAppearance,
225 UseSystemColors aUseSystemColors) {
226 MOZ_ASSERT(aAppearance == StyleAppearance::Checkbox ||
227 aAppearance == StyleAppearance::Radio);
228 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
229 bool isPressed = !isDisabled && aState.HasAllStates(NS_EVENT_STATE_HOVER |
230 NS_EVENT_STATE_ACTIVE);
231 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
232 bool isChecked = aState.HasState(NS_EVENT_STATE_CHECKED);
233 bool isIndeterminate = aAppearance == StyleAppearance::Checkbox &&
234 aState.HasState(NS_EVENT_STATE_INDETERMINATE);
236 if (bool(aUseSystemColors)) {
237 sRGBColor backgroundColor = SystemColor(StyleSystemColor::Buttonface);
238 sRGBColor borderColor = SystemColor(StyleSystemColor::Buttontext);
239 if (isDisabled) {
240 borderColor = SystemColor(StyleSystemColor::Graytext);
241 if (isChecked || isIndeterminate) {
242 backgroundColor = borderColor;
244 } else if (isChecked || isIndeterminate) {
245 backgroundColor = borderColor = SystemColor(StyleSystemColor::Highlight);
247 return {backgroundColor, borderColor};
250 sRGBColor backgroundColor = sColorWhite;
251 sRGBColor borderColor = sColorGrey40;
252 if (isDisabled) {
253 if (isChecked || isIndeterminate) {
254 backgroundColor = borderColor = sColorGrey40Alpha50;
255 } else {
256 backgroundColor = sColorWhiteAlpha50;
257 borderColor = sColorGrey40Alpha50;
259 } else {
260 if (isChecked || isIndeterminate) {
261 const auto& color = isPressed ? sAccentColorDarker
262 : isHovered ? sAccentColorDark
263 : sAccentColor;
264 backgroundColor = borderColor = color;
265 } else if (isPressed) {
266 backgroundColor = sColorGrey20;
267 borderColor = sColorGrey60;
268 } else if (isHovered) {
269 backgroundColor = sColorWhite;
270 borderColor = sColorGrey50;
271 } else {
272 backgroundColor = sColorWhite;
273 borderColor = sColorGrey40;
277 return std::make_pair(backgroundColor, borderColor);
280 sRGBColor nsNativeBasicTheme::ComputeCheckmarkColor(
281 const EventStates& aState, UseSystemColors aUseSystemColors) {
282 if (bool(aUseSystemColors)) {
283 return SystemColor(StyleSystemColor::Highlighttext);
285 if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
286 return sColorWhiteAlpha50;
288 return sAccentColorForeground;
291 sRGBColor nsNativeBasicTheme::ComputeBorderColor(
292 const EventStates& aState, UseSystemColors aUseSystemColors) {
293 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
294 if (bool(aUseSystemColors)) {
295 return SystemColor(isDisabled ? StyleSystemColor::Graytext
296 : StyleSystemColor::Buttontext);
298 bool isActive =
299 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
300 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
301 bool isFocused = aState.HasState(NS_EVENT_STATE_FOCUSRING);
302 if (isDisabled) {
303 return sColorGrey40Alpha50;
305 if (isFocused) {
306 // We draw the outline over the border for all controls that call into this,
307 // so to prevent issues where the border shows underneath if it snaps in the
308 // wrong direction, we use a transparent border. An alternative to this is
309 // ensuring that we snap the offset in PaintRoundedFocusRect the same was a
310 // we snap border widths, so that negative offsets are guaranteed to cover
311 // the border. But this looks harder to mess up.
312 return sTransparent;
314 if (isActive) {
315 return sColorGrey60;
317 if (isHovered) {
318 return sColorGrey50;
320 return sColorGrey40;
323 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeButtonColors(
324 const EventStates& aState, UseSystemColors aUseSystemColors,
325 nsIFrame* aFrame) {
326 bool isActive =
327 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
328 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
329 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
331 const sRGBColor backgroundColor = [&] {
332 if (bool(aUseSystemColors)) {
333 return SystemColor(StyleSystemColor::Buttonface);
336 if (isDisabled) {
337 return sColorGrey10Alpha50;
339 if (IsDateTimeResetButton(aFrame)) {
340 return sColorWhite;
342 if (isActive) {
343 return sColorGrey30;
345 if (isHovered) {
346 return sColorGrey20;
348 return sColorGrey10;
349 }();
351 const sRGBColor borderColor = ComputeBorderColor(aState, aUseSystemColors);
352 return std::make_pair(backgroundColor, borderColor);
355 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeTextfieldColors(
356 const EventStates& aState, UseSystemColors aUseSystemColors) {
357 const sRGBColor backgroundColor = [&] {
358 if (bool(aUseSystemColors)) {
359 return SystemColor(StyleSystemColor::TextBackground);
361 if (aState.HasState(NS_EVENT_STATE_DISABLED)) {
362 return sColorWhiteAlpha50;
364 return sColorWhite;
365 }();
366 const sRGBColor borderColor = ComputeBorderColor(aState, aUseSystemColors);
367 return std::make_pair(backgroundColor, borderColor);
370 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeProgressColors(
371 const EventStates& aState, UseSystemColors aUseSystemColors) {
372 if (bool(aUseSystemColors)) {
373 return SystemColorPair(StyleSystemColor::Highlight,
374 StyleSystemColor::Buttontext);
377 bool isActive =
378 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
379 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
380 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
382 if (isDisabled) {
383 return std::make_pair(sColorGrey40Alpha50, sColorGrey40Alpha50);
385 if (isActive || isHovered) {
386 return std::make_pair(sAccentColorDark, sAccentColorDarker);
388 return std::make_pair(sAccentColor, sAccentColorDark);
391 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeTrackColors(
392 const EventStates& aState, UseSystemColors aUseSystemColors) {
393 if (bool(aUseSystemColors)) {
394 return SystemColorPair(StyleSystemColor::TextBackground,
395 StyleSystemColor::Buttontext);
397 bool isActive =
398 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
399 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
400 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
402 if (isDisabled) {
403 return std::make_pair(sColorGrey10Alpha50, sColorGrey40Alpha50);
405 if (isActive || isHovered) {
406 return std::make_pair(sColorGrey20, sColorGrey50);
408 return std::make_pair(sColorGrey10, sColorGrey40);
411 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeRangeThumbColors(
412 const EventStates& aState, UseSystemColors aUseSystemColors) {
413 if (bool(aUseSystemColors)) {
414 return SystemColorPair(StyleSystemColor::Highlighttext,
415 StyleSystemColor::Highlight);
418 bool isActive =
419 aState.HasAllStates(NS_EVENT_STATE_HOVER | NS_EVENT_STATE_ACTIVE);
420 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
421 bool isHovered = !isDisabled && aState.HasState(NS_EVENT_STATE_HOVER);
423 const sRGBColor& backgroundColor = [&] {
424 if (isDisabled) {
425 return sColorGrey40;
427 if (isActive) {
428 return sAccentColor;
430 if (isHovered) {
431 return sColorGrey60;
433 return sColorGrey50;
434 }();
436 const sRGBColor borderColor = sColorWhite;
438 return std::make_pair(backgroundColor, borderColor);
441 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressColors(
442 UseSystemColors aUseSystemColors) {
443 if (bool(aUseSystemColors)) {
444 return SystemColorPair(StyleSystemColor::Highlight,
445 StyleSystemColor::Buttontext);
447 return std::make_pair(sAccentColor, sAccentColorDark);
450 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeProgressTrackColors(
451 UseSystemColors aUseSystemColors) {
452 if (bool(aUseSystemColors)) {
453 return SystemColorPair(StyleSystemColor::Buttonface,
454 StyleSystemColor::Buttontext);
456 return std::make_pair(sColorGrey10, sColorGrey40);
459 std::pair<sRGBColor, sRGBColor> nsNativeBasicTheme::ComputeMeterchunkColors(
460 const EventStates& aMeterState, UseSystemColors aUseSystemColors) {
461 if (bool(aUseSystemColors)) {
462 return ComputeProgressColors(aUseSystemColors);
464 sRGBColor borderColor = sColorMeterGreen20;
465 sRGBColor chunkColor = sColorMeterGreen10;
467 if (aMeterState.HasState(NS_EVENT_STATE_SUB_OPTIMUM)) {
468 borderColor = sColorMeterYellow20;
469 chunkColor = sColorMeterYellow10;
470 } else if (aMeterState.HasState(NS_EVENT_STATE_SUB_SUB_OPTIMUM)) {
471 borderColor = sColorMeterRed20;
472 chunkColor = sColorMeterRed10;
475 return std::make_pair(chunkColor, borderColor);
478 sRGBColor nsNativeBasicTheme::ComputeMenulistArrowButtonColor(
479 const EventStates& aState, UseSystemColors aUseSystemColors) {
480 bool isDisabled = aState.HasState(NS_EVENT_STATE_DISABLED);
481 if (bool(aUseSystemColors)) {
482 return SystemColor(isDisabled ? StyleSystemColor::Graytext
483 : StyleSystemColor::TextForeground);
485 return isDisabled ? sColorGrey60Alpha50 : sColorGrey60;
488 std::array<sRGBColor, 3> nsNativeBasicTheme::ComputeFocusRectColors(
489 UseSystemColors aUseSystemColors) {
490 if (bool(aUseSystemColors)) {
491 return {SystemColor(StyleSystemColor::Highlight),
492 SystemColor(StyleSystemColor::Buttontext),
493 SystemColor(StyleSystemColor::TextBackground)};
496 return {sAccentColor, sColorWhiteAlpha80, sAccentColorLight};
499 sRGBColor nsNativeBasicTheme::ComputeScrollbarColor(
500 nsIFrame* aFrame, const ComputedStyle& aStyle,
501 const EventStates& aDocumentState, UseSystemColors aUseSystemColors) {
502 const nsStyleUI* ui = aStyle.StyleUI();
503 if (bool(aUseSystemColors)) {
504 return SystemColor(StyleSystemColor::TextBackground);
506 if (ShouldUseDarkScrollbar(aFrame, aStyle)) {
507 return sRGBColor::FromU8(20, 20, 25, 77);
509 nscolor color;
510 if (ui->mScrollbarColor.IsColors()) {
511 color = ui->mScrollbarColor.AsColors().track.CalcColor(aStyle);
512 } else if (aDocumentState.HasAllStates(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
513 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbarInactive,
514 sScrollbarColor.ToABGR());
515 } else {
516 color = LookAndFeel::GetColor(LookAndFeel::ColorID::ThemedScrollbar,
517 sScrollbarColor.ToABGR());
519 return gfx::sRGBColor::FromABGR(color);
522 nscolor nsNativeBasicTheme::AdjustUnthemedScrollbarThumbColor(
523 nscolor aFaceColor, EventStates aStates) {
524 // In Windows 10, scrollbar thumb has the following colors:
526 // State | Color | Luminance
527 // -------+----------+----------
528 // Normal | Gray 205 | 61.0%
529 // Hover | Gray 166 | 38.1%
530 // Active | Gray 96 | 11.7%
532 // This function is written based on the ratios between the values.
533 bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
534 bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
535 if (!isActive && !isHover) {
536 return aFaceColor;
538 float luminance = RelativeLuminanceUtils::Compute(aFaceColor);
539 if (isActive) {
540 // 11.7 / 61.0
541 luminance = ScaleLuminanceBy(luminance, 0.192f);
542 } else {
543 // 38.1 / 61.0
544 luminance = ScaleLuminanceBy(luminance, 0.625f);
546 return RelativeLuminanceUtils::Adjust(aFaceColor, luminance);
549 /*static*/
550 nscolor nsNativeBasicTheme::GetScrollbarButtonColor(nscolor aTrackColor,
551 EventStates aStates) {
552 // See numbers in GetScrollbarArrowColor.
553 // This function is written based on ratios between values listed there.
555 bool isActive = aStates.HasState(NS_EVENT_STATE_ACTIVE);
556 bool isHover = aStates.HasState(NS_EVENT_STATE_HOVER);
557 if (!isActive && !isHover) {
558 return aTrackColor;
560 float luminance = RelativeLuminanceUtils::Compute(aTrackColor);
561 if (isActive) {
562 if (luminance >= 0.18f) {
563 luminance *= 0.134f;
564 } else {
565 luminance /= 0.134f;
566 luminance = std::min(luminance, 1.0f);
568 } else {
569 if (luminance >= 0.18f) {
570 luminance *= 0.805f;
571 } else {
572 luminance /= 0.805f;
575 return RelativeLuminanceUtils::Adjust(aTrackColor, luminance);
578 /*static*/
579 nscolor nsNativeBasicTheme::GetScrollbarArrowColor(nscolor aButtonColor) {
580 // In Windows 10 scrollbar, there are several gray colors used:
582 // State | Background (lum) | Arrow | Contrast
583 // -------+------------------+---------+---------
584 // Normal | Gray 240 (87.1%) | Gray 96 | 5.5
585 // Hover | Gray 218 (70.1%) | Black | 15.0
586 // Active | Gray 96 (11.7%) | White | 6.3
588 // Contrast value is computed based on the definition in
589 // https://www.w3.org/TR/WCAG20/#contrast-ratiodef
591 // This function is written based on these values.
593 float luminance = RelativeLuminanceUtils::Compute(aButtonColor);
594 // Color with luminance larger than 0.72 has contrast ratio over 4.6
595 // to color with luminance of gray 96, so this value is chosen for
596 // this range. It is the luminance of gray 221.
597 if (luminance >= 0.72) {
598 // ComputeRelativeLuminanceFromComponents(96). That function cannot
599 // be constexpr because of std::pow.
600 const float GRAY96_LUMINANCE = 0.117f;
601 return RelativeLuminanceUtils::Adjust(aButtonColor, GRAY96_LUMINANCE);
603 // The contrast ratio of a color to black equals that to white when its
604 // luminance is around 0.18, with a contrast ratio ~4.6 to both sides,
605 // thus the value below. It's the lumanince of gray 118.
606 if (luminance >= 0.18) {
607 return NS_RGBA(0, 0, 0, NS_GET_A(aButtonColor));
609 return NS_RGBA(255, 255, 255, NS_GET_A(aButtonColor));
612 bool nsNativeBasicTheme::ShouldUseDarkScrollbar(nsIFrame* aFrame,
613 const ComputedStyle& aStyle) {
614 if (StaticPrefs::widget_disable_dark_scrollbar()) {
615 return false;
617 if (aStyle.StyleUI()->mScrollbarColor.IsColors()) {
618 return false;
620 return nsNativeTheme::IsDarkBackground(aFrame);
623 sRGBColor nsNativeBasicTheme::ComputeScrollbarThumbColor(
624 nsIFrame* aFrame, const ComputedStyle& aStyle,
625 const EventStates& aElementState, const EventStates& aDocumentState,
626 UseSystemColors aUseSystemColors) {
627 if (!bool(aUseSystemColors) && ShouldUseDarkScrollbar(aFrame, aStyle)) {
628 return sRGBColor::FromABGR(AdjustUnthemedScrollbarThumbColor(
629 NS_RGBA(249, 249, 250, 102), aElementState));
631 const nsStyleUI* ui = aStyle.StyleUI();
632 nscolor color;
633 if (ui->mScrollbarColor.IsColors()) {
634 return sRGBColor::FromABGR(AdjustUnthemedScrollbarThumbColor(
635 ui->mScrollbarColor.AsColors().thumb.CalcColor(aStyle), aElementState));
638 auto systemColor = [&] {
639 if (aDocumentState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE)) {
640 return StyleSystemColor::ThemedScrollbarThumbInactive;
642 if (aElementState.HasState(NS_EVENT_STATE_ACTIVE)) {
643 if (bool(aUseSystemColors)) {
644 return StyleSystemColor::Highlight;
646 return StyleSystemColor::ThemedScrollbarThumbActive;
648 if (aElementState.HasState(NS_EVENT_STATE_HOVER)) {
649 if (bool(aUseSystemColors)) {
650 return StyleSystemColor::Highlight;
652 return StyleSystemColor::ThemedScrollbarThumbHover;
654 if (bool(aUseSystemColors)) {
655 return StyleSystemColor::TextForeground;
657 return StyleSystemColor::ThemedScrollbarThumb;
658 }();
660 if (NS_FAILED(LookAndFeel::GetColor(systemColor, &color))) {
661 color = AdjustUnthemedScrollbarThumbColor(sScrollbarThumbColor.ToABGR(),
662 aElementState);
664 return gfx::sRGBColor::FromABGR(color);
667 std::pair<sRGBColor, sRGBColor>
668 nsNativeBasicTheme::ComputeScrollbarButtonColors(
669 nsIFrame* aFrame, StyleAppearance aAppearance, const ComputedStyle& aStyle,
670 const EventStates& aElementState, const EventStates& aDocumentState,
671 UseSystemColors aUseSystemColors) {
672 if (bool(aUseSystemColors)) {
673 if (aElementState.HasAtLeastOneOfStates(NS_EVENT_STATE_ACTIVE |
674 NS_EVENT_STATE_HOVER)) {
675 return SystemColorPair(StyleSystemColor::Highlight,
676 StyleSystemColor::Buttonface);
678 return SystemColorPair(StyleSystemColor::TextBackground,
679 StyleSystemColor::TextForeground);
682 auto trackColor =
683 ComputeScrollbarColor(aFrame, aStyle, aDocumentState, aUseSystemColors);
684 nscolor buttonColor =
685 GetScrollbarButtonColor(trackColor.ToABGR(), aElementState);
686 nscolor arrowColor = GetScrollbarArrowColor(buttonColor);
687 return {sRGBColor::FromABGR(buttonColor), sRGBColor::FromABGR(arrowColor)};
690 static const CSSCoord kInnerFocusOutlineWidth = 2.0f;
692 template <typename PaintBackendData>
693 void nsNativeBasicTheme::PaintRoundedFocusRect(PaintBackendData& aBackendData,
694 const LayoutDeviceRect& aRect,
695 UseSystemColors aUseSystemColors,
696 DPIRatio aDpiRatio,
697 CSSCoord aRadius,
698 CSSCoord aOffset) {
699 // NOTE(emilio): If the widths or offsets here change, make sure to tweak
700 // the GetWidgetOverflow path for FocusOutline.
701 auto [innerColor, middleColor, outerColor] =
702 ComputeFocusRectColors(aUseSystemColors);
704 LayoutDeviceRect focusRect(aRect);
706 // The focus rect is painted outside of the border area (aRect), see:
708 // data:text/html,<div style="border: 1px solid; outline: 2px solid
709 // red">Foobar</div>
711 // But some controls might provide a negative offset to cover the border, if
712 // necessary.
713 CSSCoord strokeWidth = kInnerFocusOutlineWidth;
714 auto strokeWidthDevPx =
715 LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
716 CSSCoord strokeRadius = aRadius;
717 focusRect.Inflate(aOffset * aDpiRatio + strokeWidthDevPx);
719 PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, innerColor,
720 strokeWidth, strokeRadius, aDpiRatio);
722 strokeWidth = CSSCoord(1.0f);
723 strokeWidthDevPx = LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
724 strokeRadius += strokeWidth;
725 focusRect.Inflate(strokeWidthDevPx);
727 PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, middleColor,
728 strokeWidth, strokeRadius, aDpiRatio);
730 strokeWidth = CSSCoord(2.0f);
731 strokeWidthDevPx = LayoutDeviceCoord(SnapBorderWidth(strokeWidth, aDpiRatio));
732 strokeRadius += strokeWidth;
733 focusRect.Inflate(strokeWidthDevPx);
735 PaintRoundedRectWithRadius(aBackendData, focusRect, sTransparent, outerColor,
736 strokeWidth, strokeRadius, aDpiRatio);
739 void nsNativeBasicTheme::PaintRoundedRectWithRadius(
740 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
741 const LayoutDeviceRect& aClipRect, const sRGBColor& aBackgroundColor,
742 const sRGBColor& aBorderColor, CSSCoord aBorderWidth, CSSCoord aRadius,
743 DPIRatio aDpiRatio) {
744 const bool kBackfaceIsVisible = true;
745 const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
746 const LayoutDeviceCoord radius(aRadius * aDpiRatio);
747 const wr::LayoutRect dest = wr::ToLayoutRect(aRect);
748 const wr::LayoutRect clip = wr::ToLayoutRect(aClipRect);
750 // Push the background.
751 if (aBackgroundColor.a) {
752 auto backgroundColor = wr::ToColorF(ToDeviceColor(aBackgroundColor));
753 wr::LayoutRect backgroundRect = [&] {
754 LayoutDeviceRect bg = aRect;
755 bg.Deflate(borderWidth);
756 return wr::ToLayoutRect(bg);
757 }();
758 if (!radius) {
759 aWrData.mBuilder.PushRect(backgroundRect, clip, kBackfaceIsVisible,
760 backgroundColor);
761 } else {
762 // NOTE(emilio): This follows DisplayListBuilder::PushRoundedRect and
763 // draws the rounded fill as an extra thick rounded border instead of a
764 // rectangle that's clipped to a rounded clip. Refer to that method for a
765 // justification. See bug 1694269.
766 LayoutDeviceCoord backgroundRadius =
767 std::max(0.0f, float(radius) - float(borderWidth));
768 wr::BorderSide side = {backgroundColor, wr::BorderStyle::Solid};
769 const wr::BorderSide sides[4] = {side, side, side, side};
770 float h = backgroundRect.size.width * 0.6f;
771 float v = backgroundRect.size.height * 0.6f;
772 wr::LayoutSideOffsets widths = {v, h, v, h};
773 wr::BorderRadius radii = {{backgroundRadius, backgroundRadius},
774 {backgroundRadius, backgroundRadius},
775 {backgroundRadius, backgroundRadius},
776 {backgroundRadius, backgroundRadius}};
777 aWrData.mBuilder.PushBorder(backgroundRect, clip, kBackfaceIsVisible,
778 widths, {sides, 4}, radii);
782 if (borderWidth && aBorderColor.a) {
783 // Push the border.
784 const auto borderColor = ToDeviceColor(aBorderColor);
785 const auto side = wr::ToBorderSide(borderColor, StyleBorderStyle::Solid);
786 const wr::BorderSide sides[4] = {side, side, side, side};
787 const LayoutDeviceSize sideRadius(radius, radius);
788 const auto widths =
789 wr::ToBorderWidths(borderWidth, borderWidth, borderWidth, borderWidth);
790 const auto wrRadius =
791 wr::ToBorderRadius(sideRadius, sideRadius, sideRadius, sideRadius);
792 aWrData.mBuilder.PushBorder(dest, clip, kBackfaceIsVisible, widths,
793 {sides, 4}, wrRadius);
797 void nsNativeBasicTheme::FillRect(DrawTarget& aDt,
798 const LayoutDeviceRect& aRect,
799 const sRGBColor& aColor) {
800 aDt.FillRect(aRect.ToUnknownRect(), ColorPattern(ToDeviceColor(aColor)));
803 void nsNativeBasicTheme::FillRect(WebRenderBackendData& aWrData,
804 const LayoutDeviceRect& aRect,
805 const sRGBColor& aColor) {
806 const bool kBackfaceIsVisible = true;
807 auto dest = wr::ToLayoutRect(aRect);
808 aWrData.mBuilder.PushRect(dest, dest, kBackfaceIsVisible,
809 wr::ToColorF(ToDeviceColor(aColor)));
812 void nsNativeBasicTheme::PaintRoundedRectWithRadius(
813 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
814 const LayoutDeviceRect& aClipRect, const sRGBColor& aBackgroundColor,
815 const sRGBColor& aBorderColor, CSSCoord aBorderWidth, CSSCoord aRadius,
816 DPIRatio aDpiRatio) {
817 const LayoutDeviceCoord borderWidth(SnapBorderWidth(aBorderWidth, aDpiRatio));
818 const bool needsClip = !(aRect == aClipRect);
819 if (needsClip) {
820 aDrawTarget.PushClipRect(aClipRect.ToUnknownRect());
823 LayoutDeviceRect rect(aRect);
824 // Deflate the rect by half the border width, so that the middle of the
825 // stroke fills exactly the area we want to fill and not more.
826 rect.Deflate(borderWidth * 0.5f);
828 LayoutDeviceCoord radius(aRadius * aDpiRatio);
829 // Fix up the radius if it's too large with the rect we're going to paint.
831 LayoutDeviceCoord min = std::min(rect.width, rect.height);
832 if (radius * 2.0f > min) {
833 radius = min * 0.5f;
837 Maybe<ColorPattern> backgroundPattern;
838 if (aBackgroundColor.a) {
839 backgroundPattern.emplace(ToDeviceColor(aBackgroundColor));
841 Maybe<ColorPattern> borderPattern;
842 if (borderWidth && aBorderColor.a) {
843 borderPattern.emplace(ToDeviceColor(aBorderColor));
846 if (borderPattern || backgroundPattern) {
847 if (radius) {
848 RectCornerRadii radii(radius, radius, radius, radius);
849 RefPtr<Path> roundedRect =
850 MakePathForRoundedRect(aDrawTarget, rect.ToUnknownRect(), radii);
852 if (backgroundPattern) {
853 aDrawTarget.Fill(roundedRect, *backgroundPattern);
855 if (borderPattern) {
856 aDrawTarget.Stroke(roundedRect, *borderPattern,
857 StrokeOptions(borderWidth));
859 } else {
860 if (backgroundPattern) {
861 aDrawTarget.FillRect(rect.ToUnknownRect(), *backgroundPattern);
863 if (borderPattern) {
864 aDrawTarget.StrokeRect(rect.ToUnknownRect(), *borderPattern,
865 StrokeOptions(borderWidth));
870 if (needsClip) {
871 aDrawTarget.PopClip();
875 void nsNativeBasicTheme::PaintCheckboxControl(DrawTarget& aDrawTarget,
876 const LayoutDeviceRect& aRect,
877 const EventStates& aState,
878 UseSystemColors aUseSystemColors,
879 DPIRatio aDpiRatio) {
880 auto [backgroundColor, borderColor] = ComputeCheckboxColors(
881 aState, StyleAppearance::Checkbox, aUseSystemColors);
883 const CSSCoord radius = 2.0f;
884 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
885 if (backgroundColor == borderColor) {
886 borderWidth = 0.0f;
888 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
889 borderWidth, radius, aDpiRatio);
892 if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
893 PaintIndeterminateMark(aDrawTarget, aRect, aState, aUseSystemColors);
894 } else if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
895 PaintCheckMark(aDrawTarget, aRect, aState, aUseSystemColors);
898 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
899 PaintRoundedFocusRect(aDrawTarget, aRect, aUseSystemColors, aDpiRatio, 5.0f,
900 1.0f);
904 constexpr CSSCoord kCheckboxRadioContentBoxSize = 10.0f;
905 constexpr CSSCoord kCheckboxRadioBorderBoxSize =
906 kCheckboxRadioContentBoxSize + kCheckboxRadioBorderWidth * 2.0f;
908 // Returns the right scale for points in a aSize x aSize sized box, centered at
909 // 0x0 to fill aRect in the smaller dimension.
910 static float ScaleToFillRect(const LayoutDeviceRect& aRect, const float aSize) {
911 return std::min(aRect.width, aRect.height) / aSize;
914 void nsNativeBasicTheme::PaintCheckMark(DrawTarget& aDrawTarget,
915 const LayoutDeviceRect& aRect,
916 const EventStates& aState,
917 UseSystemColors aUseSystemColors) {
918 // Points come from the coordinates on a 14X14 (kCheckboxRadioBorderBoxSize)
919 // unit box centered at 0,0
920 const float checkPolygonX[] = {-4.5f, -1.5f, -0.5f, 5.0f, 4.75f,
921 3.5f, -0.5f, -1.5f, -3.5f};
922 const float checkPolygonY[] = {0.5f, 4.0f, 4.0f, -2.5f, -4.0f,
923 -4.0f, 1.0f, 1.25f, -1.0f};
924 const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(float);
925 const float scale = ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
926 auto center = aRect.Center().ToUnknownPoint();
928 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
929 Point p = center + Point(checkPolygonX[0] * scale, checkPolygonY[0] * scale);
930 builder->MoveTo(p);
931 for (int32_t i = 1; i < checkNumPoints; i++) {
932 p = center + Point(checkPolygonX[i] * scale, checkPolygonY[i] * scale);
933 builder->LineTo(p);
935 RefPtr<Path> path = builder->Finish();
937 sRGBColor fillColor = ComputeCheckmarkColor(aState, aUseSystemColors);
938 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(fillColor)));
941 void nsNativeBasicTheme::PaintIndeterminateMark(
942 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
943 const EventStates& aState, UseSystemColors aUseSystemColors) {
944 const CSSCoord borderWidth = 2.0f;
945 const float scale = ScaleToFillRect(aRect, kCheckboxRadioBorderBoxSize);
947 Rect rect = aRect.ToUnknownRect();
948 rect.y += (rect.height / 2) - (borderWidth * scale / 2);
949 rect.height = borderWidth * scale;
950 rect.x += (borderWidth * scale) + (borderWidth * scale / 8);
951 rect.width -= ((borderWidth * scale) + (borderWidth * scale / 8)) * 2;
953 sRGBColor fillColor = ComputeCheckmarkColor(aState, aUseSystemColors);
954 aDrawTarget.FillRect(rect, ColorPattern(ToDeviceColor(fillColor)));
957 template <typename PaintBackendData>
958 void nsNativeBasicTheme::PaintStrokedCircle(PaintBackendData& aPaintData,
959 const LayoutDeviceRect& aRect,
960 const sRGBColor& aBackgroundColor,
961 const sRGBColor& aBorderColor,
962 const CSSCoord aBorderWidth,
963 DPIRatio aDpiRatio) {
964 auto radius = LayoutDeviceCoord(aRect.Size().width) / aDpiRatio;
965 PaintRoundedRectWithRadius(aPaintData, aRect, aBackgroundColor, aBorderColor,
966 aBorderWidth, radius, aDpiRatio);
969 void nsNativeBasicTheme::PaintCircleShadow(WebRenderBackendData& aWrData,
970 const LayoutDeviceRect& aBoxRect,
971 const LayoutDeviceRect& aClipRect,
972 float aShadowAlpha,
973 const CSSPoint& aShadowOffset,
974 CSSCoord aShadowBlurStdDev,
975 DPIRatio aDpiRatio) {
976 const bool kBackfaceIsVisible = true;
977 const LayoutDeviceCoord stdDev = aShadowBlurStdDev * aDpiRatio;
978 const LayoutDevicePoint shadowOffset = aShadowOffset * aDpiRatio;
979 const IntSize inflation =
980 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
981 LayoutDeviceRect shadowRect = aBoxRect;
982 shadowRect.MoveBy(shadowOffset);
983 shadowRect.Inflate(inflation.width, inflation.height);
984 const auto boxRect = wr::ToLayoutRect(aBoxRect);
985 aWrData.mBuilder.PushBoxShadow(
986 wr::ToLayoutRect(shadowRect), wr::ToLayoutRect(aClipRect),
987 kBackfaceIsVisible, boxRect,
988 wr::ToLayoutVector2D(aShadowOffset * aDpiRatio),
989 wr::ToColorF(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)), stdDev,
990 /* aSpread = */ 0.0f,
991 wr::ToBorderRadius(gfx::RectCornerRadii(aBoxRect.Size().width)),
992 wr::BoxShadowClipMode::Outset);
995 void nsNativeBasicTheme::PaintCircleShadow(DrawTarget& aDrawTarget,
996 const LayoutDeviceRect& aBoxRect,
997 const LayoutDeviceRect& aClipRect,
998 float aShadowAlpha,
999 const CSSPoint& aShadowOffset,
1000 CSSCoord aShadowBlurStdDev,
1001 DPIRatio aDpiRatio) {
1002 Float stdDev = aShadowBlurStdDev * aDpiRatio;
1003 Point offset = (aShadowOffset * aDpiRatio).ToUnknownPoint();
1005 RefPtr<FilterNode> blurFilter =
1006 aDrawTarget.CreateFilter(FilterType::GAUSSIAN_BLUR);
1007 if (!blurFilter) {
1008 return;
1011 blurFilter->SetAttribute(ATT_GAUSSIAN_BLUR_STD_DEVIATION, stdDev);
1013 IntSize inflation =
1014 gfxAlphaBoxBlur::CalculateBlurRadius(gfxPoint(stdDev, stdDev));
1015 Rect inflatedRect = aBoxRect.ToUnknownRect();
1016 inflatedRect.Inflate(inflation.width, inflation.height);
1017 Rect sourceRectInFilterSpace =
1018 inflatedRect - aBoxRect.TopLeft().ToUnknownPoint();
1019 Point destinationPointOfSourceRect = inflatedRect.TopLeft() + offset;
1021 IntSize dtSize = RoundedToInt(aBoxRect.Size().ToUnknownSize());
1022 RefPtr<DrawTarget> ellipseDT = aDrawTarget.CreateSimilarDrawTargetForFilter(
1023 dtSize, SurfaceFormat::A8, blurFilter, blurFilter,
1024 sourceRectInFilterSpace, destinationPointOfSourceRect);
1025 if (!ellipseDT) {
1026 return;
1029 AutoClipRect clipRect(aDrawTarget, aClipRect);
1031 RefPtr<Path> ellipse = MakePathForEllipse(
1032 *ellipseDT, (aBoxRect - aBoxRect.TopLeft()).Center().ToUnknownPoint(),
1033 aBoxRect.Size().ToUnknownSize());
1034 ellipseDT->Fill(ellipse,
1035 ColorPattern(DeviceColor(0.0f, 0.0f, 0.0f, aShadowAlpha)));
1036 RefPtr<SourceSurface> ellipseSurface = ellipseDT->Snapshot();
1038 blurFilter->SetInput(IN_GAUSSIAN_BLUR_IN, ellipseSurface);
1039 aDrawTarget.DrawFilter(blurFilter, sourceRectInFilterSpace,
1040 destinationPointOfSourceRect);
1043 template <typename PaintBackendData>
1044 void nsNativeBasicTheme::PaintRadioControl(PaintBackendData& aPaintData,
1045 const LayoutDeviceRect& aRect,
1046 const EventStates& aState,
1047 UseSystemColors aUseSystemColors,
1048 DPIRatio aDpiRatio) {
1049 auto [backgroundColor, borderColor] =
1050 ComputeCheckboxColors(aState, StyleAppearance::Radio, aUseSystemColors);
1052 CSSCoord borderWidth = kCheckboxRadioBorderWidth;
1053 if (backgroundColor == borderColor) {
1054 borderWidth = 0.0f;
1056 PaintStrokedCircle(aPaintData, aRect, backgroundColor, borderColor,
1057 borderWidth, aDpiRatio);
1060 if (aState.HasState(NS_EVENT_STATE_CHECKED)) {
1061 LayoutDeviceRect rect(aRect);
1062 rect.Deflate(SnapBorderWidth(kCheckboxRadioBorderWidth, aDpiRatio));
1064 auto checkColor = ComputeCheckmarkColor(aState, aUseSystemColors);
1065 PaintStrokedCircle(aPaintData, rect, backgroundColor, checkColor,
1066 kCheckboxRadioBorderWidth, aDpiRatio);
1069 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1070 PaintRoundedFocusRect(aPaintData, aRect, aUseSystemColors, aDpiRatio, 5.0f,
1071 1.0f);
1075 template <typename PaintBackendData>
1076 void nsNativeBasicTheme::PaintTextField(PaintBackendData& aPaintData,
1077 const LayoutDeviceRect& aRect,
1078 const EventStates& aState,
1079 UseSystemColors aUseSystemColors,
1080 DPIRatio aDpiRatio) {
1081 auto [backgroundColor, borderColor] =
1082 ComputeTextfieldColors(aState, aUseSystemColors);
1084 const CSSCoord radius = 2.0f;
1086 PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
1087 kTextFieldBorderWidth, radius, aDpiRatio);
1089 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1090 PaintRoundedFocusRect(aPaintData, aRect, aUseSystemColors, aDpiRatio,
1091 radius + kTextFieldBorderWidth,
1092 -kTextFieldBorderWidth);
1096 template <typename PaintBackendData>
1097 void nsNativeBasicTheme::PaintListbox(PaintBackendData& aPaintData,
1098 const LayoutDeviceRect& aRect,
1099 const EventStates& aState,
1100 UseSystemColors aUseSystemColors,
1101 DPIRatio aDpiRatio) {
1102 const CSSCoord radius = 2.0f;
1103 auto [backgroundColor, borderColor] =
1104 ComputeTextfieldColors(aState, aUseSystemColors);
1106 PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
1107 kMenulistBorderWidth, radius, aDpiRatio);
1109 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1110 PaintRoundedFocusRect(aPaintData, aRect, aUseSystemColors, aDpiRatio,
1111 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
1115 template <typename PaintBackendData>
1116 void nsNativeBasicTheme::PaintMenulist(PaintBackendData& aDrawTarget,
1117 const LayoutDeviceRect& aRect,
1118 const EventStates& aState,
1119 UseSystemColors aUseSystemColors,
1120 DPIRatio aDpiRatio) {
1121 const CSSCoord radius = 4.0f;
1122 auto [backgroundColor, borderColor] =
1123 ComputeButtonColors(aState, aUseSystemColors);
1125 PaintRoundedRectWithRadius(aDrawTarget, aRect, backgroundColor, borderColor,
1126 kMenulistBorderWidth, radius, aDpiRatio);
1128 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1129 PaintRoundedFocusRect(aDrawTarget, aRect, aUseSystemColors, aDpiRatio,
1130 radius + kMenulistBorderWidth, -kMenulistBorderWidth);
1134 void nsNativeBasicTheme::PaintArrow(DrawTarget& aDrawTarget,
1135 const LayoutDeviceRect& aRect,
1136 const float aArrowPolygonX[],
1137 const float aArrowPolygonY[],
1138 const float aArrowPolygonSize,
1139 const int32_t aArrowNumPoints,
1140 const sRGBColor aFillColor) {
1141 const float scale = ScaleToFillRect(aRect, aArrowPolygonSize);
1143 auto center = aRect.Center().ToUnknownPoint();
1145 RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder();
1146 Point p =
1147 center + Point(aArrowPolygonX[0] * scale, aArrowPolygonY[0] * scale);
1148 builder->MoveTo(p);
1149 for (int32_t i = 1; i < aArrowNumPoints; i++) {
1150 p = center + Point(aArrowPolygonX[i] * scale, aArrowPolygonY[i] * scale);
1151 builder->LineTo(p);
1153 RefPtr<Path> path = builder->Finish();
1155 aDrawTarget.Fill(path, ColorPattern(ToDeviceColor(aFillColor)));
1158 void nsNativeBasicTheme::PaintMenulistArrowButton(
1159 nsIFrame* aFrame, DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
1160 const EventStates& aState, UseSystemColors aUseSystemColors) {
1161 const float kPolygonX[] = {-4.0f, -0.5f, 0.5f, 4.0f, 4.0f,
1162 3.0f, 0.0f, 0.0f, -3.0f, -4.0f};
1163 const float kPolygonY[] = {-1, 3.0f, 3.0f, -1.0f, -2.0f,
1164 -2.0f, 1.5f, 1.5f, -2.0f, -2.0f};
1166 const float kPolygonSize = kMinimumDropdownArrowButtonWidth;
1168 sRGBColor arrowColor =
1169 ComputeMenulistArrowButtonColor(aState, aUseSystemColors);
1170 PaintArrow(aDrawTarget, aRect, kPolygonX, kPolygonY, kPolygonSize,
1171 ArrayLength(kPolygonX), arrowColor);
1174 void nsNativeBasicTheme::PaintSpinnerButton(
1175 nsIFrame* aFrame, DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect,
1176 const EventStates& aState, StyleAppearance aAppearance,
1177 UseSystemColors aUseSystemColors, DPIRatio aDpiRatio) {
1178 auto [backgroundColor, borderColor] =
1179 ComputeButtonColors(aState, aUseSystemColors);
1181 aDrawTarget.FillRect(aRect.ToUnknownRect(),
1182 ColorPattern(ToDeviceColor(backgroundColor)));
1184 const float kPolygonX[] = {-3.5f, -0.5f, 0.5f, 3.5f, 3.5f,
1185 2.5f, 0.0f, 0.0f, -2.5f, -3.5f};
1186 float polygonY[] = {-1.5f, 1.5f, 1.5f, -1.5f, -2.5f,
1187 -2.5f, 0.0f, 0.0f, -2.5f, -2.5f};
1189 const float kPolygonSize = kMinimumSpinnerButtonHeight;
1190 if (aAppearance == StyleAppearance::SpinnerUpbutton) {
1191 for (auto& coord : polygonY) {
1192 coord = -coord;
1196 PaintArrow(aDrawTarget, aRect, kPolygonX, polygonY, kPolygonSize,
1197 ArrayLength(kPolygonX), borderColor);
1200 template <typename PaintBackendData>
1201 void nsNativeBasicTheme::PaintRange(nsIFrame* aFrame,
1202 PaintBackendData& aPaintData,
1203 const LayoutDeviceRect& aRect,
1204 const EventStates& aState,
1205 UseSystemColors aUseSystemColors,
1206 DPIRatio aDpiRatio, bool aHorizontal) {
1207 nsRangeFrame* rangeFrame = do_QueryFrame(aFrame);
1208 if (!rangeFrame) {
1209 return;
1212 double progress = rangeFrame->GetValueAsFractionOfRange();
1213 auto rect = aRect;
1214 LayoutDeviceRect thumbRect(0, 0, kMinimumRangeThumbSize * aDpiRatio,
1215 kMinimumRangeThumbSize * aDpiRatio);
1216 LayoutDeviceRect progressClipRect(aRect);
1217 LayoutDeviceRect trackClipRect(aRect);
1218 const LayoutDeviceCoord verticalSize = kRangeHeight * aDpiRatio;
1219 if (aHorizontal) {
1220 rect.height = verticalSize;
1221 rect.y = aRect.y + (aRect.height - rect.height) / 2;
1222 thumbRect.y = aRect.y + (aRect.height - thumbRect.height) / 2;
1224 if (IsFrameRTL(aFrame)) {
1225 thumbRect.x =
1226 aRect.x + (aRect.width - thumbRect.width) * (1.0 - progress);
1227 float midPoint = thumbRect.Center().X();
1228 trackClipRect.SetBoxX(aRect.X(), midPoint);
1229 progressClipRect.SetBoxX(midPoint, aRect.XMost());
1230 } else {
1231 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) * progress;
1232 float midPoint = thumbRect.Center().X();
1233 progressClipRect.SetBoxX(aRect.X(), midPoint);
1234 trackClipRect.SetBoxX(midPoint, aRect.XMost());
1236 } else {
1237 rect.width = verticalSize;
1238 rect.x = aRect.x + (aRect.width - rect.width) / 2;
1239 thumbRect.x = aRect.x + (aRect.width - thumbRect.width) / 2;
1241 thumbRect.y =
1242 aRect.y + (aRect.height - thumbRect.height) * (1.0 - progress);
1243 float midPoint = thumbRect.Center().Y();
1244 trackClipRect.SetBoxY(aRect.Y(), midPoint);
1245 progressClipRect.SetBoxY(midPoint, aRect.YMost());
1248 const CSSCoord borderWidth = 1.0f;
1249 const CSSCoord radius = 2.0f;
1251 auto [progressColor, progressBorderColor] =
1252 ComputeRangeProgressColors(aState, aUseSystemColors);
1253 auto [trackColor, trackBorderColor] =
1254 ComputeRangeTrackColors(aState, aUseSystemColors);
1256 PaintRoundedRectWithRadius(aPaintData, rect, progressClipRect, progressColor,
1257 progressBorderColor, borderWidth, radius,
1258 aDpiRatio);
1260 PaintRoundedRectWithRadius(aPaintData, rect, trackClipRect, trackColor,
1261 trackBorderColor, borderWidth, radius, aDpiRatio);
1263 if (!aState.HasState(NS_EVENT_STATE_DISABLED)) {
1264 // Ensure the shadow doesn't expand outside of our overflow rect declared in
1265 // GetWidgetOverflow().
1266 auto overflowRect = aRect;
1267 overflowRect.Inflate(CSSCoord(6.0f) * aDpiRatio);
1268 // Thumb shadow
1269 PaintCircleShadow(aPaintData, thumbRect, overflowRect, 0.3f,
1270 CSSPoint(0.0f, 2.0f), 2.0f, aDpiRatio);
1273 // Draw the thumb on top.
1274 const CSSCoord thumbBorderWidth = 2.0f;
1275 auto [thumbColor, thumbBorderColor] =
1276 ComputeRangeThumbColors(aState, aUseSystemColors);
1278 PaintStrokedCircle(aPaintData, thumbRect, thumbColor, thumbBorderColor,
1279 thumbBorderWidth, aDpiRatio);
1281 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1282 PaintRoundedFocusRect(aPaintData, aRect, aUseSystemColors, aDpiRatio,
1283 radius, 1.0f);
1287 template <typename PaintBackendData>
1288 void nsNativeBasicTheme::PaintProgress(nsIFrame* aFrame,
1289 PaintBackendData& aPaintData,
1290 const LayoutDeviceRect& aRect,
1291 const EventStates& aState,
1292 UseSystemColors aUseSystemColors,
1293 DPIRatio aDpiRatio, bool aIsMeter) {
1294 const CSSCoord borderWidth = 1.0f;
1295 const CSSCoord radius = aIsMeter ? 5.0f : 2.0f;
1297 LayoutDeviceRect rect(aRect);
1298 const LayoutDeviceCoord thickness =
1299 (aIsMeter ? kMeterHeight : kProgressbarHeight) * aDpiRatio;
1301 const bool isHorizontal = !nsNativeTheme::IsVerticalProgress(aFrame);
1302 if (isHorizontal) {
1303 // Center it vertically.
1304 rect.y += (rect.height - thickness) / 2;
1305 rect.height = thickness;
1306 } else {
1307 // Center it horizontally.
1308 rect.x += (rect.width - thickness) / 2;
1309 rect.width = thickness;
1313 // Paint the track, unclipped.
1314 auto [backgroundColor, borderColor] =
1315 ComputeProgressTrackColors(aUseSystemColors);
1316 PaintRoundedRectWithRadius(aPaintData, rect, rect, backgroundColor,
1317 borderColor, borderWidth, radius, aDpiRatio);
1320 // Now paint the chunk, clipped as needed.
1321 LayoutDeviceRect clipRect = rect;
1322 if (aState.HasState(NS_EVENT_STATE_INDETERMINATE)) {
1323 // For indeterminate progress, we paint an animated chunk of 1/3 of the
1324 // progress size.
1326 // Animation speed and math borrowed from GTK.
1327 const LayoutDeviceCoord size = isHorizontal ? rect.width : rect.height;
1328 const LayoutDeviceCoord barSize = size * 0.3333f;
1329 const LayoutDeviceCoord travel = 2.0f * (size - barSize);
1331 // Period equals to travel / pixelsPerMillisecond where pixelsPerMillisecond
1332 // equals progressSize / 1000.0. This is equivalent to 1600.
1333 const unsigned kPeriod = 1600;
1335 const int t = PR_IntervalToMilliseconds(PR_IntervalNow()) % kPeriod;
1336 const LayoutDeviceCoord dx = travel * float(t) / float(kPeriod);
1337 if (isHorizontal) {
1338 rect.width = barSize;
1339 rect.x += (dx < travel * .5f) ? dx : travel - dx;
1340 } else {
1341 rect.height = barSize;
1342 rect.y += (dx < travel * .5f) ? dx : travel - dx;
1344 clipRect = rect;
1345 // Queue the next frame if needed.
1346 if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 60)) {
1347 NS_WARNING("Couldn't refresh indeterminate <progress>");
1349 } else {
1350 // This is the progress chunk, clip it to the right amount.
1351 double position = [&] {
1352 if (aIsMeter) {
1353 auto* meter = dom::HTMLMeterElement::FromNode(aFrame->GetContent());
1354 if (!meter) {
1355 return 0.0;
1357 return meter->Value() / meter->Max();
1359 auto* progress = dom::HTMLProgressElement::FromNode(aFrame->GetContent());
1360 if (!progress) {
1361 return 0.0;
1363 return progress->Value() / progress->Max();
1364 }();
1365 if (isHorizontal) {
1366 double clipWidth = rect.width * position;
1367 clipRect.width = clipWidth;
1368 if (IsFrameRTL(aFrame)) {
1369 clipRect.x += rect.width - clipWidth;
1371 } else {
1372 double clipHeight = rect.height * position;
1373 clipRect.height = clipHeight;
1374 clipRect.y += rect.height - clipHeight;
1378 auto [backgroundColor, borderColor] =
1379 aIsMeter ? ComputeMeterchunkColors(aState, aUseSystemColors)
1380 : ComputeProgressColors(aUseSystemColors);
1381 PaintRoundedRectWithRadius(aPaintData, rect, clipRect, backgroundColor,
1382 borderColor, borderWidth, radius, aDpiRatio);
1385 template <typename PaintBackendData>
1386 void nsNativeBasicTheme::PaintButton(nsIFrame* aFrame,
1387 PaintBackendData& aPaintData,
1388 const LayoutDeviceRect& aRect,
1389 const EventStates& aState,
1390 UseSystemColors aUseSystemColors,
1391 DPIRatio aDpiRatio) {
1392 const CSSCoord radius = 4.0f;
1393 auto [backgroundColor, borderColor] =
1394 ComputeButtonColors(aState, aUseSystemColors, aFrame);
1396 PaintRoundedRectWithRadius(aPaintData, aRect, backgroundColor, borderColor,
1397 kButtonBorderWidth, radius, aDpiRatio);
1399 if (aState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1400 PaintRoundedFocusRect(aPaintData, aRect, aUseSystemColors, aDpiRatio,
1401 radius + kButtonBorderWidth, -kButtonBorderWidth);
1405 template <typename PaintBackendData>
1406 bool nsNativeBasicTheme::DoPaintDefaultScrollbarThumb(
1407 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
1408 bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
1409 const EventStates& aElementState, const EventStates& aDocumentState,
1410 UseSystemColors aUseSystemColors, DPIRatio aDpiRatio) {
1411 sRGBColor thumbColor = ComputeScrollbarThumbColor(
1412 aFrame, aStyle, aElementState, aDocumentState, aUseSystemColors);
1413 FillRect(aPaintData, aRect, thumbColor);
1414 return true;
1417 bool nsNativeBasicTheme::PaintScrollbarThumb(
1418 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
1419 nsIFrame* aFrame, const ComputedStyle& aStyle,
1420 const EventStates& aElementState, const EventStates& aDocumentState,
1421 UseSystemColors aUseSystemColors, DPIRatio aDpiRatio) {
1422 return DoPaintDefaultScrollbarThumb(aDrawTarget, aRect, aHorizontal, aFrame,
1423 aStyle, aElementState, aDocumentState,
1424 aUseSystemColors, aDpiRatio);
1427 bool nsNativeBasicTheme::PaintScrollbarThumb(
1428 WebRenderBackendData& aWrData, const LayoutDeviceRect& aRect,
1429 bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
1430 const EventStates& aElementState, const EventStates& aDocumentState,
1431 UseSystemColors aUseSystemColors, DPIRatio aDpiRatio) {
1432 return DoPaintDefaultScrollbarThumb(aWrData, aRect, aHorizontal, aFrame,
1433 aStyle, aElementState, aDocumentState,
1434 aUseSystemColors, aDpiRatio);
1437 template <typename PaintBackendData>
1438 bool nsNativeBasicTheme::DoPaintDefaultScrollbar(
1439 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
1440 bool aHorizontal, nsIFrame* aFrame, const ComputedStyle& aStyle,
1441 const EventStates& aDocumentState, UseSystemColors aUseSystemColors,
1442 DPIRatio aDpiRatio) {
1443 auto scrollbarColor =
1444 ComputeScrollbarColor(aFrame, aStyle, aDocumentState, aUseSystemColors);
1445 FillRect(aPaintData, aRect, scrollbarColor);
1446 return true;
1449 bool nsNativeBasicTheme::PaintScrollbar(DrawTarget& aDrawTarget,
1450 const LayoutDeviceRect& aRect,
1451 bool aHorizontal, nsIFrame* aFrame,
1452 const ComputedStyle& aStyle,
1453 const EventStates& aDocumentState,
1454 UseSystemColors aUseSystemColors,
1455 DPIRatio aDpiRatio) {
1456 return DoPaintDefaultScrollbar(aDrawTarget, aRect, aHorizontal, aFrame,
1457 aStyle, aDocumentState, aUseSystemColors,
1458 aDpiRatio);
1461 bool nsNativeBasicTheme::PaintScrollbar(WebRenderBackendData& aWrData,
1462 const LayoutDeviceRect& aRect,
1463 bool aHorizontal, nsIFrame* aFrame,
1464 const ComputedStyle& aStyle,
1465 const EventStates& aDocumentState,
1466 UseSystemColors aUseSystemColors,
1467 DPIRatio aDpiRatio) {
1468 return DoPaintDefaultScrollbar(aWrData, aRect, aHorizontal, aFrame, aStyle,
1469 aDocumentState, aUseSystemColors, aDpiRatio);
1472 template <typename PaintBackendData>
1473 bool nsNativeBasicTheme::DoPaintDefaultScrollCorner(
1474 PaintBackendData& aPaintData, const LayoutDeviceRect& aRect,
1475 nsIFrame* aFrame, const ComputedStyle& aStyle,
1476 const EventStates& aDocumentState, UseSystemColors aUseSystemColors,
1477 DPIRatio aDpiRatio) {
1478 auto scrollbarColor =
1479 ComputeScrollbarColor(aFrame, aStyle, aDocumentState, aUseSystemColors);
1480 FillRect(aPaintData, aRect, scrollbarColor);
1481 return true;
1484 bool nsNativeBasicTheme::PaintScrollCorner(
1485 DrawTarget& aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame,
1486 const ComputedStyle& aStyle, const EventStates& aDocumentState,
1487 UseSystemColors aUseSystemColors, DPIRatio aDpiRatio) {
1488 return DoPaintDefaultScrollCorner(aDrawTarget, aRect, aFrame, aStyle,
1489 aDocumentState, aUseSystemColors,
1490 aDpiRatio);
1493 bool nsNativeBasicTheme::PaintScrollCorner(WebRenderBackendData& aWrData,
1494 const LayoutDeviceRect& aRect,
1495 nsIFrame* aFrame,
1496 const ComputedStyle& aStyle,
1497 const EventStates& aDocumentState,
1498 UseSystemColors aUseSystemColors,
1499 DPIRatio aDpiRatio) {
1500 return DoPaintDefaultScrollCorner(aWrData, aRect, aFrame, aStyle,
1501 aDocumentState, aUseSystemColors,
1502 aDpiRatio);
1505 void nsNativeBasicTheme::PaintScrollbarButton(
1506 DrawTarget& aDrawTarget, StyleAppearance aAppearance,
1507 const LayoutDeviceRect& aRect, nsIFrame* aFrame,
1508 const ComputedStyle& aStyle, const EventStates& aElementState,
1509 const EventStates& aDocumentState, UseSystemColors aUseSystemColors,
1510 DPIRatio aDpiRatio) {
1511 auto [buttonColor, arrowColor] =
1512 ComputeScrollbarButtonColors(aFrame, aAppearance, aStyle, aElementState,
1513 aDocumentState, aUseSystemColors);
1514 aDrawTarget.FillRect(aRect.ToUnknownRect(),
1515 ColorPattern(ToDeviceColor(buttonColor)));
1517 // Start with Up arrow.
1518 float arrowPolygonX[] = {-4.0f, 0.0f, 4.0f, 4.0f, 0.0f, -4.0f};
1519 float arrowPolygonY[] = {0.0f, -4.0f, 0.0f, 3.0f, -1.0f, 3.0f};
1521 const float kPolygonSize = kMinimumScrollbarSize;
1523 const int32_t arrowNumPoints = ArrayLength(arrowPolygonX);
1524 switch (aAppearance) {
1525 case StyleAppearance::ScrollbarbuttonUp:
1526 break;
1527 case StyleAppearance::ScrollbarbuttonDown:
1528 for (int32_t i = 0; i < arrowNumPoints; i++) {
1529 arrowPolygonY[i] *= -1;
1531 break;
1532 case StyleAppearance::ScrollbarbuttonLeft:
1533 for (int32_t i = 0; i < arrowNumPoints; i++) {
1534 int32_t temp = arrowPolygonX[i];
1535 arrowPolygonX[i] = arrowPolygonY[i];
1536 arrowPolygonY[i] = temp;
1538 break;
1539 case StyleAppearance::ScrollbarbuttonRight:
1540 for (int32_t i = 0; i < arrowNumPoints; i++) {
1541 int32_t temp = arrowPolygonX[i];
1542 arrowPolygonX[i] = arrowPolygonY[i] * -1;
1543 arrowPolygonY[i] = temp;
1545 break;
1546 default:
1547 return;
1549 PaintArrow(aDrawTarget, aRect, arrowPolygonX, arrowPolygonY, kPolygonSize,
1550 arrowNumPoints, arrowColor);
1553 NS_IMETHODIMP
1554 nsNativeBasicTheme::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
1555 StyleAppearance aAppearance,
1556 const nsRect& aRect,
1557 const nsRect& /* aDirtyRect */) {
1558 if (!DoDrawWidgetBackground(*aContext->GetDrawTarget(), aFrame, aAppearance,
1559 aRect)) {
1560 return NS_ERROR_NOT_IMPLEMENTED;
1562 return NS_OK;
1565 bool nsNativeBasicTheme::CreateWebRenderCommandsForWidget(
1566 mozilla::wr::DisplayListBuilder& aBuilder,
1567 mozilla::wr::IpcResourceUpdateQueue& aResources,
1568 const mozilla::layers::StackingContextHelper& aSc,
1569 mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
1570 StyleAppearance aAppearance, const nsRect& aRect) {
1571 if (!StaticPrefs::widget_non_native_theme_webrender()) {
1572 return false;
1574 WebRenderBackendData data{aBuilder, aResources, aSc, aManager};
1575 return DoDrawWidgetBackground(data, aFrame, aAppearance, aRect);
1578 static LayoutDeviceRect ToSnappedRect(const nsRect& aRect,
1579 nscoord aTwipsPerPixel, DrawTarget& aDt) {
1580 return LayoutDeviceRect::FromUnknownRect(
1581 NSRectToSnappedRect(aRect, aTwipsPerPixel, aDt));
1584 static LayoutDeviceRect ToSnappedRect(
1585 const nsRect& aRect, nscoord aTwipsPerPixel,
1586 nsNativeBasicTheme::WebRenderBackendData& aDt) {
1587 // TODO: Do we need to do any more snapping here?
1588 return LayoutDeviceRect::FromAppUnits(aRect, aTwipsPerPixel);
1591 auto nsNativeBasicTheme::ShouldUseSystemColors(const dom::Document& aDoc)
1592 -> UseSystemColors {
1593 // TODO: Do we really want to use system colors even when the page can
1594 // override the high contrast theme? (mUseDocumentColors = true?).
1595 return UseSystemColors(
1596 PreferenceSheet::PrefsFor(aDoc).NonNativeThemeShouldUseSystemColors());
1599 template <typename PaintBackendData>
1600 bool nsNativeBasicTheme::DoDrawWidgetBackground(PaintBackendData& aPaintData,
1601 nsIFrame* aFrame,
1602 StyleAppearance aAppearance,
1603 const nsRect& aRect) {
1604 static_assert(std::is_same_v<PaintBackendData, DrawTarget> ||
1605 std::is_same_v<PaintBackendData, WebRenderBackendData>);
1607 const nsPresContext* pc = aFrame->PresContext();
1608 const nscoord twipsPerPixel = pc->AppUnitsPerDevPixel();
1609 const auto devPxRect = ToSnappedRect(aRect, twipsPerPixel, aPaintData);
1611 const EventStates docState = pc->Document()->GetDocumentState();
1612 const auto useSystemColors = ShouldUseSystemColors(*pc->Document());
1613 EventStates eventState = GetContentState(aFrame, aAppearance);
1614 if (aAppearance == StyleAppearance::MozMenulistArrowButton) {
1615 bool isHTML = IsHTMLContent(aFrame);
1616 nsIFrame* parentFrame = aFrame->GetParent();
1617 bool isMenulist = !isHTML && parentFrame->IsMenuFrame();
1618 // HTML select and XUL menulist dropdown buttons get state from the
1619 // parent.
1620 if (isHTML || isMenulist) {
1621 aFrame = parentFrame;
1622 eventState = GetContentState(parentFrame, aAppearance);
1626 // Hack to avoid skia fuzziness: Add a dummy clip if the widget doesn't
1627 // overflow devPxRect.
1628 Maybe<AutoClipRect> maybeClipRect;
1629 if constexpr (std::is_same_v<PaintBackendData, DrawTarget>) {
1630 if (aAppearance != StyleAppearance::FocusOutline &&
1631 aAppearance != StyleAppearance::Range &&
1632 !eventState.HasState(NS_EVENT_STATE_FOCUSRING)) {
1633 maybeClipRect.emplace(aPaintData, devPxRect);
1637 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1639 switch (aAppearance) {
1640 case StyleAppearance::Radio: {
1641 auto rect = CheckBoxRadioRect(devPxRect);
1642 PaintRadioControl(aPaintData, rect, eventState, useSystemColors,
1643 dpiRatio);
1644 break;
1646 case StyleAppearance::Checkbox: {
1647 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1648 // TODO: Need to figure out how to best draw this using WR.
1649 return false;
1650 } else {
1651 auto rect = CheckBoxRadioRect(devPxRect);
1652 PaintCheckboxControl(aPaintData, rect, eventState, useSystemColors,
1653 dpiRatio);
1655 break;
1657 case StyleAppearance::Textarea:
1658 case StyleAppearance::Textfield:
1659 case StyleAppearance::NumberInput:
1660 PaintTextField(aPaintData, devPxRect, eventState, useSystemColors,
1661 dpiRatio);
1662 break;
1663 case StyleAppearance::Listbox:
1664 PaintListbox(aPaintData, devPxRect, eventState, useSystemColors,
1665 dpiRatio);
1666 break;
1667 case StyleAppearance::MenulistButton:
1668 case StyleAppearance::Menulist:
1669 PaintMenulist(aPaintData, devPxRect, eventState, useSystemColors,
1670 dpiRatio);
1671 break;
1672 case StyleAppearance::MozMenulistArrowButton:
1673 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1674 // TODO: Need to figure out how to best draw this using WR.
1675 return false;
1676 } else {
1677 PaintMenulistArrowButton(aFrame, aPaintData, devPxRect, eventState,
1678 useSystemColors);
1680 break;
1681 case StyleAppearance::SpinnerUpbutton:
1682 case StyleAppearance::SpinnerDownbutton:
1683 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1684 // TODO: Need to figure out how to best draw this using WR.
1685 return false;
1686 } else {
1687 PaintSpinnerButton(aFrame, aPaintData, devPxRect, eventState,
1688 aAppearance, useSystemColors, dpiRatio);
1690 break;
1691 case StyleAppearance::Range:
1692 PaintRange(aFrame, aPaintData, devPxRect, eventState, useSystemColors,
1693 dpiRatio, IsRangeHorizontal(aFrame));
1694 break;
1695 case StyleAppearance::RangeThumb:
1696 // Painted as part of StyleAppearance::Range.
1697 break;
1698 case StyleAppearance::ProgressBar:
1699 PaintProgress(aFrame, aPaintData, devPxRect, eventState, useSystemColors,
1700 dpiRatio,
1701 /* aIsMeter = */ false);
1702 break;
1703 case StyleAppearance::Progresschunk:
1704 /* Painted as part of the progress bar */
1705 break;
1706 case StyleAppearance::Meter:
1707 PaintProgress(aFrame, aPaintData, devPxRect, eventState, useSystemColors,
1708 dpiRatio, /* aIsMeter = */ true);
1709 break;
1710 case StyleAppearance::Meterchunk:
1711 /* Painted as part of the meter bar */
1712 break;
1713 case StyleAppearance::ScrollbarthumbHorizontal:
1714 case StyleAppearance::ScrollbarthumbVertical: {
1715 bool isHorizontal =
1716 aAppearance == StyleAppearance::ScrollbarthumbHorizontal;
1717 return PaintScrollbarThumb(aPaintData, devPxRect, isHorizontal, aFrame,
1718 *nsLayoutUtils::StyleForScrollbar(aFrame),
1719 eventState, docState, useSystemColors,
1720 dpiRatio);
1722 case StyleAppearance::ScrollbartrackHorizontal:
1723 case StyleAppearance::ScrollbartrackVertical: {
1724 bool isHorizontal =
1725 aAppearance == StyleAppearance::ScrollbartrackHorizontal;
1726 return PaintScrollbarTrack(aPaintData, devPxRect, isHorizontal, aFrame,
1727 *nsLayoutUtils::StyleForScrollbar(aFrame),
1728 docState, useSystemColors, dpiRatio);
1730 case StyleAppearance::ScrollbarHorizontal:
1731 case StyleAppearance::ScrollbarVertical: {
1732 bool isHorizontal = aAppearance == StyleAppearance::ScrollbarHorizontal;
1733 return PaintScrollbar(aPaintData, devPxRect, isHorizontal, aFrame,
1734 *nsLayoutUtils::StyleForScrollbar(aFrame), docState,
1735 useSystemColors, dpiRatio);
1737 case StyleAppearance::Scrollcorner:
1738 return PaintScrollCorner(aPaintData, devPxRect, aFrame,
1739 *nsLayoutUtils::StyleForScrollbar(aFrame),
1740 docState, useSystemColors, dpiRatio);
1741 case StyleAppearance::ScrollbarbuttonUp:
1742 case StyleAppearance::ScrollbarbuttonDown:
1743 case StyleAppearance::ScrollbarbuttonLeft:
1744 case StyleAppearance::ScrollbarbuttonRight:
1745 // For scrollbar-width:thin, we don't display the buttons.
1746 if (!IsScrollbarWidthThin(aFrame)) {
1747 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1748 // TODO: Need to figure out how to best draw this using WR.
1749 return false;
1750 } else {
1751 PaintScrollbarButton(aPaintData, aAppearance, devPxRect, aFrame,
1752 *nsLayoutUtils::StyleForScrollbar(aFrame),
1753 eventState, docState, useSystemColors, dpiRatio);
1756 break;
1757 case StyleAppearance::Button:
1758 PaintButton(aFrame, aPaintData, devPxRect, eventState, useSystemColors,
1759 dpiRatio);
1760 break;
1761 case StyleAppearance::FocusOutline:
1762 PaintAutoStyleOutline(aFrame, aPaintData, devPxRect, useSystemColors,
1763 dpiRatio);
1764 break;
1765 default:
1766 // Various appearance values are used for XUL elements. Normally these
1767 // will not be available in content documents (and thus in the content
1768 // processes where the native basic theme can be used), but tests are
1769 // run with the remote XUL pref enabled and so we can get in here. So
1770 // we just return an error rather than assert.
1771 return false;
1774 return true;
1777 template <typename PaintBackendData>
1778 void nsNativeBasicTheme::PaintAutoStyleOutline(nsIFrame* aFrame,
1779 PaintBackendData& aPaintData,
1780 const LayoutDeviceRect& aRect,
1781 UseSystemColors aUseSystemColors,
1782 DPIRatio aDpiRatio) {
1783 auto [innerColor, middleColor, outerColor] =
1784 ComputeFocusRectColors(aUseSystemColors);
1785 Unused << middleColor;
1786 Unused << outerColor;
1788 LayoutDeviceRect rect(aRect);
1789 auto width =
1790 LayoutDeviceCoord(SnapBorderWidth(kInnerFocusOutlineWidth, aDpiRatio));
1791 rect.Inflate(width);
1793 nscoord cssRadii[8];
1794 if (!aFrame->GetBorderRadii(cssRadii)) {
1795 return PaintRoundedRectWithRadius(aPaintData, rect, sRGBColor::White(0.0f),
1796 innerColor, kInnerFocusOutlineWidth,
1797 /* aRadius = */ 0.0f, aDpiRatio);
1800 nsPresContext* pc = aFrame->PresContext();
1801 const nscoord offset = aFrame->StyleOutline()->mOutlineOffset.ToAppUnits();
1802 const Float devPixelOffset = pc->AppUnitsToFloatDevPixels(offset);
1804 RectCornerRadii innerRadii;
1805 nsCSSRendering::ComputePixelRadii(cssRadii, pc->AppUnitsPerDevPixel(),
1806 &innerRadii);
1808 const auto borderColor = ToDeviceColor(innerColor);
1809 // NOTE(emilio): This doesn't use PaintRoundedRectWithRadius because we need
1810 // to support arbitrary radii.
1811 RectCornerRadii outerRadii;
1812 if constexpr (std::is_same_v<PaintBackendData, WebRenderBackendData>) {
1813 const Float widths[4] = {devPixelOffset, devPixelOffset, devPixelOffset,
1814 devPixelOffset};
1815 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1817 const auto dest = wr::ToLayoutRect(rect);
1818 const auto side = wr::ToBorderSide(borderColor, StyleBorderStyle::Solid);
1819 const wr::BorderSide sides[4] = {side, side, side, side};
1820 const bool kBackfaceIsVisible = true;
1821 const auto wrWidths = wr::ToBorderWidths(width, width, width, width);
1822 const auto wrRadius = wr::ToBorderRadius(outerRadii);
1823 aPaintData.mBuilder.PushBorder(dest, dest, kBackfaceIsVisible, wrWidths,
1824 {sides, 4}, wrRadius);
1825 } else {
1826 const LayoutDeviceCoord halfWidth = width * 0.5f;
1827 rect.Deflate(halfWidth);
1828 const Float widths[4] = {
1829 halfWidth + devPixelOffset, halfWidth + devPixelOffset,
1830 halfWidth + devPixelOffset, halfWidth + devPixelOffset};
1831 nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outerRadii);
1832 RefPtr<Path> path =
1833 MakePathForRoundedRect(aPaintData, rect.ToUnknownRect(), outerRadii);
1834 aPaintData.Stroke(path, ColorPattern(borderColor), StrokeOptions(width));
1838 LayoutDeviceIntMargin nsNativeBasicTheme::GetWidgetBorder(
1839 nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
1840 switch (aAppearance) {
1841 case StyleAppearance::Textfield:
1842 case StyleAppearance::Textarea:
1843 case StyleAppearance::NumberInput:
1844 case StyleAppearance::Listbox:
1845 case StyleAppearance::Menulist:
1846 case StyleAppearance::MenulistButton:
1847 case StyleAppearance::Button:
1848 // Return the border size from the UA sheet, even though what we paint
1849 // doesn't actually match that. We know this is the UA sheet border
1850 // because we disable native theming when different border widths are
1851 // specified by authors, see nsNativeBasicTheme::IsWidgetStyled.
1853 // The Rounded() bit is technically redundant, but needed to appease the
1854 // type system, we should always end up with full device pixels due to
1855 // round_border_to_device_pixels at style time.
1856 return LayoutDeviceIntMargin::FromAppUnits(
1857 aFrame->StyleBorder()->GetComputedBorder(),
1858 aFrame->PresContext()->AppUnitsPerDevPixel())
1859 .Rounded();
1860 case StyleAppearance::Checkbox:
1861 case StyleAppearance::Radio: {
1862 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1863 LayoutDeviceIntCoord w =
1864 SnapBorderWidth(kCheckboxRadioBorderWidth, dpiRatio);
1865 return LayoutDeviceIntMargin(w, w, w, w);
1867 default:
1868 return LayoutDeviceIntMargin();
1872 bool nsNativeBasicTheme::GetWidgetPadding(nsDeviceContext* aContext,
1873 nsIFrame* aFrame,
1874 StyleAppearance aAppearance,
1875 LayoutDeviceIntMargin* aResult) {
1876 switch (aAppearance) {
1877 // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
1878 // and have a meaningful baseline, so they can't have
1879 // author-specified padding.
1880 case StyleAppearance::Radio:
1881 case StyleAppearance::Checkbox:
1882 aResult->SizeTo(0, 0, 0, 0);
1883 return true;
1884 default:
1885 break;
1887 return false;
1890 bool nsNativeBasicTheme::GetWidgetOverflow(nsDeviceContext* aContext,
1891 nsIFrame* aFrame,
1892 StyleAppearance aAppearance,
1893 nsRect* aOverflowRect) {
1894 nsIntMargin overflow;
1895 switch (aAppearance) {
1896 case StyleAppearance::FocusOutline:
1897 // 2px * one segment
1898 overflow.SizeTo(2, 2, 2, 2);
1899 break;
1900 case StyleAppearance::Radio:
1901 case StyleAppearance::Checkbox:
1902 case StyleAppearance::Range:
1903 // 2px for each outline segment, plus 1px separation, plus we paint with a
1904 // 1px extra offset, so 6px.
1905 overflow.SizeTo(6, 6, 6, 6);
1906 break;
1907 case StyleAppearance::Textarea:
1908 case StyleAppearance::Textfield:
1909 case StyleAppearance::NumberInput:
1910 case StyleAppearance::Listbox:
1911 case StyleAppearance::MenulistButton:
1912 case StyleAppearance::Menulist:
1913 case StyleAppearance::Button:
1914 // 2px for each segment, plus 1px separation, but we paint 1px inside
1915 // the border area so 4px overflow.
1916 overflow.SizeTo(4, 4, 4, 4);
1917 break;
1918 default:
1919 return false;
1922 // TODO: This should convert from device pixels to app units, not from CSS
1923 // pixels. And it should take the dpi ratio into account.
1924 // Using CSS pixels can cause the overflow to be too small if the page is
1925 // zoomed out.
1926 aOverflowRect->Inflate(nsMargin(CSSPixel::ToAppUnits(overflow.top),
1927 CSSPixel::ToAppUnits(overflow.right),
1928 CSSPixel::ToAppUnits(overflow.bottom),
1929 CSSPixel::ToAppUnits(overflow.left)));
1931 return true;
1934 auto nsNativeBasicTheme::GetScrollbarSizes(nsPresContext* aPresContext,
1935 StyleScrollbarWidth aWidth, Overlay)
1936 -> ScrollbarSizes {
1937 CSSCoord size = aWidth == StyleScrollbarWidth::Thin
1938 ? kMinimumThinScrollbarSize
1939 : kMinimumScrollbarSize;
1940 LayoutDeviceIntCoord s =
1941 (size * GetDPIRatioForScrollbarPart(aPresContext)).Rounded();
1942 return {s, s};
1945 nscoord nsNativeBasicTheme::GetCheckboxRadioPrefSize() {
1946 return CSSPixel::ToAppUnits(kCheckboxRadioContentBoxSize);
1949 NS_IMETHODIMP
1950 nsNativeBasicTheme::GetMinimumWidgetSize(nsPresContext* aPresContext,
1951 nsIFrame* aFrame,
1952 StyleAppearance aAppearance,
1953 LayoutDeviceIntSize* aResult,
1954 bool* aIsOverridable) {
1955 DPIRatio dpiRatio = GetDPIRatio(aFrame, aAppearance);
1957 aResult->width = aResult->height = 0;
1958 *aIsOverridable = true;
1960 switch (aAppearance) {
1961 case StyleAppearance::Button:
1962 if (IsColorPickerButton(aFrame)) {
1963 aResult->height = (kMinimumColorPickerHeight * dpiRatio).Rounded();
1965 break;
1966 case StyleAppearance::RangeThumb:
1967 aResult->SizeTo((kMinimumRangeThumbSize * dpiRatio).Rounded(),
1968 (kMinimumRangeThumbSize * dpiRatio).Rounded());
1969 break;
1970 case StyleAppearance::MozMenulistArrowButton:
1971 aResult->width = (kMinimumDropdownArrowButtonWidth * dpiRatio).Rounded();
1972 break;
1973 case StyleAppearance::SpinnerUpbutton:
1974 case StyleAppearance::SpinnerDownbutton:
1975 aResult->width = (kMinimumSpinnerButtonWidth * dpiRatio).Rounded();
1976 aResult->height = (kMinimumSpinnerButtonHeight * dpiRatio).Rounded();
1977 break;
1978 case StyleAppearance::ScrollbarbuttonUp:
1979 case StyleAppearance::ScrollbarbuttonDown:
1980 case StyleAppearance::ScrollbarbuttonLeft:
1981 case StyleAppearance::ScrollbarbuttonRight:
1982 // For scrollbar-width:thin, we don't display the buttons.
1983 if (IsScrollbarWidthThin(aFrame)) {
1984 aResult->SizeTo(0, 0);
1985 break;
1987 [[fallthrough]];
1988 case StyleAppearance::ScrollbarthumbVertical:
1989 case StyleAppearance::ScrollbarthumbHorizontal: {
1990 auto* style = nsLayoutUtils::StyleForScrollbar(aFrame);
1991 auto width = style->StyleUIReset()->mScrollbarWidth;
1992 auto sizes = GetScrollbarSizes(aPresContext, width, Overlay::No);
1993 MOZ_ASSERT(sizes.mHorizontal == sizes.mVertical);
1994 // TODO: for short scrollbars it could be nice if the thumb could shrink
1995 // under this size.
1996 aResult->SizeTo(sizes.mHorizontal, sizes.mHorizontal);
1997 break;
1999 default:
2000 break;
2003 return NS_OK;
2006 nsITheme::Transparency nsNativeBasicTheme::GetWidgetTransparency(
2007 nsIFrame* aFrame, StyleAppearance aAppearance) {
2008 return eUnknownTransparency;
2011 NS_IMETHODIMP
2012 nsNativeBasicTheme::WidgetStateChanged(nsIFrame* aFrame,
2013 StyleAppearance aAppearance,
2014 nsAtom* aAttribute, bool* aShouldRepaint,
2015 const nsAttrValue* aOldValue) {
2016 if (!aAttribute) {
2017 // Hover/focus/active changed. Always repaint.
2018 *aShouldRepaint = true;
2019 } else {
2020 // Check the attribute to see if it's relevant.
2021 // disabled, checked, dlgtype, default, etc.
2022 *aShouldRepaint = false;
2023 if ((aAttribute == nsGkAtoms::disabled) ||
2024 (aAttribute == nsGkAtoms::checked) ||
2025 (aAttribute == nsGkAtoms::selected) ||
2026 (aAttribute == nsGkAtoms::visuallyselected) ||
2027 (aAttribute == nsGkAtoms::menuactive) ||
2028 (aAttribute == nsGkAtoms::sortDirection) ||
2029 (aAttribute == nsGkAtoms::focused) ||
2030 (aAttribute == nsGkAtoms::_default) ||
2031 (aAttribute == nsGkAtoms::open) || (aAttribute == nsGkAtoms::hover)) {
2032 *aShouldRepaint = true;
2036 return NS_OK;
2039 NS_IMETHODIMP
2040 nsNativeBasicTheme::ThemeChanged() { return NS_OK; }
2042 bool nsNativeBasicTheme::WidgetAppearanceDependsOnWindowFocus(
2043 StyleAppearance aAppearance) {
2044 return IsWidgetScrollbarPart(aAppearance);
2047 nsITheme::ThemeGeometryType nsNativeBasicTheme::ThemeGeometryTypeForWidget(
2048 nsIFrame* aFrame, StyleAppearance aAppearance) {
2049 return eThemeGeometryTypeUnknown;
2052 bool nsNativeBasicTheme::ThemeSupportsWidget(nsPresContext* aPresContext,
2053 nsIFrame* aFrame,
2054 StyleAppearance aAppearance) {
2055 switch (aAppearance) {
2056 case StyleAppearance::Radio:
2057 case StyleAppearance::Checkbox:
2058 case StyleAppearance::FocusOutline:
2059 case StyleAppearance::Textarea:
2060 case StyleAppearance::Textfield:
2061 case StyleAppearance::Range:
2062 case StyleAppearance::RangeThumb:
2063 case StyleAppearance::ProgressBar:
2064 case StyleAppearance::Progresschunk:
2065 case StyleAppearance::Meter:
2066 case StyleAppearance::Meterchunk:
2067 case StyleAppearance::ScrollbarbuttonUp:
2068 case StyleAppearance::ScrollbarbuttonDown:
2069 case StyleAppearance::ScrollbarbuttonLeft:
2070 case StyleAppearance::ScrollbarbuttonRight:
2071 case StyleAppearance::ScrollbarthumbHorizontal:
2072 case StyleAppearance::ScrollbarthumbVertical:
2073 case StyleAppearance::ScrollbartrackHorizontal:
2074 case StyleAppearance::ScrollbartrackVertical:
2075 case StyleAppearance::ScrollbarHorizontal:
2076 case StyleAppearance::ScrollbarVertical:
2077 case StyleAppearance::Scrollcorner:
2078 case StyleAppearance::Button:
2079 case StyleAppearance::Listbox:
2080 case StyleAppearance::Menulist:
2081 case StyleAppearance::MenulistButton:
2082 case StyleAppearance::NumberInput:
2083 case StyleAppearance::MozMenulistArrowButton:
2084 case StyleAppearance::SpinnerUpbutton:
2085 case StyleAppearance::SpinnerDownbutton:
2086 return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
2087 default:
2088 return false;
2092 bool nsNativeBasicTheme::WidgetIsContainer(StyleAppearance aAppearance) {
2093 switch (aAppearance) {
2094 case StyleAppearance::MozMenulistArrowButton:
2095 case StyleAppearance::Radio:
2096 case StyleAppearance::Checkbox:
2097 return false;
2098 default:
2099 return true;
2103 bool nsNativeBasicTheme::ThemeDrawsFocusForWidget(StyleAppearance aAppearance) {
2104 return true;
2107 bool nsNativeBasicTheme::ThemeNeedsComboboxDropmarker() { return true; }