diff options
Diffstat (limited to 'widget/nsXPLookAndFeel.cpp')
-rw-r--r-- | widget/nsXPLookAndFeel.cpp | 1560 |
1 files changed, 1560 insertions, 0 deletions
diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp new file mode 100644 index 0000000000..7091544139 --- /dev/null +++ b/widget/nsXPLookAndFeel.cpp @@ -0,0 +1,1560 @@ +/* -*- mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ArrayUtils.h" + +#include "nscore.h" + +#include "nsXPLookAndFeel.h" +#include "nsLookAndFeel.h" +#include "HeadlessLookAndFeel.h" +#include "RemoteLookAndFeel.h" +#include "nsContentUtils.h" +#include "nsCRT.h" +#include "nsFont.h" +#include "nsIFrame.h" +#include "nsIXULRuntime.h" +#include "Theme.h" +#include "SurfaceCacheUtils.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/ServoStyleSet.h" +#include "mozilla/ServoCSSParser.h" +#include "mozilla/StaticPrefs_browser.h" +#include "mozilla/StaticPrefs_editor.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_ui.h" +#include "mozilla/StaticPrefs_widget.h" +#include "mozilla/dom/Document.h" +#include "mozilla/PreferenceSheet.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/widget/WidgetMessageUtils.h" +#include "mozilla/RelativeLuminanceUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/TelemetryScalarEnums.h" + +#include "gfxPlatform.h" +#include "gfxFont.h" + +#include "qcms.h" + +#include <bitset> + +using namespace mozilla; + +using IntID = mozilla::LookAndFeel::IntID; +using FloatID = mozilla::LookAndFeel::FloatID; +using ColorID = mozilla::LookAndFeel::ColorID; +using FontID = mozilla::LookAndFeel::FontID; + +template <typename Index, typename Value, Index kEnd> +class EnumeratedCache { + mozilla::EnumeratedArray<Index, kEnd, Value> mEntries; + std::bitset<size_t(kEnd)> mValidity; + + public: + constexpr EnumeratedCache() = default; + + bool IsValid(Index aIndex) const { return mValidity[size_t(aIndex)]; } + + const Value* Get(Index aIndex) const { + return IsValid(aIndex) ? &mEntries[aIndex] : nullptr; + } + + void Insert(Index aIndex, Value aValue) { + mValidity[size_t(aIndex)] = true; + mEntries[aIndex] = aValue; + } + + void Remove(Index aIndex) { + mValidity[size_t(aIndex)] = false; + mEntries[aIndex] = Value(); + } + + void Clear() { + mValidity.reset(); + for (auto& entry : mEntries) { + entry = Value(); + } + } +}; + +using ColorCache = EnumeratedCache<ColorID, Maybe<nscolor>, ColorID::End>; + +struct ColorCaches { + using UseStandins = LookAndFeel::UseStandins; + + ColorCache mCaches[2][2]; + + constexpr ColorCaches() = default; + + ColorCache& Get(ColorScheme aScheme, UseStandins aUseStandins) { + return mCaches[aScheme == ColorScheme::Dark] + [aUseStandins == UseStandins::Yes]; + } + + void Clear() { + for (auto& c : mCaches) { + for (auto& cache : c) { + cache.Clear(); + } + } + } +}; + +static ColorCaches sColorCaches; + +static EnumeratedCache<FloatID, Maybe<float>, FloatID::End> sFloatCache; +static EnumeratedCache<IntID, Maybe<int32_t>, IntID::End> sIntCache; +static EnumeratedCache<FontID, widget::LookAndFeelFont, FontID::End> sFontCache; + +// To make one of these prefs toggleable from a reftest add a user +// pref in testing/profiles/reftest/user.js. For example, to make +// ui.useAccessibilityTheme toggleable, add: +// +// user_pref("ui.useAccessibilityTheme", 0); +// +// This needs to be of the same length and in the same order as +// LookAndFeel::IntID values. +static const char sIntPrefs[][45] = { + "ui.caretBlinkTime", + "ui.caretBlinkCount", + "ui.caretWidth", + "ui.caretVisibleWithSelection", + "ui.selectTextfieldsOnKeyFocus", + "ui.submenuDelay", + "ui.menusCanOverlapOSBar", + "ui.useOverlayScrollbars", + "ui.allowOverlayScrollbarsOverlap", + "ui.skipNavigatingDisabledMenuItem", + "ui.dragThresholdX", + "ui.dragThresholdY", + "ui.useAccessibilityTheme", + "ui.scrollArrowStyle", + "ui.scrollButtonLeftMouseButtonAction", + "ui.scrollButtonMiddleMouseButtonAction", + "ui.scrollButtonRightMouseButtonAction", + "ui.treeOpenDelay", + "ui.treeCloseDelay", + "ui.treeLazyScrollDelay", + "ui.treeScrollDelay", + "ui.treeScrollLinesMax", + "accessibility.tabfocus", // Weird one... + "ui.chosenMenuItemsShouldBlink", + "ui.showKeyboardCues", + "ui.windowsAccentColorInTitlebar", + "ui.windowsDefaultTheme", + "ui.dwmCompositor", + "ui.windowsClassic", + "ui.windowsGlass", + "ui.macGraphiteTheme", + "ui.macBigSurTheme", + "ui.macRTL", + "ui.alertNotificationOrigin", + "ui.scrollToClick", + "ui.IMERawInputUnderlineStyle", + "ui.IMESelectedRawTextUnderlineStyle", + "ui.IMEConvertedTextUnderlineStyle", + "ui.IMESelectedConvertedTextUnderlineStyle", + "ui.SpellCheckerUnderlineStyle", + "ui.menuBarDrag", + "ui.scrollbarButtonAutoRepeatBehavior", + "ui.tooltipDelay", + "ui.swipeAnimationEnabled", + "ui.scrollbarDisplayOnMouseMove", + "ui.scrollbarFadeBeginDelay", + "ui.scrollbarFadeDuration", + "ui.contextMenuOffsetVertical", + "ui.contextMenuOffsetHorizontal", + "ui.GtkCSDAvailable", + "ui.GtkCSDMinimizeButton", + "ui.GtkCSDMaximizeButton", + "ui.GtkCSDCloseButton", + "ui.GtkCSDMinimizeButtonPosition", + "ui.GtkCSDMaximizeButtonPosition", + "ui.GtkCSDCloseButtonPosition", + "ui.GtkCSDReversedPlacement", + "ui.systemUsesDarkTheme", + "ui.prefersReducedMotion", + "ui.primaryPointerCapabilities", + "ui.allPointerCapabilities", + "ui.systemVerticalScrollbarWidth", + "ui.systemHorizontalScrollbarHeight", + "ui.touchDeviceSupportPresent", + "ui.titlebarRadius", + "ui.GtkMenuRadius", + "ui.dynamicRange", + "ui.videoDynamicRange", + "ui.panelAnimations", +}; + +static_assert(ArrayLength(sIntPrefs) == size_t(LookAndFeel::IntID::End), + "Should have a pref for each int value"); + +// This array MUST be kept in the same order as the float id list in +// LookAndFeel.h +// clang-format off +static const char sFloatPrefs[][37] = { + "ui.IMEUnderlineRelativeSize", + "ui.SpellCheckerUnderlineRelativeSize", + "ui.caretAspectRatio", + "ui.textScaleFactor", + "ui.cursorScale", +}; +// clang-format on + +static_assert(ArrayLength(sFloatPrefs) == size_t(LookAndFeel::FloatID::End), + "Should have a pref for each float value"); + +// This array MUST be kept in the same order as the color list in +// specified/color.rs +static const char sColorPrefs[][41] = { + "ui.activeborder", + "ui.activecaption", + "ui.appworkspace", + "ui.background", + "ui.buttonface", + "ui.buttonhighlight", + "ui.buttonshadow", + "ui.buttontext", + "ui.buttonborder", + "ui.captiontext", + "ui.-moz-field", + "ui.-moz-disabledfield", + "ui.-moz-fieldtext", + "ui.mark", + "ui.marktext", + "ui.-moz-comboboxtext", + "ui.-moz-combobox", + "ui.graytext", + "ui.highlight", + "ui.highlighttext", + "ui.inactiveborder", + "ui.inactivecaption", + "ui.inactivecaptiontext", + "ui.infobackground", + "ui.infotext", + "ui.menu", + "ui.menutext", + "ui.scrollbar", + "ui.threeddarkshadow", + "ui.threedface", + "ui.threedhighlight", + "ui.threedlightshadow", + "ui.threedshadow", + "ui.window", + "ui.windowframe", + "ui.windowtext", + "ui.-moz-buttondefault", + "ui.-moz-default-color", + "ui.-moz-default-background-color", + "ui.-moz-dialog", + "ui.-moz-dialogtext", + "ui.-moz-dragtargetzone", + "ui.-moz-cellhighlight", + "ui.-moz_cellhighlighttext", + "ui.selecteditem", + "ui.selecteditemtext", + "ui.-moz-buttonhoverface", + "ui.-moz_buttonhovertext", + "ui.-moz_menuhover", + "ui.-moz_menuhoverdisabled", + "ui.-moz_menuhovertext", + "ui.-moz_menubartext", + "ui.-moz_menubarhovertext", + "ui.-moz_eventreerow", + "ui.-moz_oddtreerow", + "ui.-moz-buttonactivetext", + "ui.-moz-buttonactiveface", + "ui.-moz-buttondisabledface", + "ui.-moz_mac_chrome_active", + "ui.-moz_mac_chrome_inactive", + "ui.-moz-mac-defaultbuttontext", + "ui.-moz-mac-focusring", + "ui.-moz-mac-menuselect", + "ui.-moz-mac-menushadow", + "ui.-moz-mac-menutextdisable", + "ui.-moz-mac-menutextselect", + "ui.-moz_mac_disabledtoolbartext", + "ui.-moz-mac-secondaryhighlight", + "ui.-moz-mac-menupopup", + "ui.-moz-mac-menuitem", + "ui.-moz-mac-active-menuitem", + "ui.-moz-mac-source-list", + "ui.-moz-mac-source-list-selection", + "ui.-moz-mac-active-source-list-selection", + "ui.-moz-mac-tooltip", + "ui.accentcolor", + "ui.accentcolortext", + "ui.-moz-autofill-background", + "ui.-moz-win-mediatext", + "ui.-moz-win-communicationstext", + "ui.-moz-nativehyperlinktext", + "ui.-moz-nativevisitedhyperlinktext", + "ui.-moz-hyperlinktext", + "ui.-moz-activehyperlinktext", + "ui.-moz-visitedhyperlinktext", + "ui.-moz-colheadertext", + "ui.-moz-colheaderhovertext", + "ui.textSelectDisabledBackground", + "ui.textSelectAttentionBackground", + "ui.textSelectAttentionForeground", + "ui.textHighlightBackground", + "ui.textHighlightForeground", + "ui.IMERawInputBackground", + "ui.IMERawInputForeground", + "ui.IMERawInputUnderline", + "ui.IMESelectedRawTextBackground", + "ui.IMESelectedRawTextForeground", + "ui.IMESelectedRawTextUnderline", + "ui.IMEConvertedTextBackground", + "ui.IMEConvertedTextForeground", + "ui.IMEConvertedTextUnderline", + "ui.IMESelectedConvertedTextBackground", + "ui.IMESelectedConvertedTextForeground", + "ui.IMESelectedConvertedTextUnderline", + "ui.SpellCheckerUnderline", + "ui.themedScrollbar", + "ui.themedScrollbarInactive", + "ui.themedScrollbarThumb", + "ui.themedScrollbarThumbHover", + "ui.themedScrollbarThumbActive", + "ui.themedScrollbarThumbInactive", +}; + +static_assert(ArrayLength(sColorPrefs) == size_t(LookAndFeel::ColorID::End), + "Should have a pref for each color value"); + +// This array MUST be kept in the same order as the SystemFont enum. +static const char sFontPrefs[][41] = { + "ui.font.caption", + "ui.font.icon", + "ui.font.menu", + "ui.font.message-box", + "ui.font.small-caption", + "ui.font.status-bar", + "ui.font.-moz-pull-down-menu", + "ui.font.-moz-button", + "ui.font.-moz-list", + "ui.font.-moz-field", +}; + +static_assert(ArrayLength(sFontPrefs) == size_t(LookAndFeel::FontID::End), + "Should have a pref for each font value"); + +const char* nsXPLookAndFeel::GetColorPrefName(ColorID aId) { + return sColorPrefs[size_t(aId)]; +} + +bool nsXPLookAndFeel::sInitialized = false; + +nsXPLookAndFeel* nsXPLookAndFeel::sInstance = nullptr; +bool nsXPLookAndFeel::sShutdown = false; + +auto LookAndFeel::SystemZoomSettings() -> ZoomSettings { + ZoomSettings settings; + switch (StaticPrefs::browser_display_os_zoom_behavior()) { + case 0: + default: + break; + case 1: + settings.mFullZoom = GetTextScaleFactor(); + break; + case 2: + settings.mTextZoom = GetTextScaleFactor(); + break; + } + return settings; +} + +// static +nsXPLookAndFeel* nsXPLookAndFeel::GetInstance() { + if (sInstance) { + return sInstance; + } + + NS_ENSURE_TRUE(!sShutdown, nullptr); + + // If we're in a content process, then the parent process will have supplied + // us with an initial FullLookAndFeel object. + // We grab this data from the ContentChild, + // where it's been temporarily stashed, and initialize our new LookAndFeel + // object with it. + + FullLookAndFeel* lnf = nullptr; + + if (auto* cc = mozilla::dom::ContentChild::GetSingleton()) { + lnf = &cc->BorrowLookAndFeelData(); + } + + if (lnf) { + sInstance = new widget::RemoteLookAndFeel(std::move(*lnf)); + } else if (gfxPlatform::IsHeadless()) { + sInstance = new widget::HeadlessLookAndFeel(); + } else { + sInstance = new nsLookAndFeel(); + } + + // This is only ever used once during initialization, and can be cleared now. + if (lnf) { + *lnf = {}; + } + + widget::Theme::Init(); + return sInstance; +} + +// static +void nsXPLookAndFeel::Shutdown() { + if (sShutdown) { + return; + } + + sShutdown = true; + delete sInstance; + sInstance = nullptr; + + // This keeps strings alive, so need to clear to make leak checking happy. + sFontCache.Clear(); + + widget::Theme::Shutdown(); +} + +static void IntPrefChanged(const nsACString& aPref) { + // Most Int prefs can't change our system colors or fonts, but + // ui.systemUsesDarkTheme can, since it affects the effective color-scheme + // (affecting system colors). + auto changeKind = aPref.EqualsLiteral("ui.systemUsesDarkTheme") + ? widget::ThemeChangeKind::Style + : widget::ThemeChangeKind::MediaQueriesOnly; + LookAndFeel::NotifyChangedAllWindows(changeKind); +} + +static void FloatPrefChanged(const nsACString& aPref) { + // Most float prefs can't change our system colors or fonts, but + // textScaleFactor affects layout. + auto changeKind = aPref.EqualsLiteral("ui.textScaleFactor") + ? widget::ThemeChangeKind::StyleAndLayout + : widget::ThemeChangeKind::MediaQueriesOnly; + LookAndFeel::NotifyChangedAllWindows(changeKind); +} + +static void ColorPrefChanged() { + // Color prefs affect style, because they by definition change system colors. + LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style); +} + +static void FontPrefChanged() { + // Color prefs affect style, because they by definition change system fonts. + LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::Style); +} + +// static +void nsXPLookAndFeel::OnPrefChanged(const char* aPref, void* aClosure) { + nsDependentCString prefName(aPref); + for (const char* pref : sIntPrefs) { + if (prefName.Equals(pref)) { + IntPrefChanged(prefName); + return; + } + } + + for (const char* pref : sFloatPrefs) { + if (prefName.Equals(pref)) { + FloatPrefChanged(prefName); + return; + } + } + + for (const char* pref : sColorPrefs) { + // We use StringBeginsWith to handle .dark prefs too. + if (StringBeginsWith(prefName, nsDependentCString(pref))) { + ColorPrefChanged(); + return; + } + } + + for (const char* pref : sFontPrefs) { + if (StringBeginsWith(prefName, nsDependentCString(pref))) { + FontPrefChanged(); + return; + } + } +} + +bool LookAndFeel::WindowsNonNativeMenusEnabled() { + switch (StaticPrefs::browser_display_windows_non_native_menus()) { + case 0: + return false; + case 1: + return true; + default: +#ifdef XP_WIN + return IsWin10OrLater(); +#else + return false; +#endif + } +} + +static constexpr struct { + nsLiteralCString mName; + widget::ThemeChangeKind mChangeKind = + widget::ThemeChangeKind::MediaQueriesOnly; +} kMediaQueryPrefs[] = { + {"browser.display.windows.non_native_menus"_ns}, + // Affects whether standins are used for the accent color. + {"widget.non-native-theme.use-theme-accent"_ns, + widget::ThemeChangeKind::Style}, + // These two affect system colors on Windows. + {"widget.windows.uwp-system-colors.enabled"_ns, + widget::ThemeChangeKind::Style}, + // These two affect system colors on Windows. + {"widget.windows.uwp-system-colors.highlight-accent"_ns, + widget::ThemeChangeKind::Style}, + // Affects env(). + {"layout.css.prefers-color-scheme.content-override"_ns, + widget::ThemeChangeKind::Style}, + // Affects media queries and scrollbar sizes, so gotta relayout. + {"widget.gtk.overlay-scrollbars.enabled"_ns, + widget::ThemeChangeKind::StyleAndLayout}, + // Affects zoom settings which includes text and full zoom. + {"browser.display.os-zoom-behavior"_ns, + widget::ThemeChangeKind::StyleAndLayout}, + // This affects not only the media query, but also the native theme, so we + // need to re-layout. + {"browser.theme.toolbar-theme"_ns, widget::ThemeChangeKind::AllBits}, + {"browser.theme.content-theme"_ns}, + {"layout.css.moz-box-flexbox-emulation.enabled"_ns}, + {"mathml.legacy_maction_and_semantics_implementations.disabled"_ns}, + {"mathml.ms_lquote_rquote_attributes.disabled"_ns}, +}; + +// Read values from the user's preferences. +// This is done once at startup, but since the user's preferences +// haven't actually been read yet at that time, we also have to +// set a callback to inform us of changes to each pref. +void nsXPLookAndFeel::Init() { + MOZ_RELEASE_ASSERT(NS_IsMainThread()); + + // Say we're already initialized, and take the chance that it might fail; + // protects against some other process writing to our static variables. + sInitialized = true; + + RecomputeColorSchemes(); + + // XXX If we could reorganize the pref names, we should separate the branch + // for each types. Then, we could reduce the unnecessary loop from + // nsXPLookAndFeel::OnPrefChanged(). + Preferences::RegisterPrefixCallback(OnPrefChanged, "ui."); + // We really do just want the accessibility.tabfocus pref, not other prefs + // that start with that string. + Preferences::RegisterCallback(OnPrefChanged, "accessibility.tabfocus"); + + for (auto& pref : kMediaQueryPrefs) { + Preferences::RegisterCallback( + [](const char*, void* aChangeKind) { + auto changeKind = + widget::ThemeChangeKind(reinterpret_cast<uintptr_t>(aChangeKind)); + LookAndFeel::NotifyChangedAllWindows(changeKind); + }, + pref.mName, reinterpret_cast<void*>(uintptr_t(pref.mChangeKind))); + } +} + +nsXPLookAndFeel::~nsXPLookAndFeel() { + NS_ASSERTION(sInstance == this, + "This destroying instance isn't the singleton instance"); + sInstance = nullptr; +} + +static bool IsSpecialColor(LookAndFeel::ColorID aID, nscolor aColor) { + using ColorID = LookAndFeel::ColorID; + + if (aColor == NS_SAME_AS_FOREGROUND_COLOR) { + return true; + } + + switch (aID) { + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMESelectedConvertedTextUnderline: + case ColorID::SpellCheckerUnderline: + return NS_IS_SELECTION_SPECIAL_COLOR(aColor); + default: + break; + } + /* + * In GetColor(), every color that is not a special color is color + * corrected. Use false to make other colors color corrected. + */ + return false; +} + +nscolor nsXPLookAndFeel::GetStandinForNativeColor(ColorID aID, + ColorScheme aScheme) { + if (aScheme == ColorScheme::Dark) { + if (auto color = GenericDarkColor(aID)) { + return *color; + } + } + + // The stand-in colors are taken from what the non-native theme needs (for + // field/button colors), the Windows 7 Aero theme except Mac-specific colors + // which are taken from Mac OS 10.7. + +#define COLOR(name_, r, g, b) \ + case ColorID::name_: \ + return NS_RGB(r, g, b); + +#define COLORA(name_, r, g, b, a) \ + case ColorID::name_: \ + return NS_RGBA(r, g, b, a); + + switch (aID) { + // These are here for the purposes of headless mode. + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedConvertedTextBackground: + case ColorID::IMERawInputBackground: + case ColorID::IMEConvertedTextBackground: + return NS_TRANSPARENT; + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedConvertedTextForeground: + case ColorID::IMERawInputForeground: + case ColorID::IMEConvertedTextForeground: + return NS_SAME_AS_FOREGROUND_COLOR; + case ColorID::IMERawInputUnderline: + case ColorID::IMEConvertedTextUnderline: + return NS_40PERCENT_FOREGROUND_COLOR; + case ColorID::Accentcolor: + return widget::sDefaultAccent.ToABGR(); + case ColorID::Accentcolortext: + return widget::sDefaultAccentText.ToABGR(); + COLOR(SpellCheckerUnderline, 0xff, 0x00, 0x00) + COLOR(TextSelectDisabledBackground, 0xaa, 0xaa, 0xaa) + + // CSS 2 colors: + COLOR(Activeborder, 0xB4, 0xB4, 0xB4) + COLOR(Activecaption, 0x99, 0xB4, 0xD1) + COLOR(Appworkspace, 0xAB, 0xAB, 0xAB) + COLOR(Background, 0x00, 0x00, 0x00) + COLOR(Buttonhighlight, 0xFF, 0xFF, 0xFF) + COLOR(Buttonshadow, 0xA0, 0xA0, 0xA0) + + // Buttons and comboboxes should be kept in sync since they are drawn with + // the same colors by the non-native theme. + COLOR(Buttonface, 0xe9, 0xe9, 0xed) + COLORA(MozButtondisabledface, 0xe9, 0xe9, 0xed, 128) + + COLOR(MozCombobox, 0xe9, 0xe9, 0xed) + + COLOR(Buttontext, 0x00, 0x00, 0x00) + COLOR(MozComboboxtext, 0x00, 0x00, 0x00) + + COLOR(Captiontext, 0x00, 0x00, 0x00) + COLOR(Graytext, 0x6D, 0x6D, 0x6D) + COLOR(Highlight, 0x33, 0x99, 0xFF) + COLOR(Highlighttext, 0xFF, 0xFF, 0xFF) + COLOR(Inactiveborder, 0xF4, 0xF7, 0xFC) + COLOR(Inactivecaption, 0xBF, 0xCD, 0xDB) + COLOR(Inactivecaptiontext, 0x43, 0x4E, 0x54) + COLOR(Infobackground, 0xFF, 0xFF, 0xE1) + COLOR(Infotext, 0x00, 0x00, 0x00) + COLOR(Menu, 0xF0, 0xF0, 0xF0) + COLOR(Menutext, 0x00, 0x00, 0x00) + COLOR(Scrollbar, 0xC8, 0xC8, 0xC8) + COLOR(Threeddarkshadow, 0x69, 0x69, 0x69) + COLOR(Threedface, 0xF0, 0xF0, 0xF0) + COLOR(Threedhighlight, 0xFF, 0xFF, 0xFF) + COLOR(Threedlightshadow, 0xE3, 0xE3, 0xE3) + COLOR(Threedshadow, 0xA0, 0xA0, 0xA0) + COLOR(Buttonborder, 0xE3, 0xE3, 0xE3) + COLOR(Mark, 0xFF, 0xFF, 0x00) + COLOR(Marktext, 0x00, 0x00, 0x00) + COLOR(Window, 0xFF, 0xFF, 0xFF) + COLOR(Windowframe, 0x64, 0x64, 0x64) + COLOR(Windowtext, 0x00, 0x00, 0x00) + COLOR(MozButtondefault, 0x69, 0x69, 0x69) + COLOR(Field, 0xFF, 0xFF, 0xFF) + COLORA(MozDisabledfield, 0xFF, 0xFF, 0xFF, 128) + COLOR(Fieldtext, 0x00, 0x00, 0x00) + COLOR(MozDialog, 0xF0, 0xF0, 0xF0) + COLOR(MozDialogtext, 0x00, 0x00, 0x00) + COLOR(MozColheadertext, 0x00, 0x00, 0x00) + COLOR(MozColheaderhovertext, 0x00, 0x00, 0x00) + COLOR(MozDragtargetzone, 0xFF, 0xFF, 0xFF) + COLOR(MozCellhighlight, 0xF0, 0xF0, 0xF0) + COLOR(MozCellhighlighttext, 0x00, 0x00, 0x00) + COLOR(Selecteditem, 0x33, 0x99, 0xFF) + COLOR(Selecteditemtext, 0xFF, 0xFF, 0xFF) + COLOR(MozButtonhoverface, 0xd0, 0xd0, 0xd7) + COLOR(MozButtonhovertext, 0x00, 0x00, 0x00) + COLOR(MozButtonactiveface, 0xb1, 0xb1, 0xb9) + COLOR(MozButtonactivetext, 0x00, 0x00, 0x00) + COLOR(MozMenuhover, 0x33, 0x99, 0xFF) + COLOR(MozMenuhovertext, 0x00, 0x00, 0x00) + COLOR(MozMenubartext, 0x00, 0x00, 0x00) + COLOR(MozMenubarhovertext, 0x00, 0x00, 0x00) + COLOR(MozMenuhoverdisabled, 0xF0, 0xF0, 0xF0) + COLOR(MozEventreerow, 0xFF, 0xFF, 0xFF) + COLOR(MozOddtreerow, 0xFF, 0xFF, 0xFF) + COLOR(MozMacChromeActive, 0xB2, 0xB2, 0xB2) + COLOR(MozMacChromeInactive, 0xE1, 0xE1, 0xE1) + COLOR(MozMacFocusring, 0x60, 0x9D, 0xD7) + COLOR(MozMacMenuselect, 0x38, 0x75, 0xD7) + COLOR(MozMacMenushadow, 0xA3, 0xA3, 0xA3) + COLOR(MozMacMenutextdisable, 0x88, 0x88, 0x88) + COLOR(MozMacMenutextselect, 0xFF, 0xFF, 0xFF) + COLOR(MozMacDisabledtoolbartext, 0x3F, 0x3F, 0x3F) + COLOR(MozMacSecondaryhighlight, 0xD4, 0xD4, 0xD4) + COLOR(MozMacMenupopup, 0xe6, 0xe6, 0xe6) + COLOR(MozMacMenuitem, 0xe6, 0xe6, 0xe6) + COLOR(MozMacActiveMenuitem, 0x0a, 0x64, 0xdc) + COLOR(MozMacSourceList, 0xf7, 0xf7, 0xf7) + COLOR(MozMacSourceListSelection, 0xc8, 0xc8, 0xc8) + COLOR(MozMacActiveSourceListSelection, 0x0a, 0x64, 0xdc) + COLOR(MozMacTooltip, 0xf7, 0xf7, 0xf7) + // Seems to be the default color (hardcoded because of bug 1065998) + COLOR(MozWinMediatext, 0xFF, 0xFF, 0xFF) + COLOR(MozWinCommunicationstext, 0xFF, 0xFF, 0xFF) + COLOR(MozNativehyperlinktext, 0x00, 0x66, 0xCC) + COLOR(MozNativevisitedhyperlinktext, 0x55, 0x1A, 0x8B) + default: + break; + } + return NS_RGB(0xFF, 0xFF, 0xFF); +} + +#undef COLOR +#undef COLORA + +// Taken from in-content/common.inc.css's dark theme. +Maybe<nscolor> nsXPLookAndFeel::GenericDarkColor(ColorID aID) { + nscolor color = NS_RGB(0, 0, 0); + static constexpr nscolor kWindowBackground = NS_RGB(28, 27, 34); + static constexpr nscolor kWindowText = NS_RGB(251, 251, 254); + switch (aID) { + case ColorID::Window: // --in-content-page-background + case ColorID::Background: + color = kWindowBackground; + break; + + case ColorID::Menu: + color = NS_RGB(0x2b, 0x2a, 0x33); + break; + + case ColorID::MozMenuhovertext: + case ColorID::MozMenubarhovertext: + case ColorID::Menutext: + color = NS_RGB(0xfb, 0xfb, 0xfe); + break; + + case ColorID::MozMenuhover: + color = NS_RGB(0x52, 0x52, 0x5e); + break; + + case ColorID::MozMenuhoverdisabled: + color = NS_RGB(0x3a, 0x39, 0x44); + break; + + case ColorID::MozOddtreerow: + case ColorID::MozDialog: // --in-content-box-background + color = NS_RGB(35, 34, 43); + break; + case ColorID::Windowtext: // --in-content-page-color + case ColorID::MozDialogtext: + case ColorID::Fieldtext: + case ColorID::Buttontext: // --in-content-button-text-color (via + // --in-content-page-color) + case ColorID::MozComboboxtext: + case ColorID::MozButtonhovertext: + case ColorID::MozButtonactivetext: + color = kWindowText; + break; + case ColorID::Buttonshadow: + case ColorID::Threedshadow: + case ColorID::Threedlightshadow: + case ColorID::Buttonborder: // --in-content-box-border-color computed + // with kWindowText above + // kWindowBackground. + case ColorID::Graytext: // opacity: 0.4 of kWindowText blended over the + // "Window" background color, which happens to be + // the same :-) + color = NS_ComposeColors(kWindowBackground, NS_RGBA(251, 251, 254, 102)); + break; + case ColorID::MozCellhighlight: + case ColorID::Selecteditem: // --in-content-primary-button-background / + // --in-content-item-selected + color = NS_RGB(0, 221, 255); + break; + case ColorID::Field: + case ColorID::Buttonface: // --in-content-button-background + case ColorID::Threedface: + case ColorID::MozCombobox: + case ColorID::MozCellhighlighttext: + case ColorID::Selecteditemtext: // --in-content-primary-button-text-color / + // --in-content-item-selected-text + color = NS_RGB(43, 42, 51); + break; + case ColorID::Threeddarkshadow: // Same as Threedlightshadow but with the + // background. + case ColorID::MozDisabledfield: // opacity: 0.4 of the face above blended + // over the "Window" background color. + case ColorID::MozButtondisabledface: + color = NS_ComposeColors(kWindowBackground, NS_RGBA(43, 42, 51, 102)); + break; + case ColorID::MozButtonhoverface: // --in-content-button-background-hover + color = NS_RGB(82, 82, 94); + break; + case ColorID::MozButtonactiveface: // --in-content-button-background-active + color = NS_RGB(91, 91, 102); + break; + case ColorID::Highlight: + color = NS_RGBA(0, 221, 255, 78); + break; + case ColorID::Highlighttext: + color = NS_SAME_AS_FOREGROUND_COLOR; + break; + case ColorID::MozNativehyperlinktext: + // If you change this color, you probably also want to change the default + // value of browser.anchor_color.dark. + color = NS_RGB(0x8c, 0x8c, 0xff); + break; + case ColorID::MozNativevisitedhyperlinktext: + // If you change this color, you probably also want to change the default + // value of browser.visited_color.dark. + color = NS_RGB(0xff, 0xad, 0xff); + break; + case ColorID::SpellCheckerUnderline: + // This is the default for active links in dark mode as well + // (browser.active_color.dark). See bug 1755564 for some analysis and + // other options too. + color = NS_RGB(0xff, 0x66, 0x66); + break; + default: + return Nothing(); + } + return Some(color); +} + +// Uncomment the #define below if you want to debug system color use in a skin +// that uses them. When set, it will make all system color pairs that are +// appropriate for foreground/background pairing the same. This means if the +// skin is using system colors correctly you will not be able to see *any* text. +// +// #define DEBUG_SYSTEM_COLOR_USE + +#ifdef DEBUG_SYSTEM_COLOR_USE +static nsresult SystemColorUseDebuggingColor(LookAndFeel::ColorID aID, + nscolor& aResult) { + using ColorID = LookAndFeel::ColorID; + + switch (aID) { + // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors + case ColorID::Activecaption: + // active window caption background + case ColorID::Captiontext: + // text in active window caption + aResult = NS_RGB(0xff, 0x00, 0x00); + break; + + case ColorID::Highlight: + // background of selected item + case ColorID::Highlighttext: + // text of selected item + aResult = NS_RGB(0xff, 0xff, 0x00); + break; + + case ColorID::Inactivecaption: + // inactive window caption + case ColorID::Inactivecaptiontext: + // text in inactive window caption + aResult = NS_RGB(0x66, 0x66, 0x00); + break; + + case ColorID::Infobackground: + // tooltip background color + case ColorID::Infotext: + // tooltip text color + aResult = NS_RGB(0x00, 0xff, 0x00); + break; + + case ColorID::Menu: + // menu background + case ColorID::Menutext: + // menu text + aResult = NS_RGB(0x00, 0xff, 0xff); + break; + + case ColorID::Threedface: + case ColorID::Buttonface: + // 3-D face color + case ColorID::Buttontext: + // text on push buttons + aResult = NS_RGB(0x00, 0x66, 0x66); + break; + + case ColorID::Window: + case ColorID::Windowtext: + aResult = NS_RGB(0x00, 0x00, 0xff); + break; + + // from the CSS3 working draft (not yet finalized) + // http://www.w3.org/tr/2000/wd-css3-userint-20000216.html#color + + case ColorID::Field: + case ColorID::Fieldtext: + aResult = NS_RGB(0xff, 0x00, 0xff); + break; + + case ColorID::MozDialog: + case ColorID::MozDialogtext: + aResult = NS_RGB(0x66, 0x00, 0x66); + break; + + default: + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} +#endif + +static nsresult GetPrefColor(const char* aPref, nscolor& aResult) { + nsAutoCString colorStr; + MOZ_TRY(Preferences::GetCString(aPref, colorStr)); + if (!ServoCSSParser::ComputeColor(nullptr, NS_RGB(0, 0, 0), colorStr, + &aResult)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +static nsresult GetColorFromPref(LookAndFeel::ColorID aID, ColorScheme aScheme, + nscolor& aResult) { + const char* prefName = sColorPrefs[size_t(aID)]; + if (aScheme == ColorScheme::Dark) { + nsAutoCString darkPrefName(prefName); + darkPrefName.Append(".dark"); + if (NS_SUCCEEDED(GetPrefColor(darkPrefName.get(), aResult))) { + return NS_OK; + } + } + return GetPrefColor(prefName, aResult); +} + +// All these routines will return NS_OK if they have a value, +// in which case the nsLookAndFeel should use that value; +// otherwise we'll return NS_ERROR_NOT_AVAILABLE, in which case, the +// platform-specific nsLookAndFeel should use its own values instead. +nsresult nsXPLookAndFeel::GetColorValue(ColorID aID, ColorScheme aScheme, + UseStandins aUseStandins, + nscolor& aResult) { + if (!sInitialized) { + Init(); + } + +#ifdef DEBUG_SYSTEM_COLOR_USE + if (NS_SUCCEEDED(SystemColorUseDebuggingColor(aID, aResult))) { + return NS_OK; + } +#endif + + auto& cache = sColorCaches.Get(aScheme, aUseStandins); + if (const auto* cached = cache.Get(aID)) { + if (cached->isNothing()) { + return NS_ERROR_FAILURE; + } + aResult = cached->value(); + return NS_OK; + } + + // NOTE: Servo holds a lock and the main thread is paused, so writing to the + // global cache here is fine. + auto result = GetUncachedColor(aID, aScheme, aUseStandins); + cache.Insert(aID, result); + if (!result) { + return NS_ERROR_FAILURE; + } + aResult = *result; + return NS_OK; +} + +Maybe<nscolor> nsXPLookAndFeel::GetUncachedColor(ColorID aID, + ColorScheme aScheme, + UseStandins aUseStandins) { + if (aUseStandins == UseStandins::Yes) { + return Some(GetStandinForNativeColor(aID, aScheme)); + } + nscolor r; + if (NS_SUCCEEDED(GetColorFromPref(aID, aScheme, r))) { + return Some(r); + } + if (NS_SUCCEEDED(NativeGetColor(aID, aScheme, r))) { + if (gfxPlatform::GetCMSMode() == CMSMode::All && !IsSpecialColor(aID, r)) { + qcms_transform* transform = gfxPlatform::GetCMSInverseRGBTransform(); + if (transform) { + uint8_t color[4]; + color[0] = NS_GET_R(r); + color[1] = NS_GET_G(r); + color[2] = NS_GET_B(r); + color[3] = NS_GET_A(r); + qcms_transform_data(transform, color, color, 1); + r = NS_RGBA(color[0], color[1], color[2], color[3]); + } + } + + return Some(r); + } + return Nothing(); +} + +nsresult nsXPLookAndFeel::GetIntValue(IntID aID, int32_t& aResult) { + if (!sInitialized) { + Init(); + } + + if (const auto* cached = sIntCache.Get(aID)) { + if (cached->isNothing()) { + return NS_ERROR_FAILURE; + } + aResult = cached->value(); + return NS_OK; + } + + if (NS_SUCCEEDED(Preferences::GetInt(sIntPrefs[size_t(aID)], &aResult))) { + sIntCache.Insert(aID, Some(aResult)); + return NS_OK; + } + + if (NS_FAILED(NativeGetInt(aID, aResult))) { + sIntCache.Insert(aID, Nothing()); + return NS_ERROR_FAILURE; + } + + sIntCache.Insert(aID, Some(aResult)); + return NS_OK; +} + +nsresult nsXPLookAndFeel::GetFloatValue(FloatID aID, float& aResult) { + if (!sInitialized) { + Init(); + } + + if (const auto* cached = sFloatCache.Get(aID)) { + if (cached->isNothing()) { + return NS_ERROR_FAILURE; + } + aResult = cached->value(); + return NS_OK; + } + + int32_t pref = 0; + if (NS_SUCCEEDED(Preferences::GetInt(sFloatPrefs[size_t(aID)], &pref))) { + aResult = float(pref) / 100.0f; + sFloatCache.Insert(aID, Some(aResult)); + return NS_OK; + } + + if (NS_FAILED(NativeGetFloat(aID, aResult))) { + sFloatCache.Insert(aID, Nothing()); + return NS_ERROR_FAILURE; + } + + sFloatCache.Insert(aID, Some(aResult)); + return NS_OK; +} + +bool nsXPLookAndFeel::LookAndFeelFontToStyle(const LookAndFeelFont& aFont, + nsString& aName, + gfxFontStyle& aStyle) { + if (!aFont.haveFont()) { + return false; + } + aName = aFont.name(); + aStyle = gfxFontStyle(); + aStyle.size = aFont.size(); + aStyle.weight = FontWeight::FromInt(aFont.weight()); + aStyle.style = + aFont.italic() ? FontSlantStyle::ITALIC : FontSlantStyle::NORMAL; + aStyle.systemFont = true; + return true; +} + +widget::LookAndFeelFont nsXPLookAndFeel::StyleToLookAndFeelFont( + const nsAString& aName, const gfxFontStyle& aStyle) { + LookAndFeelFont font; + font.haveFont() = true; + font.name() = aName; + font.size() = aStyle.size; + font.weight() = aStyle.weight.ToFloat(); + font.italic() = aStyle.style.IsItalic(); + MOZ_ASSERT(aStyle.style.IsNormal() || aStyle.style.IsItalic(), + "Cannot handle oblique font style"); +#ifdef DEBUG + { + // Assert that all the remaining font style properties have their + // default values. + gfxFontStyle candidate = aStyle; + gfxFontStyle defaults{}; + candidate.size = defaults.size; + candidate.weight = defaults.weight; + candidate.style = defaults.style; + MOZ_ASSERT(candidate.Equals(defaults), + "Some font style properties not supported"); + } +#endif + return font; +} + +bool nsXPLookAndFeel::GetFontValue(FontID aID, nsString& aName, + gfxFontStyle& aStyle) { + if (const LookAndFeelFont* cached = sFontCache.Get(aID)) { + return LookAndFeelFontToStyle(*cached, aName, aStyle); + } + + LookAndFeelFont font; + auto GetFontsFromPrefs = [&]() -> bool { + nsDependentCString pref(sFontPrefs[size_t(aID)]); + if (NS_FAILED(Preferences::GetString(pref.get(), aName))) { + return false; + } + font.haveFont() = true; + font.name() = aName; + font.size() = Preferences::GetFloat(nsAutoCString(pref + ".size"_ns).get()); + // This is written this way rather than using the fallback so that an empty + // pref (such like the one about:config creates) doesn't cause system fonts + // to have zero-size. + if (font.size() < 1.0f) { + font.size() = StyleFONT_MEDIUM_PX; + } + font.weight() = Preferences::GetFloat( + nsAutoCString(pref + ".weight"_ns).get(), FontWeight::NORMAL.ToFloat()); + font.italic() = + Preferences::GetBool(nsAutoCString(pref + ".italic"_ns).get()); + return true; + }; + + if (GetFontsFromPrefs()) { + LookAndFeelFontToStyle(font, aName, aStyle); + } else if (NativeGetFont(aID, aName, aStyle)) { + font = StyleToLookAndFeelFont(aName, aStyle); + } else { + MOZ_ASSERT(!font.haveFont()); + } + bool success = font.haveFont(); + sFontCache.Insert(aID, std::move(font)); + return success; +} + +void nsXPLookAndFeel::RefreshImpl() { + // Wipe out our caches. + sColorCaches.Clear(); + sFontCache.Clear(); + sFloatCache.Clear(); + sIntCache.Clear(); + RecomputeColorSchemes(); + + // Clear any cached FullLookAndFeel data, which is now invalid. + if (XRE_IsParentProcess()) { + widget::RemoteLookAndFeel::ClearCachedData(); + } +} + +static bool sRecordedLookAndFeelTelemetry = false; + +void nsXPLookAndFeel::RecordTelemetry() { + if (!XRE_IsParentProcess()) { + return; + } + + if (sRecordedLookAndFeelTelemetry) { + return; + } + + sRecordedLookAndFeelTelemetry = true; + + int32_t i; + Telemetry::ScalarSet( + Telemetry::ScalarID::WIDGET_DARK_MODE, + NS_SUCCEEDED(GetIntValue(IntID::SystemUsesDarkTheme, i)) && i != 0); + + RecordLookAndFeelSpecificTelemetry(); +} + +namespace mozilla { + +static widget::ThemeChangeKind sGlobalThemeChangeKind{0}; + +void LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind aKind) { + sGlobalThemeChanged = true; + sGlobalThemeChangeKind |= aKind; + + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + const char16_t kind[] = {char16_t(aKind), 0}; + obs->NotifyObservers(nullptr, "internal-look-and-feel-changed", kind); + } +} + +void LookAndFeel::DoHandleGlobalThemeChange() { + MOZ_ASSERT(sGlobalThemeChanged); + sGlobalThemeChanged = false; + auto kind = std::exchange(sGlobalThemeChangeKind, widget::ThemeChangeKind(0)); + + // Tell the theme that it changed, so it can flush any handles to stale theme + // data. + // + // We can use the *DoNotUseDirectly functions directly here, because we want + // to notify all possible themes in a given process (but just once). + if (XRE_IsParentProcess() || + !StaticPrefs::widget_non_native_theme_enabled()) { + if (nsCOMPtr<nsITheme> theme = do_GetNativeThemeDoNotUseDirectly()) { + theme->ThemeChanged(); + } + } + if (nsCOMPtr<nsITheme> theme = do_GetBasicNativeThemeDoNotUseDirectly()) { + theme->ThemeChanged(); + } + + // Clear all cached LookAndFeel colors. + LookAndFeel::Refresh(); + + // Reset default background and foreground colors for the document since they + // may be using system colors. + PreferenceSheet::Refresh(); + + // Vector images (SVG) may be using theme colors so we discard all cached + // surfaces. (We could add a vector image only version of DiscardAll, but + // in bug 940625 we decided theme changes are rare enough not to bother.) + image::SurfaceCacheUtils::DiscardAll(); + + if (XRE_IsParentProcess()) { + dom::ContentParent::BroadcastThemeUpdate(kind); + } + + nsContentUtils::AddScriptRunner( + NS_NewRunnableFunction("HandleGlobalThemeChange", [] { + if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) { + obs->NotifyObservers(nullptr, "look-and-feel-changed", nullptr); + } + })); +} + +#define BIT_FOR(_c) (1ull << size_t(ColorID::_c)) + +// We want to use a non-native color scheme for the non-native theme (except in +// high-contrast mode), so spoof some of the colors with stand-ins to prevent +// lack of contrast. +static constexpr std::bitset<size_t(ColorID::End)> sNonNativeThemeStandinColors{ + // Used by default button styles. + BIT_FOR(Buttonface) | BIT_FOR(Buttontext) | BIT_FOR(MozButtonhoverface) | + BIT_FOR(MozButtonhovertext) | BIT_FOR(MozButtonactiveface) | + BIT_FOR(MozButtonactivetext) | BIT_FOR(MozButtondisabledface) | + BIT_FOR(Buttonborder) | + // Used by select elements. + BIT_FOR(MozCombobox) | BIT_FOR(MozComboboxtext) | + BIT_FOR(Threedlightshadow) | + // For symmetry with the above. + BIT_FOR(Threeddarkshadow) | + // Used by fieldset borders. + BIT_FOR(Threedface) | + // Used by input / textarea. + BIT_FOR(Field) | BIT_FOR(Fieldtext) | + // Used by disabled form controls. + BIT_FOR(MozDisabledfield) | BIT_FOR(Graytext) | + // Some pages expect these to return windows-like colors, see bug 1773795. + // Also, per spec these should match Canvas/CanvasText, see + // https://drafts.csswg.org/css-color-4/#window + BIT_FOR(Window) | BIT_FOR(Windowtext)}; +#undef BIT_FOR + +static bool ShouldUseStandinsForNativeColorForNonNativeTheme( + const dom::Document& aDoc, LookAndFeel::ColorID aColor, + const PreferenceSheet::Prefs& aPrefs) { + const bool shouldUseStandinsForColor = [&] { + if (sNonNativeThemeStandinColors[size_t(aColor)]) { + return true; + } + // There are platforms where we want the content-exposed accent color to be + // the windows blue rather than the system accent color, for now. + return !StaticPrefs::widget_non_native_theme_use_theme_accent() && + (aColor == LookAndFeel::ColorID::Accentcolor || + aColor == LookAndFeel::ColorID::Accentcolortext); + }(); + + return shouldUseStandinsForColor && aDoc.ShouldAvoidNativeTheme() && + !aPrefs.NonNativeThemeShouldBeHighContrast(); +} + +ColorScheme LookAndFeel::sChromeColorScheme; +ColorScheme LookAndFeel::sContentColorScheme; +bool LookAndFeel::sColorSchemeInitialized; +bool LookAndFeel::sGlobalThemeChanged; + +bool LookAndFeel::IsDarkColor(nscolor aColor) { + // Given https://www.w3.org/TR/WCAG20/#contrast-ratiodef, this is the + // threshold that tells us whether contrast is better against white or black. + // + // Contrast ratio against black is: (L + 0.05) / 0.05 + // Contrast ratio against white is: 1.05 / (L + 0.05) + // + // So the intersection is: + // + // (L + 0.05) / 0.05 = 1.05 / (L + 0.05) + // + // And the solution to that equation is: + // + // sqrt(1.05 * 0.05) - 0.05 + // + // So we consider a color dark if the contrast is below this threshold, and + // it's at least half-opaque. + constexpr float kThreshold = 0.179129; + return NS_GET_A(aColor) > 127 && + RelativeLuminanceUtils::Compute(aColor) < kThreshold; +} + +auto LookAndFeel::ColorSchemeSettingForChrome() -> ChromeColorSchemeSetting { + switch (StaticPrefs::browser_theme_toolbar_theme()) { + case 0: // Dark + return ChromeColorSchemeSetting::Dark; + case 1: // Light + return ChromeColorSchemeSetting::Light; + default: + return ChromeColorSchemeSetting::System; + } +} + +ColorScheme LookAndFeel::ThemeDerivedColorSchemeForContent() { + switch (StaticPrefs::browser_theme_content_theme()) { + case 0: // Dark + return ColorScheme::Dark; + case 1: // Light + return ColorScheme::Light; + default: + return SystemColorScheme(); + } +} + +void LookAndFeel::RecomputeColorSchemes() { + sColorSchemeInitialized = true; + + sChromeColorScheme = [] { + switch (ColorSchemeSettingForChrome()) { + case ChromeColorSchemeSetting::Light: + return ColorScheme::Light; + case ChromeColorSchemeSetting::Dark: + return ColorScheme::Dark; + case ChromeColorSchemeSetting::System: + break; + } + return SystemColorScheme(); + }(); + + sContentColorScheme = [] { + switch (StaticPrefs::layout_css_prefers_color_scheme_content_override()) { + case 0: + return ColorScheme::Dark; + case 1: + return ColorScheme::Light; + default: + return ThemeDerivedColorSchemeForContent(); + } + }(); +} + +ColorScheme LookAndFeel::ColorSchemeForStyle( + const dom::Document& aDoc, const StyleColorSchemeFlags& aFlags, + ColorSchemeMode aMode) { + using Choice = PreferenceSheet::Prefs::ColorSchemeChoice; + + const auto& prefs = PreferenceSheet::PrefsFor(aDoc); + switch (prefs.mColorSchemeChoice) { + case Choice::Standard: + break; + case Choice::UserPreferred: + return aDoc.PreferredColorScheme(); + case Choice::Light: + return ColorScheme::Light; + case Choice::Dark: + return ColorScheme::Dark; + } + + StyleColorSchemeFlags style(aFlags); + if (!style) { + style.bits = aDoc.GetColorSchemeBits(); + } + const bool supportsDark = bool(style & StyleColorSchemeFlags::DARK); + const bool supportsLight = bool(style & StyleColorSchemeFlags::LIGHT); + if (supportsLight && supportsDark) { + // Both color-schemes are explicitly supported, use the preferred one. + return aDoc.PreferredColorScheme(); + } + if (supportsDark || supportsLight) { + // One color-scheme is explicitly supported and one isn't, so use the one + // the content supports. + return supportsDark ? ColorScheme::Dark : ColorScheme::Light; + } + // No value specified. Chrome docs always supports both, so use the preferred + // color-scheme. + if (aMode == ColorSchemeMode::Preferred || + nsContentUtils::IsChromeDoc(&aDoc)) { + return aDoc.PreferredColorScheme(); + } + // Default content to light. + return ColorScheme::Light; +} + +LookAndFeel::ColorScheme LookAndFeel::ColorSchemeForFrame( + const nsIFrame* aFrame, ColorSchemeMode aMode) { + return ColorSchemeForStyle(*aFrame->PresContext()->Document(), + aFrame->StyleUI()->mColorScheme.bits, aMode); +} + +// static +Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, ColorScheme aScheme, + UseStandins aUseStandins) { + nscolor result; + nsresult rv = nsLookAndFeel::GetInstance()->GetColorValue( + aId, aScheme, aUseStandins, result); + if (NS_FAILED(rv)) { + return Nothing(); + } + return Some(result); +} + +// Returns whether there is a CSS color name for this color. +static bool ColorIsCSSAccessible(LookAndFeel::ColorID aId) { + using ColorID = LookAndFeel::ColorID; + + switch (aId) { + case ColorID::TextSelectDisabledBackground: + case ColorID::TextSelectAttentionBackground: + case ColorID::TextSelectAttentionForeground: + case ColorID::TextHighlightBackground: + case ColorID::TextHighlightForeground: + case ColorID::ThemedScrollbar: + case ColorID::ThemedScrollbarInactive: + case ColorID::ThemedScrollbarThumb: + case ColorID::ThemedScrollbarThumbActive: + case ColorID::ThemedScrollbarThumbInactive: + case ColorID::ThemedScrollbarThumbHover: + case ColorID::IMERawInputBackground: + case ColorID::IMERawInputForeground: + case ColorID::IMERawInputUnderline: + case ColorID::IMESelectedRawTextBackground: + case ColorID::IMESelectedRawTextForeground: + case ColorID::IMESelectedRawTextUnderline: + case ColorID::IMEConvertedTextBackground: + case ColorID::IMEConvertedTextForeground: + case ColorID::IMEConvertedTextUnderline: + case ColorID::IMESelectedConvertedTextBackground: + case ColorID::IMESelectedConvertedTextForeground: + case ColorID::IMESelectedConvertedTextUnderline: + case ColorID::SpellCheckerUnderline: + return false; + default: + break; + } + + return true; +} + +LookAndFeel::UseStandins LookAndFeel::ShouldUseStandins( + const dom::Document& aDoc, ColorID aId) { + const auto& prefs = PreferenceSheet::PrefsFor(aDoc); + if (ShouldUseStandinsForNativeColorForNonNativeTheme(aDoc, aId, prefs)) { + return UseStandins::Yes; + } + if (prefs.mUseStandins && ColorIsCSSAccessible(aId)) { + return UseStandins::Yes; + } + return UseStandins::No; +} + +Maybe<nscolor> LookAndFeel::GetColor(ColorID aId, const nsIFrame* aFrame) { + const auto* doc = aFrame->PresContext()->Document(); + return GetColor(aId, ColorSchemeForFrame(aFrame), + ShouldUseStandins(*doc, aId)); +} + +// static +nsresult LookAndFeel::GetInt(IntID aID, int32_t* aResult) { + return nsLookAndFeel::GetInstance()->GetIntValue(aID, *aResult); +} + +// static +nsresult LookAndFeel::GetFloat(FloatID aID, float* aResult) { + return nsLookAndFeel::GetInstance()->GetFloatValue(aID, *aResult); +} + +// static +bool LookAndFeel::GetFont(FontID aID, nsString& aName, gfxFontStyle& aStyle) { + return nsLookAndFeel::GetInstance()->GetFontValue(aID, aName, aStyle); +} + +// static +char16_t LookAndFeel::GetPasswordCharacter() { + return nsLookAndFeel::GetInstance()->GetPasswordCharacterImpl(); +} + +// static +bool LookAndFeel::GetEchoPassword() { + if (StaticPrefs::editor_password_mask_delay() >= 0) { + return StaticPrefs::editor_password_mask_delay() > 0; + } + return nsLookAndFeel::GetInstance()->GetEchoPasswordImpl(); +} + +// static +uint32_t LookAndFeel::GetPasswordMaskDelay() { + int32_t delay = StaticPrefs::editor_password_mask_delay(); + if (delay < 0) { + return nsLookAndFeel::GetInstance()->GetPasswordMaskDelayImpl(); + } + return delay; +} + +bool LookAndFeel::DrawInTitlebar() { + switch (StaticPrefs::browser_tabs_inTitlebar()) { + case 0: + return false; + case 1: + return true; + default: + break; + } + return nsLookAndFeel::GetInstance()->GetDefaultDrawInTitlebar(); +} + +void LookAndFeel::GetThemeInfo(nsACString& aOut) { + nsLookAndFeel::GetInstance()->GetThemeInfo(aOut); +} + +// static +void LookAndFeel::Refresh() { + nsLookAndFeel::GetInstance()->RefreshImpl(); + widget::Theme::LookAndFeelChanged(); +} + +// static +void LookAndFeel::NativeInit() { nsLookAndFeel::GetInstance()->NativeInit(); } + +// static +void LookAndFeel::SetData(widget::FullLookAndFeel&& aTables) { + nsLookAndFeel::GetInstance()->SetDataImpl(std::move(aTables)); +} + +} // namespace mozilla |