Bug 1805294 [wpt PR 37463] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / widget / nsXPLookAndFeel.cpp
blobd4104e99ff069d5ba3ef4016c95edea4a44f4c00
1 /* -*- mode: C++; tab-width: 4; 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 "mozilla/ArrayUtils.h"
8 #include "nscore.h"
10 #include "nsXPLookAndFeel.h"
11 #include "nsLookAndFeel.h"
12 #include "HeadlessLookAndFeel.h"
13 #include "RemoteLookAndFeel.h"
14 #include "nsContentUtils.h"
15 #include "nsCRT.h"
16 #include "nsFont.h"
17 #include "nsIFrame.h"
18 #include "nsIXULRuntime.h"
19 #include "Theme.h"
20 #include "SurfaceCacheUtils.h"
21 #include "mozilla/dom/ContentParent.h"
22 #include "mozilla/dom/ContentChild.h"
23 #include "mozilla/Preferences.h"
24 #include "mozilla/Services.h"
25 #include "mozilla/ServoStyleSet.h"
26 #include "mozilla/ServoCSSParser.h"
27 #include "mozilla/StaticPrefs_browser.h"
28 #include "mozilla/StaticPrefs_editor.h"
29 #include "mozilla/StaticPrefs_layout.h"
30 #include "mozilla/StaticPrefs_ui.h"
31 #include "mozilla/StaticPrefs_widget.h"
32 #include "mozilla/dom/Document.h"
33 #include "mozilla/PreferenceSheet.h"
34 #include "mozilla/gfx/2D.h"
35 #include "mozilla/widget/WidgetMessageUtils.h"
36 #include "mozilla/RelativeLuminanceUtils.h"
37 #include "mozilla/Telemetry.h"
38 #include "mozilla/TelemetryScalarEnums.h"
40 #include "gfxPlatform.h"
41 #include "gfxFont.h"
43 #include "qcms.h"
45 #include <bitset>
47 using namespace mozilla;
49 using IntID = mozilla::LookAndFeel::IntID;
50 using FloatID = mozilla::LookAndFeel::FloatID;
51 using ColorID = mozilla::LookAndFeel::ColorID;
52 using FontID = mozilla::LookAndFeel::FontID;
54 template <typename Index, typename Value, Index kEnd>
55 class EnumeratedCache {
56 mozilla::EnumeratedArray<Index, kEnd, Value> mEntries;
57 std::bitset<size_t(kEnd)> mValidity;
59 public:
60 constexpr EnumeratedCache() = default;
62 bool IsValid(Index aIndex) const { return mValidity[size_t(aIndex)]; }
64 const Value* Get(Index aIndex) const {
65 return IsValid(aIndex) ? &mEntries[aIndex] : nullptr;
68 void Insert(Index aIndex, Value aValue) {
69 mValidity[size_t(aIndex)] = true;
70 mEntries[aIndex] = aValue;
73 void Remove(Index aIndex) {
74 mValidity[size_t(aIndex)] = false;
75 mEntries[aIndex] = Value();
78 void Clear() {
79 mValidity.reset();
80 for (auto& entry : mEntries) {
81 entry = Value();
86 using ColorCache = EnumeratedCache<ColorID, Maybe<nscolor>, ColorID::End>;
88 struct ColorCaches {
89 using UseStandins = LookAndFeel::UseStandins;
91 ColorCache mCaches[2][2];
93 constexpr ColorCaches() = default;
95 ColorCache& Get(ColorScheme aScheme, UseStandins aUseStandins) {
96 return mCaches[aScheme == ColorScheme::Dark]
97 [aUseStandins == UseStandins::Yes];
100 void Clear() {
101 for (auto& c : mCaches) {
102 for (auto& cache : c) {
103 cache.Clear();
109 static ColorCaches sColorCaches;
111 static EnumeratedCache<FloatID, Maybe<float>, FloatID::End> sFloatCache;
112 static EnumeratedCache<IntID, Maybe<int32_t>, IntID::End> sIntCache;
113 static EnumeratedCache<FontID, widget::LookAndFeelFont, FontID::End> sFontCache;
115 // To make one of these prefs toggleable from a reftest add a user
116 // pref in testing/profiles/reftest/user.js. For example, to make
117 // ui.useAccessibilityTheme toggleable, add:
119 // user_pref("ui.useAccessibilityTheme", 0);
121 // This needs to be of the same length and in the same order as
122 // LookAndFeel::IntID values.
123 static const char sIntPrefs[][45] = {
124 "ui.caretBlinkTime",
125 "ui.caretBlinkCount",
126 "ui.caretWidth",
127 "ui.caretVisibleWithSelection",
128 "ui.selectTextfieldsOnKeyFocus",
129 "ui.submenuDelay",
130 "ui.menusCanOverlapOSBar",
131 "ui.useOverlayScrollbars",
132 "ui.allowOverlayScrollbarsOverlap",
133 "ui.skipNavigatingDisabledMenuItem",
134 "ui.dragThresholdX",
135 "ui.dragThresholdY",
136 "ui.useAccessibilityTheme",
137 "ui.scrollArrowStyle",
138 "ui.scrollButtonLeftMouseButtonAction",
139 "ui.scrollButtonMiddleMouseButtonAction",
140 "ui.scrollButtonRightMouseButtonAction",
141 "ui.treeOpenDelay",
142 "ui.treeCloseDelay",
143 "ui.treeLazyScrollDelay",
144 "ui.treeScrollDelay",
145 "ui.treeScrollLinesMax",
146 "accessibility.tabfocus", // Weird one...
147 "ui.chosenMenuItemsShouldBlink",
148 "ui.windowsAccentColorInTitlebar",
149 "ui.windowsDefaultTheme",
150 "ui.dwmCompositor",
151 "ui.windowsClassic",
152 "ui.windowsGlass",
153 "ui.macGraphiteTheme",
154 "ui.macBigSurTheme",
155 "ui.macRTL",
156 "ui.alertNotificationOrigin",
157 "ui.scrollToClick",
158 "ui.IMERawInputUnderlineStyle",
159 "ui.IMESelectedRawTextUnderlineStyle",
160 "ui.IMEConvertedTextUnderlineStyle",
161 "ui.IMESelectedConvertedTextUnderlineStyle",
162 "ui.SpellCheckerUnderlineStyle",
163 "ui.menuBarDrag",
164 "ui.scrollbarButtonAutoRepeatBehavior",
165 "ui.tooltipDelay",
166 "ui.swipeAnimationEnabled",
167 "ui.scrollbarDisplayOnMouseMove",
168 "ui.scrollbarFadeBeginDelay",
169 "ui.scrollbarFadeDuration",
170 "ui.contextMenuOffsetVertical",
171 "ui.contextMenuOffsetHorizontal",
172 "ui.GtkCSDAvailable",
173 "ui.GtkCSDMinimizeButton",
174 "ui.GtkCSDMaximizeButton",
175 "ui.GtkCSDCloseButton",
176 "ui.GtkCSDMinimizeButtonPosition",
177 "ui.GtkCSDMaximizeButtonPosition",
178 "ui.GtkCSDCloseButtonPosition",
179 "ui.GtkCSDReversedPlacement",
180 "ui.systemUsesDarkTheme",
181 "ui.prefersReducedMotion",
182 "ui.primaryPointerCapabilities",
183 "ui.allPointerCapabilities",
184 "ui.systemVerticalScrollbarWidth",
185 "ui.systemHorizontalScrollbarHeight",
186 "ui.touchDeviceSupportPresent",
187 "ui.titlebarRadius",
188 "ui.GtkMenuRadius",
189 "ui.dynamicRange",
190 "ui.videoDynamicRange",
191 "ui.panelAnimations",
194 static_assert(ArrayLength(sIntPrefs) == size_t(LookAndFeel::IntID::End),
195 "Should have a pref for each int value");
197 // This array MUST be kept in the same order as the float id list in
198 // LookAndFeel.h
199 // clang-format off
200 static const char sFloatPrefs[][37] = {
201 "ui.IMEUnderlineRelativeSize",
202 "ui.SpellCheckerUnderlineRelativeSize",
203 "ui.caretAspectRatio",
204 "ui.textScaleFactor",
205 "ui.cursorScale",
207 // clang-format on
209 static_assert(ArrayLength(sFloatPrefs) == size_t(LookAndFeel::FloatID::End),
210 "Should have a pref for each float value");
212 // This array MUST be kept in the same order as the color list in
213 // specified/color.rs
214 static const char sColorPrefs[][41] = {
215 "ui.activeborder",
216 "ui.activecaption",
217 "ui.appworkspace",
218 "ui.background",
219 "ui.buttonface",
220 "ui.buttonhighlight",
221 "ui.buttonshadow",
222 "ui.buttontext",
223 "ui.buttonborder",
224 "ui.captiontext",
225 "ui.-moz-field",
226 "ui.-moz-disabledfield",
227 "ui.-moz-fieldtext",
228 "ui.mark",
229 "ui.marktext",
230 "ui.-moz-comboboxtext",
231 "ui.-moz-combobox",
232 "ui.graytext",
233 "ui.highlight",
234 "ui.highlighttext",
235 "ui.inactiveborder",
236 "ui.inactivecaption",
237 "ui.inactivecaptiontext",
238 "ui.infobackground",
239 "ui.infotext",
240 "ui.menu",
241 "ui.menutext",
242 "ui.scrollbar",
243 "ui.threeddarkshadow",
244 "ui.threedface",
245 "ui.threedhighlight",
246 "ui.threedlightshadow",
247 "ui.threedshadow",
248 "ui.window",
249 "ui.windowframe",
250 "ui.windowtext",
251 "ui.-moz-buttondefault",
252 "ui.-moz-default-color",
253 "ui.-moz-default-background-color",
254 "ui.-moz-dialog",
255 "ui.-moz-dialogtext",
256 "ui.-moz-dragtargetzone",
257 "ui.-moz-cellhighlight",
258 "ui.-moz_cellhighlighttext",
259 "ui.selecteditem",
260 "ui.selecteditemtext",
261 "ui.-moz-buttonhoverface",
262 "ui.-moz_buttonhovertext",
263 "ui.-moz_menuhover",
264 "ui.-moz_menuhoverdisabled",
265 "ui.-moz_menuhovertext",
266 "ui.-moz_menubartext",
267 "ui.-moz_menubarhovertext",
268 "ui.-moz_eventreerow",
269 "ui.-moz_oddtreerow",
270 "ui.-moz-buttonactivetext",
271 "ui.-moz-buttonactiveface",
272 "ui.-moz-buttondisabledface",
273 "ui.-moz_mac_chrome_active",
274 "ui.-moz_mac_chrome_inactive",
275 "ui.-moz-mac-defaultbuttontext",
276 "ui.-moz-mac-focusring",
277 "ui.-moz-mac-menuselect",
278 "ui.-moz-mac-menushadow",
279 "ui.-moz-mac-menutextdisable",
280 "ui.-moz-mac-menutextselect",
281 "ui.-moz_mac_disabledtoolbartext",
282 "ui.-moz-mac-secondaryhighlight",
283 "ui.-moz-mac-menupopup",
284 "ui.-moz-mac-menuitem",
285 "ui.-moz-mac-active-menuitem",
286 "ui.-moz-mac-source-list",
287 "ui.-moz-mac-source-list-selection",
288 "ui.-moz-mac-active-source-list-selection",
289 "ui.-moz-mac-tooltip",
290 "ui.accentcolor",
291 "ui.accentcolortext",
292 "ui.-moz-autofill-background",
293 "ui.-moz-win-mediatext",
294 "ui.-moz-win-communicationstext",
295 "ui.-moz-nativehyperlinktext",
296 "ui.-moz-nativevisitedhyperlinktext",
297 "ui.-moz-hyperlinktext",
298 "ui.-moz-activehyperlinktext",
299 "ui.-moz-visitedhyperlinktext",
300 "ui.-moz-colheadertext",
301 "ui.-moz-colheaderhovertext",
302 "ui.textSelectDisabledBackground",
303 "ui.textSelectAttentionBackground",
304 "ui.textSelectAttentionForeground",
305 "ui.textHighlightBackground",
306 "ui.textHighlightForeground",
307 "ui.IMERawInputBackground",
308 "ui.IMERawInputForeground",
309 "ui.IMERawInputUnderline",
310 "ui.IMESelectedRawTextBackground",
311 "ui.IMESelectedRawTextForeground",
312 "ui.IMESelectedRawTextUnderline",
313 "ui.IMEConvertedTextBackground",
314 "ui.IMEConvertedTextForeground",
315 "ui.IMEConvertedTextUnderline",
316 "ui.IMESelectedConvertedTextBackground",
317 "ui.IMESelectedConvertedTextForeground",
318 "ui.IMESelectedConvertedTextUnderline",
319 "ui.SpellCheckerUnderline",
320 "ui.themedScrollbar",
321 "ui.themedScrollbarInactive",
322 "ui.themedScrollbarThumb",
323 "ui.themedScrollbarThumbHover",
324 "ui.themedScrollbarThumbActive",
325 "ui.themedScrollbarThumbInactive",
328 static_assert(ArrayLength(sColorPrefs) == size_t(LookAndFeel::ColorID::End),
329 "Should have a pref for each color value");
331 // This array MUST be kept in the same order as the SystemFont enum.
332 static const char sFontPrefs[][41] = {
333 "ui.font.caption",
334 "ui.font.icon",
335 "ui.font.menu",
336 "ui.font.message-box",
337 "ui.font.small-caption",
338 "ui.font.status-bar",
339 "ui.font.-moz-pull-down-menu",
340 "ui.font.-moz-button",
341 "ui.font.-moz-list",
342 "ui.font.-moz-field",
345 static_assert(ArrayLength(sFontPrefs) == size_t(LookAndFeel::FontID::End),
346 "Should have a pref for each font value");
348 const char* nsXPLookAndFeel::GetColorPrefName(ColorID aId) {
349 return sColorPrefs[size_t(aId)];
352 bool nsXPLookAndFeel::sInitialized = false;
354 nsXPLookAndFeel* nsXPLookAndFeel::sInstance = nullptr;
355 bool nsXPLookAndFeel::sShutdown = false;
357 auto LookAndFeel::SystemZoomSettings() -> ZoomSettings {
358 ZoomSettings settings;
359 switch (StaticPrefs::browser_display_os_zoom_behavior()) {
360 case 0:
361 default:
362 break;
363 case 1:
364 settings.mFullZoom = GetTextScaleFactor();
365 break;
366 case 2:
367 settings.mTextZoom = GetTextScaleFactor();
368 break;
370 return settings;
373 // static
374 nsXPLookAndFeel* nsXPLookAndFeel::GetInstance() {
375 if (sInstance) {
376 return sInstance;
379 NS_ENSURE_TRUE(!sShutdown, nullptr);
381 // If we're in a content process, then the parent process will have supplied
382 // us with an initial FullLookAndFeel object.
383 // We grab this data from the ContentChild,
384 // where it's been temporarily stashed, and initialize our new LookAndFeel
385 // object with it.
387 FullLookAndFeel* lnf = nullptr;
389 if (auto* cc = mozilla::dom::ContentChild::GetSingleton()) {
390 lnf = &cc->BorrowLookAndFeelData();
393 if (lnf) {
394 sInstance = new widget::RemoteLookAndFeel(std::move(*lnf));
395 } else if (gfxPlatform::IsHeadless()) {
396 sInstance = new widget::HeadlessLookAndFeel();
397 } else {
398 sInstance = new nsLookAndFeel();
401 // This is only ever used once during initialization, and can be cleared now.
402 if (lnf) {
403 *lnf = {};
406 widget::Theme::Init();
407 return sInstance;
410 // static
411 void nsXPLookAndFeel::Shutdown() {
412 if (sShutdown) {
413 return;
416 sShutdown = true;
417 delete sInstance;
418 sInstance = nullptr;
420 // This keeps strings alive, so need to clear to make leak checking happy.
421 sFontCache.Clear();
423 widget::Theme::Shutdown();
426 static void IntPrefChanged(const nsACString& aPref) {
427 // Most Int prefs can't change our system colors or fonts, but
428 // ui.systemUsesDarkTheme can, since it affects the effective color-scheme
429 // (affecting system colors).
430 auto changeKind = aPref.EqualsLiteral("ui.systemUsesDarkTheme")
431 ? widget::ThemeChangeKind::Style
432 : widget::ThemeChangeKind::MediaQueriesOnly;
433 LookAndFeel::NotifyChangedAllWindows(changeKind);
436 static void FloatPrefChanged(const nsACString& aPref) {
437 // Most float prefs can't change our system colors or fonts, but
438 // textScaleFactor affects layout.
439 auto changeKind = aPref.EqualsLiteral("ui.textScaleFactor")
440 ? widget::ThemeChangeKind::StyleAndLayout
441 : widget::ThemeChangeKind::MediaQueriesOnly;
442 LookAndFeel::NotifyChangedAllWindows(changeKind);
445 static void ColorPrefChanged() {
446 // Color prefs affect style, because they by definition change system colors.
447 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
450 static void FontPrefChanged() {
451 // Color prefs affect style, because they by definition change system fonts.
452 LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style);
455 // static
456 void nsXPLookAndFeel::OnPrefChanged(const char* aPref, void* aClosure) {
457 nsDependentCString prefName(aPref);
458 for (const char* pref : sIntPrefs) {
459 if (prefName.Equals(pref)) {
460 IntPrefChanged(prefName);
461 return;
465 for (const char* pref : sFloatPrefs) {
466 if (prefName.Equals(pref)) {
467 FloatPrefChanged(prefName);
468 return;
472 for (const char* pref : sColorPrefs) {
473 // We use StringBeginsWith to handle .dark prefs too.
474 if (StringBeginsWith(prefName, nsDependentCString(pref))) {
475 ColorPrefChanged();
476 return;
480 for (const char* pref : sFontPrefs) {
481 if (StringBeginsWith(prefName, nsDependentCString(pref))) {
482 FontPrefChanged();
483 return;
488 bool LookAndFeel::WindowsNonNativeMenusEnabled() {
489 switch (StaticPrefs::browser_display_windows_non_native_menus()) {
490 case 0:
491 return false;
492 case 1:
493 return true;
494 default:
495 #ifdef XP_WIN
496 return IsWin10OrLater();
497 #else
498 return false;
499 #endif
503 static constexpr struct {
504 nsLiteralCString mName;
505 widget::ThemeChangeKind mChangeKind =
506 widget::ThemeChangeKind::MediaQueriesOnly;
507 } kMediaQueryPrefs[] = {
508 {"browser.display.windows.non_native_menus"_ns},
509 // Affects whether standins are used for the accent color.
510 {"widget.non-native-theme.use-theme-accent"_ns,
511 widget::ThemeChangeKind::Style},
512 // These two affect system colors on Windows.
513 {"widget.windows.uwp-system-colors.enabled"_ns,
514 widget::ThemeChangeKind::Style},
515 // These two affect system colors on Windows.
516 {"widget.windows.uwp-system-colors.highlight-accent"_ns,
517 widget::ThemeChangeKind::Style},
518 // Affects env().
519 {"layout.css.prefers-color-scheme.content-override"_ns,
520 widget::ThemeChangeKind::Style},
521 // Affects media queries and scrollbar sizes, so gotta relayout.
522 {"widget.gtk.overlay-scrollbars.enabled"_ns,
523 widget::ThemeChangeKind::StyleAndLayout},
524 // Affects zoom settings which includes text and full zoom.
525 {"browser.display.os-zoom-behavior"_ns,
526 widget::ThemeChangeKind::StyleAndLayout},
527 // This affects not only the media query, but also the native theme, so we
528 // need to re-layout.
529 {"browser.theme.toolbar-theme"_ns, widget::ThemeChangeKind::AllBits},
530 {"browser.theme.content-theme"_ns},
531 {"layout.css.moz-box-flexbox-emulation.enabled"_ns},
532 {"mathml.legacy_maction_and_semantics_implementations.disabled"_ns},
533 {"mathml.ms_lquote_rquote_attributes.disabled"_ns},
536 // Read values from the user's preferences.
537 // This is done once at startup, but since the user's preferences
538 // haven't actually been read yet at that time, we also have to
539 // set a callback to inform us of changes to each pref.
540 void nsXPLookAndFeel::Init() {
541 MOZ_RELEASE_ASSERT(NS_IsMainThread());
543 // Say we're already initialized, and take the chance that it might fail;
544 // protects against some other process writing to our static variables.
545 sInitialized = true;
547 RecomputeColorSchemes();
549 // XXX If we could reorganize the pref names, we should separate the branch
550 // for each types. Then, we could reduce the unnecessary loop from
551 // nsXPLookAndFeel::OnPrefChanged().
552 Preferences::RegisterPrefixCallback(OnPrefChanged, "ui.");
553 // We really do just want the accessibility.tabfocus pref, not other prefs
554 // that start with that string.
555 Preferences::RegisterCallback(OnPrefChanged, "accessibility.tabfocus");
557 for (auto& pref : kMediaQueryPrefs) {
558 Preferences::RegisterCallback(
559 [](const char*, void* aChangeKind) {
560 auto changeKind =
561 widget::ThemeChangeKind(reinterpret_cast<uintptr_t>(aChangeKind));
562 LookAndFeel::NotifyChangedAllWindows(changeKind);
564 pref.mName, reinterpret_cast<void*>(uintptr_t(pref.mChangeKind)));
568 nsXPLookAndFeel::~nsXPLookAndFeel() {
569 NS_ASSERTION(sInstance == this,
570 "This destroying instance isn't the singleton instance");
571 sInstance = nullptr;
574 static bool IsSpecialColor(LookAndFeel::ColorID aID, nscolor aColor) {
575 using ColorID = LookAndFeel::ColorID;
577 if (aColor == NS_SAME_AS_FOREGROUND_COLOR) {
578 return true;
581 switch (aID) {
582 case ColorID::IMESelectedRawTextBackground:
583 case ColorID::IMESelectedConvertedTextBackground:
584 case ColorID::IMERawInputBackground:
585 case ColorID::IMEConvertedTextBackground:
586 case ColorID::IMESelectedRawTextForeground:
587 case ColorID::IMESelectedConvertedTextForeground:
588 case ColorID::IMERawInputForeground:
589 case ColorID::IMEConvertedTextForeground:
590 case ColorID::IMERawInputUnderline:
591 case ColorID::IMEConvertedTextUnderline:
592 case ColorID::IMESelectedRawTextUnderline:
593 case ColorID::IMESelectedConvertedTextUnderline:
594 case ColorID::SpellCheckerUnderline:
595 return NS_IS_SELECTION_SPECIAL_COLOR(aColor);
596 default:
597 break;
600 * In GetColor(), every color that is not a special color is color
601 * corrected. Use false to make other colors color corrected.
603 return false;
606 nscolor nsXPLookAndFeel::GetStandinForNativeColor(ColorID aID,
607 ColorScheme aScheme) {
608 if (aScheme == ColorScheme::Dark) {
609 if (auto color = GenericDarkColor(aID)) {
610 return *color;
614 // The stand-in colors are taken from what the non-native theme needs (for
615 // field/button colors), the Windows 7 Aero theme except Mac-specific colors
616 // which are taken from Mac OS 10.7.
618 #define COLOR(name_, r, g, b) \
619 case ColorID::name_: \
620 return NS_RGB(r, g, b);
622 #define COLORA(name_, r, g, b, a) \
623 case ColorID::name_: \
624 return NS_RGBA(r, g, b, a);
626 switch (aID) {
627 // These are here for the purposes of headless mode.
628 case ColorID::IMESelectedRawTextBackground:
629 case ColorID::IMESelectedConvertedTextBackground:
630 case ColorID::IMERawInputBackground:
631 case ColorID::IMEConvertedTextBackground:
632 return NS_TRANSPARENT;
633 case ColorID::IMESelectedRawTextForeground:
634 case ColorID::IMESelectedConvertedTextForeground:
635 case ColorID::IMERawInputForeground:
636 case ColorID::IMEConvertedTextForeground:
637 return NS_SAME_AS_FOREGROUND_COLOR;
638 case ColorID::IMERawInputUnderline:
639 case ColorID::IMEConvertedTextUnderline:
640 return NS_40PERCENT_FOREGROUND_COLOR;
641 case ColorID::Accentcolor:
642 return widget::sDefaultAccent.ToABGR();
643 case ColorID::Accentcolortext:
644 return widget::sDefaultAccentText.ToABGR();
645 COLOR(SpellCheckerUnderline, 0xff, 0x00, 0x00)
646 COLOR(TextSelectDisabledBackground, 0xaa, 0xaa, 0xaa)
648 // CSS 2 colors:
649 COLOR(Activeborder, 0xB4, 0xB4, 0xB4)
650 COLOR(Activecaption, 0x99, 0xB4, 0xD1)
651 COLOR(Appworkspace, 0xAB, 0xAB, 0xAB)
652 COLOR(Background, 0x00, 0x00, 0x00)
653 COLOR(Buttonhighlight, 0xFF, 0xFF, 0xFF)
654 COLOR(Buttonshadow, 0xA0, 0xA0, 0xA0)
656 // Buttons and comboboxes should be kept in sync since they are drawn with
657 // the same colors by the non-native theme.
658 COLOR(Buttonface, 0xe9, 0xe9, 0xed)
659 COLORA(MozButtondisabledface, 0xe9, 0xe9, 0xed, 128)
661 COLOR(MozCombobox, 0xe9, 0xe9, 0xed)
663 COLOR(Buttontext, 0x00, 0x00, 0x00)
664 COLOR(MozComboboxtext, 0x00, 0x00, 0x00)
666 COLOR(Captiontext, 0x00, 0x00, 0x00)
667 COLOR(Graytext, 0x6D, 0x6D, 0x6D)
668 COLOR(Highlight, 0x33, 0x99, 0xFF)
669 COLOR(Highlighttext, 0xFF, 0xFF, 0xFF)
670 COLOR(Inactiveborder, 0xF4, 0xF7, 0xFC)
671 COLOR(Inactivecaption, 0xBF, 0xCD, 0xDB)
672 COLOR(Inactivecaptiontext, 0x43, 0x4E, 0x54)
673 COLOR(Infobackground, 0xFF, 0xFF, 0xE1)
674 COLOR(Infotext, 0x00, 0x00, 0x00)
675 COLOR(Menu, 0xF0, 0xF0, 0xF0)
676 COLOR(Menutext, 0x00, 0x00, 0x00)
677 COLOR(Scrollbar, 0xC8, 0xC8, 0xC8)
678 COLOR(Threeddarkshadow, 0x69, 0x69, 0x69)
679 COLOR(Threedface, 0xF0, 0xF0, 0xF0)
680 COLOR(Threedhighlight, 0xFF, 0xFF, 0xFF)
681 COLOR(Threedlightshadow, 0xE3, 0xE3, 0xE3)
682 COLOR(Threedshadow, 0xA0, 0xA0, 0xA0)
683 COLOR(Buttonborder, 0xE3, 0xE3, 0xE3)
684 COLOR(Mark, 0xFF, 0xFF, 0x00)
685 COLOR(Marktext, 0x00, 0x00, 0x00)
686 COLOR(Window, 0xFF, 0xFF, 0xFF)
687 COLOR(Windowframe, 0x64, 0x64, 0x64)
688 COLOR(Windowtext, 0x00, 0x00, 0x00)
689 COLOR(MozButtondefault, 0x69, 0x69, 0x69)
690 COLOR(Field, 0xFF, 0xFF, 0xFF)
691 COLORA(MozDisabledfield, 0xFF, 0xFF, 0xFF, 128)
692 COLOR(Fieldtext, 0x00, 0x00, 0x00)
693 COLOR(MozDialog, 0xF0, 0xF0, 0xF0)
694 COLOR(MozDialogtext, 0x00, 0x00, 0x00)
695 COLOR(MozColheadertext, 0x00, 0x00, 0x00)
696 COLOR(MozColheaderhovertext, 0x00, 0x00, 0x00)
697 COLOR(MozDragtargetzone, 0xFF, 0xFF, 0xFF)
698 COLOR(MozCellhighlight, 0xF0, 0xF0, 0xF0)
699 COLOR(MozCellhighlighttext, 0x00, 0x00, 0x00)
700 COLOR(Selecteditem, 0x33, 0x99, 0xFF)
701 COLOR(Selecteditemtext, 0xFF, 0xFF, 0xFF)
702 COLOR(MozButtonhoverface, 0xd0, 0xd0, 0xd7)
703 COLOR(MozButtonhovertext, 0x00, 0x00, 0x00)
704 COLOR(MozButtonactiveface, 0xb1, 0xb1, 0xb9)
705 COLOR(MozButtonactivetext, 0x00, 0x00, 0x00)
706 COLOR(MozMenuhover, 0x33, 0x99, 0xFF)
707 COLOR(MozMenuhovertext, 0x00, 0x00, 0x00)
708 COLOR(MozMenubartext, 0x00, 0x00, 0x00)
709 COLOR(MozMenubarhovertext, 0x00, 0x00, 0x00)
710 COLOR(MozMenuhoverdisabled, 0xF0, 0xF0, 0xF0)
711 COLOR(MozEventreerow, 0xFF, 0xFF, 0xFF)
712 COLOR(MozOddtreerow, 0xFF, 0xFF, 0xFF)
713 COLOR(MozMacChromeActive, 0xB2, 0xB2, 0xB2)
714 COLOR(MozMacChromeInactive, 0xE1, 0xE1, 0xE1)
715 COLOR(MozMacFocusring, 0x60, 0x9D, 0xD7)
716 COLOR(MozMacMenuselect, 0x38, 0x75, 0xD7)
717 COLOR(MozMacMenushadow, 0xA3, 0xA3, 0xA3)
718 COLOR(MozMacMenutextdisable, 0x88, 0x88, 0x88)
719 COLOR(MozMacMenutextselect, 0xFF, 0xFF, 0xFF)
720 COLOR(MozMacDisabledtoolbartext, 0x3F, 0x3F, 0x3F)
721 COLOR(MozMacSecondaryhighlight, 0xD4, 0xD4, 0xD4)
722 COLOR(MozMacMenupopup, 0xe6, 0xe6, 0xe6)
723 COLOR(MozMacMenuitem, 0xe6, 0xe6, 0xe6)
724 COLOR(MozMacActiveMenuitem, 0x0a, 0x64, 0xdc)
725 COLOR(MozMacSourceList, 0xf7, 0xf7, 0xf7)
726 COLOR(MozMacSourceListSelection, 0xc8, 0xc8, 0xc8)
727 COLOR(MozMacActiveSourceListSelection, 0x0a, 0x64, 0xdc)
728 COLOR(MozMacTooltip, 0xf7, 0xf7, 0xf7)
729 // Seems to be the default color (hardcoded because of bug 1065998)
730 COLOR(MozWinMediatext, 0xFF, 0xFF, 0xFF)
731 COLOR(MozWinCommunicationstext, 0xFF, 0xFF, 0xFF)
732 COLOR(MozNativehyperlinktext, 0x00, 0x66, 0xCC)
733 COLOR(MozNativevisitedhyperlinktext, 0x55, 0x1A, 0x8B)
734 default:
735 break;
737 return NS_RGB(0xFF, 0xFF, 0xFF);
740 #undef COLOR
741 #undef COLORA
743 // Taken from in-content/common.inc.css's dark theme.
744 Maybe<nscolor> nsXPLookAndFeel::GenericDarkColor(ColorID aID) {
745 nscolor color = NS_RGB(0, 0, 0);
746 static constexpr nscolor kWindowBackground = NS_RGB(28, 27, 34);
747 static constexpr nscolor kWindowText = NS_RGB(251, 251, 254);
748 switch (aID) {
749 case ColorID::Window: // --in-content-page-background
750 case ColorID::Background:
751 color = kWindowBackground;
752 break;
754 case ColorID::Menu:
755 color = NS_RGB(0x2b, 0x2a, 0x33);
756 break;
758 case ColorID::MozMenuhovertext:
759 case ColorID::MozMenubarhovertext:
760 case ColorID::Menutext:
761 color = NS_RGB(0xfb, 0xfb, 0xfe);
762 break;
764 case ColorID::MozMenuhover:
765 color = NS_RGB(0x52, 0x52, 0x5e);
766 break;
768 case ColorID::MozMenuhoverdisabled:
769 color = NS_RGB(0x3a, 0x39, 0x44);
770 break;
772 case ColorID::MozOddtreerow:
773 case ColorID::MozDialog: // --in-content-box-background
774 color = NS_RGB(35, 34, 43);
775 break;
776 case ColorID::Windowtext: // --in-content-page-color
777 case ColorID::MozDialogtext:
778 case ColorID::Fieldtext:
779 case ColorID::Buttontext: // --in-content-button-text-color (via
780 // --in-content-page-color)
781 case ColorID::MozComboboxtext:
782 case ColorID::MozButtonhovertext:
783 case ColorID::MozButtonactivetext:
784 color = kWindowText;
785 break;
786 case ColorID::Buttonshadow:
787 case ColorID::Threedshadow:
788 case ColorID::Threedlightshadow:
789 case ColorID::Buttonborder: // --in-content-box-border-color computed
790 // with kWindowText above
791 // kWindowBackground.
792 case ColorID::Graytext: // opacity: 0.4 of kWindowText blended over the
793 // "Window" background color, which happens to be
794 // the same :-)
795 color = NS_ComposeColors(kWindowBackground, NS_RGBA(251, 251, 254, 102));
796 break;
797 case ColorID::MozCellhighlight:
798 case ColorID::Selecteditem: // --in-content-primary-button-background /
799 // --in-content-item-selected
800 color = NS_RGB(0, 221, 255);
801 break;
802 case ColorID::Field:
803 case ColorID::Buttonface: // --in-content-button-background
804 case ColorID::Threedface:
805 case ColorID::MozCombobox:
806 case ColorID::MozCellhighlighttext:
807 case ColorID::Selecteditemtext: // --in-content-primary-button-text-color /
808 // --in-content-item-selected-text
809 color = NS_RGB(43, 42, 51);
810 break;
811 case ColorID::Threeddarkshadow: // Same as Threedlightshadow but with the
812 // background.
813 case ColorID::MozDisabledfield: // opacity: 0.4 of the face above blended
814 // over the "Window" background color.
815 case ColorID::MozButtondisabledface:
816 color = NS_ComposeColors(kWindowBackground, NS_RGBA(43, 42, 51, 102));
817 break;
818 case ColorID::MozButtonhoverface: // --in-content-button-background-hover
819 color = NS_RGB(82, 82, 94);
820 break;
821 case ColorID::MozButtonactiveface: // --in-content-button-background-active
822 color = NS_RGB(91, 91, 102);
823 break;
824 case ColorID::Highlight:
825 color = NS_RGBA(0, 221, 255, 78);
826 break;
827 case ColorID::Highlighttext:
828 color = NS_SAME_AS_FOREGROUND_COLOR;
829 break;
830 case ColorID::MozNativehyperlinktext:
831 // If you change this color, you probably also want to change the default
832 // value of browser.anchor_color.dark.
833 color = NS_RGB(0x8c, 0x8c, 0xff);
834 break;
835 case ColorID::MozNativevisitedhyperlinktext:
836 // If you change this color, you probably also want to change the default
837 // value of browser.visited_color.dark.
838 color = NS_RGB(0xff, 0xad, 0xff);
839 break;
840 case ColorID::SpellCheckerUnderline:
841 // This is the default for active links in dark mode as well
842 // (browser.active_color.dark). See bug 1755564 for some analysis and
843 // other options too.
844 color = NS_RGB(0xff, 0x66, 0x66);
845 break;
846 default:
847 return Nothing();
849 return Some(color);
852 // Uncomment the #define below if you want to debug system color use in a skin
853 // that uses them. When set, it will make all system color pairs that are
854 // appropriate for foreground/background pairing the same. This means if the
855 // skin is using system colors correctly you will not be able to see *any* text.
857 // #define DEBUG_SYSTEM_COLOR_USE
859 #ifdef DEBUG_SYSTEM_COLOR_USE
860 static nsresult SystemColorUseDebuggingColor(LookAndFeel::ColorID aID,
861 nscolor& aResult) {
862 using ColorID = LookAndFeel::ColorID;
864 switch (aID) {
865 // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
866 case ColorID::Activecaption:
867 // active window caption background
868 case ColorID::Captiontext:
869 // text in active window caption
870 aResult = NS_RGB(0xff, 0x00, 0x00);
871 break;
873 case ColorID::Highlight:
874 // background of selected item
875 case ColorID::Highlighttext:
876 // text of selected item
877 aResult = NS_RGB(0xff, 0xff, 0x00);
878 break;
880 case ColorID::Inactivecaption:
881 // inactive window caption
882 case ColorID::Inactivecaptiontext:
883 // text in inactive window caption
884 aResult = NS_RGB(0x66, 0x66, 0x00);
885 break;
887 case ColorID::Infobackground:
888 // tooltip background color
889 case ColorID::Infotext:
890 // tooltip text color
891 aResult = NS_RGB(0x00, 0xff, 0x00);
892 break;
894 case ColorID::Menu:
895 // menu background
896 case ColorID::Menutext:
897 // menu text
898 aResult = NS_RGB(0x00, 0xff, 0xff);
899 break;
901 case ColorID::Threedface:
902 case ColorID::Buttonface:
903 // 3-D face color
904 case ColorID::Buttontext:
905 // text on push buttons
906 aResult = NS_RGB(0x00, 0x66, 0x66);
907 break;
909 case ColorID::Window:
910 case ColorID::Windowtext:
911 aResult = NS_RGB(0x00, 0x00, 0xff);
912 break;
914 // from the CSS3 working draft (not yet finalized)
915 // http://www.w3.org/tr/2000/wd-css3-userint-20000216.html#color
917 case ColorID::Field:
918 case ColorID::Fieldtext:
919 aResult = NS_RGB(0xff, 0x00, 0xff);
920 break;
922 case ColorID::MozDialog:
923 case ColorID::MozDialogtext:
924 aResult = NS_RGB(0x66, 0x00, 0x66);
925 break;
927 default:
928 return NS_ERROR_NOT_AVAILABLE;
931 return NS_OK;
933 #endif
935 static nsresult GetPrefColor(const char* aPref, nscolor& aResult) {
936 nsAutoCString colorStr;
937 MOZ_TRY(Preferences::GetCString(aPref, colorStr));
938 if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), colorStr,
939 &aResult)) {
940 return NS_ERROR_FAILURE;
942 return NS_OK;
945 static nsresult GetColorFromPref(LookAndFeel::ColorID aID, ColorScheme aScheme,
946 nscolor& aResult) {
947 const char* prefName = sColorPrefs[size_t(aID)];
948 if (aScheme == ColorScheme::Dark) {
949 nsAutoCString darkPrefName(prefName);
950 darkPrefName.Append(".dark");
951 if (NS_SUCCEEDED(GetPrefColor(darkPrefName.get(), aResult))) {
952 return NS_OK;
955 return GetPrefColor(prefName, aResult);
958 // All these routines will return NS_OK if they have a value,
959 // in which case the nsLookAndFeel should use that value;
960 // otherwise we'll return NS_ERROR_NOT_AVAILABLE, in which case, the
961 // platform-specific nsLookAndFeel should use its own values instead.
962 nsresult nsXPLookAndFeel::GetColorValue(ColorID aID, ColorScheme aScheme,
963 UseStandins aUseStandins,
964 nscolor& aResult) {
965 if (!sInitialized) {
966 Init();
969 #ifdef DEBUG_SYSTEM_COLOR_USE
970 if (NS_SUCCEEDED(SystemColorUseDebuggingColor(aID, aResult))) {
971 return NS_OK;
973 #endif
975 auto& cache = sColorCaches.Get(aScheme, aUseStandins);
976 if (const auto* cached = cache.Get(aID)) {
977 if (cached->isNothing()) {
978 return NS_ERROR_FAILURE;
980 aResult = cached->value();
981 return NS_OK;
984 // NOTE: Servo holds a lock and the main thread is paused, so writing to the
985 // global cache here is fine.
986 auto result = GetUncachedColor(aID, aScheme, aUseStandins);
987 cache.Insert(aID, result);
988 if (!result) {
989 return NS_ERROR_FAILURE;
991 aResult = *result;
992 return NS_OK;
995 Maybe<nscolor> nsXPLookAndFeel::GetUncachedColor(ColorID aID,
996 ColorScheme aScheme,
997 UseStandins aUseStandins) {
998 if (aUseStandins == UseStandins::Yes) {
999 return Some(GetStandinForNativeColor(aID, aScheme));
1001 nscolor r;
1002 if (NS_SUCCEEDED(GetColorFromPref(aID, aScheme, r))) {
1003 return Some(r);
1005 if (NS_SUCCEEDED(NativeGetColor(aID, aScheme, r))) {
1006 if (gfxPlatform::GetCMSMode() == CMSMode::All && !IsSpecialColor(aID, r)) {
1007 qcms_transform* transform = gfxPlatform::GetCMSInverseRGBTransform();
1008 if (transform) {
1009 uint8_t color[4];
1010 color[0] = NS_GET_R(r);
1011 color[1] = NS_GET_G(r);
1012 color[2] = NS_GET_B(r);
1013 color[3] = NS_GET_A(r);
1014 qcms_transform_data(transform, color, color, 1);
1015 r = NS_RGBA(color[0], color[1], color[2], color[3]);
1019 return Some(r);
1021 return Nothing();
1024 nsresult nsXPLookAndFeel::GetIntValue(IntID aID, int32_t& aResult) {
1025 if (!sInitialized) {
1026 Init();
1029 if (const auto* cached = sIntCache.Get(aID)) {
1030 if (cached->isNothing()) {
1031 return NS_ERROR_FAILURE;
1033 aResult = cached->value();
1034 return NS_OK;
1037 if (NS_SUCCEEDED(Preferences::GetInt(sIntPrefs[size_t(aID)], &aResult))) {
1038 sIntCache.Insert(aID, Some(aResult));
1039 return NS_OK;
1042 if (NS_FAILED(NativeGetInt(aID, aResult))) {
1043 sIntCache.Insert(aID, Nothing());
1044 return NS_ERROR_FAILURE;
1047 sIntCache.Insert(aID, Some(aResult));
1048 return NS_OK;
1051 nsresult nsXPLookAndFeel::GetFloatValue(FloatID aID, float& aResult) {
1052 if (!sInitialized) {
1053 Init();
1056 if (const auto* cached = sFloatCache.Get(aID)) {
1057 if (cached->isNothing()) {
1058 return NS_ERROR_FAILURE;
1060 aResult = cached->value();
1061 return NS_OK;
1064 int32_t pref = 0;
1065 if (NS_SUCCEEDED(Preferences::GetInt(sFloatPrefs[size_t(aID)], &pref))) {
1066 aResult = float(pref) / 100.0f;
1067 sFloatCache.Insert(aID, Some(aResult));
1068 return NS_OK;
1071 if (NS_FAILED(NativeGetFloat(aID, aResult))) {
1072 sFloatCache.Insert(aID, Nothing());
1073 return NS_ERROR_FAILURE;
1076 sFloatCache.Insert(aID, Some(aResult));
1077 return NS_OK;
1080 bool nsXPLookAndFeel::LookAndFeelFontToStyle(const LookAndFeelFont& aFont,
1081 nsString& aName,
1082 gfxFontStyle& aStyle) {
1083 if (!aFont.haveFont()) {
1084 return false;
1086 aName = aFont.name();
1087 aStyle = gfxFontStyle();
1088 aStyle.size = aFont.size();
1089 aStyle.weight = FontWeight::FromInt(aFont.weight());
1090 aStyle.style =
1091 aFont.italic() ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL;
1092 aStyle.systemFont = true;
1093 return true;
1096 widget::LookAndFeelFont nsXPLookAndFeel::StyleToLookAndFeelFont(
1097 const nsAString& aName, const gfxFontStyle& aStyle) {
1098 LookAndFeelFont font;
1099 font.haveFont() = true;
1100 font.name() = aName;
1101 font.size() = aStyle.size;
1102 font.weight() = aStyle.weight.ToFloat();
1103 font.italic() = aStyle.style.IsItalic();
1104 MOZ_ASSERT(aStyle.style.IsNormal() || aStyle.style.IsItalic(),
1105 "Cannot handle oblique font style");
1106 #ifdef DEBUG
1108 // Assert that all the remaining font style properties have their
1109 // default values.
1110 gfxFontStyle candidate = aStyle;
1111 gfxFontStyle defaults{};
1112 candidate.size = defaults.size;
1113 candidate.weight = defaults.weight;
1114 candidate.style = defaults.style;
1115 MOZ_ASSERT(candidate.Equals(defaults),
1116 "Some font style properties not supported");
1118 #endif
1119 return font;
1122 bool nsXPLookAndFeel::GetFontValue(FontID aID, nsString& aName,
1123 gfxFontStyle& aStyle) {
1124 if (const LookAndFeelFont* cached = sFontCache.Get(aID)) {
1125 return LookAndFeelFontToStyle(*cached, aName, aStyle);
1128 LookAndFeelFont font;
1129 auto GetFontsFromPrefs = [&]() -> bool {
1130 nsDependentCString pref(sFontPrefs[size_t(aID)]);
1131 if (NS_FAILED(Preferences::GetString(pref.get(), aName))) {
1132 return false;
1134 font.haveFont() = true;
1135 font.name() = aName;
1136 font.size() = Preferences::GetFloat(nsAutoCString(pref + ".size"_ns).get());
1137 // This is written this way rather than using the fallback so that an empty
1138 // pref (such like the one about:config creates) doesn't cause system fonts
1139 // to have zero-size.
1140 if (font.size() < 1.0f) {
1141 font.size() = StyleFONT_MEDIUM_PX;
1143 font.weight() = Preferences::GetFloat(
1144 nsAutoCString(pref + ".weight"_ns).get(), FontWeight::NORMAL.ToFloat());
1145 font.italic() =
1146 Preferences::GetBool(nsAutoCString(pref + ".italic"_ns).get());
1147 return true;
1150 if (GetFontsFromPrefs()) {
1151 LookAndFeelFontToStyle(font, aName, aStyle);
1152 } else if (NativeGetFont(aID, aName, aStyle)) {
1153 font = StyleToLookAndFeelFont(aName, aStyle);
1154 } else {
1155 MOZ_ASSERT(!font.haveFont());
1157 bool success = font.haveFont();
1158 sFontCache.Insert(aID, std::move(font));
1159 return success;
1162 void nsXPLookAndFeel::RefreshImpl() {
1163 // Wipe out our caches.
1164 sColorCaches.Clear();
1165 sFontCache.Clear();
1166 sFloatCache.Clear();
1167 sIntCache.Clear();
1168 RecomputeColorSchemes();
1170 // Clear any cached FullLookAndFeel data, which is now invalid.
1171 if (XRE_IsParentProcess()) {
1172 widget::RemoteLookAndFeel::ClearCachedData();
1176 static bool sRecordedLookAndFeelTelemetry = false;
1178 void nsXPLookAndFeel::RecordTelemetry() {
1179 if (!XRE_IsParentProcess()) {
1180 return;
1183 if (sRecordedLookAndFeelTelemetry) {
1184 return;
1187 sRecordedLookAndFeelTelemetry = true;
1189 int32_t i;
1190 Telemetry::ScalarSet(
1191 Telemetry::ScalarID::WIDGET_DARK_MODE,
1192 NS_SUCCEEDED(GetIntValue(IntID::SystemUsesDarkTheme, i)) && i != 0);
1194 RecordLookAndFeelSpecificTelemetry();
1197 namespace mozilla {
1199 static widget::ThemeChangeKind sGlobalThemeChangeKind{0};
1201 void LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind aKind) {
1202 sGlobalThemeChanged = true;
1203 sGlobalThemeChangeKind |= aKind;
1205 if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
1206 const char16_t kind[] = {char16_t(aKind), 0};
1207 obs->NotifyObservers(nullptr, "internal-look-and-feel-changed", kind);
1211 void LookAndFeel::DoHandleGlobalThemeChange() {
1212 MOZ_ASSERT(sGlobalThemeChanged);
1213 sGlobalThemeChanged = false;
1214 auto kind = std::exchange(sGlobalThemeChangeKind, widget::ThemeChangeKind(0));
1216 // Tell the theme that it changed, so it can flush any handles to stale theme
1217 // data.
1219 // We can use the *DoNotUseDirectly functions directly here, because we want
1220 // to notify all possible themes in a given process (but just once).
1221 if (XRE_IsParentProcess() ||
1222 !StaticPrefs::widget_non_native_theme_enabled()) {
1223 if (nsCOMPtr<nsITheme> theme = do_GetNativeThemeDoNotUseDirectly()) {
1224 theme->ThemeChanged();
1227 if (nsCOMPtr<nsITheme> theme = do_GetBasicNativeThemeDoNotUseDirectly()) {
1228 theme->ThemeChanged();
1231 // Clear all cached LookAndFeel colors.
1232 LookAndFeel::Refresh();
1234 // Reset default background and foreground colors for the document since they
1235 // may be using system colors.
1236 PreferenceSheet::Refresh();
1238 // Vector images (SVG) may be using theme colors so we discard all cached
1239 // surfaces. (We could add a vector image only version of DiscardAll, but
1240 // in bug 940625 we decided theme changes are rare enough not to bother.)
1241 image::SurfaceCacheUtils::DiscardAll();
1243 if (XRE_IsParentProcess()) {
1244 dom::ContentParent::BroadcastThemeUpdate(kind);
1247 nsContentUtils::AddScriptRunner(
1248 NS_NewRunnableFunction("HandleGlobalThemeChange", [] {
1249 if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
1250 obs->NotifyObservers(nullptr, "look-and-feel-changed", nullptr);
1252 }));
1255 #define BIT_FOR(_c) (1ull << size_t(ColorID::_c))
1257 // We want to use a non-native color scheme for the non-native theme (except in
1258 // high-contrast mode), so spoof some of the colors with stand-ins to prevent
1259 // lack of contrast.
1260 static constexpr std::bitset<size_t(ColorID::End)> sNonNativeThemeStandinColors{
1261 // Used by default button styles.
1262 BIT_FOR(Buttonface) | BIT_FOR(Buttontext) | BIT_FOR(MozButtonhoverface) |
1263 BIT_FOR(MozButtonhovertext) | BIT_FOR(MozButtonactiveface) |
1264 BIT_FOR(MozButtonactivetext) | BIT_FOR(MozButtondisabledface) |
1265 BIT_FOR(Buttonborder) |
1266 // Used by select elements.
1267 BIT_FOR(MozCombobox) | BIT_FOR(MozComboboxtext) |
1268 BIT_FOR(Threedlightshadow) |
1269 // For symmetry with the above.
1270 BIT_FOR(Threeddarkshadow) |
1271 // Used by fieldset borders.
1272 BIT_FOR(Threedface) |
1273 // Used by input / textarea.
1274 BIT_FOR(Field) | BIT_FOR(Fieldtext) |
1275 // Used by disabled form controls.
1276 BIT_FOR(MozDisabledfield) | BIT_FOR(Graytext) |
1277 // Some pages expect these to return windows-like colors, see bug 1773795.
1278 // Also, per spec these should match Canvas/CanvasText, see
1279 // https://drafts.csswg.org/css-color-4/#window
1280 BIT_FOR(Window) | BIT_FOR(Windowtext)};
1281 #undef BIT_FOR
1283 static bool ShouldUseStandinsForNativeColorForNonNativeTheme(
1284 const dom::Document& aDoc, LookAndFeel::ColorID aColor,
1285 const PreferenceSheet::Prefs& aPrefs) {
1286 const bool shouldUseStandinsForColor = [&] {
1287 if (sNonNativeThemeStandinColors[size_t(aColor)]) {
1288 return true;
1290 // There are platforms where we want the content-exposed accent color to be
1291 // the windows blue rather than the system accent color, for now.
1292 return !StaticPrefs::widget_non_native_theme_use_theme_accent() &&
1293 (aColor == LookAndFeel::ColorID::Accentcolor ||
1294 aColor == LookAndFeel::ColorID::Accentcolortext);
1295 }();
1297 return shouldUseStandinsForColor && aDoc.ShouldAvoidNativeTheme() &&
1298 !aPrefs.NonNativeThemeShouldBeHighContrast();
1301 ColorScheme LookAndFeel::sChromeColorScheme;
1302 ColorScheme LookAndFeel::sContentColorScheme;
1303 bool LookAndFeel::sColorSchemeInitialized;
1304 bool LookAndFeel::sGlobalThemeChanged;
1306 bool LookAndFeel::IsDarkColor(nscolor aColor) {
1307 // Given https://www.w3.org/TR/WCAG20/#contrast-ratiodef, this is the
1308 // threshold that tells us whether contrast is better against white or black.
1310 // Contrast ratio against black is: (L + 0.05) / 0.05
1311 // Contrast ratio against white is: 1.05 / (L + 0.05)
1313 // So the intersection is:
1315 // (L + 0.05) / 0.05 = 1.05 / (L + 0.05)
1317 // And the solution to that equation is:
1319 // sqrt(1.05 * 0.05) - 0.05
1321 // So we consider a color dark if the contrast is below this threshold, and
1322 // it's at least half-opaque.
1323 constexpr float kThreshold = 0.179129;
1324 return NS_GET_A(aColor) > 127 &&
1325 RelativeLuminanceUtils::Compute(aColor) < kThreshold;
1328 auto LookAndFeel::ColorSchemeSettingForChrome() -> ChromeColorSchemeSetting {
1329 switch (StaticPrefs::browser_theme_toolbar_theme()) {
1330 case 0: // Dark
1331 return ChromeColorSchemeSetting::Dark;
1332 case 1: // Light
1333 return ChromeColorSchemeSetting::Light;
1334 default:
1335 return ChromeColorSchemeSetting::System;
1339 ColorScheme LookAndFeel::ThemeDerivedColorSchemeForContent() {
1340 switch (StaticPrefs::browser_theme_content_theme()) {
1341 case 0: // Dark
1342 return ColorScheme::Dark;
1343 case 1: // Light
1344 return ColorScheme::Light;
1345 default:
1346 return SystemColorScheme();
1350 void LookAndFeel::RecomputeColorSchemes() {
1351 sColorSchemeInitialized = true;
1353 sChromeColorScheme = [] {
1354 switch (ColorSchemeSettingForChrome()) {
1355 case ChromeColorSchemeSetting::Light:
1356 return ColorScheme::Light;
1357 case ChromeColorSchemeSetting::Dark:
1358 return ColorScheme::Dark;
1359 case ChromeColorSchemeSetting::System:
1360 break;
1362 return SystemColorScheme();
1363 }();
1365 sContentColorScheme = [] {
1366 switch (StaticPrefs::layout_css_prefers_color_scheme_content_override()) {
1367 case 0:
1368 return ColorScheme::Dark;
1369 case 1:
1370 return ColorScheme::Light;
1371 default:
1372 return ThemeDerivedColorSchemeForContent();
1374 }();
1377 ColorScheme LookAndFeel::ColorSchemeForStyle(
1378 const dom::Document& aDoc, const StyleColorSchemeFlags& aFlags,
1379 ColorSchemeMode aMode) {
1380 using Choice = PreferenceSheet::Prefs::ColorSchemeChoice;
1382 const auto& prefs = PreferenceSheet::PrefsFor(aDoc);
1383 switch (prefs.mColorSchemeChoice) {
1384 case Choice::Standard:
1385 break;
1386 case Choice::UserPreferred:
1387 return aDoc.PreferredColorScheme();
1388 case Choice::Light:
1389 return ColorScheme::Light;
1390 case Choice::Dark:
1391 return ColorScheme::Dark;
1394 StyleColorSchemeFlags style(aFlags);
1395 if (!style) {
1396 style.bits = aDoc.GetColorSchemeBits();
1398 const bool supportsDark = bool(style & StyleColorSchemeFlags::DARK);
1399 const bool supportsLight = bool(style & StyleColorSchemeFlags::LIGHT);
1400 if (supportsLight && supportsDark) {
1401 // Both color-schemes are explicitly supported, use the preferred one.
1402 return aDoc.PreferredColorScheme();
1404 if (supportsDark || supportsLight) {
1405 // One color-scheme is explicitly supported and one isn't, so use the one
1406 // the content supports.
1407 return supportsDark ? ColorScheme::Dark : ColorScheme::Light;
1409 // No value specified. Chrome docs always supports both, so use the preferred
1410 // color-scheme.
1411 if (aMode == ColorSchemeMode::Preferred ||
1412 nsContentUtils::IsChromeDoc(&aDoc)) {
1413 return aDoc.PreferredColorScheme();
1415 // Default content to light.
1416 return ColorScheme::Light;
1419 LookAndFeel::ColorScheme LookAndFeel::ColorSchemeForFrame(
1420 const nsIFrame* aFrame, ColorSchemeMode aMode) {
1421 return ColorSchemeForStyle(*aFrame->PresContext()->Document(),
1422 aFrame->StyleUI()->mColorScheme.bits, aMode);
1425 // static
1426 Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, ColorScheme aScheme,
1427 UseStandins aUseStandins) {
1428 nscolor result;
1429 nsresult rv = nsLookAndFeel::GetInstance()->GetColorValue(
1430 aId, aScheme, aUseStandins, result);
1431 if (NS_FAILED(rv)) {
1432 return Nothing();
1434 return Some(result);
1437 // Returns whether there is a CSS color name for this color.
1438 static bool ColorIsCSSAccessible(LookAndFeel::ColorID aId) {
1439 using ColorID = LookAndFeel::ColorID;
1441 switch (aId) {
1442 case ColorID::TextSelectDisabledBackground:
1443 case ColorID::TextSelectAttentionBackground:
1444 case ColorID::TextSelectAttentionForeground:
1445 case ColorID::TextHighlightBackground:
1446 case ColorID::TextHighlightForeground:
1447 case ColorID::ThemedScrollbar:
1448 case ColorID::ThemedScrollbarInactive:
1449 case ColorID::ThemedScrollbarThumb:
1450 case ColorID::ThemedScrollbarThumbActive:
1451 case ColorID::ThemedScrollbarThumbInactive:
1452 case ColorID::ThemedScrollbarThumbHover:
1453 case ColorID::IMERawInputBackground:
1454 case ColorID::IMERawInputForeground:
1455 case ColorID::IMERawInputUnderline:
1456 case ColorID::IMESelectedRawTextBackground:
1457 case ColorID::IMESelectedRawTextForeground:
1458 case ColorID::IMESelectedRawTextUnderline:
1459 case ColorID::IMEConvertedTextBackground:
1460 case ColorID::IMEConvertedTextForeground:
1461 case ColorID::IMEConvertedTextUnderline:
1462 case ColorID::IMESelectedConvertedTextBackground:
1463 case ColorID::IMESelectedConvertedTextForeground:
1464 case ColorID::IMESelectedConvertedTextUnderline:
1465 case ColorID::SpellCheckerUnderline:
1466 return false;
1467 default:
1468 break;
1471 return true;
1474 LookAndFeel::UseStandins LookAndFeel::ShouldUseStandins(
1475 const dom::Document& aDoc, ColorID aId) {
1476 const auto& prefs = PreferenceSheet::PrefsFor(aDoc);
1477 if (ShouldUseStandinsForNativeColorForNonNativeTheme(aDoc, aId, prefs)) {
1478 return UseStandins::Yes;
1480 if (prefs.mUseStandins && ColorIsCSSAccessible(aId)) {
1481 return UseStandins::Yes;
1483 return UseStandins::No;
1486 Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, const nsIFrame* aFrame) {
1487 const auto* doc = aFrame->PresContext()->Document();
1488 return GetColor(aId, ColorSchemeForFrame(aFrame),
1489 ShouldUseStandins(*doc, aId));
1492 // static
1493 nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) {
1494 return nsLookAndFeel::GetInstance()->GetIntValue(aID, *aResult);
1497 // static
1498 nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) {
1499 return nsLookAndFeel::GetInstance()->GetFloatValue(aID, *aResult);
1502 // static
1503 bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle) {
1504 return nsLookAndFeel::GetInstance()->GetFontValue(aID, aName, aStyle);
1507 // static
1508 char16_t LookAndFeel::GetPasswordCharacter() {
1509 return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl();
1512 // static
1513 bool LookAndFeel::GetEchoPassword() {
1514 if (StaticPrefs::editor_password_mask_delay() >= 0) {
1515 return StaticPrefs::editor_password_mask_delay() > 0;
1517 return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl();
1520 // static
1521 uint32_t LookAndFeel::GetPasswordMaskDelay() {
1522 int32_t delay = StaticPrefs::editor_password_mask_delay();
1523 if (delay < 0) {
1524 return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl();
1526 return delay;
1529 bool LookAndFeel::DrawInTitlebar() {
1530 switch (StaticPrefs::browser_tabs_inTitlebar()) {
1531 case 0:
1532 return false;
1533 case 1:
1534 return true;
1535 default:
1536 break;
1538 return nsLookAndFeel::GetInstance()->GetDefaultDrawInTitlebar();
1541 void LookAndFeel::GetThemeInfo(nsACString& aOut) {
1542 nsLookAndFeel::GetInstance()->GetThemeInfo(aOut);
1545 // static
1546 void LookAndFeel::Refresh() {
1547 nsLookAndFeel::GetInstance()->RefreshImpl();
1548 widget::Theme::LookAndFeelChanged();
1551 // static
1552 void LookAndFeel::NativeInit() { nsLookAndFeel::GetInstance()->NativeInit(); }
1554 // static
1555 void LookAndFeel::SetData(widget::FullLookAndFeel&& aTables) {
1556 nsLookAndFeel::GetInstance()->SetDataImpl(std::move(aTables));
1559 } // namespace mozilla