diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index 9fec60c8d8264..53d8b3afbe2e4 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -2319,16 +2319,6 @@ bool nsContentUtils::ShouldResistFingerprinting( return !isExemptDomain; } -/* static */ -bool nsContentUtils::UseStandinsForNativeColors() { - return ShouldResistFingerprinting( - "we want to have consistent colors across the browser if RFP is " - "enabled, so we check the global preference" - "not excluding chrome browsers or webpages, so we call the legacy " - "RFP function to prevent that") || - StaticPrefs::ui_use_standins_for_native_colors(); -} - /* static */ void nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting( int32_t aChromeWidth, int32_t aChromeHeight, int32_t aScreenWidth, diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 5af161619f8a8..fc05b99e9e016 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -369,9 +369,6 @@ class nsContentUtils { */ static bool ShouldResistFingerprinting(const char* aJustification); - // Prevent system colors from being exposed to CSS or canvas. - static bool UseStandinsForNativeColors(); - // A helper function to calculate the rounded window size for fingerprinting // resistance. The rounded size is based on the chrome UI size and available // screen size. If the inputWidth/Height is greater than the available content diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp index f3638ba648bb5..bf04669f797a8 100644 --- a/layout/base/nsPresContext.cpp +++ b/layout/base/nsPresContext.cpp @@ -330,6 +330,7 @@ static const char* gExactCallbackPrefs[] = { "layout.css.dpi", "privacy.resistFingerprinting", "privacy.trackingprotection.enabled", + "ui.use_standins_for_native_colors", nullptr, }; diff --git a/layout/style/PreferenceSheet.cpp b/layout/style/PreferenceSheet.cpp index df8db108e490a..08b4083141bef 100644 --- a/layout/style/PreferenceSheet.cpp +++ b/layout/style/PreferenceSheet.cpp @@ -76,22 +76,26 @@ auto PreferenceSheet::PrefsKindFor(const Document& aDoc) -> PrefsKind { return PrefsKind::Content; } -static bool UseAccessibilityTheme(bool aIsChrome) { - return !aIsChrome && - !!LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme, 0); -} - -static bool UseDocumentColors(bool aIsChrome, bool aUseAcccessibilityTheme) { +static bool UseDocumentColors(bool aUseAcccessibilityTheme) { switch (StaticPrefs::browser_display_document_color_use()) { case 1: return true; case 2: - return aIsChrome; + return false; default: return !aUseAcccessibilityTheme; } } +static bool UseStandinsForNativeColors() { + return nsContentUtils::ShouldResistFingerprinting( + "we want to have consistent colors across the browser if RFP is " + "enabled, so we check the global preference" + "not excluding chrome browsers or webpages, so we call the legacy " + "RFP function to prevent that") || + StaticPrefs::ui_use_standins_for_native_colors(); +} + void PreferenceSheet::Prefs::LoadColors(bool aIsLight) { auto& colors = aIsLight ? mLightColors : mDarkColors; @@ -102,13 +106,6 @@ void PreferenceSheet::Prefs::LoadColors(bool aIsLight) { std::swap(colors.mDefault, colors.mDefaultBackground); } - const bool useStandins = nsContentUtils::UseStandinsForNativeColors(); - // Users should be able to choose to use system colors or preferred colors - // when HCM is disabled, and in both OS-level HCM and FF-level HCM. - // To make this possible, we don't consider UseDocumentColors and - // mUseAccessibilityTheme when computing the following bool. - const bool usePrefColors = !useStandins && !mIsChrome && - !StaticPrefs::browser_display_use_system_colors(); const auto scheme = aIsLight ? ColorScheme::Light : ColorScheme::Dark; // Link colors might be provided by the OS, but they might not be. If they are @@ -119,13 +116,16 @@ void PreferenceSheet::Prefs::LoadColors(bool aIsLight) { GetColor("browser.active_color", scheme, colors.mActiveLink); GetColor("browser.visited_color", scheme, colors.mVisitedLink); - if (usePrefColors) { + // Historically we've given more weight to the "use standins" setting than the + // "use system colors" one. In practice most users don't use standins because + // it's hidden behind prefs. + if (mUsePrefColors && !mUseStandins) { GetColor("browser.display.background_color", scheme, colors.mDefaultBackground); GetColor("browser.display.foreground_color", scheme, colors.mDefault); } else { using ColorID = LookAndFeel::ColorID; - const auto standins = LookAndFeel::UseStandins(useStandins); + const auto standins = LookAndFeel::UseStandins(mUseStandins); colors.mDefault = LookAndFeel::Color(ColorID::Windowtext, scheme, standins, colors.mDefault); colors.mDefaultBackground = LookAndFeel::Color( @@ -181,11 +181,49 @@ void PreferenceSheet::Prefs::Load(bool aIsChrome) { *this = {}; mIsChrome = aIsChrome; - mUseAccessibilityTheme = UseAccessibilityTheme(aIsChrome); + + // Chrome documents always use system colors, not stand-ins, not forced, etc. + if (!aIsChrome) { + mUseAccessibilityTheme = + LookAndFeel::GetInt(LookAndFeel::IntID::UseAccessibilityTheme); + mUseDocumentColors = UseDocumentColors(mUseAccessibilityTheme); + mUsePrefColors = !StaticPrefs::browser_display_use_system_colors(); + mUseStandins = UseStandinsForNativeColors(); + } LoadColors(true); LoadColors(false); - mUseDocumentColors = UseDocumentColors(aIsChrome, mUseAccessibilityTheme); + + mColorSchemeChoice = [&] { + // When not forcing colors, we use the standard mechanism, the document is + // in control taking into account user preferences if it wishes to. + if (mUseDocumentColors) { + return ColorSchemeChoice::Standard; + } +#ifdef XP_WIN + // Windows overrides the light colors with the HCM colors when HCM is + // active, so make sure to always use the light system colors in that case. + // For the same reason, we need to force the light color set to be used. + if (mUseAccessibilityTheme) { + mMustUseLightColorSet = true; + return ColorSchemeChoice::Light; + } +#endif + // When forcing preference colors, we derive a color-scheme from those. That + // way we can use the system colors more appropriately suited for the user, + // avoiding contrast issues like bug 1762018. + if (mUsePrefColors) { + // We need to forcibly use the light color-set for this case too, as those + // are the colors exposed to the user in the preferences page. + mMustUseLightColorSet = true; + return LookAndFeel::IsDarkColor(mLightColors.mDefaultBackground) + ? ColorSchemeChoice::Dark + : ColorSchemeChoice::Light; + } + // When using system colors, we can generally just use the preferred color + // scheme of the document unconditionally (modulo the HCM case above). + return ColorSchemeChoice::UserPreferred; + }(); } void PreferenceSheet::Initialize() { @@ -205,8 +243,10 @@ void PreferenceSheet::Initialize() { // despite having made it into mLightColors), because it both wastes ink and // it might interact poorly with the color adjustments we do while printing. // - // So we override the light colors with our hardcoded default colors. + // So we override the light colors with our hardcoded default colors, and + // force the use of stand-ins. sPrintPrefs.mLightColors = Prefs().mLightColors; + sPrintPrefs.mUseStandins = true; } nsAutoString useDocumentColorPref; diff --git a/layout/style/PreferenceSheet.h b/layout/style/PreferenceSheet.h index 3001254ef1218..3b3c2f70b7259 100644 --- a/layout/style/PreferenceSheet.h +++ b/layout/style/PreferenceSheet.h @@ -33,13 +33,33 @@ struct PreferenceSheet { } mLightColors, mDarkColors; const Colors& ColorsFor(ColorScheme aScheme) const { - return aScheme == ColorScheme::Light ? mLightColors : mDarkColors; + return mMustUseLightColorSet || aScheme == ColorScheme::Light + ? mLightColors + : mDarkColors; } bool mIsChrome = false; bool mUseAccessibilityTheme = false; - bool mUseDocumentColors = true; + bool mUsePrefColors = false; + bool mUseStandins = false; + bool mMustUseLightColorSet = false; + + // Sometimes we can force a color scheme on a document, or honor the + // preferred color-scheme in more cases, depending on whether we're forcing + // colors or not. + enum class ColorSchemeChoice : uint8_t { + // We're not forcing colors, use standard algorithm based on specified + // style and meta tags and so on. + Standard, + // We can honor whatever the preferred color-scheme for the document is + // (the preferred color-scheme of the user, since we're forcing colors). + UserPreferred, + Light, + Dark, + }; + + ColorSchemeChoice mColorSchemeChoice = ColorSchemeChoice::Standard; // Whether the non-native theme should use real system colors for widgets. bool NonNativeThemeShouldBeHighContrast() const; diff --git a/widget/LookAndFeel.h b/widget/LookAndFeel.h index 051b3962c33a1..9fe38d6329197 100644 --- a/widget/LookAndFeel.h +++ b/widget/LookAndFeel.h @@ -397,6 +397,8 @@ class LookAndFeel { : ColorScheme::Light; } + static bool IsDarkColor(nscolor); + enum class ChromeColorSchemeSetting { Light, Dark, System }; static ChromeColorSchemeSetting ColorSchemeSettingForChrome(); static ColorScheme ThemeDerivedColorSchemeForContent(); diff --git a/widget/nsNativeTheme.cpp b/widget/nsNativeTheme.cpp index deefead7bc7db..1217277e72dfd 100644 --- a/widget/nsNativeTheme.cpp +++ b/widget/nsNativeTheme.cpp @@ -31,7 +31,6 @@ #include "mozilla/PresShell.h" #include "mozilla/StaticPrefs_layout.h" #include "mozilla/dom/DocumentInlines.h" -#include "mozilla/RelativeLuminanceUtils.h" #include using namespace mozilla; @@ -556,28 +555,6 @@ static nsIFrame* GetBodyFrame(nsIFrame* aCanvasFrame) { return body->GetPrimaryFrame(); } -bool nsNativeTheme::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; -} - /* static */ bool nsNativeTheme::IsDarkBackground(nsIFrame* aFrame) { // Try to find the scrolled frame. Note that for stuff like xul there @@ -619,7 +596,7 @@ bool nsNativeTheme::IsDarkBackground(nsIFrame* aFrame) { } } - return IsDarkColor(color); + return LookAndFeel::IsDarkColor(color); } /*static*/ diff --git a/widget/nsNativeTheme.h b/widget/nsNativeTheme.h index 9b835f5a2bf2d..9088e07da893b 100644 --- a/widget/nsNativeTheme.h +++ b/widget/nsNativeTheme.h @@ -156,7 +156,6 @@ class nsNativeTheme : public nsITimerCallback, public nsINamed { bool IsRangeHorizontal(nsIFrame* aFrame); static bool IsDarkBackground(nsIFrame*); - static bool IsDarkColor(nscolor aColor); static bool IsWidgetScrollbarPart(mozilla::StyleAppearance); diff --git a/widget/nsXPLookAndFeel.cpp b/widget/nsXPLookAndFeel.cpp index ea7af265522d4..e65e1574fb0bd 100644 --- a/widget/nsXPLookAndFeel.cpp +++ b/widget/nsXPLookAndFeel.cpp @@ -32,6 +32,7 @@ #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" @@ -1104,7 +1105,8 @@ void LookAndFeel::DoHandleGlobalThemeChange() { } static bool ShouldUseStandinsForNativeColorForNonNativeTheme( - const dom::Document& aDoc, LookAndFeel::ColorID aColor) { + const dom::Document& aDoc, LookAndFeel::ColorID aColor, + const PreferenceSheet::Prefs& aPrefs) { using ColorID = LookAndFeel::ColorID; if (!aDoc.ShouldAvoidNativeTheme()) { return false; @@ -1134,9 +1136,7 @@ static bool ShouldUseStandinsForNativeColorForNonNativeTheme( case ColorID::Fieldtext: case ColorID::Graytext: - - return !PreferenceSheet::PrefsFor(aDoc) - .NonNativeThemeShouldBeHighContrast(); + return !aPrefs.NonNativeThemeShouldBeHighContrast(); default: break; @@ -1150,6 +1150,28 @@ 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 @@ -1203,25 +1225,18 @@ void LookAndFeel::RecomputeColorSchemes() { ColorScheme LookAndFeel::ColorSchemeForStyle( const dom::Document& aDoc, const StyleColorSchemeFlags& aFlags) { - if (PreferenceSheet::MayForceColors()) { - auto& prefs = PreferenceSheet::PrefsFor(aDoc); - if (!prefs.mUseDocumentColors) { - // When forcing colors, we can use our preferred color-scheme. Do this - // only if we're using system colors, as dark preference colors are not - // exposed on the UI. - // - // Also, use light if we're using a high-contrast-theme on Windows, since - // Windows overrides the light colors with HCM colors when HCM is active. -#ifdef XP_WIN - if (prefs.mUseAccessibilityTheme) { - return ColorScheme::Light; - } -#endif - if (StaticPrefs::browser_display_use_system_colors()) { - return aDoc.PreferredColorScheme(); - } + 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); @@ -1305,15 +1320,11 @@ static bool ColorIsCSSAccessible(LookAndFeel::ColorID aId) { LookAndFeel::UseStandins LookAndFeel::ShouldUseStandins( const dom::Document& aDoc, ColorID aId) { - if (ShouldUseStandinsForNativeColorForNonNativeTheme(aDoc, aId)) { - return UseStandins::Yes; - } - if (nsContentUtils::UseStandinsForNativeColors() && - ColorIsCSSAccessible(aId) && !nsContentUtils::IsChromeDoc(&aDoc)) { + const auto& prefs = PreferenceSheet::PrefsFor(aDoc); + if (ShouldUseStandinsForNativeColorForNonNativeTheme(aDoc, aId, prefs)) { return UseStandins::Yes; } - if (aDoc.IsStaticDocument() && - !PreferenceSheet::ContentPrefs().mUseDocumentColors) { + if (prefs.mUseStandins && ColorIsCSSAccessible(aId)) { return UseStandins::Yes; } return UseStandins::No;