tor-browser

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

commit 81206b849b429f789787e824e1c15ee3f03d2117
parent 51de2c8143aeb7db40f41d158374c09670f33c6c
Author: Mark Striemer <mstriemer@mozilla.com>
Date:   Sat, 13 Dec 2025 20:56:42 +0000

Bug 1972367 - Convert Zoom settings section to config-based prefs r=fluent-reviewers,desktop-theme-reviewers,bolsson,hjones

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

Diffstat:
Mbrowser/components/preferences/main.inc.xhtml | 16+---------------
Mbrowser/components/preferences/main.js | 208+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Mbrowser/components/preferences/tests/browser_setting_group_in_progress.js | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Mbrowser/components/preferences/widgets/setting-control/setting-control.css | 5+++++
Mbrowser/components/preferences/widgets/setting-control/setting-control.mjs | 33+++++++++++++++++++--------------
Mbrowser/locales/en-US/browser/preferences/preferences.ftl | 6++++--
Mbrowser/themes/shared/preferences/preferences.css | 1+
Apython/l10n/fluent_migrations/bug_1972367_preferences_zoom.py | 25+++++++++++++++++++++++++
8 files changed, 249 insertions(+), 143 deletions(-)

diff --git a/browser/components/preferences/main.inc.xhtml b/browser/components/preferences/main.inc.xhtml @@ -200,21 +200,7 @@ </groupbox> <!-- Zoom --> -<groupbox id="zoomGroup" data-category="paneGeneral" hidden="true" data-srd-groupid="zoom"> - <label><html:h2 data-l10n-id="preferences-zoom-header"/></label> - <hbox id="zoomBox" align="center" hidden="true"> - <label control="defaultZoom" data-l10n-id="preferences-default-zoom"/> - <menulist id="defaultZoom"> - <menupopup/> - </menulist> - </hbox> - <html:moz-message-bar id="text-zoom-override-warning" data-l10n-id="preferences-text-zoom-override-warning"/> - <checkbox id="zoomText" - data-l10n-id="preferences-zoom-text-only"/> - -</groupbox> - -<html:setting-group hidden="true" data-category="paneGeneral" groupid="zoom" /> +<html:setting-group id="zoomGroup" groupid="zoom" data-category="paneGeneral" hidden="true" /> <!-- Languages --> <groupbox id="languagesGroup" data-category="paneGeneral" hidden="true"> diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js @@ -142,6 +142,9 @@ Preferences.addAll([ { id: "accessibility.typeaheadfind", type: "bool" }, { id: "accessibility.blockautorefresh", type: "bool" }, + /* Zoom */ + { id: "browser.zoom.full", type: "bool" }, + /* Browsing * general.autoScroll - when set to true, clicking the scroll wheel on the mouse activates a @@ -633,7 +636,6 @@ Preferences.addSetting({ }, }); -Preferences.addSetting({ id: "zoomPlaceholder" }); Preferences.addSetting({ id: "containersPane", onUserClick(e) { @@ -1662,6 +1664,114 @@ Preferences.addSetting({ }, }); +/** + * Helper object for managing the various zoom related settings. + */ +const ZoomHelpers = { + win: window.browsingContext.topChromeWindow, + get FullZoom() { + return this.win.FullZoom; + }, + get ZoomManager() { + return this.win.ZoomManager; + }, + + /** + * Set the global default zoom value. + * + * @param {number} newZoom - The new zoom + * @returns {Promise<void>} + */ + async setDefaultZoom(newZoom) { + let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService( + Ci.nsIContentPrefService2 + ); + let nonPrivateLoadContext = Cu.createLoadContext(); + let resolvers = Promise.withResolvers(); + /* Because our setGlobal function takes in a browsing context, and + * because we want to keep this property consistent across both private + * and non-private contexts, we create a non-private context and use that + * to set the property, regardless of our actual context. + */ + cps2.setGlobal(this.FullZoom.name, newZoom, nonPrivateLoadContext, { + handleCompletion: resolvers.resolve, + handleError: resolvers.reject, + }); + return resolvers.promise; + }, + + async getDefaultZoom() { + /** @import { ZoomUI as GlobalZoomUI } from "resource:///modules/ZoomUI.sys.mjs" */ + /** @type {GlobalZoomUI} */ + let ZoomUI = this.win.ZoomUI; + return await ZoomUI.getGlobalValue(); + }, + + /** + * The possible zoom values. + * + * @returns {number[]} + */ + get zoomValues() { + return this.ZoomManager.zoomValues; + }, + + toggleFullZoom() { + this.ZoomManager.toggleZoom(); + }, +}; +Preferences.addSetting( + class extends Preferences.AsyncSetting { + static id = "defaultZoom"; + /** @type {Record<"options", object[]>} */ + optionsConfig; + + /** + * @param {string} val - zoom value as a string + */ + async set(val) { + ZoomHelpers.setDefaultZoom( + parseFloat((parseInt(val, 10) / 100).toFixed(2)) + ); + } + async get() { + return Math.round((await ZoomHelpers.getDefaultZoom()) * 100); + } + async getControlConfig() { + if (!this.optionsConfig) { + this.optionsConfig = { + options: ZoomHelpers.zoomValues.map(a => { + let value = Math.round(a * 100); + return { + value, + l10nId: "preferences-default-zoom-value", + l10nArgs: { percentage: value }, + }; + }), + }; + } + return this.optionsConfig; + } + } +); +Preferences.addSetting({ + id: "zoomTextPref", + pref: "browser.zoom.full", +}); +Preferences.addSetting({ + id: "zoomText", + deps: ["zoomTextPref"], + // Use the Setting since the ZoomManager getter may not have updated yet. + get: (_, { zoomTextPref }) => !zoomTextPref.value, + set: () => ZoomHelpers.toggleFullZoom(), + disabled: ({ zoomTextPref }) => zoomTextPref.locked, +}); +Preferences.addSetting({ + id: "zoomWarning", + deps: ["zoomText"], + visible: ({ zoomText }) => Boolean(zoomText.value), +}); + SettingGroupManager.registerGroups({ containers: { // This section is marked as in progress for testing purposes @@ -1978,21 +2088,24 @@ SettingGroupManager.registerGroups({ ], }, zoom: { - // This section is marked as in progress for testing purposes - inProgress: true, + l10nId: "preferences-zoom-header2", + headingLevel: 2, items: [ { - id: "zoomPlaceholder", - control: "moz-message-bar", - controlAttrs: { - message: "Placeholder for updated zoom controls", - }, + id: "defaultZoom", + l10nId: "preferences-default-zoom-label", + control: "moz-select", }, { - id: "containersPane", - control: "moz-button", + id: "zoomText", + l10nId: "preferences-zoom-text-only", + }, + { + id: "zoomWarning", + l10nId: "preferences-text-zoom-override-warning", + control: "moz-message-bar", controlAttrs: { - label: "Manage container settings", + type: "warning", }, }, ], @@ -3361,11 +3474,6 @@ var gMainPane = { gMainPane.initPrimaryBrowserLanguageUI(); } - // We call `initDefaultZoomValues` to set and unhide the - // default zoom preferences menu, and to establish a - // listener for future menu changes. - gMainPane.initDefaultZoomValues(); - gMainPane.initTranslations(); // Initialize settings groups from the config object. @@ -3736,53 +3844,6 @@ var gMainPane = { return undefined; }, - /** - * Fetch the existing default zoom value, initialise and unhide - * the preferences menu. This method also establishes a listener - * to ensure handleDefaultZoomChange is called on future menu - * changes. - */ - async initDefaultZoomValues() { - let win = window.browsingContext.topChromeWindow; - let selected = await win.ZoomUI.getGlobalValue(); - let menulist = document.getElementById("defaultZoom"); - - new SelectionChangedMenulist(menulist, event => { - let parsedZoom = parseFloat((event.target.value / 100).toFixed(2)); - gMainPane.handleDefaultZoomChange(parsedZoom); - }); - - setEventListener("zoomText", "command", function () { - win.ZoomManager.toggleZoom(); - document.getElementById("text-zoom-override-warning").hidden = - !document.getElementById("zoomText").checked; - }); - - let zoomValues = win.ZoomManager.zoomValues.map(a => { - return Math.round(a * 100); - }); - - let fragment = document.createDocumentFragment(); - for (let zoomLevel of zoomValues) { - let menuitem = document.createXULElement("menuitem"); - document.l10n.setAttributes(menuitem, "preferences-default-zoom-value", { - percentage: zoomLevel, - }); - menuitem.setAttribute("value", zoomLevel); - fragment.appendChild(menuitem); - } - - let menupopup = menulist.querySelector("menupopup"); - menupopup.appendChild(fragment); - menulist.value = Math.round(selected * 100); - - let checkbox = document.getElementById("zoomText"); - checkbox.checked = !win.ZoomManager.useFullZoom; - document.getElementById("text-zoom-override-warning").hidden = - !checkbox.checked; - document.getElementById("zoomBox").hidden = false; - }, - updateColorsButton() { document.getElementById("colors").disabled = Preferences.get("browser.display.document_color_use").value != 2; @@ -4404,27 +4465,6 @@ var gMainPane = { }, /** - * Takes as newZoom a floating point value representing the - * new default zoom. This value should not be a string, and - * should not carry a percentage sign/other localisation - * characteristics. - */ - handleDefaultZoomChange(newZoom) { - let cps2 = Cc["@mozilla.org/content-pref/service;1"].getService( - Ci.nsIContentPrefService2 - ); - let nonPrivateLoadContext = Cu.createLoadContext(); - /* Because our setGlobal function takes in a browsing context, and - * because we want to keep this property consistent across both private - * and non-private contexts, we crate a non-private context and use that - * to set the property, regardless of our actual context. - */ - - let win = window.browsingContext.topChromeWindow; - cps2.setGlobal(win.FullZoom.name, newZoom, nonPrivateLoadContext); - }, - - /** * Shows a subdialog containing the profile selector page. */ manageProfiles() { diff --git a/browser/components/preferences/tests/browser_setting_group_in_progress.js b/browser/components/preferences/tests/browser_setting_group_in_progress.js @@ -3,45 +3,87 @@ "use strict"; -async function openPrefsWithSettings({ allEnabled, zoomEnabled }) { +async function openPrefsWithSettings({ allEnabled, sectionEnabled }) { await SpecialPowers.pushPrefEnv({ set: [ ["browser.settings-redesign.enabled", allEnabled], - ["browser.settings-redesign.zoom.enabled", zoomEnabled], + ["browser.settings-redesign.mysection.enabled", sectionEnabled], ], }); - await openPreferencesViaOpenPreferencesAPI("general", { leaveOpen: true }); - return gBrowser.selectedBrowser.contentDocument; + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + let doc = gBrowser.selectedBrowser.contentDocument; + let win = doc.ownerGlobal; + win.Preferences.addSetting({ + id: "testSetting", + get: () => true, + }); + win.SettingGroupManager.registerGroup("mysection", { + inProgress: true, + l10nId: "downloads-header-2", + headingLevel: 2, + items: [ + { + id: "testSetting", + controlAttrs: { + label: "Test setting", + }, + }, + ], + }); + let zoomGroup = doc.getElementById("zoomGroup"); + let legacyGroup = doc.createXULElement("groupbox"); + legacyGroup.id = "mysectionGroup"; + legacyGroup.setAttribute("data-srd-groupid", "mysection"); + legacyGroup.setAttribute("data-category", "paneGeneral"); + legacyGroup.hidden = true; + let mysectionGroup = doc.createElement("setting-group"); + mysectionGroup.setAttribute("groupid", "mysection"); + mysectionGroup.setAttribute("data-category", "paneGeneral"); + mysectionGroup.hidden = true; + zoomGroup.parentElement.insertBefore(mysectionGroup, zoomGroup); + zoomGroup.parentElement.insertBefore(legacyGroup, zoomGroup); + win.initSettingGroup("mysection"); + let paneLoaded = waitForPaneChange("general"); + EventUtils.synthesizeMouseAtCenter( + doc.getElementById("category-general"), + {}, + win + ); + await paneLoaded; + return doc; } add_task(async function test_section_disabled() { - let doc = await openPrefsWithSettings({ - allEnabled: false, - zoomEnabled: false, - }); - let legacyGroup = doc.getElementById("zoomGroup"); - let redesignGroup = doc.querySelector('setting-group[groupid="zoom"]'); - ok(legacyGroup.checkVisibility(), "The legacy group is visible"); - is( - legacyGroup.dataset.category, - "paneGeneral", - "The legacy group has a category" - ); - ok( - !legacyGroup.hasAttribute("data-hidden-from-search"), - "The legacy group is visible to search" - ); - ok(!redesignGroup, "The redesign group was removed"); - gBrowser.removeCurrentTab(); + try { + let doc = await openPrefsWithSettings({ + allEnabled: false, + sectionEnabled: false, + }); + let legacyGroup = doc.getElementById("mysectionGroup"); + let redesignGroup = doc.querySelector('setting-group[groupid="mysection"]'); + ok(legacyGroup.checkVisibility(), "The legacy group is visible"); + is( + legacyGroup.dataset.category, + "paneGeneral", + "The legacy group has a category" + ); + ok( + !legacyGroup.hasAttribute("data-hidden-from-search"), + "The legacy group is visible to search" + ); + ok(!redesignGroup, "The redesign group was removed"); + } finally { + gBrowser.removeCurrentTab(); + } }); add_task(async function test_section_enabled() { let doc = await openPrefsWithSettings({ allEnabled: false, - zoomEnabled: true, + sectionEnabled: true, }); - let legacyGroup = doc.getElementById("zoomGroup"); - let redesignGroup = doc.querySelector('setting-group[groupid="zoom"]'); + let legacyGroup = doc.getElementById("mysectionGroup"); + let redesignGroup = doc.querySelector('setting-group[groupid="mysection"]'); ok(!legacyGroup.checkVisibility(), "The legacy group is hidden"); ok(!legacyGroup.dataset.category, "The legacy group category is removed"); is( @@ -61,10 +103,10 @@ add_task(async function test_section_enabled() { add_task(async function test_all_enabled() { let doc = await openPrefsWithSettings({ allEnabled: true, - zoomEnabled: false, + sectionEnabled: false, }); - let legacyGroup = doc.getElementById("zoomGroup"); - let redesignGroup = doc.querySelector('setting-group[groupid="zoom"]'); + let legacyGroup = doc.getElementById("mysectionGroup"); + let redesignGroup = doc.querySelector('setting-group[groupid="mysection"]'); ok(!legacyGroup.checkVisibility(), "The legacy group is hidden"); ok(!legacyGroup.dataset.category, "The legacy group category is removed"); is( diff --git a/browser/components/preferences/widgets/setting-control/setting-control.css b/browser/components/preferences/widgets/setting-control/setting-control.css @@ -6,3 +6,8 @@ .reenable-extensions-message-bar { margin-block: var(--space-large); } + +setting-control[hidden]:has(> moz-message-bar) { + display: block; + margin-block-start: calc(-1 * var(--space-large)); +} diff --git a/browser/components/preferences/widgets/setting-control/setting-control.mjs b/browser/components/preferences/widgets/setting-control/setting-control.mjs @@ -33,6 +33,8 @@ import MozInputFolder from "chrome://global/content/elements/moz-input-folder.mj * @property {string} [control] * The element tag to render, default assumed based on parent control. * @property {any} [value] A value to set on the option. + * @property {boolean} [disabled] If the option should be disabled. + * @property {boolean} [hidden] If the option should be hidden. */ /** @@ -222,7 +224,6 @@ export class SettingControl extends SettingElement { * * @override * @param {SettingElementConfig} config - * @returns {ReturnType<SettingElement['getCommonPropertyMapping']>} */ getCommonPropertyMapping(config) { return { @@ -238,11 +239,12 @@ export class SettingControl extends SettingElement { * @param {SettingOptionConfig} config */ getOptionPropertyMapping(config) { - const props = this.getCommonPropertyMapping(config); - props[".value"] = config.value; - props[".disabled"] = config.disabled; - props[".hidden"] = config.hidden; - return props; + return { + ...this.getCommonPropertyMapping(config), + ".value": config.value, + ".disabled": config.disabled, + ".hidden": config.hidden, + }; } /** @@ -251,14 +253,17 @@ export class SettingControl extends SettingElement { * @param {SettingControlConfig} config */ getControlPropertyMapping(config) { - const props = this.getCommonPropertyMapping(config); - props[".parentDisabled"] = this.parentDisabled; - props["?disabled"] = - this.setting.disabled || - this.setting.locked || - this.isControlledByExtension(); - - return props; + return { + ...this.getCommonPropertyMapping(config), + ".parentDisabled": this.parentDisabled, + "?disabled": + this.setting.disabled || + this.setting.locked || + this.isControlledByExtension(), + // Hide moz-message-bar directly to maintain the role=alert functionality. + // This setting-control will be visually hidden in CSS. + ".hidden": config.control == "moz-message-bar" && this.hidden, + }; } getValue() { diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl @@ -379,9 +379,11 @@ advanced-fonts = .accesskey = A # Zoom is a noun, and the message is used as header for a group of options -preferences-zoom-header = Zoom +preferences-zoom-header2 = + .label = Zoom -preferences-default-zoom = Default zoom +preferences-default-zoom-label = + .label = Default zoom .accesskey = z # Variables: diff --git a/browser/themes/shared/preferences/preferences.css b/browser/themes/shared/preferences/preferences.css @@ -14,6 +14,7 @@ --heading-font-size-xlarge: var(--font-size-xlarge); --heading-font-size-large: var(--font-size-large); --heading-font-size-medium: var(--font-size-medium); + --select-max-width: 20rem; user-select: text; } diff --git a/python/l10n/fluent_migrations/bug_1972367_preferences_zoom.py b/python/l10n/fluent_migrations/bug_1972367_preferences_zoom.py @@ -0,0 +1,25 @@ +# 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 1972367 - Convert Zoom settings section to config-based prefs, part {index}.""" + + target = "browser/browser/preferences/preferences.ftl" + + ctx.add_transforms( + target, + target, + transforms_from( + """ +preferences-zoom-header2 = + .label = {COPY_PATTERN(from_path, "preferences-zoom-header")} +preferences-default-zoom-label = + .label = {COPY_PATTERN(from_path, "preferences-default-zoom")} + .accesskey = {COPY_PATTERN(from_path, "preferences-default-zoom.accesskey")} +""", + from_path=target, + ), + )