Skip to content

Commit

Permalink
Bug 1762018 - Derive a color scheme when forcing preference colors. r…
Browse files Browse the repository at this point in the history
…=morgan

This patch is a bit bigger than I'd like, but it's mostly moving code
around to centralize the color/color-scheme decisions we make when
forcing colors.

In practice, the only behavior change should be that when "use system
colors" is false and we force colors, we force a color-scheme that
matches the user-chosen background (via the LookAndFeel::IsDarkColor
check).

That should make sure that text from system colors is light and matches
the user expectations.

Before this patch, we used to force the color-scheme to light, but that
was just so that we ended up looking at mLightColors. Instead, we
achieve that via a separate bit (mForcedLightColorSet, naming up for
debate, not a fan), so that we can use the right system colors
otherwise.

Another alternative I considered is making all non-link system colors
return mDefaultBackground / mDefault depending on whether they are
background / foreground colors. That seemed a lot more work and
potentially a regression in various ways. I think this should be
strictly an improvement instead.

Differential Revision: https://phabricator.services.mozilla.com/D142423
  • Loading branch information
emilio committed Apr 1, 2022
1 parent 44e031f commit e470240
Show file tree
Hide file tree
Showing 9 changed files with 125 additions and 88 deletions.
10 changes: 0 additions & 10 deletions dom/base/nsContentUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
3 changes: 0 additions & 3 deletions dom/base/nsContentUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions layout/base/nsPresContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ static const char* gExactCallbackPrefs[] = {
"layout.css.dpi",
"privacy.resistFingerprinting",
"privacy.trackingprotection.enabled",
"ui.use_standins_for_native_colors",
nullptr,
};

Expand Down
78 changes: 59 additions & 19 deletions layout/style/PreferenceSheet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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
Expand All @@ -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(
Expand Down Expand Up @@ -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() {
Expand All @@ -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;
Expand Down
24 changes: 22 additions & 2 deletions layout/style/PreferenceSheet.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions widget/LookAndFeel.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ class LookAndFeel {
: ColorScheme::Light;
}

static bool IsDarkColor(nscolor);

enum class ChromeColorSchemeSetting { Light, Dark, System };
static ChromeColorSchemeSetting ColorSchemeSettingForChrome();
static ColorScheme ThemeDerivedColorSchemeForContent();
Expand Down
25 changes: 1 addition & 24 deletions widget/nsNativeTheme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
#include "mozilla/PresShell.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/RelativeLuminanceUtils.h"
#include <algorithm>

using namespace mozilla;
Expand Down Expand Up @@ -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 <tree> there
Expand Down Expand Up @@ -619,7 +596,7 @@ bool nsNativeTheme::IsDarkBackground(nsIFrame* aFrame) {
}
}

return IsDarkColor(color);
return LookAndFeel::IsDarkColor(color);
}

/*static*/
Expand Down
1 change: 0 additions & 1 deletion widget/nsNativeTheme.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
69 changes: 40 additions & 29 deletions widget/nsXPLookAndFeel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1134,9 +1136,7 @@ static bool ShouldUseStandinsForNativeColorForNonNativeTheme(
case ColorID::Fieldtext:

case ColorID::Graytext:

return !PreferenceSheet::PrefsFor(aDoc)
.NonNativeThemeShouldBeHighContrast();
return !aPrefs.NonNativeThemeShouldBeHighContrast();

default:
break;
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit e470240

Please sign in to comment.