tor-browser

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

commit c01bee7d4143bff35e8b0565c259bd118595e3d3
parent a5f0adfbc8343f1ae430cb347c18e524a7704b90
Author: Meg Viar <lmegviar@gmail.com>
Date:   Thu,  6 Nov 2025 13:54:29 +0000

Bug 1998516 - Add ability to set a pref to the value of an existing pref via the SET_PREF special message action r=mimi,omc-reviewers

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

Diffstat:
Mbrowser/app/profile/firefox.js | 5++++-
Mtoolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs | 46++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json | 2+-
Mtoolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 232 insertions(+), 2 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -1835,11 +1835,14 @@ pref("browser.newtab.preload", true); pref("termsofuse.acceptedVersion", 0); // Stringified timestamp of when the user last accepted the TOU pref("termsofuse.acceptedDate", "0"); +// Stringified timestamp of when the user first accepted the TOU, only set if +// they have accepted more than one version. +pref("termsofuse.firstAcceptedDate", "0"); // The most up-to-date version of the TOU, we set the minimum and current // version as 4 to distinguish it from version numbers used in previous TOU // experiments and rollouts pref("termsofuse.currentVersion", 4); -// The minimum version fo the TOU that a user must have accepted to not be +// The minimum version of the TOU that a user must have accepted to not be // presented with the TOU modal pref("termsofuse.minimumVersion", 4); // Should we bypass the TOU modal notification completely, currently only true diff --git a/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs b/toolkit/components/messaging-system/lib/SpecialMessageActions.sys.mjs @@ -193,6 +193,49 @@ export const SpecialMessageActions = { }, /** + * Set a target pref's value to the value of a source pref. + * + * @param {string} targetPref - The name of a pref to be updated. + * @param {string} sourcePref - The name of the source pref whose value will be copied to the target pref. + */ + copyPrefValue(targetPref, sourcePref) { + const sourceType = Services.prefs.getPrefType(sourcePref); + const targetType = Services.prefs.getPrefType(targetPref); + if ( + targetType !== Services.prefs.PREF_INVALID && + targetType !== sourceType + ) { + throw new Error( + `Special message action with type SET_PREF(copyFromPref), target pref "${targetPref}" has type ${targetType} which does not match source pref "${sourcePref}" type ${sourceType}.` + ); + } + switch (sourceType) { + case Services.prefs.PREF_STRING: + Services.prefs.setStringPref( + targetPref, + Services.prefs.getStringPref(sourcePref) + ); + break; + case Services.prefs.PREF_INT: + Services.prefs.setIntPref( + targetPref, + Services.prefs.getIntPref(sourcePref) + ); + break; + case Services.prefs.PREF_BOOL: + Services.prefs.setBoolPref( + targetPref, + Services.prefs.getBoolPref(sourcePref) + ); + break; + default: + throw new Error( + `Special message action with type SET_PREF(copyFromPref), pref of "${sourcePref}" is invalid or not a supported type.` + ); + } + }, + + /** * Set prefs with special message actions * * @param {Object} pref - A pref to be updated. @@ -235,6 +278,7 @@ export const SpecialMessageActions = { "sidebar.visibility", "termsofuse.acceptedVersion", "termsofuse.acceptedDate", + "termsofuse.firstAcceptedDate", "termsofuse.currentVersion", "termsofuse.minimumVersion", "privacy.trackingprotection.allow_list.baseline.enabled", @@ -263,6 +307,8 @@ export const SpecialMessageActions = { case "object": if (pref.value.timestamp) { Services.prefs.setStringPref(pref.name, Date.now().toString()); + } else if (pref.value.copyFromPref) { + this.copyPrefValue(pref.name, pref.value.copyFromPref); } else { Services.prefs.clearUserPref(pref.name); } diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/SpecialMessageActionSchemas.json @@ -544,7 +544,7 @@ "type": ["boolean", "string", "number", "null", "object"] } }, - "description": "An object representing a pref containing a name and a value. The value can be an object with the property `timestamp` with a truthy value to set the pref to a string of the current time in ms." + "description": "An object representing a pref containing a name and a value. The value can be an object with the property `timestamp` with a truthy value to set the pref to a string of the current time in ms. It can also be an object with the property 'copyFromPref' with a string value that is the name of a source pref whose value will be copied to the target pref." }, "onImpression": { "type": "boolean", diff --git a/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js b/toolkit/components/messaging-system/schemas/SpecialMessageActionSchemas/test/browser/browser_sma_set_prefs.js @@ -333,3 +333,184 @@ add_task(async function test_update_namespaced_timestamp_pref_onImpression() { Services.prefs.clearUserPref(namespaced); }); + +add_task(async function test_copyFromPref_string() { + const sourcePrefName = "messaging-system-action.source.string"; + Services.prefs.setStringPref(sourcePrefName, "test"); + + const targetPrefName = "messaging-system-action.target.string"; + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + Services.prefs.clearUserPref(sourcePrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await SMATestUtils.executeAndValidateAction(action); + + Assert.equal( + Services.prefs.getStringPref(targetPrefName), + Services.prefs.getStringPref(sourcePrefName), + "Copied source string pref value to target pref" + ); +}); + +add_task(async function test_copyFromPref_bool() { + const sourcePrefName = "messaging-system-action.source.bool"; + Services.prefs.setBoolPref(sourcePrefName, true); + + const targetPrefName = "messaging-system-action.target.bool"; + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + Services.prefs.clearUserPref(sourcePrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await SMATestUtils.executeAndValidateAction(action); + + Assert.equal( + Services.prefs.getBoolPref(targetPrefName), + Services.prefs.getBoolPref(sourcePrefName), + "Copied source bool pref value to target pref" + ); +}); + +add_task(async function test_copyFromPref_int() { + const sourcePrefName = "messaging-system-action.source.int"; + Services.prefs.setIntPref(sourcePrefName, 42); + + const targetPrefName = "messaging-system-action.target.int"; + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + Services.prefs.clearUserPref(sourcePrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await SMATestUtils.executeAndValidateAction(action); + + Assert.equal( + Services.prefs.getIntPref(targetPrefName), + Services.prefs.getIntPref(sourcePrefName), + "Copied source int pref value to target pref" + ); +}); + +add_task(async function test_copyFromPref_type_mismatch_throws() { + const sourcePrefName = "messaging-system-action.source.mismatch.int"; + Services.prefs.setIntPref(sourcePrefName, 7); + + const targetPrefName = "messaging-system-action.target.mismatch.bool"; + Services.prefs.setBoolPref(targetPrefName, true); + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + Services.prefs.clearUserPref(sourcePrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await Assert.rejects( + SMATestUtils.executeAndValidateAction(action), + /SET_PREF\(copyFromPref\).*type/i, + "Throws when destination type does not match source type" + ); +}); + +add_task(async function test_copyFromPref_missing_source_throws() { + const sourcePrefName = "messaging-system-action.source.missing"; + const targetPrefName = "messaging-system-action.target.missing"; + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await Assert.rejects( + SMATestUtils.executeAndValidateAction(action), + /SET_PREF\(copyFromPref\).*(does not exist|invalid|unsupported)/i, + "Throws when source pref does not exist" + ); + + Assert.equal( + Services.prefs.getPrefType(targetPrefName), + Services.prefs.PREF_INVALID, + "Target pref not created on missing source" + ); +}); + +add_task(async function test_copyFromPref_overwrite_same_type() { + const sourcePrefName = "messaging-system-action.source.overwrite.string"; + Services.prefs.setStringPref(sourcePrefName, "new"); + + const targetPrefName = "messaging-system-action.target.overwrite.string"; + Services.prefs.setStringPref(targetPrefName, "old"); + + registerCleanupFunction(() => { + Services.prefs.clearUserPref(targetPrefName); + Services.prefs.clearUserPref(sourcePrefName); + }); + + const action = { + type: "SET_PREF", + data: { + pref: { + name: targetPrefName, + value: { copyFromPref: sourcePrefName }, + }, + }, + }; + + await SMATestUtils.executeAndValidateAction(action); + + Assert.equal( + Services.prefs.getStringPref(targetPrefName), + Services.prefs.getStringPref(sourcePrefName), + "Overwrote existing target value with source value (same type)" + ); +});