tor-browser

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

commit 68cbdbcf72b8fd0f2ca0267f55b472ff8c956d71
parent 739e0962df7fed95b8539c97cc8c658de07021b6
Author: Daniel Thorn <dthorn@mozilla.com>
Date:   Wed,  8 Oct 2025 14:34:50 +0000

Bug 1992246 - Move PrefUtils from normandy to nimbus r=nimbus-reviewers,firefox-ai-ml-reviewers,kcochrane,beth,tarek

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

Diffstat:
Mbrowser/components/genai/GenAI.sys.mjs | 2+-
Mbrowser/components/genai/LinkPreview.sys.mjs | 2+-
Mbrowser/components/sidebar/SidebarManager.sys.mjs | 2+-
Mtoolkit/components/nimbus/lib/ExperimentManager.sys.mjs | 2+-
Mtoolkit/components/nimbus/lib/ExperimentStore.sys.mjs | 2+-
Mtoolkit/components/nimbus/lib/PrefFlipsFeature.sys.mjs | 2+-
Mtoolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js | 2+-
Mtoolkit/components/nimbus/test/unit/test_ExperimentManager_prefs.js | 2+-
Mtoolkit/components/nimbus/test/unit/test_prefFlips.js | 2+-
Mtoolkit/components/normandy/actions/PreferenceRollbackAction.sys.mjs | 2+-
Mtoolkit/components/normandy/actions/PreferenceRolloutAction.sys.mjs | 2+-
Dtoolkit/components/normandy/lib/PrefUtils.sys.mjs | 130-------------------------------------------------------------------------------
Mtoolkit/components/normandy/lib/PreferenceExperiments.sys.mjs | 2+-
Mtoolkit/components/normandy/lib/PreferenceRollouts.sys.mjs | 2+-
Dtoolkit/components/normandy/test/unit/test_PrefUtils.js | 223-------------------------------------------------------------------------------
Mtoolkit/components/normandy/test/unit/xpcshell.toml | 2--
Atoolkit/modules/PrefUtils.sys.mjs | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/modules/moz.build | 4++++
Atoolkit/modules/tests/xpcshell/test_PrefUtils.js | 223+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/modules/tests/xpcshell/xpcshell.toml | 2++
20 files changed, 367 insertions(+), 368 deletions(-)

diff --git a/browser/components/genai/GenAI.sys.mjs b/browser/components/genai/GenAI.sys.mjs @@ -13,7 +13,7 @@ ChromeUtils.defineESModuleGetters(lazy, { ContentAnalysisUtils: "resource://gre/modules/ContentAnalysisUtils.sys.mjs", EveryWindow: "resource:///modules/EveryWindow.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", SidebarManager: "moz-src:///browser/components/sidebar/SidebarManager.sys.mjs", }); diff --git a/browser/components/genai/LinkPreview.sys.mjs b/browser/components/genai/LinkPreview.sys.mjs @@ -10,7 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, { LinkPreviewModel: "moz-src:///browser/components/genai/LinkPreviewModel.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", Region: "resource://gre/modules/Region.sys.mjs", }); diff --git a/browser/components/sidebar/SidebarManager.sys.mjs b/browser/components/sidebar/SidebarManager.sys.mjs @@ -22,7 +22,7 @@ ChromeUtils.defineESModuleGetters(lazy, { CustomizableUI: "moz-src:///browser/components/customizableui/CustomizableUI.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", SessionStore: "resource:///modules/sessionstore/SessionStore.sys.mjs", SidebarState: "moz-src:///browser/components/sidebar/SidebarState.sys.mjs", }); diff --git a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs @@ -15,7 +15,7 @@ ChromeUtils.defineESModuleGetters(lazy, { NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", NimbusTelemetry: "resource://nimbus/lib/Telemetry.sys.mjs", NormandyUtils: "resource://normandy/lib/NormandyUtils.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", EnrollmentsContext: "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs", MatchStatus: "resource://nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs", diff --git a/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs b/toolkit/components/nimbus/lib/ExperimentStore.sys.mjs @@ -11,7 +11,7 @@ ChromeUtils.defineESModuleGetters(lazy, { NimbusEnrollments: "resource://nimbus/lib/Enrollments.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", NimbusMigrations: "resource://nimbus/lib/Migrations.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", ProfilesDatastoreService: "moz-src:///toolkit/profile/ProfilesDatastoreService.sys.mjs", }); diff --git a/toolkit/components/nimbus/lib/PrefFlipsFeature.sys.mjs b/toolkit/components/nimbus/lib/PrefFlipsFeature.sys.mjs @@ -7,7 +7,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ExperimentManager: "resource://nimbus/lib/ExperimentManager.sys.mjs", NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", UnenrollmentCause: "resource://nimbus/lib/ExperimentManager.sys.mjs", }); diff --git a/toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js b/toolkit/components/nimbus/test/unit/test_ExperimentAPI_ExperimentFeature.js @@ -4,7 +4,7 @@ const { NimbusTelemetry } = ChromeUtils.importESModule( "resource://nimbus/lib/Telemetry.sys.mjs" ); const { PrefUtils } = ChromeUtils.importESModule( - "resource://normandy/lib/PrefUtils.sys.mjs" + "moz-src:///toolkit/modules/PrefUtils.sys.mjs" ); const TEST_FALLBACK_PREF = "testprefbranch.config"; diff --git a/toolkit/components/nimbus/test/unit/test_ExperimentManager_prefs.js b/toolkit/components/nimbus/test/unit/test_ExperimentManager_prefs.js @@ -5,7 +5,7 @@ const { ObjectUtils } = ChromeUtils.importESModule( "resource://gre/modules/ObjectUtils.sys.mjs" ); const { PrefUtils } = ChromeUtils.importESModule( - "resource://normandy/lib/PrefUtils.sys.mjs" + "moz-src:///toolkit/modules/PrefUtils.sys.mjs" ); const { ProfilesDatastoreService } = ChromeUtils.importESModule( diff --git a/toolkit/components/nimbus/test/unit/test_prefFlips.js b/toolkit/components/nimbus/test/unit/test_prefFlips.js @@ -2,7 +2,7 @@ * http://creativecommons.org/publicdomain/zero/1.0/ */ const { PrefUtils } = ChromeUtils.importESModule( - "resource://normandy/lib/PrefUtils.sys.mjs" + "moz-src:///toolkit/modules/PrefUtils.sys.mjs" ); const { JsonSchema } = ChromeUtils.importESModule( "resource://gre/modules/JsonSchema.sys.mjs" diff --git a/toolkit/components/normandy/actions/PreferenceRollbackAction.sys.mjs b/toolkit/components/normandy/actions/PreferenceRollbackAction.sys.mjs @@ -7,7 +7,7 @@ import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs", diff --git a/toolkit/components/normandy/actions/PreferenceRolloutAction.sys.mjs b/toolkit/components/normandy/actions/PreferenceRolloutAction.sys.mjs @@ -7,7 +7,7 @@ import { BaseAction } from "resource://normandy/actions/BaseAction.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { ActionSchemas: "resource://normandy/actions/schemas/index.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs", diff --git a/toolkit/components/normandy/lib/PrefUtils.sys.mjs b/toolkit/components/normandy/lib/PrefUtils.sys.mjs @@ -1,130 +0,0 @@ -/* 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/. */ - -const lazy = {}; -ChromeUtils.defineESModuleGetters(lazy, { - LogManager: "resource://normandy/lib/LogManager.sys.mjs", -}); - -ChromeUtils.defineLazyGetter(lazy, "log", () => { - return lazy.LogManager.getLogger("preference-experiments"); -}); - -const kPrefBranches = { - user: Services.prefs, - default: Services.prefs.getDefaultBranch(""), -}; - -export var PrefUtils = { - /** - * Get a preference of any type from the named branch. - * @param {string} pref - * @param {object} [options] - * @param {"default"|"user"} [options.branchName="user"] One of "default" or "user" - * @param {string|boolean|integer|null} [options.defaultValue] - * The value to return if the preference does not exist. Defaults to null. - */ - getPref(pref, { branch = "user", defaultValue = null } = {}) { - const branchObj = kPrefBranches[branch]; - if (!branchObj) { - throw new this.UnexpectedPreferenceBranch( - `"${branch}" is not a valid preference branch` - ); - } - const type = branchObj.getPrefType(pref); - - try { - switch (type) { - case Services.prefs.PREF_BOOL: { - return branchObj.getBoolPref(pref); - } - case Services.prefs.PREF_STRING: { - return branchObj.getStringPref(pref); - } - case Services.prefs.PREF_INT: { - return branchObj.getIntPref(pref); - } - case Services.prefs.PREF_INVALID: { - return defaultValue; - } - } - } catch (e) { - if (branch === "default" && e.result === Cr.NS_ERROR_UNEXPECTED) { - // There is a value for the pref on the user branch but not on the default branch. This is ok. - return defaultValue; - } - // Unexpected error, re-throw it - throw e; - } - - // If `type` isn't any of the above, throw an error. Don't do this in a - // default branch of switch so that error handling is easier. - throw new TypeError(`Unknown preference type (${type}) for ${pref}.`); - }, - - /** - * Set a preference on the named branch - * @param {string} pref - * @param {string|boolean|integer|null} value The value to set. - * @param {object} options - * @param {"user"|"default"} options.branchName The branch to make the change on. - */ - setPref(pref, value, { branch = "user" } = {}) { - if (value === null) { - this.clearPref(pref, { branch }); - return; - } - const branchObj = kPrefBranches[branch]; - if (!branchObj) { - throw new this.UnexpectedPreferenceBranch( - `"${branch}" is not a valid preference branch` - ); - } - switch (typeof value) { - case "boolean": { - branchObj.setBoolPref(pref, value); - break; - } - case "string": { - branchObj.setStringPref(pref, value); - break; - } - case "number": { - branchObj.setIntPref(pref, value); - break; - } - default: { - throw new TypeError( - `Unexpected value type (${typeof value}) for ${pref}.` - ); - } - } - }, - - /** - * Remove a preference from a branch. Note that default branch preferences - * cannot effectively be cleared. If "default" is passed for a branch, an - * error will be logged and nothing else will happen. - * - * @param {string} pref - * @param {object} options - * @param {"user"|"default"} options.branchName The branch to clear - */ - clearPref(pref, { branch = "user" } = {}) { - if (branch === "user") { - kPrefBranches.user.clearUserPref(pref); - } else if (branch === "default") { - lazy.log.warn( - `Cannot reset pref ${pref} on the default branch. Pref will be cleared at next restart.` - ); - } else { - throw new this.UnexpectedPreferenceBranch( - `"${branch}" is not a valid preference branch` - ); - } - }, - - UnexpectedPreferenceType: class extends Error {}, - UnexpectedPreferenceBranch: class extends Error {}, -}; diff --git a/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs b/toolkit/components/normandy/lib/PreferenceExperiments.sys.mjs @@ -79,7 +79,7 @@ import { LogManager } from "resource://normandy/lib/LogManager.sys.mjs"; const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { JSONFile: "resource://gre/modules/JSONFile.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs", }); diff --git a/toolkit/components/normandy/lib/PreferenceRollouts.sys.mjs b/toolkit/components/normandy/lib/PreferenceRollouts.sys.mjs @@ -8,7 +8,7 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs", IndexedDB: "resource://gre/modules/IndexedDB.sys.mjs", - PrefUtils: "resource://normandy/lib/PrefUtils.sys.mjs", + PrefUtils: "moz-src:///toolkit/modules/PrefUtils.sys.mjs", TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs", TelemetryEvents: "resource://normandy/lib/TelemetryEvents.sys.mjs", }); diff --git a/toolkit/components/normandy/test/unit/test_PrefUtils.js b/toolkit/components/normandy/test/unit/test_PrefUtils.js @@ -1,223 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. -http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const { PrefUtils } = ChromeUtils.importESModule( - "resource://normandy/lib/PrefUtils.sys.mjs" -); - -add_task(function getPrefGetsValues() { - const defaultBranch = Services.prefs.getDefaultBranch(""); - const userBranch = Services.prefs; - - defaultBranch.setBoolPref("test.bool", false); - userBranch.setBoolPref("test.bool", true); - defaultBranch.setIntPref("test.int", 1); - userBranch.setIntPref("test.int", 2); - defaultBranch.setStringPref("test.string", "default"); - userBranch.setStringPref("test.string", "user"); - - equal( - PrefUtils.getPref("test.bool", { branch: "user" }), - true, - "should read user branch bools" - ); - equal( - PrefUtils.getPref("test.int", { branch: "user" }), - 2, - "should read user branch ints" - ); - equal( - PrefUtils.getPref("test.string", { branch: "user" }), - "user", - "should read user branch strings" - ); - - equal( - PrefUtils.getPref("test.bool", { branch: "default" }), - false, - "should read default branch bools" - ); - equal( - PrefUtils.getPref("test.int", { branch: "default" }), - 1, - "should read default branch ints" - ); - equal( - PrefUtils.getPref("test.string", { branch: "default" }), - "default", - "should read default branch strings" - ); - - equal( - PrefUtils.getPref("test.bool"), - true, - "should read bools from the user branch by default" - ); - equal( - PrefUtils.getPref("test.int"), - 2, - "should read ints from the user branch by default" - ); - equal( - PrefUtils.getPref("test.string"), - "user", - "should read strings from the user branch by default" - ); - - equal( - PrefUtils.getPref("test.does_not_exist"), - null, - "Should return null for non-existent prefs by default" - ); - let defaultValue = Symbol(); - equal( - PrefUtils.getPref("test.does_not_exist", { defaultValue }), - defaultValue, - "Should use the passed default value" - ); -}); - -// This is an important test because the pref system can behave in strange ways -// when the user branch has a value, but the default branch does not. -add_task(function getPrefHandlesUserValueNoDefaultValue() { - Services.prefs.setStringPref("test.only-user-value", "user"); - - let defaultValue = Symbol(); - equal( - PrefUtils.getPref("test.only-user-value", { - branch: "default", - defaultValue, - }), - defaultValue - ); - equal(PrefUtils.getPref("test.only-user-value", { branch: "default" }), null); - equal(PrefUtils.getPref("test.only-user-value", { branch: "user" }), "user"); - equal(PrefUtils.getPref("test.only-user-value"), "user"); -}); - -add_task(function getPrefInvalidBranch() { - Assert.throws( - () => PrefUtils.getPref("test.pref", { branch: "invalid" }), - PrefUtils.UnexpectedPreferenceBranch - ); -}); - -add_task(function setPrefSetsValues() { - const defaultBranch = Services.prefs.getDefaultBranch(""); - const userBranch = Services.prefs; - - defaultBranch.setIntPref("test.int", 1); - userBranch.setIntPref("test.int", 2); - defaultBranch.setStringPref("test.string", "default"); - userBranch.setStringPref("test.string", "user"); - defaultBranch.setBoolPref("test.bool", false); - userBranch.setBoolPref("test.bool", true); - - PrefUtils.setPref("test.int", 3); - equal( - userBranch.getIntPref("test.int"), - 3, - "the user branch should change for ints" - ); - PrefUtils.setPref("test.int", 4, { branch: "default" }); - equal( - userBranch.getIntPref("test.int"), - 3, - "changing the default branch shouldn't affect the user branch for ints" - ); - PrefUtils.setPref("test.int", null, { branch: "user" }); - equal( - userBranch.getIntPref("test.int"), - 4, - "clearing the user branch should reveal the default value for ints" - ); - - PrefUtils.setPref("test.string", "user override"); - equal( - userBranch.getStringPref("test.string"), - "user override", - "the user branch should change for strings" - ); - PrefUtils.setPref("test.string", "default override", { branch: "default" }); - equal( - userBranch.getStringPref("test.string"), - "user override", - "changing the default branch shouldn't affect the user branch for strings" - ); - PrefUtils.setPref("test.string", null, { branch: "user" }); - equal( - userBranch.getStringPref("test.string"), - "default override", - "clearing the user branch should reveal the default value for strings" - ); - - PrefUtils.setPref("test.bool", false); - equal( - userBranch.getBoolPref("test.bool"), - false, - "the user branch should change for bools" - ); - // The above effectively unsets the user branch, since it is now the same as the default branch - PrefUtils.setPref("test.bool", true, { branch: "default" }); - equal( - userBranch.getBoolPref("test.bool"), - true, - "the default branch should change for bools" - ); - - defaultBranch.setBoolPref("test.bool", false); - userBranch.setBoolPref("test.bool", true); - equal( - userBranch.getBoolPref("test.bool"), - true, - "the precondition should hold" - ); - PrefUtils.setPref("test.bool", null, { branch: "user" }); - equal( - userBranch.getBoolPref("test.bool"), - false, - "setting the user branch to null should reveal the default value for bools" - ); -}); - -add_task(function setPrefInvalidBranch() { - Assert.throws( - () => PrefUtils.setPref("test.pref", "value", { branch: "invalid" }), - PrefUtils.UnexpectedPreferenceBranch - ); -}); - -add_task(function clearPrefClearsValues() { - const defaultBranch = Services.prefs.getDefaultBranch(""); - const userBranch = Services.prefs; - - defaultBranch.setStringPref("test.string", "default"); - userBranch.setStringPref("test.string", "user"); - equal( - userBranch.getStringPref("test.string"), - "user", - "the precondition should hold" - ); - PrefUtils.clearPref("test.string"); - equal( - userBranch.getStringPref("test.string"), - "default", - "clearing the user branch should reveal the default value for bools" - ); - - PrefUtils.clearPref("test.string", { branch: "default" }); - equal( - userBranch.getStringPref("test.string"), - "default", - "clearing the default branch shouldn't do anything" - ); -}); - -add_task(function clearPrefInvalidBranch() { - Assert.throws( - () => PrefUtils.clearPref("test.pref", { branch: "invalid" }), - PrefUtils.UnexpectedPreferenceBranch - ); -}); diff --git a/toolkit/components/normandy/test/unit/xpcshell.toml b/toolkit/components/normandy/test/unit/xpcshell.toml @@ -16,8 +16,6 @@ tags = "normandy" ["test_NormandyApi.js"] -["test_PrefUtils.js"] - ["test_RecipeRunner.js"] ["test_addon_unenroll.js"] diff --git a/toolkit/modules/PrefUtils.sys.mjs b/toolkit/modules/PrefUtils.sys.mjs @@ -0,0 +1,125 @@ +/* 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/. */ + +const kPrefBranches = { + user: Services.prefs, + default: Services.prefs.getDefaultBranch(""), +}; + +export var PrefUtils = { + /** + * Get a preference of any type from the named branch. + * @param {string} pref + * @param {object} [options] + * @param {"default"|"user"} [options.branchName="user"] One of "default" or "user" + * @param {string|boolean|integer|null} [options.defaultValue] + * The value to return if the preference does not exist. Defaults to null. + */ + getPref(pref, { branch = "user", defaultValue = null } = {}) { + const branchObj = kPrefBranches[branch]; + if (!branchObj) { + throw new this.UnexpectedPreferenceBranch( + `"${branch}" is not a valid preference branch` + ); + } + const type = branchObj.getPrefType(pref); + + try { + switch (type) { + case Services.prefs.PREF_BOOL: { + return branchObj.getBoolPref(pref); + } + case Services.prefs.PREF_STRING: { + return branchObj.getStringPref(pref); + } + case Services.prefs.PREF_INT: { + return branchObj.getIntPref(pref); + } + case Services.prefs.PREF_INVALID: { + return defaultValue; + } + } + } catch (e) { + if (branch === "default" && e.result === Cr.NS_ERROR_UNEXPECTED) { + // There is a value for the pref on the user branch but not on the default branch. This is ok. + return defaultValue; + } + // Unexpected error, re-throw it + throw e; + } + + // If `type` isn't any of the above, throw an error. Don't do this in a + // default branch of switch so that error handling is easier. + throw new TypeError(`Unknown preference type (${type}) for ${pref}.`); + }, + + /** + * Set a preference on the named branch + * @param {string} pref + * @param {string|boolean|integer|null} value The value to set. + * @param {object} options + * @param {"user"|"default"} options.branchName The branch to make the change on. + */ + setPref(pref, value, { branch = "user" } = {}) { + if (value === null) { + this.clearPref(pref, { branch }); + return; + } + const branchObj = kPrefBranches[branch]; + if (!branchObj) { + throw new this.UnexpectedPreferenceBranch( + `"${branch}" is not a valid preference branch` + ); + } + switch (typeof value) { + case "boolean": { + branchObj.setBoolPref(pref, value); + break; + } + case "string": { + branchObj.setStringPref(pref, value); + break; + } + case "number": { + branchObj.setIntPref(pref, value); + break; + } + default: { + throw new TypeError( + `Unexpected value type (${typeof value}) for ${pref}.` + ); + } + } + }, + + /** + * Remove a preference from a branch. Note that default branch preferences + * cannot effectively be cleared. If "default" is passed for a branch, an + * error will be logged and nothing else will happen. + * + * @param {string} pref + * @param {object} options + * @param {"user"|"default"} options.branchName The branch to clear + */ + clearPref(pref, { branch = "user" } = {}) { + if (branch === "user") { + kPrefBranches.user.clearUserPref(pref); + } else if (branch === "default") { + const log = console.createInstance({ + prefix: "Toolkit.PrefUtils", + maxLogLevel: "Warn", + }); + log.warn( + `Cannot reset pref ${pref} on the default branch. Pref will be cleared at next restart.` + ); + } else { + throw new this.UnexpectedPreferenceBranch( + `"${branch}" is not a valid preference branch` + ); + } + }, + + UnexpectedPreferenceType: class extends Error {}, + UnexpectedPreferenceBranch: class extends Error {}, +}; diff --git a/toolkit/modules/moz.build b/toolkit/modules/moz.build @@ -353,3 +353,7 @@ XPIDL_SOURCES += [ ] XPIDL_MODULE = "toolkit_modules" + +MOZ_SRC_FILES += [ + "PrefUtils.sys.mjs", +] diff --git a/toolkit/modules/tests/xpcshell/test_PrefUtils.js b/toolkit/modules/tests/xpcshell/test_PrefUtils.js @@ -0,0 +1,223 @@ +/* Any copyright is dedicated to the Public Domain. +http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PrefUtils } = ChromeUtils.importESModule( + "moz-src:///toolkit/modules/PrefUtils.sys.mjs" +); + +add_task(function getPrefGetsValues() { + const defaultBranch = Services.prefs.getDefaultBranch(""); + const userBranch = Services.prefs; + + defaultBranch.setBoolPref("test.bool", false); + userBranch.setBoolPref("test.bool", true); + defaultBranch.setIntPref("test.int", 1); + userBranch.setIntPref("test.int", 2); + defaultBranch.setStringPref("test.string", "default"); + userBranch.setStringPref("test.string", "user"); + + equal( + PrefUtils.getPref("test.bool", { branch: "user" }), + true, + "should read user branch bools" + ); + equal( + PrefUtils.getPref("test.int", { branch: "user" }), + 2, + "should read user branch ints" + ); + equal( + PrefUtils.getPref("test.string", { branch: "user" }), + "user", + "should read user branch strings" + ); + + equal( + PrefUtils.getPref("test.bool", { branch: "default" }), + false, + "should read default branch bools" + ); + equal( + PrefUtils.getPref("test.int", { branch: "default" }), + 1, + "should read default branch ints" + ); + equal( + PrefUtils.getPref("test.string", { branch: "default" }), + "default", + "should read default branch strings" + ); + + equal( + PrefUtils.getPref("test.bool"), + true, + "should read bools from the user branch by default" + ); + equal( + PrefUtils.getPref("test.int"), + 2, + "should read ints from the user branch by default" + ); + equal( + PrefUtils.getPref("test.string"), + "user", + "should read strings from the user branch by default" + ); + + equal( + PrefUtils.getPref("test.does_not_exist"), + null, + "Should return null for non-existent prefs by default" + ); + let defaultValue = Symbol(); + equal( + PrefUtils.getPref("test.does_not_exist", { defaultValue }), + defaultValue, + "Should use the passed default value" + ); +}); + +// This is an important test because the pref system can behave in strange ways +// when the user branch has a value, but the default branch does not. +add_task(function getPrefHandlesUserValueNoDefaultValue() { + Services.prefs.setStringPref("test.only-user-value", "user"); + + let defaultValue = Symbol(); + equal( + PrefUtils.getPref("test.only-user-value", { + branch: "default", + defaultValue, + }), + defaultValue + ); + equal(PrefUtils.getPref("test.only-user-value", { branch: "default" }), null); + equal(PrefUtils.getPref("test.only-user-value", { branch: "user" }), "user"); + equal(PrefUtils.getPref("test.only-user-value"), "user"); +}); + +add_task(function getPrefInvalidBranch() { + Assert.throws( + () => PrefUtils.getPref("test.pref", { branch: "invalid" }), + PrefUtils.UnexpectedPreferenceBranch + ); +}); + +add_task(function setPrefSetsValues() { + const defaultBranch = Services.prefs.getDefaultBranch(""); + const userBranch = Services.prefs; + + defaultBranch.setIntPref("test.int", 1); + userBranch.setIntPref("test.int", 2); + defaultBranch.setStringPref("test.string", "default"); + userBranch.setStringPref("test.string", "user"); + defaultBranch.setBoolPref("test.bool", false); + userBranch.setBoolPref("test.bool", true); + + PrefUtils.setPref("test.int", 3); + equal( + userBranch.getIntPref("test.int"), + 3, + "the user branch should change for ints" + ); + PrefUtils.setPref("test.int", 4, { branch: "default" }); + equal( + userBranch.getIntPref("test.int"), + 3, + "changing the default branch shouldn't affect the user branch for ints" + ); + PrefUtils.setPref("test.int", null, { branch: "user" }); + equal( + userBranch.getIntPref("test.int"), + 4, + "clearing the user branch should reveal the default value for ints" + ); + + PrefUtils.setPref("test.string", "user override"); + equal( + userBranch.getStringPref("test.string"), + "user override", + "the user branch should change for strings" + ); + PrefUtils.setPref("test.string", "default override", { branch: "default" }); + equal( + userBranch.getStringPref("test.string"), + "user override", + "changing the default branch shouldn't affect the user branch for strings" + ); + PrefUtils.setPref("test.string", null, { branch: "user" }); + equal( + userBranch.getStringPref("test.string"), + "default override", + "clearing the user branch should reveal the default value for strings" + ); + + PrefUtils.setPref("test.bool", false); + equal( + userBranch.getBoolPref("test.bool"), + false, + "the user branch should change for bools" + ); + // The above effectively unsets the user branch, since it is now the same as the default branch + PrefUtils.setPref("test.bool", true, { branch: "default" }); + equal( + userBranch.getBoolPref("test.bool"), + true, + "the default branch should change for bools" + ); + + defaultBranch.setBoolPref("test.bool", false); + userBranch.setBoolPref("test.bool", true); + equal( + userBranch.getBoolPref("test.bool"), + true, + "the precondition should hold" + ); + PrefUtils.setPref("test.bool", null, { branch: "user" }); + equal( + userBranch.getBoolPref("test.bool"), + false, + "setting the user branch to null should reveal the default value for bools" + ); +}); + +add_task(function setPrefInvalidBranch() { + Assert.throws( + () => PrefUtils.setPref("test.pref", "value", { branch: "invalid" }), + PrefUtils.UnexpectedPreferenceBranch + ); +}); + +add_task(function clearPrefClearsValues() { + const defaultBranch = Services.prefs.getDefaultBranch(""); + const userBranch = Services.prefs; + + defaultBranch.setStringPref("test.string", "default"); + userBranch.setStringPref("test.string", "user"); + equal( + userBranch.getStringPref("test.string"), + "user", + "the precondition should hold" + ); + PrefUtils.clearPref("test.string"); + equal( + userBranch.getStringPref("test.string"), + "default", + "clearing the user branch should reveal the default value for bools" + ); + + PrefUtils.clearPref("test.string", { branch: "default" }); + equal( + userBranch.getStringPref("test.string"), + "default", + "clearing the default branch shouldn't do anything" + ); +}); + +add_task(function clearPrefInvalidBranch() { + Assert.throws( + () => PrefUtils.clearPref("test.pref", { branch: "invalid" }), + PrefUtils.UnexpectedPreferenceBranch + ); +}); diff --git a/toolkit/modules/tests/xpcshell/xpcshell.toml b/toolkit/modules/tests/xpcshell/xpcshell.toml @@ -88,6 +88,8 @@ skip-if = ["os == 'android'"] ["test_PermissionsUtils.js"] +["test_PrefUtils.js"] + ["test_Preferences.js"] ["test_PrivacyLevel.js"]