From cf6484047a7a2d426a0d981342566c3d098cd7b6 Mon Sep 17 00:00:00 2001 From: Eitan Isaacson Date: Wed, 5 Mar 2025 19:18:08 +0000 Subject: [PATCH] Bug 1898096 - Replace color settings with "contrast control". r=emilio,fluent-reviewers,settings-reviewers,accessibility-frontend-reviewers,morgan,bolsson,masayuki,mossop,pdfjs-reviewers,calixte This patch changes the backing prefs by relying on the tristate offered by browser.display.document_color_use instead of browser.display.use_system_colors. This simplifies the color decision tree, and offers a simplified UI. The tristate preference offered to the user is as follows: 1. "Use platform's contrast settings" (document_color_use=0) 2. "Off" - never use HCM, regardless of platform setting (document_color_use=1) 3. "On" - always use HCM, regardless of platform setting (document_color_use=2) Option 3 also reveals a colors UI for the user to choose the palette the browser HCM will use (bg/text/link/visited). There are three color palettes to choose from in light of the preference above they are: 1. WIDGET_COLORS: The OS's configured colors used by its theme. 2. HARDCODED_COLORS: Colors deemed as standard and are hard coded into Firefox (eg. white on black text, blue and purple links). 3. PREFERENCE_COLORS: Colors that are stored in preferences and are configurable from the colors UI (eg. browser.visited_color and browser.display.background_color) The decision over which palette to use is as follows: * If we are styling browser UI -> WIDGET_COLORS * else, if resist fingerprinting is enabled -> HARDCODED_COLORS * else, if document_color_use==0 AND OS HCM is on -> WIDGET_COLORS * else, if document_color_use==2 -> PREFERENCE_COLORS * else -> HARDCODED_COLORS Differential Revision: https://phabricator.services.mozilla.com/D211115 --- accessible/docs/ColorsAndHighContrastMode.md | 29 ++-- accessible/docs/HCMSettings.md | 23 +-- .../browser/mac/browser_attributed_text.js | 76 ++++++++- .../telemetry/browser_HCM_telemetry.js | 152 +++++------------- .../components/preferences/dialogs/colors.js | 2 - .../preferences/dialogs/colors.xhtml | 35 ---- browser/components/preferences/main.inc.xhtml | 51 +++--- browser/components/preferences/main.js | 22 ++- ..._search_subdialogs_within_preferences_5.js | 2 +- .../browser_search_within_preferences_1.js | 2 +- .../tabbrowser/content/tabbrowser.js | 8 +- .../en-US/browser/preferences/colors.ftl | 14 -- .../en-US/browser/preferences/preferences.ftl | 16 +- editor/libeditor/CSSEditUtils.cpp | 22 +-- layout/base/tests/chrome/chrome.toml | 3 - .../chrome/default_background_window.xhtml | 67 -------- .../chrome/test_default_background.xhtml | 22 --- layout/reftests/high-contrast/reftest.list | 2 +- layout/style/PreferenceSheet.cpp | 108 +++++++------ .../pdfjs/test/browser_pdfjs_hcm.js | 17 +- 20 files changed, 264 insertions(+), 409 deletions(-) delete mode 100644 layout/base/tests/chrome/default_background_window.xhtml delete mode 100644 layout/base/tests/chrome/test_default_background.xhtml diff --git a/accessible/docs/ColorsAndHighContrastMode.md b/accessible/docs/ColorsAndHighContrastMode.md index 236f4ed107cf..16d8cc4854f6 100644 --- a/accessible/docs/ColorsAndHighContrastMode.md +++ b/accessible/docs/ColorsAndHighContrastMode.md @@ -1,42 +1,45 @@ # Colors and High Contrast Mode Firefox offers several customisations to improve the accessibility of colors used to render web content and Firefox chrome. This document describes the customisation options available and their behaviour across platforms. It also describes how these options interact with one another. It is intended for developer reference :) -## The Colors Dialog -In `about:preferences > Language and Appearance`, you'll find a button labelled "Colors". This button launches the colors dialog, which contains all of our user-facing color customisation options, as well as stylistic customisations like link underlining. This dialog also contains the select that controls what we'll refer to as "Firefox High Contrast Mode", or FF HCM. FF HCM can be enabled "Always", "Never", or "Only with High Contrast Themes". +## The Contrast Control Settings +In `about:preferences > Language and Appearance`, you'll find a subsection labeled "Contrast Control". The radio group in this section determines if high contrast mode (HCM) will be used. HCM can either be enabled on the platform level (OS HCM), or forced on in the browser (FF HCM) + +The radio buttons alter `browser.display.document_color_use`. HCM can be enabled automatically with OS settings (OS HCM, `document_color_use=0`), it can be forced off (`document_color_use=1`), or forced on (FF HCM, `document_color_use=2`). When HCM is enabled, either automatically or explicitly, web content is rendered with a predetermined palette to give the user full control of the content's color contrast. > Note: FF HCM only affects web content, so changing the option in this select will only alter color usage for web pages. It will not change FF chrome. Current behaviour on chrome pages (ie. `about:` pages) is undefined. -### User-customisable Colors -Users can choose to override background color, foreground color, visited link color, and/or unvisited link color by selecting a new color from the color inputs in the dialog. Modifications to these colors are stored in their corresponding user preference: +### User-customisable Colors Dialog +If the user has chosen to explicitly turn on Firefox HCM (`document_color_use=2`), they may customize the palette in a modal dialog. Users can choose to override background color, foreground color, visited link color, and/or unvisited link color by selecting a new color from the color inputs in the dialog. Modifications to these colors are stored in their corresponding user preference: - `browser.background_color` - `browser.foreground_color` - `browser.visited_color` - `browser.anchor_color` -### Color Usage and System Colors -Before we render any Firefox/web content, we need to select a color palette to render that content _with_. We don't always use the colors a user has selected in the colors dialog. In fact, there are three different sets of colors we can use to style Firefox and/or web content: +## Color Usage and System Colors +Before we render any Firefox/web content, we need to select a color palette to render that content _with_. There are three different sets of colors we can use to style Firefox and/or web content: - Stand-in colors - System colors - Colors-dialog colors -> Note: Web pages may supply their own style sheets, which override a user's chosen color palette. When FF HCM is set to "Always", or set to "With High Contrast Themes" and OS HCM is enabled, the chosen color palette is _forced_, meaning it cannot be overridden by web pages. FF HCM and OS HCM do not directly change the way a color palette is chosen, but they _do_ change how the color palette is used. +> Note: Web pages may supply their palette through style sheets. When FF HCM is set to "On", or set to "Use platform's contrast settings" and OS HCM is enabled, the chosen color palette is _forced_, meaning it cannot be overridden by web pages. We decide which set of colors to use in `PreferenceSheet::Load`. If `resistFingerprinting` is enabled, we use stand-in colors. These colors are pre-defined constants and are not dynamically fetched from the operating system. Check out `nsXPLookAndFeel::GetStandinForNativeColor` for more information, as well as the constants themselves. -If we aren't using stand-in colors, we'll check `browser.display.use_system_colors`, which is set from the "Use system colors" checkbox in the colors dialog. If that pref is true, we'll use system colors to style web content and Firefox chrome. +If we aren't using stand-in colors, we'll check the `browser.display.document_color_use` pref. If HCM is explicitly off (`1`), or automatically off (`0`), we will use the system colors as default text and link colors. System colors are colors queried from the operating system. They help Firefox adapt to OS-level changes that aren't strictly HCM (ie. light/dark themeing). Because these colors are OS-dependent, a user operating Firefox on a Windows machine with system colors enabled will see Firefox differently than a user with system colors enabled on MacOS. So, how do we _get_ system colors? Our style system has a set of pre-defined `ColorID`'s in `ServoStyleConsts.h`, which are mapped to platform-specific colors in `widget/[cocoa | android | windows | gtk]/LookAndFeel.cpp`. Depending on the `ColorID` queried, we may do a dynamic fetch or simply return a constant. On MacOS, for example, `ColorID::TextForeground` and `ColorID::TextBackground` are hard-coded to return black and white respectively. `ColorID::Highlight`, on the other hand, queries the OS for `NSColor.selectedTextBackgroundColor`, which is set based on the accent color a user has selected in System Preferences. > Note: The colors we fetch here are theme-relative. If a user has set their OS to a dark theme, we'll fetch colors from that palette, and likewise for a light theme. Windows HCM, though not strictly a "theme", overrides the colors stored for Windows' light theme, leading to [some confusing code, like this](https://searchfox.org/mozilla-central/rev/b462b11e71b500e084f51e61fbd9e19ea0122c78/layout/style/PreferenceSheet.cpp#202-210). - Lastly, if we are _not_ using system colors AND we are _not_ styling Firefox chrome AND we are _not_ `resistFingerprinting`, we'll use colors-dialog colors to style web content. +Lastly, if we explicitly turn on HCM (`document_color_use=2`) AND we are _not_ styling Firefox chrome AND we are _not_ `resistFingerprinting`, we'll use colors-dialog colors to style web content. - By default, `browser.display.use_system_colors` is true on Windows and false elsewhere. This means users on Windows will _not_ see their selections in the colors dialog reflected automatically in Firefox. They'll need to uncheck "Use system colors" first. - > Note: This is intentional. When Windows HCM is enabled, the system colors Windows exposes are pulled from the chosen HCM theme. With "Use system colors" checked, a Windows HCM user will see their HCM theme choices reflected in Firefox content automatically. Windows HCM is the most robust HCM offered among the operating systems we support, and so we cater to it here :) +By default, `browser.display.document_color_use` is set to `2` on Windows. If a user turns on the OS HCM Firefox will automatically go into HCM mode as well. + > Note: This is intentional. Windows HCM is the most robust HCM offered among the operating systems we support, and so we cater to it here :) -Users on non-Windows platforms will see their selections in the colors dialog reflected automatically, but they will _not_ see OS changes until they check "Use system colors". +Users on non-Windows platforms have HCM disabled by default (`document_color_use=1`). In order to enable Firefox HCM, they will either need to turn +it on explicitly (`document_color_use=2`), or set it to use the OS HCM mode and palette (`document_color_use=0`). -For a simplified flow chart of this decision tree, check out our [HCM Settings page](https://firefox-source-docs.mozilla.org/accessible/HCMSettings.html) +For a simplified flow chart of this decision tree, check out our [HCM Settings page](HCMSettings.html) ## High Contrast Mode diff --git a/accessible/docs/HCMSettings.md b/accessible/docs/HCMSettings.md index 014019828b0a..a0903364cb99 100644 --- a/accessible/docs/HCMSettings.md +++ b/accessible/docs/HCMSettings.md @@ -20,26 +20,17 @@ A -->|No| D ``` ## Settings that control color usage in content -- Colors Dialog (about:preferences > Manage Colors) - - Dropdown with options: Always, Only with High Contrast Themes, and Never - - Use System Colors checkbox - - Text, Background, Visited and Unvisited Link color inputs +- Colors Dialog (about:preferences > Contrast Control) - Extensions like Dark Reader, or changes to user.css, may override author specified colors independent of HCM. ```{mermaid} flowchart TD -A[What is the value of the dropdown in the colors dialog?] -A -->|Always|C +A[Which option is selected in 'Contrast Control'?] -A -->|Only with High Contrast Themes| B[Is a OS HCM enabled?] -B -->|Yes| C[Is the Use System Colors checkbox checked?] -C -->|Yes, and OS HCM is on| D[Use OS HCM colors to render web content] -C -->|Yes, and OS HCM is off| D2[Use OS dark/light colors to render web content] -C-->|No| E[Use colors dialog colors to render web content] +A -->|Use platform's contrast settings| B[Is a OS HCM enabled?] +A -->|Off| H +A --->|On| I[Use colors dialog colors to render all content (HCM)] -B -->|No| F[Is a color-modifying web extension or color-modifying user.css change active?] -F -->|Yes| G[Use web extension/user.css provided colors to render web content] -F -->|No| H[Use author-provided colors to render web content] - -A -->|Never|F +B -->|Yes| C[Use OS colors to render all content (HCM)] +B -->|No| H[Use system colors for all unstyled content] ``` diff --git a/accessible/tests/browser/mac/browser_attributed_text.js b/accessible/tests/browser/mac/browser_attributed_text.js index ef8ed6eb6e4f..13fe640296b9 100644 --- a/accessible/tests/browser/mac/browser_attributed_text.js +++ b/accessible/tests/browser/mac/browser_attributed_text.js @@ -52,14 +52,74 @@ addAccessibleTask( Assert.deepEqual(attributesList, [ // string, fg color, bg color, underline, underline color, heading level, font size, link id, misspelled - ["hello ", "#000000", "#ffffff", null, null, 1, 32, null, null], - ["world", "#0000ee", "#ffffff", 1, "#0000ee", 1, 32, "a1", null], - ["this ", "#000000", "#ffffff", null, null, null, 16, null, null], - ["is", "#ff0000", "#ffff00", null, null, null, 16, null, 1], - [" ", "#000000", "#ffffff", null, null, null, 16, null, null], - ["a", "#000000", "#ffffff", 1, "#008000", null, 16, null, null], - [" ", "#000000", "#ffffff", null, null, null, 16, null, null], - ["test", "#0000ee", "#ffffff", 1, "#0000ee", null, 16, "a2", null], + [ + "hello ", + "#000000", + "#ffffff", + undefined, + undefined, + 1, + 32, + null, + undefined, + ], + ["world", "#0066cc", "#ffffff", 1, "#0066cc", 1, 32, "a1", undefined], + [ + "this ", + "#000000", + "#ffffff", + undefined, + undefined, + undefined, + 16, + null, + undefined, + ], + [ + "is", + "#ff0000", + "#ffff00", + undefined, + undefined, + undefined, + 16, + null, + 1, + ], + [ + " ", + "#000000", + "#ffffff", + undefined, + undefined, + undefined, + 16, + null, + undefined, + ], + ["a", "#000000", "#ffffff", 1, "#008000", undefined, 16, null, undefined], + [ + " ", + "#000000", + "#ffffff", + undefined, + undefined, + undefined, + 16, + null, + undefined, + ], + [ + "test", + "#0066cc", + "#ffffff", + 1, + "#0066cc", + undefined, + 16, + "a2", + undefined, + ], ]); // Test different NSRange parameters for AXAttributedStringForRange diff --git a/accessible/tests/browser/telemetry/browser_HCM_telemetry.js b/accessible/tests/browser/telemetry/browser_HCM_telemetry.js index 60ac3be82fe8..ffee0573472d 100644 --- a/accessible/tests/browser/telemetry/browser_HCM_telemetry.js +++ b/accessible/tests/browser/telemetry/browser_HCM_telemetry.js @@ -26,7 +26,6 @@ function reset() { // state changes. Services.prefs.clearUserPref("browser.display.document_color_use"); Services.prefs.clearUserPref("browser.display.permit_backplate"); - Services.prefs.clearUserPref("browser.display.use_system_colors"); Services.prefs.clearUserPref("layout.css.always_underline_links"); Services.telemetry.clearEvents(); TelemetryTestUtils.assertNumberOfEvents(0); @@ -34,49 +33,15 @@ function reset() { Services.prefs.clearUserPref("browser.display.background_color"); } -async function openColorsDialog() { - await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); - const colorsButton = - gBrowser.selectedBrowser.contentDocument.getElementById("colors"); - - const dialogOpened = promiseLoadSubDialog( - "chrome://browser/content/preferences/dialogs/colors.xhtml" - ); - colorsButton.doCommand(); - - return dialogOpened; -} - -async function closeColorsDialog(dialogWin) { - const dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload"); - const button = dialogWin.document - .getElementById("ColorsDialog") - .getButton("accept"); - button.focus(); - button.doCommand(); - return dialogClosed; -} - function verifyBackplate(expectedValue) { TelemetryTestUtils.assertScalar( - TelemetryTestUtils.getProcessScalars("parent", false, true), + TelemetryTestUtils.getProcessScalars("parent", false, false), "a11y.backplate", expectedValue, "Backplate scalar is logged as " + expectedValue ); } -function verifyUseSystemColors(expectedValue) { - const snapshot = TelemetryTestUtils.getProcessScalars("parent", false, false); - ok("a11y.use_system_colors" in snapshot, "System color usage was logged."); - TelemetryTestUtils.assertScalar( - snapshot, - "a11y.use_system_colors", - expectedValue, - "System colors scalar is logged as " + expectedValue - ); -} - async function verifyAlwaysUnderlineLinks(expectedValue) { let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, false); ok( @@ -116,18 +81,14 @@ async function setBackgroundColor(color) { } add_task(async function testInit() { - const dialogWin = await openColorsDialog(); - const menulistHCM = dialogWin.document.getElementById("useDocumentColors"); + await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); + const contrastControlRadios = + gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastControlSettings" + ); if (AppConstants.platform == "win") { is( - Services.prefs.getBoolPref("browser.display.use_system_colors"), - true, - "Use system colours pref is init'd correctly" - ); - verifyUseSystemColors(true); - - is( - menulistHCM.value, + contrastControlRadios.value, "0", "HCM menulist should be set to only with HCM theme on startup for windows" ); @@ -141,14 +102,7 @@ add_task(async function testInit() { ); } else { is( - Services.prefs.getBoolPref("browser.display.use_system_colors"), - false, - "Use system colours pref is init'd correctly" - ); - verifyUseSystemColors(false); - - is( - menulistHCM.value, + contrastControlRadios.value, "1", "HCM menulist should be set to never on startup for non-windows platforms" ); @@ -161,8 +115,6 @@ add_task(async function testInit() { false ); - await closeColorsDialog(dialogWin); - // We should not have logged any colors let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true); ok( @@ -194,16 +146,19 @@ add_task(async function testInit() { }); add_task(async function testSetAlways() { - const dialogWin = await openColorsDialog(); - const menulistHCM = dialogWin.document.getElementById("useDocumentColors"); + await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); + const contrastControlRadios = + gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastControlSettings" + ); - menulistHCM.doCommand(); - const newOption = dialogWin.document.getElementById("documentColorAlways"); + const newOption = + gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastSettingsOn" + ); newOption.click(); - is(menulistHCM.value, "2", "HCM menulist should be set to always"); - - await closeColorsDialog(dialogWin); + is(contrastControlRadios.value, "2", "HCM menulist should be set to always"); // Verify correct initial value let snapshot = TelemetryTestUtils.getProcessScalars("parent", true, true); @@ -214,42 +169,31 @@ add_task(async function testSetAlways() { testIsWhite("a11y.HCM_background", snapshot); testIsBlack("a11y.HCM_foreground", snapshot); - // If we change the colors, our probes update on non-windows platforms. - // On windows, useSystemColors is on by default, and so the values we set here - // will not be written to our telemetry probes, because they capture - // used colors, not the values of browser.foreground/background_color directly. - setBackgroundColor("#000000"); snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true); - if (AppConstants.platform == "win") { - testIsWhite("a11y.HCM_background", snapshot); - } else { - testIsBlack("a11y.HCM_background", snapshot); - } + testIsBlack("a11y.HCM_background", snapshot); setForegroundColor("#ffffff"); snapshot = TelemetryTestUtils.getProcessScalars("parent", false, true); - if (AppConstants.platform == "win") { - testIsBlack("a11y.HCM_foreground", snapshot); - } else { - testIsWhite("a11y.HCM_foreground", snapshot); - } + testIsWhite("a11y.HCM_foreground", snapshot); reset(); gBrowser.removeCurrentTab(); }); add_task(async function testSetDefault() { - const dialogWin = await openColorsDialog(); - const menulistHCM = dialogWin.document.getElementById("useDocumentColors"); + await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); + const contrastControlRadios = + gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastControlSettings" + ); - menulistHCM.doCommand(); - const newOption = dialogWin.document.getElementById("documentColorAutomatic"); + const newOption = gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastSettingsAuto" + ); newOption.click(); - is(menulistHCM.value, "0", "HCM menulist should be set to default"); - - await closeColorsDialog(dialogWin); + is(contrastControlRadios.value, "0", "HCM menulist should be set to default"); // Verify correct initial value TelemetryTestUtils.assertKeyedScalar( @@ -289,16 +233,18 @@ add_task(async function testSetDefault() { }); add_task(async function testSetNever() { - const dialogWin = await openColorsDialog(); - const menulistHCM = dialogWin.document.getElementById("useDocumentColors"); + await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); + const contrastControlRadios = + gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastControlSettings" + ); - menulistHCM.doCommand(); - const newOption = dialogWin.document.getElementById("documentColorNever"); + const newOption = gBrowser.selectedBrowser.contentDocument.getElementById( + "contrastSettingsOff" + ); newOption.click(); - is(menulistHCM.value, "1", "HCM menulist should be set to never"); - - await closeColorsDialog(dialogWin); + is(contrastControlRadios.value, "1", "HCM menulist should be set to never"); // Verify correct initial value TelemetryTestUtils.assertKeyedScalar( @@ -351,30 +297,8 @@ add_task(async function testBackplate() { Services.prefs.setBoolPref("browser.display.permit_backplate", true); // Verify correct recorded value verifyBackplate(true); -}); - -add_task(async function testSystemColors() { - let expectedInitVal = false; - if (AppConstants.platform == "win") { - expectedInitVal = true; - } - - const dialogWin = await openColorsDialog(); - const checkbox = dialogWin.document.getElementById("browserUseSystemColors"); - checkbox.click(); - - is( - checkbox.checked, - !expectedInitVal, - "System colors checkbox should be modified" - ); - - await closeColorsDialog(dialogWin); - - verifyUseSystemColors(!expectedInitVal); reset(); - gBrowser.removeCurrentTab(); }); add_task(async function testAlwaysUnderlineLinks() { diff --git a/browser/components/preferences/dialogs/colors.js b/browser/components/preferences/dialogs/colors.js index ab9b9f6f0e50..141fe24104dc 100644 --- a/browser/components/preferences/dialogs/colors.js +++ b/browser/components/preferences/dialogs/colors.js @@ -13,10 +13,8 @@ document .addEventListener("command", event => Preferences.close(event)); Preferences.addAll([ - { id: "browser.display.document_color_use", type: "int" }, { id: "browser.anchor_color", type: "string" }, { id: "browser.visited_color", type: "string" }, { id: "browser.display.foreground_color", type: "string" }, { id: "browser.display.background_color", type: "string" }, - { id: "browser.display.use_system_colors", type: "bool" }, ]); diff --git a/browser/components/preferences/dialogs/colors.xhtml b/browser/components/preferences/dialogs/colors.xhtml index 2bb5fe78551e..970ea8bda245 100644 --- a/browser/components/preferences/dialogs/colors.xhtml +++ b/browser/components/preferences/dialogs/colors.xhtml @@ -65,14 +65,6 @@ preference="browser.display.background_color" /> - - - - @@ -106,33 +98,6 @@ -