tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3ccdf3d2bbdfe4e58c6a612b7a7c823cc9bc57ef
parent 64b002161e1dc7e36e55d9f101970d07083aea3c
Author: Anna Yeddi <ayeddi@mozilla.com>
Date:   Mon, 27 Oct 2025 15:03:25 +0000

Bug 1976118 - Replacing default color pickers on Colors Dialog with moz-input-color components. r=eeejay,fluent-reviewers,bolsson,desktop-theme-reviewers,hjones

Adding basic test coverage for the Colors sub-dialog in the Settings UI, while relying on the `moz-input-color` reusable component to test the input's functionality.

Differential Revision: https://phabricator.services.mozilla.com/D258062

Diffstat:
Abrowser/components/preferences/dialogs/colors.css | 12++++++++++++
Mbrowser/components/preferences/dialogs/colors.xhtml | 65++++++++++++++++++++++++++++++++---------------------------------
Mbrowser/components/preferences/dialogs/jar.mn | 1+
Mbrowser/components/preferences/main.inc.xhtml | 8++++----
Mbrowser/components/preferences/preferences.xhtml | 1+
Mbrowser/components/preferences/tests/browser.toml | 2++
Abrowser/components/preferences/tests/browser_colors_dialog.js | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/locales/en-US/browser/preferences/colors.ftl | 12++++++++----
Apython/l10n/fluent_migrations/bug_1976118_colors_dialog_with_moz_input_color_pickers.py | 33+++++++++++++++++++++++++++++++++
9 files changed, 313 insertions(+), 41 deletions(-)

diff --git a/browser/components/preferences/dialogs/colors.css b/browser/components/preferences/dialogs/colors.css @@ -0,0 +1,12 @@ +/* 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/. */ + +#custom-colors, +#custom-colors .color-container { + gap: var(--space-small); +} + +#custom-colors .color-picker { + width: 100%; +} diff --git a/browser/components/preferences/dialogs/colors.xhtml b/browser/components/preferences/dialogs/colors.xhtml @@ -22,6 +22,10 @@ rel="stylesheet" href="chrome://browser/skin/preferences/preferences.css" /> + <html:link + rel="stylesheet" + href="chrome://browser/content/preferences/dialogs/colors.css" + /> <html:link rel="localization" href="browser/preferences/colors.ftl" /> </linkset> @@ -33,63 +37,58 @@ <key id="key_close" data-l10n-id="colors-close-key" modifiers="accel" /> </keyset> - <hbox> - <groupbox flex="1"> + <hbox id="custom-colors"> + <groupbox flex="1" class="color-container"> <label ><html:h2 class="heading-medium" data-l10n-id="colors-text-and-background" /></label> - <hbox align="center"> - <label - data-l10n-id="colors-text-header" + <hbox align="center" class="color-row"> + <html:moz-input-color + value="CanvasText" + data-l10n-id="colors-text" control="foregroundtextmenu" - /> - <spacer flex="1" /> - <html:input - type="color" id="foregroundtextmenu" + class="color-picker" preference="browser.display.foreground_color" - /> + ></html:moz-input-color> </hbox> - <hbox align="center" style="margin-top: 5px"> - <label data-l10n-id="colors-background" control="backgroundmenu" /> - <spacer flex="1" /> - <html:input - type="color" + <hbox align="center" class="color-row"> + <html:moz-input-color + value="Canvas" + data-l10n-id="colors-text-background" + control="backgroundmenu" id="backgroundmenu" + class="color-picker" preference="browser.display.background_color" - /> + ></html:moz-input-color> </hbox> </groupbox> - <groupbox flex="1"> + <groupbox flex="1" class="color-container"> <label ><html:h2 class="heading-medium" data-l10n-id="colors-links-header" /></label> - <hbox align="center"> - <label - data-l10n-id="colors-unvisited-links" + <hbox align="center" class="color-row"> + <html:moz-input-color + value="LinkText" + data-l10n-id="colors-links-unvisited" control="unvisitedlinkmenu" - /> - <spacer flex="1" /> - <html:input - type="color" id="unvisitedlinkmenu" + class="color-picker" preference="browser.anchor_color" - /> + ></html:moz-input-color> </hbox> - <hbox align="center" style="margin-top: 5px"> - <label - data-l10n-id="colors-visited-links" + <hbox align="center" class="color-row"> + <html:moz-input-color + value="VisitedText" + data-l10n-id="colors-links-visited" control="visitedlinkmenu" - /> - <spacer flex="1" /> - <html:input - type="color" id="visitedlinkmenu" + class="color-picker" preference="browser.visited_color" - /> + ></html:moz-input-color> </hbox> </groupbox> </hbox> diff --git a/browser/components/preferences/dialogs/jar.mn b/browser/components/preferences/dialogs/jar.mn @@ -10,6 +10,7 @@ browser.jar: content/browser/preferences/dialogs/clearSiteData.css content/browser/preferences/dialogs/clearSiteData.js content/browser/preferences/dialogs/clearSiteData.xhtml + content/browser/preferences/dialogs/colors.css content/browser/preferences/dialogs/colors.xhtml content/browser/preferences/dialogs/colors.js content/browser/preferences/dialogs/connection.xhtml diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml @@ -192,11 +192,11 @@ data-l10n-id="preferences-colors-manage-button" search-l10n-ids=" colors-text-and-background, - colors-text-header, - colors-background, + colors-text, + colors-text-background, colors-links-header, - colors-unvisited-links, - colors-visited-links + colors-links-unvisited, + colors-links-visited "/> </vbox> </radiogroup> diff --git a/browser/components/preferences/preferences.xhtml b/browser/components/preferences/preferences.xhtml @@ -97,6 +97,7 @@ <script type="module" src="chrome://browser/content/preferences/widgets/setting-group.mjs"></script> <script type="module" src="chrome://browser/content/preferences/widgets/setting-control.mjs"></script> <script type="module" src="chrome://browser/content/preferences/widgets/security-privacy-card.mjs"></script> + <script type="module" src="chrome://global/content/elements/moz-input-color.mjs"></script> </head> <html:body xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" diff --git a/browser/components/preferences/tests/browser.toml b/browser/components/preferences/tests/browser.toml @@ -55,6 +55,8 @@ run-if = ["os == 'win'"] # Windows-specific handler application selection dialog ["browser_checkspelling.js"] +["browser_colors_dialog.js"] + ["browser_connection.js"] ["browser_connection_bug388287.js"] diff --git a/browser/components/preferences/tests/browser_colors_dialog.js b/browser/components/preferences/tests/browser_colors_dialog.js @@ -0,0 +1,220 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +async function launchPreferences() { + let prefs = await openPreferencesViaOpenPreferencesAPI("paneGeneral", { + leaveOpen: true, + }); + Assert.equal(prefs.selectedPane, "paneGeneral", "General pane was selected"); +} + +async function reset() { + // We have to add this in because the initial state of `document_color_use` + // affects the initial state of color preferenced which can change the + // starting color values. + Services.prefs.clearUserPref("browser.anchor_color"); + Services.prefs.clearUserPref("browser.visited_color"); + Services.prefs.clearUserPref("browser.display.foreground_color"); + Services.prefs.clearUserPref("browser.display.background_color"); +} + +add_setup(async function () { + info("Setting the test up"); + // Ensure default color values are used: + reset(); + // Ensure the pref is set to "Always" on all platforms (1 is default on Mac, + // but on Windows and Linux it's 0, and the colors dialog is available on 2): + Services.prefs.setIntPref("browser.display.document_color_use", 1); + + registerCleanupFunction(async function () { + reset(); + Services.prefs.clearUserPref("browser.display.document_color_use"); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + }); +}); + +add_task(async function testColorPicker() { + await launchPreferences(); + + const button = gBrowser.contentDocument.getElementById("colors"); + + const radiogroup = content.document.getElementById("contrastControlSettings"); + const radioOff = content.document.getElementById("contrastSettingsOff"); + const radioCustom = content.document.getElementById("contrastSettingsOn"); + + // Focus "Off" Contrast radio button: + radiogroup.focus(); + Assert.equal( + radiogroup, + gBrowser.contentDocument.activeElement, + "Radio group for Custom Colors is focused" + ); + Assert.equal( + radioOff, + radiogroup.querySelector("radio[focused='true']"), + "Radio group with option 'Off' is focused" + ); + Assert.ok(button.disabled, "Manage Colors button is disabled"); + + // Focus "Custom" Contrast radio button: + EventUtils.synthesizeKey("KEY_ArrowDown"); + Assert.equal( + radioCustom, + radiogroup.querySelector("radio[focused='true']"), + "Radio group with option 'Custom' is focused" + ); + Assert.ok(!button.disabled, "Manage Colors button is now enabled"); + + // Focus "Manage Colors" button: + EventUtils.synthesizeKey("KEY_Tab"); + Assert.equal( + button, + gBrowser.contentDocument.activeElement, + "Manage Colors button is focused" + ); + + info("Open Colors dialog"); + // Open Colors sub-dialog: + let promiseSubDialogLoaded = promiseLoadSubDialog( + "chrome://browser/content/preferences/dialogs/colors.xhtml" + ); + EventUtils.synthesizeKey(" "); + let colorsDialogWindow = await promiseSubDialogLoaded; + let colorsDialogDoc = colorsDialogWindow.document; + Assert.ok(colorsDialogDoc, "Colors dialog exists"); + Assert.ok( + BrowserTestUtils.isVisible(colorsDialogDoc.getElementById("ColorsDialog")), + "Colors dialog is visible" + ); + + // Pickers and preferences defaults (should be the same length and order): + const pickerControlAttrs = [ + "foregroundtextmenu", + "backgroundmenu", + "unvisitedlinkmenu", + "visitedlinkmenu", + ]; + const prefNames = [ + "browser.display.foreground_color", + "browser.display.background_color", + "browser.anchor_color", + "browser.visited_color", + ]; + // Values of static preferences (https://searchfox.org/mozilla-central/rev/270c20e4b063d80ce71f029b4adc4ba03a12edc0/modules/libpref/init/StaticPrefList.yaml#1506-1508,1439-1441,1039-1041,2097-2099): + const colorValuesHex = ["#000000", "#FFFFFF", "#0000EE", "#551A8B"]; + const colorValuesSystem = ["CanvasText", "Canvas", "LinkText", "VisitedText"]; + + // Checking the defaults are entered correctly: + Assert.equal( + prefNames.length, + pickerControlAttrs.length, + "prefNames and pickerControlAttrs arrays have the same length" + ); + Assert.equal( + prefNames.length, + colorValuesHex.length, + "prefNames and colorValuesHex arrays have the same length" + ); + Assert.equal( + prefNames.length, + colorValuesSystem.length, + "prefNames and colorValuesSystem arrays have the same length" + ); + + const expectedPickersMap = prefNames.map((pref, i) => ({ + pref, + id: pickerControlAttrs[i], + control: pickerControlAttrs[i], + // Ensure a HEX value and a system color are interchangeable in this + // scenario, i.e. CanvasText resolves to #000000 in our default test cases: + expectedValues: [colorValuesSystem[i], colorValuesHex[i]], + })); + + for (const picker of expectedPickersMap) { + // Pref: + const prefValue = Services.prefs.getStringPref(picker.pref, ""); + Assert.ok(prefValue, `Preference with id="${picker.pref}" exists`); + const prefValueNormalized = prefValue.startsWith("#") + ? prefValue.toUpperCase() + : prefValue; + + // Picker: + const pickerEl = colorsDialogDoc.getElementById(picker.id); + Assert.ok(pickerEl, `Moz-input-color picker with id="${picker.id}" exists`); + const pickerValueNormalized = pickerEl.value.startsWith("#") + ? pickerEl.value.toUpperCase() + : pickerEl.value; + + info("Test markup of a color picker"); + + // Checking default picker attributes: + Assert.equal( + pickerEl.getAttribute("id"), + picker.id, + `Picker's "id" attribute is as expected` + ); + Assert.equal( + pickerEl.getAttribute("control"), + picker.control, + `Picker's "control" attribute is as expected` + ); + Assert.equal( + pickerEl.getAttribute("preference"), + picker.pref, + `Picker's "preference" attribute is as expected` + ); + Assert.ok( + picker.expectedValues.includes(prefValueNormalized), + `Pref ${picker.pref} value "${prefValue}" is one of two expected values: ${picker.expectedValues.join(" or ")}` + ); + Assert.ok( + picker.expectedValues.includes(pickerValueNormalized), + `Picker "${picker.id}" value "${pickerEl.value}" is one of two expected values: ${picker.expectedValues.join(" or ")}` + ); + Assert.ok( + pickerEl.hasAttribute("data-l10n-id"), + `Picker has a localizable ID` + ); + Assert.equal( + pickerEl.getAttribute("data-l10n-attrs"), + "label", + `Picker has a localizable "label" attribute` + ); + Assert.ok(pickerEl.label, `Picker has a localizable label text`); + + // Checking pref and picker values are referencing the same actual color: + const prefValueIsExpected = + picker.expectedValues.includes(prefValueNormalized); + const pickerValueIsExpected = picker.expectedValues.includes( + pickerValueNormalized + ); + const valuesMatchExactly = prefValueNormalized === pickerValueNormalized; + + Assert.ok( + valuesMatchExactly || (prefValueIsExpected && pickerValueIsExpected), + `Pref and picker values should be using the same color: + pref="${prefValue}", pickerEl="${pickerEl.value}", expected= + ${picker.expectedValues.join(" or ")}` + ); + + info("Test a color picker is focusable"); + + // Confirming the focus location: + Assert.equal( + pickerEl, + colorsDialogDoc.activeElement, + `The ${picker.id} picker is focused` + ); + // Move focus to the next picker: + EventUtils.synthesizeKey("KEY_Tab"); + } + info("Close Colors dialog"); + EventUtils.synthesizeKey("KEY_Enter"); + + // Confirm the focus is returned to the "Manage Colors" button: + Assert.equal( + button, + gBrowser.contentDocument.activeElement, + "Manage Colors button is focused" + ); +}); diff --git a/browser/locales/en-US/browser/preferences/colors.ftl b/browser/locales/en-US/browser/preferences/colors.ftl @@ -11,16 +11,20 @@ colors-close-key = colors-text-and-background = Text and Background -colors-text-header = Text +colors-text = + .label = Text .accesskey = T -colors-background = Background +colors-text-background = + .label = Background .accesskey = B colors-links-header = Link Colors -colors-unvisited-links = Unvisited Links +colors-links-unvisited = + .label = Unvisited Links .accesskey = L -colors-visited-links = Visited Links +colors-links-visited = + .label = Visited Links .accesskey = V diff --git a/python/l10n/fluent_migrations/bug_1976118_colors_dialog_with_moz_input_color_pickers.py b/python/l10n/fluent_migrations/bug_1976118_colors_dialog_with_moz_input_color_pickers.py @@ -0,0 +1,33 @@ +# Any copyright is dedicated to the Public Domain. +# http://creativecommons.org/publicdomain/zero/1.0/ + +from fluent.migrate.helpers import transforms_from + + +def migrate(ctx): + """Bug 1976118 - Replacing default color pickers on Colors Dialog with moz-input-color components, part {index}.""" + + source = "browser/browser/preferences/colors.ftl" + target = source + + ctx.add_transforms( + target, + target, + transforms_from( + """ +colors-text = + .label = {COPY_PATTERN(from_path, "colors-text-header")} + .accesskey = {COPY_PATTERN(from_path, "colors-text-header.accesskey")} +colors-text-background = + .label = {COPY_PATTERN(from_path, "colors-background")} + .accesskey = {COPY_PATTERN(from_path, "colors-background.accesskey")} +colors-links-unvisited = + .label = {COPY_PATTERN(from_path, "colors-unvisited-links")} + .accesskey = {COPY_PATTERN(from_path, "colors-unvisited-links.accesskey")} +colors-links-visited = + .label = {COPY_PATTERN(from_path, "colors-visited-links")} + .accesskey = {COPY_PATTERN(from_path, "colors-visited-links.accesskey")} +""", + from_path=source, + ), + )