From 3e7f935a12170368325e1527ba583f6b5dce55de Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 8 Jan 2026 10:30:45 +0100 Subject: [PATCH 01/33] Refactor updating remote policies and fix merging policies of combined providers --- .prettierignore | 1 + .../EnterprisePoliciesParent.sys.mjs | 644 +++++++++--------- .../components/enterprisepolicies/moz.build | 3 + 3 files changed, 320 insertions(+), 328 deletions(-) diff --git a/.prettierignore b/.prettierignore index 3716d60b9a8e8..4163c29e5ad1f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1241,6 +1241,7 @@ toolkit/modules/AppConstants.sys.mjs # Files with MOZ_ENTERPRISE preprocessor directives browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs +browser/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs services/fxaccounts/FxAccounts.sys.mjs services/fxaccounts/FxAccountsClient.sys.mjs toolkit/components/downloads/DownloadCore.sys.mjs diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 85ab0258b39c7..67d966fcfbcd2 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -18,6 +18,7 @@ ChromeUtils.defineESModuleGetters(lazy, { setInterval: "resource://gre/modules/Timer.sys.mjs", // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit ConsoleClient: "resource:///modules/enterprise/ConsoleClient.sys.mjs", + schema: "resource:///modules/policies/schema.sys.mjs", }); // This is the file that will be searched for in the @@ -80,7 +81,7 @@ export function EnterprisePoliciesManager() { Services.obs.addObserver(this, "final-ui-startup", true); Services.obs.addObserver(this, "sessionstore-windows-restored", true); Services.obs.addObserver(this, "EnterprisePolicies:Restart", true); - Services.obs.addObserver(this, "EnterprisePolicies:Activate", true); + Services.obs.addObserver(this, "EnterprisePolicies:Update", true); Services.obs.addObserver(this, "distribution-customization-complete", true); } @@ -91,56 +92,73 @@ EnterprisePoliciesManager.prototype = { "nsIEnterprisePolicies", ]), + // Single or combined provider + _provider: null, + + // Caches latest set of parsed policies + _parsedPolicies: {}, + _cleanupPolicies() { - this._previousPolicies = {}; if (Services.prefs.getBoolPref(PREF_POLICIES_APPLIED, false)) { if ("_cleanup" in lazy.Policies) { let policyImpl = lazy.Policies._cleanup; - this._maybeCallbackPolicy(policyImpl); + this._scheduleActivationPolicyCallbacks(policyImpl); } Services.prefs.clearUserPref(PREF_POLICIES_APPLIED); } }, - _initialize() { + async _initialize() { this._cleanupPolicies(); - - const changesHandler = provider => { - if (!provider.hasPolicies) { - this._status = Ci.nsIEnterprisePolicies.INACTIVE; - Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, false); - return; - } - - // Because security.enterprise_roots.enabled is true by default, we can - // ignore attempts by Antivirus to try to set it via policy. - // We have to explicitly check for true or 1 because this happens before - // policy is parsed against the schema, so the value could be coming - // from the registry. - if ( - provider.policies && - Object.keys(provider.policies).length === 1 && - provider.policies.Certificates && - Object.keys(provider.policies.Certificates).length === 1 && - (provider.policies.Certificates.ImportEnterpriseRoots === true || - provider.policies.Certificates.ImportEnterpriseRoots === 1) - ) { - this._status = Ci.nsIEnterprisePolicies.INACTIVE; - return; - } - - this._status = Ci.nsIEnterprisePolicies.ACTIVE; - this._activatePolicies(provider.policies); - Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, true); - }; - this._status = Ci.nsIEnterprisePolicies.INACTIVE; Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, false); - let provider = this._chooseProvider(changesHandler); - if (provider.failed) { + const localProvider = this._chooseProvider(); +#ifdef MOZ_ENTERPRISE + const remoteProvider = RemotePoliciesProvider.getInstance(); + try { + // Poll and ingest initial set of policies + await remoteProvider.ingestPolicies(); + } catch (e) { + console.error("Unable to find policies in payload."); + } + if (localProvider.hasPolicies) { + this._provider = new CombinedProvider(remoteProvider, localProvider); + } else { + this._provider = remoteProvider; + } +#else + this._provider = localProvider; +#endif + + if (this._provider.failed) { this._status = Ci.nsIEnterprisePolicies.FAILED; + return; + } + + if (!this._provider.hasPolicies) { + this._status = Ci.nsIEnterprisePolicies.INACTIVE; + return; } + + // Because security.enterprise_roots.enabled is true by default, we can + // ignore attempts by Antivirus to try to set it via policy. + // We have to explicitly check for true or 1 because this happens before + // policy is parsed against the schema, so the value could be coming + // from the registry. + const policies = this._provider.policies; + if ( + Object.keys(policies).length === 1 && + policies.Certificates && + Object.keys(policies.Certificates).length === 1 && + (policies.Certificates.ImportEnterpriseRoots === true || + policies.Certificates.ImportEnterpriseRoots === 1) + ) { + this._status = Ci.nsIEnterprisePolicies.INACTIVE; + return; + } + + this._activatePolicies(); }, _reportEnterpriseTelemetry() { @@ -148,45 +166,68 @@ EnterprisePoliciesManager.prototype = { Glean.policies.isEnterprise.set(this.isEnterprise); }, - _chooseProvider(handler) { + _chooseProvider() { let platformProvider = null; - if (AppConstants.platform == "win" && AppConstants.MOZ_SYSTEM_POLICIES) { - platformProvider = new WindowsGPOPoliciesProvider(); - platformProvider.onPoliciesChanges(handler); - } else if ( - AppConstants.platform == "macosx" && - AppConstants.MOZ_SYSTEM_POLICIES - ) { - platformProvider = new macOSPoliciesProvider(); - platformProvider.onPoliciesChanges(handler); + if (AppConstants.MOZ_SYSTEM_POLICIES) { + if (AppConstants.platform == "win") { + platformProvider = new WindowsGPOPoliciesProvider(); + } else if (AppConstants.platform == "macosx") { + platformProvider = new macOSPoliciesProvider(); + } } - let jsonProvider = new JSONPoliciesProvider(); - jsonProvider.onPoliciesChanges(handler); - let remoteProvider = RemotePoliciesProvider.createInstance(); - remoteProvider.onPoliciesChanges(handler); if (platformProvider && platformProvider.hasPolicies) { if (jsonProvider.hasPolicies) { - return new CombinedProvider( - new CombinedProvider(remoteProvider, platformProvider), - jsonProvider - ); + return new CombinedProvider(platformProvider, jsonProvider); } - return new CombinedProvider(remoteProvider, platformProvider); + return platformProvider; } - if (jsonProvider.hasPolicies) { - return new CombinedProvider(remoteProvider, jsonProvider); + return jsonProvider; + }, + + /** + * Activates the policies that are provided during initialization. + */ + _activatePolicies() { + this._status = Ci.nsIEnterprisePolicies.ACTIVE; + + lazy.log.debug(this._provider); + + for (const [policyName, policyParams] of Object.entries( + this._provider.policies || {} + )) { + const { isValid, parsedParams } = this._validatePolicyParams( + policyName, + policyParams + ); + + if (!isValid) { + continue; + } + + this._parsedPolicies[policyName] = parsedParams; + + const policyImpl = lazy.Policies[policyName]; + this._scheduleActivationPolicyCallbacks(policyImpl, parsedParams); } - return remoteProvider; }, - _activatePolicies(unparsedPolicies) { - const { schema } = ChromeUtils.importESModule( - // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit - "resource:///modules/policies/schema.sys.mjs" - ); + /** + * Parses, validates and applies any policy changes by comparing + * the previously parsed set of policies with the updated set + * from the remote provider. + * + * - Apply no changes to a policy if it remains unchanged. + * - Re-apply a policy if the parameters changed. + * - Remove a policy if it's missing in the updated set. + */ + _updatePolicies() { + lazy.log.debug("EnterprisePoliciesManager: _updatePolicies: "); + + if (this._provider.isCombined) { + this._provider.mergePolicies(); + } - // Make a deep copy that will be trimmed later let previousPolicies = null; try { previousPolicies = structuredClone(this._parsedPolicies || {}); @@ -199,83 +240,82 @@ EnterprisePoliciesManager.prototype = { } } - this._parsedPolicies = {}; - - const policyNames = Object.keys(unparsedPolicies || {}); - - for (let policyName of policyNames) { - let policySchema = schema.properties[policyName]; - let policyParameters = unparsedPolicies[policyName]; + this._schedulePolicyUpdates(previousPolicies); + this._schedulePolicyRemovals(previousPolicies); - if (!policySchema) { - lazy.log.error(`Unknown policy: ${policyName}`); - continue; + for (const timing of Object.keys(this._callbacks)) { + if (timing !== "onRemove") { + const topic = this.topicByCallbackTiming[timing]; + if (!this._topicsObserved.has(topic)) { + // Only run callbacks for a timing that + // has already been observed. + continue; + } } + this._runPoliciesCallbacks(timing); + } + }, - let { valid: parametersAreValid, parsedValue: parsedParameters } = - lazy.JsonSchemaValidator.validate(policyParameters, policySchema, { - allowAdditionalProperties: true, - }); - - if (!parametersAreValid) { - lazy.log.error(`Invalid parameters specified for ${policyName}.`); - continue; - } + /** + * Parse and schedule a policy update + * + * @param {object} previousPolicies + */ + _schedulePolicyUpdates(previousPolicies) { + this._parsedPolicies = {}; - let policyImpl = lazy.Policies[policyName]; - if (!policyImpl) { - // This means there is an entry in the schema, but no implementaton. - // We only do this when we deprecate policies. - lazy.log.info(`${policyName} has been deprecated.`); - continue; - } + for (const [policyName, policyParams] of Object.entries( + this._provider.policies || {} + )) { + const { isValid, parsedParams } = this._validatePolicyParams( + policyName, + policyParams + ); - if (policyImpl.validate && !policyImpl.validate(parsedParameters)) { - lazy.log.error( - `Parameters for ${policyName} did not validate successfully.` - ); + if (!isValid) { continue; } - this._parsedPolicies[policyName] = parsedParameters; + this._parsedPolicies[policyName] = parsedParams; // verify the previous values if (policyName in previousPolicies) { const previousParameters = JSON.stringify(previousPolicies[policyName]); - if (previousParameters == JSON.stringify(parsedParameters)) { + if (previousParameters == JSON.stringify(parsedParams)) { + // Policy already active. No changes to policy needed. continue; } } - this._maybeCallbackPolicy(policyImpl, parsedParameters); + const policyImpl = lazy.Policies[policyName]; + this._scheduleActivationPolicyCallbacks(policyImpl, parsedParams); } + }, - // Only keep in this._previousPolicies the policies that are not part of the - // policies that were just received - this._previousPolicies = Object.fromEntries( - Object.keys(previousPolicies) - .filter(previousPolicyName => !policyNames.includes(previousPolicyName)) - .map(name => [name, previousPolicies[name]]) - ); - - const previousNames = Object.keys(this._previousPolicies).filter( - policyName => { - let policyImpl = lazy.Policies[policyName]; - if (!policyImpl) { - // This means there is an entry in the schema, but no implementaton. - // We only do this when we deprecate policies. - lazy.log.info(`${policyName} has been deprecated.`); - return false; - } - return true; + /** + * Schedule policy removals + * + * @param {object} previousPolicies + */ + _schedulePolicyRemovals(previousPolicies) { + // Schedule callbacks to remove policies that are no longer present + // in the latest set of parsed policies. + for (const [policyName, policyParams] of Object.entries(previousPolicies)) { + if (this._parsedPolicies[policyName] !== undefined) { + // Policy remains active. + continue; } - ); - for (let policyName of previousNames) { - let policyImpl = lazy.Policies[policyName]; + const policyImpl = lazy.Policies[policyName]; + if (!policyImpl) { + // This means there is an entry in the schema, but no implementation. + // We only do this when we deprecate policies. + lazy.log.warn(`The policy ${policyName} has been deprecated.`); + continue; + } - const onRemove = "onRemove" in policyImpl && policyImpl.onRemove; - if (!onRemove) { + if (!policyImpl.onRemove) { + lazy.log.warn(`Unable to remove the policy ${policyName}.`); continue; } @@ -283,15 +323,76 @@ EnterprisePoliciesManager.prototype = { policyImpl.onRemove, policyImpl, this /* the EnterprisePoliciesManager */, - this._previousPolicies[policyName], + policyParams, ]); } }, - // Schedule a policy callback if there is one to schedule - _maybeCallbackPolicy(policyImpl, parsedParameters = undefined) { + /** + * Validate and parse the policy parameters + * + * @param {object} policyName policy name + * @param {object} policyParams policy parameters + * @returns {{ isValid: boolean, parsedParams: object|null}} + */ + _validatePolicyParams(policyName, policyParams) { + const policySchema = lazy.schema.properties[policyName]; + + if (!policySchema) { + lazy.log.error(`Unknown policy: ${policyName}`); + return { isValid: false, parsedParams: null }; + } + + const { valid: isValid, parsedValue: parsedParams } = + lazy.JsonSchemaValidator.validate(policyParams, policySchema, { + allowAdditionalProperties: true, + }); + + if (!isValid) { + lazy.log.error(`Invalid parameters specified for ${policyName}.`); + return { isValid: false, parsedParams: null }; + } + + const policyImpl = lazy.Policies[policyName]; + if (!policyImpl) { + // This means there is an entry in the schema, but no implementaton. + // We only do this when we deprecate policies. + lazy.log.info(`${policyName} has been deprecated.`); + return { isValid: false, parsedParams: null }; + } + + if (policyImpl.validate && !policyImpl.validate(parsedParams)) { + lazy.log.error( + `Parameters for ${policyName} did not validate successfully.` + ); + return { isValid: false, parsedParams: null }; + } + + return { isValid, parsedParams }; + }, + + /** + * Policy implementation + * + * @typedef {object} PolicyImpl + * @property {Function} [onBeforeAddons] - callback that is invoked when notified of a policies-startup event + * @property {Function} [onProfileAfterChange] - callback that is invoked when notified of a profile-after-change event + * @property {Function} [onBeforeUIStartup] - callback that is invoked when notified of a final-ui-startup event + * @property {Function} [onAllWindowsRestored] - callback that is invoked when notified of a sessionstore-windows-restored event + * @property {Function} [onRemove] - callback that is invoked when a policy is explicitely removed + */ + + /** + * Schedule all "activating" callbacks, meaning any + * "onRemove" callbacks are skipped + * + * @param {PolicyImpl} policyImpl policy implementation + * @param {object} [parsedParams] parsed policy parameters + */ + _scheduleActivationPolicyCallbacks(policyImpl, parsedParams = undefined) { for (let timing of Object.keys(this._callbacks)) { if (timing === "onRemove") { + // Callbacks that remove policies are explicitely scheduled. continue; } @@ -301,7 +402,7 @@ EnterprisePoliciesManager.prototype = { policyCallback, policyImpl, this /* the EnterprisePoliciesManager */, - parsedParameters, + parsedParams, ]); } } @@ -331,7 +432,7 @@ EnterprisePoliciesManager.prototype = { onRemove: [], }, - _schedulePolicyCallback(timing, callback) { + _schedulePolicyCallback(timing, callbackArgs) { // Check for existence of the same callback. Since callback are .bind() // they cannot be just pushed to the array and checked for existence with // .includes() as each bind is a new different object. @@ -348,15 +449,15 @@ EnterprisePoliciesManager.prototype = { const exists = this._callbacks[timing].filter( e => - e[0] == callback[0] && - e[1] == callback[1] && - e[2] == callback[2] && - JSON.stringify(e[3]) == JSON.stringify(callback[3]) + e[0] == callbackArgs[0] && + e[1] == callbackArgs[1] && + e[2] == callbackArgs[2] && + JSON.stringify(e[3]) == JSON.stringify(callbackArgs[3]) ); if (exists.length) { return; } - this._callbacks[timing].push(callback); + this._callbacks[timing].push(callbackArgs); }, _runPoliciesCallbacks(timing) { @@ -381,6 +482,8 @@ EnterprisePoliciesManager.prototype = { this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED; this._parsedPolicies = undefined; + this._provider = null; + this._topicsObserved = null; for (let timing of Object.keys(this._callbacks)) { this._callbacks[timing] = []; } @@ -401,24 +504,24 @@ EnterprisePoliciesManager.prototype = { await notifyTopicOnIdle("distribution-customization-complete"); }, - observersReceived: [], + _topicsObserved: new Set(), - // nsIObserver implementation - observe: function BG_observe(subject, topic, data) { - const policiesCallbackMapping = { - onBeforeAddons: "policies-startup", - onProfileAfterChange: "profile-after-change", - onBeforeUIStartup: "final-ui-startup", - onAllWindowsRestored: "sessionstore-windows-restored", - }; + topicByCallbackTiming: { + onBeforeAddons: "policies-startup", + onProfileAfterChange: "profile-after-change", + onBeforeUIStartup: "final-ui-startup", + onAllWindowsRestored: "sessionstore-windows-restored", + }, - this.observersReceived.push(topic); + // nsIObserver implementation + async observe(aSubject, aTopic) { + this._topicsObserved.add(aTopic); - switch (topic) { + switch (aTopic) { case "policies-startup": // Before the first set of policy callbacks runs, we must // initialize the service. - this._initialize(); + await this._initialize(); this._runPoliciesCallbacks("onBeforeAddons"); break; @@ -439,22 +542,8 @@ EnterprisePoliciesManager.prototype = { this._restart().then(null, console.error); break; - case "EnterprisePolicies:Activate": { - const parsed = JSON.parse(data); - this._activatePolicies(parsed.policies); - - // Only run callbacks that are ready right now. The rest is handled by - // this._activatePolicies() - Object.keys(this._callbacks) - .filter( - cbName => - cbName !== "onRemove" && - this.observersReceived.includes(policiesCallbackMapping[cbName]) - ) - .map(cb => this._runPoliciesCallbacks(cb)); - - this._runPoliciesCallbacks("onRemove"); - + case "EnterprisePolicies:Update": { + this._updatePolicies(); break; } @@ -649,47 +738,46 @@ let ExtensionPolicies = null; let ExtensionSettings = null; let InstallSources = null; -// TODO: Those providers should likely inherit from a class to share some -// common parts. - -/* - * JSON PROVIDER OF POLICIES - * - * This is a platform-agnostic provider which looks for - * policies specified through a policies.json file stored - * in the installation's distribution folder. +/** + * Basic policies provider */ - -class JSONPoliciesProvider { +class PoliciesProvider { constructor() { - this._changesHandlers = []; - this._policies = null; - this._readData(); + this._policies = {}; + this._failed = false; } - onPoliciesChanges(handler) { - this._changesHandlers.push(handler); - if (this.hasPolicies) { - this.triggerOnPoliciesChanges(); - } - } - - triggerOnPoliciesChanges() { - this._changesHandlers.forEach(callback => callback(this)); + get policies() { + return this._policies; } get hasPolicies() { return this._policies !== null && !isEmptyObject(this._policies); } - get policies() { - return this._policies; - } - get failed() { return this._failed; } + get isCombined() { + return false; + } +} + +/* + * JSON PROVIDER OF POLICIES + * + * This is a platform-agnostic provider which looks for + * policies specified through a policies.json file stored + * in the installation's distribution folder. + */ + +class JSONPoliciesProvider extends PoliciesProvider { + constructor() { + super(); + this._readData(); + } + _getConfigurationFile() { let configFile = null; @@ -793,24 +881,22 @@ class JSONPoliciesProvider { * Uses JSON like JSONPoliciesProvider */ -class RemotePoliciesProvider { +class RemotePoliciesProvider extends PoliciesProvider { POLLING_FREQUENCY_PREF = "browser.policies.live_polling.frequency"; POLLING_FREQUENCY_FALLBACK = 60_000; POLLING_ENABLED_PREF = "browser.policies.live_polling.enabled"; static #instance = null; - static createInstance() { - if (!RemotePoliciesProvider.#instance) { - RemotePoliciesProvider.#instance = new RemotePoliciesProvider(); + static getInstance() { + if (!this.#instance) { + this.#instance = new this(); } - return RemotePoliciesProvider.#instance; + return this.#instance; } constructor() { - this._changesHandlers = []; - this._policies = null; + super(); this._socket = null; - this._hasRemoteConnection = false; this._poller = null; this._pollingFrequency = Services.prefs.getIntPref( this.POLLING_FREQUENCY_PREF, @@ -833,17 +919,6 @@ class RemotePoliciesProvider { } } - onPoliciesChanges(handler) { - this._changesHandlers.push(handler); - if (this.hasPolicies) { - this.triggerOnPoliciesChanges(); - } - } - - triggerOnPoliciesChanges() { - this._changesHandlers.forEach(callback => callback(this)); - } - observe(aSubject, aTopic, aData) { switch (aTopic) { case "nsPref:changed": @@ -886,43 +961,23 @@ class RemotePoliciesProvider { } } - get hasRemoteConnection() { - return this._hasRemoteConnection; - } - - get hasPolicies() { - return this._policies !== null && !isEmptyObject(this._policies); - } - - get policies() { - return this._policies; - } - - get failed() { - return this._failed; - } - _stopPolling() { if (!this._poller) { return; } - this._hasRemoteConnection = false; lazy.clearInterval(this._poller); this._poller = null; } - _performPolling() { - lazy.ConsoleClient.getRemotePolicies() - .then(jsonResponse => { - this._hasRemoteConnection = true; - this._ingestPolicies(jsonResponse); - }) - .catch(error => { - console.warn( - `RemotePoliciesProvider performPolling() with frequency ${this._pollingFrequency} caused error ${error}` - ); - this._hasRemoteConnection = false; - }); + async _performPolling() { + try { + await this.ingestPolicies(); + Services.obs.notifyObservers(null, "EnterprisePolicies:Update"); + } catch (e) { + lazy.log.error( + `RemotePoliciesProvider performPolling() with frequency ${this._pollingFrequency} caused error ${e}` + ); + } } _startPolling() { @@ -936,31 +991,22 @@ class RemotePoliciesProvider { ); } - _ingestPolicies(payload) { - if ("policies" in payload) { - this._policies = payload.policies; - this.triggerOnPoliciesChanges(); - Services.obs.notifyObservers( - null, - "EnterprisePolicies:Activate", - JSON.stringify(payload) - ); - } else { - // TODO, this is haha. meh. Maybe restart should be done by activate. + async ingestPolicies() { + const res = await lazy.ConsoleClient.getRemotePolicies(); + if (!res.policies) { this._policies = {}; - Services.obs.notifyObservers(null, "EnterprisePolicies:Restart"); - // Make sure that handler is triggered even when payload is empty as - // in "_cleanup" - this.triggerOnPoliciesChanges(); + console.error( + `Clearing remote policies because no policies were found in the response: ${res}.` + ); + return; } + this._policies = res.policies; } } -class WindowsGPOPoliciesProvider { +class WindowsGPOPoliciesProvider extends PoliciesProvider { constructor() { - this._changesHandlers = []; - this._policies = null; - + super(); let wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance( Ci.nsIWindowsRegKey ); @@ -974,29 +1020,6 @@ class WindowsGPOPoliciesProvider { } } - onPoliciesChanges(handler) { - this._changesHandlers.push(handler); - if (this.hasPolicies) { - this.triggerOnPoliciesChanges(); - } - } - - triggerOnPoliciesChanges() { - this._changesHandlers.forEach(callback => callback(this.hasPolicies)); - } - - get hasPolicies() { - return this._policies !== null && !isEmptyObject(this._policies); - } - - get policies() { - return this._policies; - } - - get failed() { - return this._failed; - } - _readData(wrk, root) { try { let regLocation = "SOFTWARE\\Policies"; @@ -1026,10 +1049,9 @@ class WindowsGPOPoliciesProvider { } } -class macOSPoliciesProvider { +class macOSPoliciesProvider extends PoliciesProvider { constructor() { - this._changesHandlers = []; - this._policies = null; + super(); let prefReader = Cc["@mozilla.org/mac-preferences-reader;1"].createInstance( Ci.nsIMacPreferencesReader ); @@ -1038,69 +1060,35 @@ class macOSPoliciesProvider { } this._policies = lazy.macOSPoliciesParser.readPolicies(prefReader); } - - onPoliciesChanges(handler) { - this._changesHandlers.push(handler); - if (this.hasPolicies) { - this.triggerOnPoliciesChanges(); - } - } - - triggerOnPoliciesChanges() { - this._changesHandlers.forEach(callback => callback(this.hasPolicies)); - } - - get hasPolicies() { - return this._policies !== null && Object.keys(this._policies).length; - } - - get policies() { - return this._policies; - } - - get failed() { - return this._failed; - } } -class CombinedProvider { +class CombinedProvider extends PoliciesProvider { constructor(primaryProvider, secondaryProvider) { - this._readyProviders = 0; - this._primary = primaryProvider; - this._secondary = secondaryProvider; - this._primary.onPoliciesChanges(this.providerPoliciesChanged.bind(this)); - this._secondary.onPoliciesChanges(this.providerPoliciesChanged.bind(this)); + super(); + this._primaryProvider = primaryProvider; + this._secondaryProvider = secondaryProvider; + this.mergePolicies(); } - providerPoliciesChanged() { - this._readyProviders++; - if (this._readyProviders === 2) { - this.combine(); - } - } - - combine() { - // Combine policies with primary taking precedence. + mergePolicies() { + // Combine policies with primaryProvider taking precedence. // We only do this for top level policies. - this._policies = this._primary._policies; - for (let policyName of Object.keys(this._secondary.policies)) { + this._policies = structuredClone(this._primaryProvider.policies); + for (let [policyName, policyParams] of Object.entries( + this._secondaryProvider.policies || {} + )) { if (!(policyName in this._policies)) { - this._policies[policyName] = this._secondary.policies[policyName]; + this._policies[policyName] = policyParams; } } } - get hasPolicies() { - // Combined provider always has policies. - return true; - } - - get policies() { - return this._policies; - } - get failed() { // Combined provider never fails. return false; } + + get isCombined() { + return true; + } } diff --git a/toolkit/components/enterprisepolicies/moz.build b/toolkit/components/enterprisepolicies/moz.build index 4fcb377c80145..e1b0919bf2605 100644 --- a/toolkit/components/enterprisepolicies/moz.build +++ b/toolkit/components/enterprisepolicies/moz.build @@ -19,6 +19,9 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": EXTRA_JS_MODULES += [ "EnterprisePolicies.sys.mjs", "EnterprisePoliciesContent.sys.mjs", + ] + + EXTRA_PP_JS_MODULES += [ "EnterprisePoliciesParent.sys.mjs", ] From 19bc1659b4d149a96b9c97fe5e7ffb1d7aa75a82 Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Fri, 16 Jan 2026 21:46:43 +0100 Subject: [PATCH 02/33] Refactor enterprisepolicies tests for remote policies --- .../tests/BackgroundTask_policies.sys.mjs | 2 +- .../EnterprisePoliciesParent.sys.mjs | 120 ++++++---- .../components/enterprisepolicies/moz.build | 3 - .../nsIEnterprisePolicies.idl | 7 + .../tests/EnterprisePolicyTesting.sys.mjs | 137 ++++++----- .../tests/browser/browser.toml | 18 +- .../browser/browser_policies_basic_tests.js | 4 - .../browser_policies_basiclive_tests.js | 27 --- .../tests/browser/browser_policies_diffing.js | 164 -------------- .../enterprisepolicies/tests/browser/head.js | 46 ++-- .../tests/browser/remote/browser.toml | 22 ++ .../remote/browser_remote_policies_basic.js | 8 + .../remote/browser_remote_policies_diffing.js | 212 ++++++++++++++++++ .../browser_remote_policies_live_proxy.js} | 34 +-- .../browser_remote_policies_missing_live.js} | 37 ++- .../browser_remote_policies_prefs.js} | 137 +++++------ .../tests/browser/remote/head.js | 30 +++ .../enterprisepolicies/tests/moz.build | 5 + toolkit/modules/Preferences.sys.mjs | 2 +- 19 files changed, 561 insertions(+), 454 deletions(-) delete mode 100644 toolkit/components/enterprisepolicies/tests/browser/browser_policies_basiclive_tests.js delete mode 100644 toolkit/components/enterprisepolicies/tests/browser/browser_policies_diffing.js create mode 100644 toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml create mode 100644 toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_basic.js create mode 100644 toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js rename toolkit/components/enterprisepolicies/tests/browser/{browser_policies_live_proxy.js => remote/browser_remote_policies_live_proxy.js} (90%) rename toolkit/components/enterprisepolicies/tests/browser/{browser_policies_missing_live.js => remote/browser_remote_policies_missing_live.js} (79%) rename toolkit/components/enterprisepolicies/tests/browser/{browser_policies_prefs.js => remote/browser_remote_policies_prefs.js} (55%) create mode 100644 toolkit/components/enterprisepolicies/tests/browser/remote/head.js diff --git a/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs b/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs index 90bb71db8e0d7..67687ee5b0d19 100644 --- a/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs +++ b/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs @@ -7,7 +7,7 @@ import { EnterprisePolicyTesting } from "resource://testing-common/EnterprisePol export async function runBackgroundTask(commandLine) { let filePath = commandLine.getArgument(0); - await EnterprisePolicyTesting.setupPolicyEngineWithJson(filePath); + await EnterprisePolicyTesting.setupPolicyEngineWithJsonFile(filePath); let checker = Cc["@mozilla.org/updates/update-checker;1"].getService( Ci.nsIUpdateChecker diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 67d966fcfbcd2..d05d31924713f 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -18,7 +18,6 @@ ChromeUtils.defineESModuleGetters(lazy, { setInterval: "resource://gre/modules/Timer.sys.mjs", // eslint-disable-next-line mozilla/no-browser-refs-in-toolkit ConsoleClient: "resource:///modules/enterprise/ConsoleClient.sys.mjs", - schema: "resource:///modules/policies/schema.sys.mjs", }); // This is the file that will be searched for in the @@ -47,6 +46,8 @@ const PREF_LOGLEVEL = "browser.policies.loglevel"; // To allow for cleaning up old policies const PREF_POLICIES_APPLIED = "browser.policies.applied"; +const PREF_REMOTE_POLICIES_ENABLED = "browser.policies.remote.enabled"; + ChromeUtils.defineLazyGetter(lazy, "log", () => { let { ConsoleAPI } = ChromeUtils.importESModule( "resource://gre/modules/Console.sys.mjs" @@ -80,6 +81,7 @@ export function EnterprisePoliciesManager() { Services.obs.addObserver(this, "profile-after-change", true); Services.obs.addObserver(this, "final-ui-startup", true); Services.obs.addObserver(this, "sessionstore-windows-restored", true); + Services.obs.addObserver(this, "EnterprisePolicies:Reset", true); Services.obs.addObserver(this, "EnterprisePolicies:Restart", true); Services.obs.addObserver(this, "EnterprisePolicies:Update", true); Services.obs.addObserver(this, "distribution-customization-complete", true); @@ -98,6 +100,13 @@ EnterprisePoliciesManager.prototype = { // Caches latest set of parsed policies _parsedPolicies: {}, + isRemotePoliciesSupported() { + return ( + AppConstants.MOZ_ENTERPRISE && + Services.prefs.getBoolPref(PREF_REMOTE_POLICIES_ENABLED, false) + ); + }, + _cleanupPolicies() { if (Services.prefs.getBoolPref(PREF_POLICIES_APPLIED, false)) { if ("_cleanup" in lazy.Policies) { @@ -110,26 +119,33 @@ EnterprisePoliciesManager.prototype = { async _initialize() { this._cleanupPolicies(); + + this._policiesSchema = ChromeUtils.importESModule( + "resource:///modules/policies/schema.sys.mjs" + ).schema; + this._status = Ci.nsIEnterprisePolicies.INACTIVE; Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, false); const localProvider = this._chooseProvider(); -#ifdef MOZ_ENTERPRISE - const remoteProvider = RemotePoliciesProvider.getInstance(); - try { - // Poll and ingest initial set of policies - await remoteProvider.ingestPolicies(); - } catch (e) { - console.error("Unable to find policies in payload."); - } - if (localProvider.hasPolicies) { - this._provider = new CombinedProvider(remoteProvider, localProvider); + if (this.isRemotePoliciesSupported()) { + const remoteProvider = RemotePoliciesProvider.getInstance(); + try { + // Poll and ingest initial set of policies + await remoteProvider.ingestPolicies(); + // Will apply policy updates once policies manager is initialized + remoteProvider.startPolling(); + } catch (e) { + console.error("Unable to find policies in payload."); + } + if (localProvider.hasPolicies) { + this._provider = new CombinedProvider(remoteProvider, localProvider); + } else { + this._provider = remoteProvider; + } } else { - this._provider = remoteProvider; + this._provider = localProvider; } -#else - this._provider = localProvider; -#endif if (this._provider.failed) { this._status = Ci.nsIEnterprisePolicies.FAILED; @@ -202,6 +218,7 @@ EnterprisePoliciesManager.prototype = { ); if (!isValid) { + console.warn(`Parameters for policy ${policyName} are invalid`); continue; } @@ -222,7 +239,10 @@ EnterprisePoliciesManager.prototype = { * - Remove a policy if it's missing in the updated set. */ _updatePolicies() { - lazy.log.debug("EnterprisePoliciesManager: _updatePolicies: "); + if (this._status === Ci.nsIEnterprisePolicies.UNINITIALIZED) { + // Abort if we are still initializing or restarting the policy engine. + return; + } if (this._provider.isCombined) { this._provider.mergePolicies(); @@ -336,7 +356,7 @@ EnterprisePoliciesManager.prototype = { * @returns {{ isValid: boolean, parsedParams: object|null}} */ _validatePolicyParams(policyName, policyParams) { - const policySchema = lazy.schema.properties[policyName]; + const policySchema = this._policiesSchema.properties[policyName]; if (!policySchema) { lazy.log.error(`Unknown policy: ${policyName}`); @@ -474,19 +494,26 @@ EnterprisePoliciesManager.prototype = { } }, - async _restart() { + async _resetEngine() { DisallowedFeatures = {}; Services.ppmm.sharedData.delete("EnterprisePolicies:Status"); Services.ppmm.sharedData.delete("EnterprisePolicies:DisallowedFeatures"); this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED; - this._parsedPolicies = undefined; + this._parsedPolicies = {}; + if (this.isRemotePoliciesSupported) { + RemotePoliciesProvider.dropInstance(); + } this._provider = null; - this._topicsObserved = null; + this._topicsObserved = new Set(); for (let timing of Object.keys(this._callbacks)) { this._callbacks[timing] = []; } + }, + + async _restart() { + await this._resetEngine(); // Simulate the startup process. This step-by-step is a bit ugly but it // tries to emulate the same behavior as of a normal startup. @@ -538,12 +565,20 @@ EnterprisePoliciesManager.prototype = { this._runPoliciesCallbacks("onAllWindowsRestored"); break; + case "EnterprisePolicies:Reset": + this._resetEngine().then(null, console.error); + break; + case "EnterprisePolicies:Restart": this._restart().then(null, console.error); break; case "EnterprisePolicies:Update": { this._updatePolicies(); + Services.obs.notifyObservers( + null, + "EnterprisePolicies:PolicyUpdatesApplied" + ); break; } @@ -848,11 +883,14 @@ class JSONPoliciesProvider extends PoliciesProvider { if (data) { lazy.log.debug(`policies.json path = ${configFile.path}`); lazy.log.debug(`policies.json content = ${data}`); - this._policies = JSON.parse(data).policies; + const { policies } = JSON.parse(data); - if (!this._policies) { + if (!policies) { lazy.log.error("Policies file doesn't contain a 'policies' object"); + this._policies = {}; this._failed = true; + } else { + this._policies = policies; } } } catch (ex) { @@ -894,9 +932,19 @@ class RemotePoliciesProvider extends PoliciesProvider { return this.#instance; } + static dropInstance() { + if (!this.#instance) { + // No instance was initialized. + return; + } + if (this.#instance._poller) { + this.#instance._stopPolling(); + } + this.#instance = null; + } + constructor() { super(); - this._socket = null; this._poller = null; this._pollingFrequency = Services.prefs.getIntPref( this.POLLING_FREQUENCY_PREF, @@ -909,14 +957,6 @@ class RemotePoliciesProvider extends PoliciesProvider { Services.prefs.addObserver(this.POLLING_FREQUENCY_PREF, this); Services.prefs.addObserver(this.POLLING_ENABLED_PREF, this); Services.obs.addObserver(this, "xpcom-shutdown"); - - this.init(); - } - - init() { - if (this._isPollingEnabled) { - this._startPolling(); - } } observe(aSubject, aTopic, aData) { @@ -933,7 +973,7 @@ class RemotePoliciesProvider extends PoliciesProvider { return; } this._stopPolling(); - this._startPolling(); + this.startPolling(); } else if (aData === this.POLLING_ENABLED_PREF) { const p = this._isPollingEnabled; this._isPollingEnabled = Services.prefs.getBoolPref( @@ -944,7 +984,7 @@ class RemotePoliciesProvider extends PoliciesProvider { return; } if (this._isPollingEnabled) { - this._startPolling(); + this.startPolling(); } else { this._stopPolling(); } @@ -980,7 +1020,7 @@ class RemotePoliciesProvider extends PoliciesProvider { } } - _startPolling() { + startPolling() { if (!this._isPollingEnabled) { return; } @@ -992,12 +1032,17 @@ class RemotePoliciesProvider extends PoliciesProvider { } async ingestPolicies() { + if (!this._isPollingEnabled) { + return; + } + const res = await lazy.ConsoleClient.getRemotePolicies(); if (!res.policies) { this._policies = {}; console.error( - `Clearing remote policies because no policies were found in the response: ${res}.` + `Clearing remote policies because no policies were found in the response: ${JSON.stringify(res)}.` ); + this._failed = true; return; } this._policies = res.policies; @@ -1058,7 +1103,7 @@ class macOSPoliciesProvider extends PoliciesProvider { if (!prefReader.policiesEnabled()) { return; } - this._policies = lazy.macOSPoliciesParser.readPolicies(prefReader); + this._policies = lazy.macOSPoliciesParser.readPolicies(prefReader) || {}; } } @@ -1084,8 +1129,7 @@ class CombinedProvider extends PoliciesProvider { } get failed() { - // Combined provider never fails. - return false; + return this._primaryProvider.failed && this._secondaryProvider.failed; } get isCombined() { diff --git a/toolkit/components/enterprisepolicies/moz.build b/toolkit/components/enterprisepolicies/moz.build index e1b0919bf2605..4fcb377c80145 100644 --- a/toolkit/components/enterprisepolicies/moz.build +++ b/toolkit/components/enterprisepolicies/moz.build @@ -19,9 +19,6 @@ if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": EXTRA_JS_MODULES += [ "EnterprisePolicies.sys.mjs", "EnterprisePoliciesContent.sys.mjs", - ] - - EXTRA_PP_JS_MODULES += [ "EnterprisePoliciesParent.sys.mjs", ] diff --git a/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl b/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl index 62b1b568ce7e3..dba497fe2b670 100644 --- a/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl +++ b/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl @@ -21,6 +21,13 @@ interface nsIEnterprisePolicies : nsISupports boolean isAllowed(in ACString feature); + /** + * Whether remote policies are supported (limited to enterprise builds) + * + * @return A boolean value defining is remote policies are supported. + */ + boolean isRemotePoliciesSupported(); + /** * Get the active policies that have been successfully parsed. * diff --git a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs index 3d3701e6b0fe5..60326cc6aef2c 100644 --- a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs +++ b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs @@ -10,11 +10,30 @@ const lazy = {}; ChromeUtils.defineESModuleGetters(lazy, { FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs", modifySchemaForTests: "resource:///modules/policies/schema.sys.mjs", - HttpServer: "resource://testing-common/httpd.sys.mjs", - setTimeout: "resource://gre/modules/Timer.sys.mjs", + sinon: "resource://testing-common/Sinon.sys.mjs", }); -export var EnterprisePolicyTesting = { +export const REMOTE_POLICIES_TESTING_PREF = "browser.policies.remote.enabled"; + +export const EnterprisePolicyTesting = { + get remotePoliciesStub() { + return this._remotePoliciesStub; + }, + + set remotePoliciesStub(stub) { + this._remotePoliciesStub = stub; + }, + + resolveOnceAllPoliciesApplied(resolve) { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver( + observer, + "EnterprisePolicies:AllPoliciesApplied" + ); + resolve(); + }, "EnterprisePolicies:AllPoliciesApplied"); + }, + // |json| must be an object representing the desired policy configuration, OR a // path to the JSON file containing the policy configuration. setupPolicyEngineWithJson: async function setupPolicyEngineWithJson( @@ -34,15 +53,8 @@ export var EnterprisePolicyTesting = { Services.prefs.setStringPref("browser.policies.alternatePath", filePath); - let promise = new Promise(resolve => { - Services.obs.addObserver(function observer() { - Services.obs.removeObserver( - observer, - "EnterprisePolicies:AllPoliciesApplied" - ); - resolve(); - }, "EnterprisePolicies:AllPoliciesApplied"); - }); + const { promise, resolve } = Promise.withResolvers(); + this.resolveOnceAllPoliciesApplied(resolve); // Clear any previously used custom schema or assign a new one lazy.modifySchemaForTests(customSchema || null); @@ -51,60 +63,59 @@ export var EnterprisePolicyTesting = { return promise; }, - servePolicyWithJson: async function servePolicyWithJson( - json, - customSchema, - registerCleanupFunction - ) { - if (this._httpd === undefined) { - this._httpd = new lazy.HttpServer(); - await this._httpd.start(-1); - const serverAddr = `http://localhost:${this._httpd.identity.primaryPort}`; - - const tokenData = { - access_token: "test_access_token", - refresh_token: "test_refresh_token", - expires_in: 3600, - token_type: "Bearer", - }; - - // Set up mock token endpoint for ConsoleClient (token refresh never hits it yet) - this._httpd.registerPathHandler("/sso/token", (req, resp) => { - resp.setStatusLine(req.httpVersion, 200, "OK"); - resp.setHeader("Content-Type", "application/json"); - resp.write(JSON.stringify(tokenData)); - }); - - Services.prefs.setStringPref("enterprise.console.address", serverAddr); - Services.prefs.setBoolPref("browser.policies.live_polling.enabled", true); - Services.felt.setTokens( - tokenData.access_token, - tokenData.refresh_token, - tokenData.expires_in + resolveOnceAllPolicyUpdatesApplied(resolve) { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver( + observer, + "EnterprisePolicies:PolicyUpdatesApplied" ); + resolve(); + }, "EnterprisePolicies:PolicyUpdatesApplied"); + }, + + nextPolicyUpdatesApplied() { + const { promise, resolve } = Promise.withResolvers(); + this.resolveOnceAllPolicyUpdatesApplied(resolve); + return promise; + }, - registerCleanupFunction(async () => { - await new Promise(resolve => this._httpd.stop(resolve)); - this._httpd = undefined; - Services.prefs.clearUserPref("enterprise.console.address"); - Services.prefs.clearUserPref("browser.policies.live_polling.enabled"); - const { ConsoleClient } = ChromeUtils.importESModule( - "resource:///modules/enterprise/ConsoleClient.sys.mjs" - ); - ConsoleClient.clearTokenData(); - }); + async servePolicyWithRemoteJson(json, customSchema) { + lazy.modifySchemaForTests(customSchema || null); + + const policiesAppliedPromise = this.applyRemotePolicies(json, false); + + Services.obs.notifyObservers(null, "EnterprisePolicies:Restart"); + + return policiesAppliedPromise; + }, + + async applyRemotePolicies(policies, isUpdate = true) { + const { promise, resolve } = Promise.withResolvers(); + if (isUpdate) { + // Resolve once policies are updated + this.resolveOnceAllPolicyUpdatesApplied(resolve); + } else { + // Resolve once all policies are applied on initial activation + this.resolveOnceAllPoliciesApplied(resolve); } - let { promise, resolve } = Promise.withResolvers(); + const { ConsoleClient } = ChromeUtils.importESModule( + "resource:///modules/enterprise/ConsoleClient.sys.mjs" + ); - this._httpd.registerPathHandler("/api/browser/policies", (req, resp) => { - resp.setStatusLine(req.httpVersion, 200, "OK"); - resp.write(JSON.stringify(json)); - lazy.modifySchemaForTests(customSchema || null); - lazy.setTimeout(() => { - resolve(); - }, 100); - }); + if (this.remotePoliciesStub) { + this.remotePoliciesStub.restore(); + } + this.remotePoliciesStub = lazy.sinon.stub( + ConsoleClient, + "getRemotePolicies" + ); + + const returnRemotePolicies = () => { + return Promise.resolve(policies); + }; + + this.remotePoliciesStub.callsFake(returnRemotePolicies); return promise; }, @@ -208,7 +219,9 @@ export var PoliciesPrefTracker = { // If a pref was used through setDefaultPref instead // of setAndLockPref, it wasn't locked, but calling // unlockPref is harmless - Preferences.unlock(prefName); + if (Services.prefs.prefIsLocked(prefName)) { + Preferences.unlock(prefName); + } if (stored.originalDefaultValue !== undefined) { defaults.set(prefName, stored.originalDefaultValue); diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/browser.toml index 054db55d80de7..b680d961d6914 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/browser.toml @@ -2,28 +2,20 @@ head = "head.js" support-files = ["config_broken_json.json"] prefs = [ - "enterprise.console.address=", - "browser.policies.testUseHttp=false", - "browser.policies.live_polling.frequency=500", + "browser.policies.remote.enabled=false", +] +environment = [ + "MOZ_BYPASS_FELT=1", + "MOZ_AUTOMATION=1", ] ["browser_policies_basic_tests.js"] -["browser_policies_basiclive_tests.js"] - ["browser_policies_broken_json.js"] -["browser_policies_diffing.js"] - ["browser_policies_gpo.js"] run-if = [ "os == 'win'", ] -["browser_policies_live_proxy.js"] - -["browser_policies_missing_live.js"] - ["browser_policies_mistyped_json.js"] - -["browser_policies_prefs.js"] diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basic_tests.js b/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basic_tests.js index 8f4eec8716da0..513b38366eb84 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basic_tests.js +++ b/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basic_tests.js @@ -3,10 +3,6 @@ "use strict"; -add_setup(function test_set_local_file_usage() { - SpecialPowers.pushPrefEnv({ set: [["browser.policies.testUseHttp", false]] }); -}); - add_task(test_simple_policies); add_task(async function test_policy_cleanup() { diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basiclive_tests.js b/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basiclive_tests.js deleted file mode 100644 index 4b24dd9e9c1a7..0000000000000 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_basiclive_tests.js +++ /dev/null @@ -1,27 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -// TODO: We only test the RemotePoliciesProvider here, it would be nice -// to enhance with testing combinations of providers - -add_setup(async function test_set_http_server_usage() { - await SpecialPowers.pushPrefEnv({ - set: [["browser.policies.testUseHttp", true]], - }); - - await EnterprisePolicyTesting.servePolicyWithJson( - {}, - {}, - registerCleanupFunction - ); - assertOverHttp(); -}); - -add_task(test_simple_policies); - -add_task(async function test_policy_cleanup() { - await EnterprisePolicyTesting.servePolicyWithJson({}, {}); - assert_policy_cleanup(); -}); diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_diffing.js b/toolkit/components/enterprisepolicies/tests/browser/browser_policies_diffing.js deleted file mode 100644 index c258fc1f72759..0000000000000 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_diffing.js +++ /dev/null @@ -1,164 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -add_setup(async function test_set_http_server_usage() { - await SpecialPowers.pushPrefEnv({ - set: [ - ["browser.policies.live_polling_freq", 250], - ["browser.policies.testUseHttp", true], - ], - }); - - await EnterprisePolicyTesting.servePolicyWithJson( - {}, - {}, - registerCleanupFunction - ); - - if (Services.prefs.getBoolPref("browser.policies.testUseHttp")) { - assertOverHttp(); - } -}); - -const lazy = {}; - -ChromeUtils.defineESModuleGetters(lazy, { - Policies: "resource:///modules/policies/Policies.sys.mjs", - setTimeout: "resource://gre/modules/Timer.sys.mjs", -}); - -add_task(async function test_simple_policy_removal() { - const customSchema = { - properties: { - BlockSomePage: { - type: "boolean", - }, - }, - }; - - let blockSomePageApplied = false; - - // Inspired by BlockAboutConfig - lazy.Policies.BlockSomePage = { - onBeforeUIStartup(manager, param) { - if (param) { - blockSomePageApplied = true; - } - }, - onRemove(manager, oldParam) { - if (oldParam) { - // Previous policy param was "true" so revert and disable the blocking - blockSomePageApplied = false; - } - }, - }; - - await setupPolicyEngineWithJson( - { - policies: { - BlockSomePage: true, - }, - }, - customSchema - ); - - ok(blockSomePageApplied, "BlockSomePage enabled"); - - await setupPolicyEngineWithJson( - { - policies: {}, - }, - customSchema - ); - - ok(!blockSomePageApplied, "BlockSomePage disabled"); - - delete lazy.Policies.BlockSomePage; -}); - -add_task(async function test_simple_policy_stays() { - const customSchema = { - properties: { - BlockAnotherPage: { - type: "boolean", - }, - }, - }; - - let blockAnotherPageApplied = false; - - // Inspired by BlockAboutConfig - lazy.Policies.BlockAnotherPage = { - onBeforeUIStartup(manager, param) { - info(`BlockAnotherPage.onBeforeUIStartup(${param})`); - if (param) { - blockAnotherPageApplied = true; - } - }, - onRemove(manager, oldParam) { - info(`BlockAnotherPage.onRemove(${oldParam})`); - if (oldParam) { - // Previous policy param was "true" so revert and disable the blocking - blockAnotherPageApplied = false; - } - }, - }; - - await setupPolicyEngineWithJson( - { - policies: { - BlockAnotherPage: true, - }, - }, - customSchema - ); - - // We received payload and applied once - ok(blockAnotherPageApplied, "BlockAnotherPage enabled"); - - // This is not really representative of how things can happen but rather to - // verify that the policy's callback was not called a second time. - // - // Intended behavior is: - // - poll - // + get policy1 with param X=Y - // + apply policy1 with callback onBeforeUIStartup - // - poll - // + get policy1 with param X=Y - // + no change to policy1 so no call to onBeforeUIStartup - // + no state changed - // - // => This is where check happens because we locally changed the state, so - // it is expected that the state stays this way (and is technically - // incorrect WRT policy at the moment) - // - - blockAnotherPageApplied = false; - - // polling happens on a specific frequency so wait enough to be certain - await new Promise(resolve => lazy.setTimeout(resolve, 500)); - - ok(!blockAnotherPageApplied, "BlockAnotherPage not re-enabled by policy"); - - // Set back the correct value - blockAnotherPageApplied = true; - - // Now publish a new instance where the policy has been removed - await setupPolicyEngineWithJson( - { - policies: {}, - }, - customSchema - ); - - // Policy being removed it means the blocking should get lifted - ok(!blockAnotherPageApplied, "BlockAnotherPage disabled"); - - delete lazy.Policies.BlockAnotherPage; -}); - -add_task(async function policy_cleanup() { - await EnterprisePolicyTesting.servePolicyWithJson({}, {}); -}); diff --git a/toolkit/components/enterprisepolicies/tests/browser/head.js b/toolkit/components/enterprisepolicies/tests/browser/head.js index defbbbd718a48..e64e2b7f340ea 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/head.js +++ b/toolkit/components/enterprisepolicies/tests/browser/head.js @@ -4,10 +4,17 @@ "use strict"; -const { EnterprisePolicyTesting, PoliciesPrefTracker } = - ChromeUtils.importESModule( - "resource://testing-common/EnterprisePolicyTesting.sys.mjs" - ); +const { + EnterprisePolicyTesting, + PoliciesPrefTracker, + REMOTE_POLICIES_TESTING_PREF, +} = ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" +); + +const { Policies } = ChromeUtils.importESModule( + "resource:///modules/policies/Policies.sys.mjs" +); PoliciesPrefTracker.start(); registerCleanupFunction(() => { @@ -16,11 +23,14 @@ registerCleanupFunction(() => { async function setupPolicyEngineWithJson(json, customSchema) { PoliciesPrefTracker.restoreDefaultValues(); - const useHttp = Services.prefs.getBoolPref("browser.policies.testUseHttp"); - if (!useHttp) { - return setupPolicyWithJsonFile(json, customSchema); + const isRemotePoliciesTesting = Services.prefs.getBoolPref( + REMOTE_POLICIES_TESTING_PREF, + false + ); + if (isRemotePoliciesTesting) { + return servePolicyWithRemoteJson(json, customSchema); } - return servePolicyWithJson(json, customSchema, registerCleanupFunction); + return setupPolicyWithJsonFile(json, customSchema); } async function setupPolicyWithJsonFile(json, customSchema) { @@ -34,22 +44,14 @@ async function setupPolicyWithJsonFile(json, customSchema) { return EnterprisePolicyTesting.setupPolicyEngineWithJson(json, customSchema); } -function assertOverHttp() { - Assert.notEqual( - EnterprisePolicyTesting._httpd, - undefined, - "Making sure HTTP delivery" - ); -} - -async function servePolicyWithJson(json, customSchema) { - return EnterprisePolicyTesting.servePolicyWithJson(json, customSchema); +async function servePolicyWithRemoteJson(json, customSchema) { + return EnterprisePolicyTesting.servePolicyWithRemoteJson(json, customSchema); } function assert_policy_cleanup() { is( - Services.policies.getActivePolicies(), - undefined, + JSON.stringify(Services.policies.getActivePolicies()), + JSON.stringify({}), "No policies should be defined" ); is( @@ -60,10 +62,6 @@ function assert_policy_cleanup() { } async function test_simple_policies() { - let { Policies } = ChromeUtils.importESModule( - "resource:///modules/policies/Policies.sys.mjs" - ); - let policy0Ran = false, policy1Ran = false, policy2Ran = false, diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml new file mode 100644 index 0000000000000..c6e3bbc07d920 --- /dev/null +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml @@ -0,0 +1,22 @@ +[DEFAULT] +head = "head.js" +support-files = ["../head.js"] +prefs = [ + "browser.policies.remote.enabled=true", + "browser.policies.live_polling.enabled=true", + "browser.policies.live_polling.frequency=500", +] +environment = [ + "MOZ_BYPASS_FELT=1", + "MOZ_AUTOMATION=1", +] + +["browser_remote_policies_basic.js"] + +["browser_remote_policies_diffing.js"] + +["browser_remote_policies_live_proxy.js"] + +["browser_remote_policies_missing_live.js"] + +["browser_remote_policies_prefs.js"] diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_basic.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_basic.js new file mode 100644 index 0000000000000..9c3a8176ce58b --- /dev/null +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_basic.js @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../head.js */ + +"use strict"; + +add_task(test_simple_policies); diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js new file mode 100644 index 0000000000000..bcb8702ebe0c2 --- /dev/null +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js @@ -0,0 +1,212 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const customSchema = { + properties: { + TestPolicy: { + type: "string", + }, + }, +}; + +let policyValue = POLICY_PARAM_STATE.DEFAULT; + +const TestPolicy = { + onBeforeUIStartup(manager, param) { + policyValue = param; + }, + onRemove(_manager, _oldParam) { + policyValue = POLICY_PARAM_STATE.REMOVED; + }, +}; + + +add_setup(async () => { + Policies.TestPolicy = TestPolicy; + + registerCleanupFunction(async () => { + dump("In cleanup") + delete Policies.TestPolicy; + }); +}); + +add_task(async function test_policy_update_apply_new_policy() { + policyValue = POLICY_PARAM_STATE.DEFAULT; + + await setupPolicyEngineWithJson( + { + policies: {}, + }, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + {}, + "Expected no policies to be applied." + ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.DEFAULT, + "Expected the default policy parameter." + ); + + const policies = { + policies: { + TestPolicy: POLICY_PARAM_STATE.APPLIED, + }, + }; + + await EnterprisePolicyTesting.applyRemotePolicies(policies); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.APPLIED }, + "Expected remote policy TestPolicy with parameter APPLIED." + ); + +}); + +add_task(async function test_policy_update_apply_policy_param_update() { + policyValue = POLICY_PARAM_STATE.DEFAULT; + + await setupPolicyEngineWithJson( + { + policies: { + TestPolicy: POLICY_PARAM_STATE.APPLIED, + }, + }, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.APPLIED }, + "Expected remote policy TestPolicy with parameter APPLIED." + ); + + policyValue = POLICY_PARAM_STATE.DEFAULT; + + const policies = { + policies: { + TestPolicy: POLICY_PARAM_STATE.UPDATED, + }, + }; + + await EnterprisePolicyTesting.applyRemotePolicies(policies); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.UPDATED }, + "Expected remote policy TestPolicy with parameter UPDATED." + ); +}); + +add_task(async function test_policy_update_remove_old_policy() { + policyValue = POLICY_PARAM_STATE.DEFAULT; + + await setupPolicyEngineWithJson( + { + policies: { + TestPolicy: POLICY_PARAM_STATE.APPLIED, + }, + }, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.APPLIED }, + "Expected remote policy TestPolicy with parameter APPLIED." + ); + + const policies = { + policies: {}, + }; + + await EnterprisePolicyTesting.applyRemotePolicies(policies); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + {}, + "Expected remote policy TestPolicy to be removed." + ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.REMOVED, + "Expected the policy parameter to be of state REMOVED." + ); +}); + +add_task(async function test_policy_update_no_changes() { + policyValue = POLICY_PARAM_STATE.DEFAULT; + + await setupPolicyEngineWithJson( + { + policies: { + TestPolicy: POLICY_PARAM_STATE.APPLIED, + }, + }, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.APPLIED }, + "Expected remote policy TestPolicy with parameter APPLIED." + ); + + // This is not really representative of how things can happen but rather to + // verify that the policy's callback was not called a second time. + // + // Intended behavior is: + // - poll + // + get policy1 with param X=Y + // + apply policy1 with callback onBeforeUIStartup + // - poll + // + get policy1 with param X=Y + // + no change to policy1 so no call to onBeforeUIStartup + // + no state changed + // + // => This is where check happens because we locally changed the state, so + // it is expected that the state stays this way (and is technically + // incorrect WRT policy at the moment) + // + + // Revert back to DEFAULT + policyValue = POLICY_PARAM_STATE.DEFAULT; + + // Wait for next policy update to complete + await EnterprisePolicyTesting.nextPolicyUpdatesApplied(); + + // Verify that the policy's callback wasn't called a second time. + Assert.deepEqual( + Services.policies.getActivePolicies(), + { TestPolicy: POLICY_PARAM_STATE.APPLIED }, + "Expected no changes to the active policy specifications." + ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.DEFAULT, + "Expected local changes to policy parameters to not get overridden." + ); + + const policies = { + policies: {}, + }; + + await EnterprisePolicyTesting.applyRemotePolicies(policies); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + {}, + "Expected remote policy TestPolicy to be removed." + ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.REMOVED, + "Expected the policy parameter to be of state REMOVED." + ); +}); diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_live_proxy.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_live_proxy.js similarity index 90% rename from toolkit/components/enterprisepolicies/tests/browser/browser_policies_live_proxy.js rename to toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_live_proxy.js index b346e00857a31..56eb6c18e3448 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_live_proxy.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_live_proxy.js @@ -43,20 +43,6 @@ function checkProxyPref(proxytype, address, port, unlocked = true) { } } -add_setup(async function test_set_http_server_usage() { - await SpecialPowers.pushPrefEnv({ - set: [["browser.policies.testUseHttp", true]], - }); - - await EnterprisePolicyTesting.servePolicyWithJson( - {}, - {}, - registerCleanupFunction - ); - - assertOverHttp(); -}); - add_task(async function test_apply_then_remove_proxy() { // Assert proxy settings are not set checkProxyPref("http", "", 0); @@ -70,6 +56,7 @@ add_task(async function test_apply_then_remove_proxy() { "changeProxySettings is allowed" ); + info("Setting up policy engine.") await setupPolicyEngineWithJson( { policies: { @@ -96,12 +83,11 @@ add_task(async function test_apply_then_remove_proxy() { "changeProxySettings is blocked" ); - // New policy removing proxy - await setupPolicyEngineWithJson( + // Remove Proxy policy + await EnterprisePolicyTesting.applyRemotePolicies( { policies: {}, }, - null ); // Assert proxy settings are remove @@ -157,12 +143,11 @@ add_task(async function test_apply_then_remove_proxy_locked() { "changeProxySettings is blocked" ); - // New policy removing proxy - await setupPolicyEngineWithJson( + // Remove Proxy policy + await EnterprisePolicyTesting.applyRemotePolicies( { policies: {}, }, - null ); // Assert proxy settings are remove @@ -206,7 +191,7 @@ add_task(async function test_apply_proxy_then_change_proxy() { ); // Network change from device posture? New policy - await setupPolicyEngineWithJson( + await EnterprisePolicyTesting.applyRemotePolicies( { policies: { Proxy: { @@ -217,7 +202,6 @@ add_task(async function test_apply_proxy_then_change_proxy() { }, }, }, - null ); // Assert proxy settings are set @@ -231,8 +215,4 @@ add_task(async function test_apply_proxy_then_change_proxy() { true, "changeProxySettings is allowed" ); -}); - -add_task(async function policy_cleanup() { - await EnterprisePolicyTesting.servePolicyWithJson({}, {}); -}); +}); \ No newline at end of file diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_missing_live.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js similarity index 79% rename from toolkit/components/enterprisepolicies/tests/browser/browser_policies_missing_live.js rename to toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js index d9bacd2285426..5b935a1fb4709 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_missing_live.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js @@ -10,7 +10,7 @@ ChromeUtils.defineESModuleGetters(lazy, { }); add_task(async function check_all_policies_are_live() { - const allPolicies = new Set(Object.keys(lazy.Policies)); + const policies = new Set(Object.keys(lazy.Policies)); // Set of policies we know cannot be live const notLivePolicies = new Set([ @@ -108,44 +108,33 @@ add_task(async function check_all_policies_are_live() { "WindowsSSO", ]); - const allLivePolicies = allPolicies.difference(notLivePolicies); + const livePolicies = policies.difference(notLivePolicies); - let liveEnabled = new Set(); + let policiesAppliable = new Set(); - for (let policyName of allPolicies) { + for (const policyName of policies) { const policy = lazy.Policies[policyName]; const hasOnRemove = typeof policy.onRemove === "function"; if (hasOnRemove) { - liveEnabled.add(policyName); + policiesAppliable.add(policyName); } } - const notEnabled = [ - ...allLivePolicies - .difference(liveEnabled) - .entries() - .map(e => e[0]), - ]; - if (notEnabled.length) { - console.debug(`Not enabled live policies`, JSON.stringify(notEnabled)); + const livePoliciesNotAppliable = livePolicies.difference(policiesAppliable) + if (livePoliciesNotAppliable.size) { + console.debug(`Live policies that are not appliable because of missing remove functions ${JSON.stringify(livePoliciesNotAppliable)}`); } - Assert.equal(notEnabled.length, 0, "Not all policies are live. Work better."); + Assert.equal(livePoliciesNotAppliable.size, 0, "Not all policies are live. Work better."); - const liveAndNotLive = [ - ...liveEnabled - .intersection(notLivePolicies) - .entries() - .map(e => e[0]), - ]; - if (liveAndNotLive.length) { + const liveAndNotLive = policiesAppliable.intersection(notLivePolicies) + if (liveAndNotLive.size) { console.debug( - `Inconsistent state: live and not live`, - JSON.stringify(liveAndNotLive) + `Inconsistent state: live and not live ${JSON.stringify(liveAndNotLive)}` ); } Assert.equal( - liveAndNotLive.length, + liveAndNotLive.size, 0, "There should be no policy both live and not live." ); diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_prefs.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js similarity index 55% rename from toolkit/components/enterprisepolicies/tests/browser/browser_policies_prefs.js rename to toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js index ed0c920529c89..25701c3db1e92 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser_policies_prefs.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js @@ -3,89 +3,54 @@ "use strict"; -const lazy = {}; +const { setAndLockPref, unsetAndUnlockPref } = ChromeUtils.importESModule( + "resource:///modules/policies/Policies.sys.mjs" +); -ChromeUtils.defineESModuleGetters(lazy, { - Policies: "resource:///modules/policies/Policies.sys.mjs", - setAndLockPref: "resource:///modules/policies/Policies.sys.mjs", - unsetAndUnlockPref: "resource:///modules/policies/Policies.sys.mjs", -}); - -add_setup(async function test_set_http_server_usage() { - await SpecialPowers.pushPrefEnv({ - set: [["browser.policies.testUseHttp", true]], - }); - - await EnterprisePolicyTesting.servePolicyWithJson( - {}, - {}, - registerCleanupFunction - ); - - if (Services.prefs.getBoolPref("browser.policies.testUseHttp")) { - assertOverHttp(); - } -}); +const PREF_VALUE = { + DEFAULT: "default", + USER_CHANGED: "user-changed", + POLICY_DEFAULT: "policy-default", + POLICY_CHANGED: "policy-changed", +} add_task(async function test_simple_policy_pref_setAndLock() { - const customSchema = { - properties: { - SetSomePref: { - type: "boolean", - }, - }, - }; - + const prefName = "browser.tests.some_random_pref"; - // just something random - const prefValue = "fcf57517-a524-4468-bff7-0817b2ad6a31"; try { Services.prefs.getStringPref(prefName); ok(false, `Pref ${prefName} exists, this should not happen`); } catch { - ok(true, `Pref ${prefName} does not exists`); + ok(true, `Pref ${prefName} does not exist`); } - lazy.Policies.SetSomePref = { - onBeforeUIStartup(manager, param) { - if (param) { - lazy.setAndLockPref(prefName, `${prefValue}-policyDefault`); - } - }, - onRemove(manager, oldParams) { - if (oldParams) { - lazy.unsetAndUnlockPref(prefName, `${prefValue}-policyDefault`); - } - }, - }; - let defaults = Services.prefs.getDefaultBranch(""); - defaults.setStringPref(prefName, prefValue); + defaults.setStringPref(prefName, PREF_VALUE.DEFAULT); // Assert default pref value is( Services.prefs.getStringPref(prefName), - prefValue, - "default pref value returned via Services.prefs." + PREF_VALUE.DEFAULT, + "Correct default pref value returned via Services.prefs." ); is( defaults.getStringPref(prefName), - prefValue, - "default pref value returned via defaults." + PREF_VALUE.DEFAULT, + "Correct default pref value returned via defaults." ); - Services.prefs.setStringPref(prefName, `${prefValue}-user`); + Services.prefs.setStringPref(prefName, PREF_VALUE.USER_CHANGED); // Assert user value works is( Services.prefs.getStringPref(prefName), - `${prefValue}-user`, + PREF_VALUE.USER_CHANGED, "user pref value returned via Services.prefs." ); is( defaults.getStringPref(prefName), - prefValue, + PREF_VALUE.DEFAULT, "default pref value returned via defaults." ); @@ -96,10 +61,31 @@ add_task(async function test_simple_policy_pref_setAndLock() { "Pref reports as not locked" ); + const customSchema = { + properties: { + SetSomePref: { + type: "string", + }, + }, + }; + + Policies.SetSomePref = { + onBeforeUIStartup(manager, param) { + if (param) { + setAndLockPref(prefName, param); + } + }, + onRemove(manager, oldParams) { + if (oldParams) { + unsetAndUnlockPref(prefName); + } + }, + }; + await setupPolicyEngineWithJson( { policies: { - SetSomePref: true, + SetSomePref: PREF_VALUE.POLICY_DEFAULT, }, }, customSchema @@ -108,41 +94,60 @@ add_task(async function test_simple_policy_pref_setAndLock() { // Assert pref value set and locked, default value returned is( Services.prefs.getStringPref(prefName), - `${prefValue}-policyDefault`, + PREF_VALUE.POLICY_DEFAULT, "new default pref value returned via Services.prefs." ); is( defaults.getStringPref(prefName), - `${prefValue}-policyDefault`, + PREF_VALUE.POLICY_DEFAULT, "new default pref value returned via defaults." ); is(true, Services.prefs.prefIsLocked(prefName), "Pref reports as locked"); - await setupPolicyEngineWithJson( + await EnterprisePolicyTesting.applyRemotePolicies( + { + policies: { + SetSomePref: PREF_VALUE.POLICY_CHANGED, + }, + }, + ); + + // Assert pref value set and locked, default value returned + is( + Services.prefs.getStringPref(prefName), + PREF_VALUE.POLICY_CHANGED, + "new changed pref value returned via Services.prefs." + ); + is( + defaults.getStringPref(prefName), + PREF_VALUE.POLICY_CHANGED, + "new changed pref value returned via defaults." + ); + is(true, Services.prefs.prefIsLocked(prefName), "Pref remains as locked"); + + await EnterprisePolicyTesting.applyRemotePolicies( { policies: {}, }, - customSchema ); // Assert original default pref and user value returned again is( Services.prefs.getStringPref(prefName), - `${prefValue}-user`, + PREF_VALUE.USER_CHANGED, "original user pref value returned via Services.prefs." ); is( defaults.getStringPref(prefName), - prefValue, + PREF_VALUE.DEFAULT, "original default pref value returned via defaults." ); - is(false, Services.prefs.prefIsLocked(prefName), "Pref reports as locked"); - delete lazy.Policies.SetSomePref; + is(false, Services.prefs.prefIsLocked(prefName), "Pref reports as unlocked"); + + delete Policies.SetSomePref; Services.prefs.deleteBranch(prefName); }); -add_task(async function policy_cleanup() { - await EnterprisePolicyTesting.servePolicyWithJson({}, {}); -}); + \ No newline at end of file diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/head.js b/toolkit/components/enterprisepolicies/tests/browser/remote/head.js new file mode 100644 index 0000000000000..30f91d300c188 --- /dev/null +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/head.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* import-globals-from ../head.js */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/components/enterprisepolicies/tests/browser/head.js", + this +); + +const POLICY_PARAM_STATE = { + DEFAULT: "default", + APPLIED: "applied", + APPLIED_LOCAL_POLICY: "applied-by-local-policy", + APPLIED_REMOTE_POLICY: "applied-by-remote-policy", + UPDATED: "updated", + REMOVED: "removed", +}; + +add_setup(async () => { + registerCleanupFunction(async () => { + Services.obs.notifyObservers(null, "EnterprisePolicies:Reset"); + if (EnterprisePolicyTesting.remotePoliciesStub) { + EnterprisePolicyTesting.remotePoliciesStub.restore(); + EnterprisePolicyTesting.remotePoliciesStub = null; + } + }); +}); diff --git a/toolkit/components/enterprisepolicies/tests/moz.build b/toolkit/components/enterprisepolicies/tests/moz.build index 14d1ec3803619..6bad3c01cf2b5 100644 --- a/toolkit/components/enterprisepolicies/tests/moz.build +++ b/toolkit/components/enterprisepolicies/tests/moz.build @@ -8,6 +8,11 @@ BROWSER_CHROME_MANIFESTS += [ "browser/browser.toml", ] +if CONFIG["MOZ_ENTERPRISE"]: + BROWSER_CHROME_MANIFESTS += [ + "browser/remote/browser.toml", + ] + TESTING_JS_MODULES += [ "EnterprisePolicyTesting.sys.mjs", ] diff --git a/toolkit/modules/Preferences.sys.mjs b/toolkit/modules/Preferences.sys.mjs index 91b68b5bfbfee..d5bd7e86acf85 100644 --- a/toolkit/modules/Preferences.sys.mjs +++ b/toolkit/modules/Preferences.sys.mjs @@ -246,7 +246,7 @@ Preferences.lock = function (prefName) { * Unlock a pref so it can be changed. * * @param prefName {String|Array} - * the pref to lock, or an array of prefs to lock + * the pref to unlock, or an array of prefs to unlock */ Preferences.unlock = function (prefName) { if (Array.isArray(prefName)) { From a8500bab1de6c0430f578cbf8a7cc1f66600bbcc Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 22 Jan 2026 14:31:32 +0100 Subject: [PATCH 03/33] Add comment in firefox-enterprise.js --- .../enterprise/pref/firefox-enterprise.js | 2 +- .../tests/EnterprisePolicyTesting.sys.mjs | 62 ++++++++++++++----- .../remote/browser_remote_policies_diffing.js | 2 +- 3 files changed, 47 insertions(+), 19 deletions(-) diff --git a/browser/branding/enterprise/pref/firefox-enterprise.js b/browser/branding/enterprise/pref/firefox-enterprise.js index 8fbe25c9ae0fc..523109f4e3ed5 100644 --- a/browser/branding/enterprise/pref/firefox-enterprise.js +++ b/browser/branding/enterprise/pref/firefox-enterprise.js @@ -2,7 +2,7 @@ * 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/. */ -/* global pref */ +/* Preferences here are going to be applied both to Felt and to the launched Firefox. */ pref("enterprise.console.address", "https://console.enterfox.eu"); diff --git a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs index 60326cc6aef2c..f7874ed5bd47d 100644 --- a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs +++ b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs @@ -11,11 +11,14 @@ ChromeUtils.defineESModuleGetters(lazy, { FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs", modifySchemaForTests: "resource:///modules/policies/schema.sys.mjs", sinon: "resource://testing-common/Sinon.sys.mjs", + ConsoleClient: "resource:///modules/enterprise/ConsoleClient.sys.mjs", }); export const REMOTE_POLICIES_TESTING_PREF = "browser.policies.remote.enabled"; export const EnterprisePolicyTesting = { + + /* The stub wrapping ConsoleClient.getRemotePolicies to control which remote policies are fetched */ get remotePoliciesStub() { return this._remotePoliciesStub; }, @@ -24,6 +27,12 @@ export const EnterprisePolicyTesting = { this._remotePoliciesStub = stub; }, + /** + * Observe for all policies to be applied. This notification + * is sent when the policy engine is started up or reseted. + * + * @param {Promise} resolve Promise that resolves once all policies are applied. + */ resolveOnceAllPoliciesApplied(resolve) { Services.obs.addObserver(function observer() { Services.obs.removeObserver( @@ -34,6 +43,22 @@ export const EnterprisePolicyTesting = { }, "EnterprisePolicies:AllPoliciesApplied"); }, + /** + * Observe for a policy update. This notification is sent once + * we check the console for updated policies. + * + * @param {Promise} resolve Promise that resolves once the policy update is handled. + */ + resolveOnceAllPolicyUpdatesApplied(resolve) { + Services.obs.addObserver(function observer() { + Services.obs.removeObserver( + observer, + "EnterprisePolicies:PolicyUpdatesApplied" + ); + resolve(); + }, "EnterprisePolicies:PolicyUpdatesApplied"); + }, + // |json| must be an object representing the desired policy configuration, OR a // path to the JSON file containing the policy configuration. setupPolicyEngineWithJson: async function setupPolicyEngineWithJson( @@ -63,32 +88,39 @@ export const EnterprisePolicyTesting = { return promise; }, - resolveOnceAllPolicyUpdatesApplied(resolve) { - Services.obs.addObserver(function observer() { - Services.obs.removeObserver( - observer, - "EnterprisePolicies:PolicyUpdatesApplied" - ); - resolve(); - }, "EnterprisePolicies:PolicyUpdatesApplied"); - }, - nextPolicyUpdatesApplied() { + awaitNextPolicyUpdate() { const { promise, resolve } = Promise.withResolvers(); this.resolveOnceAllPolicyUpdatesApplied(resolve); return promise; }, - async servePolicyWithRemoteJson(json, customSchema) { + /** + * Apply the custom schema, setup the remote policies stub and + * trigger a restart of the policy engine. + * + * @param {object} policies + * @param {object} customSchema + * @returns {Promise} Promise that resolves once the set of policies are applied + */ + async servePolicyWithRemoteJson(policies, customSchema) { lazy.modifySchemaForTests(customSchema || null); - const policiesAppliedPromise = this.applyRemotePolicies(json, false); + const policiesAppliedPromise = this.applyRemotePolicies(policies, false); Services.obs.notifyObservers(null, "EnterprisePolicies:Restart"); return policiesAppliedPromise; }, + /** + * Listen for the policies to be applied and stub the remote policies. + * + * @param {object} policies + * @param {boolean} isUpdate Whether the promise resolves once all policies are + * applied on startup or once the policy update is complete + * @returns {Promise} Promise that resolves once the set of policies are applied + */ async applyRemotePolicies(policies, isUpdate = true) { const { promise, resolve } = Promise.withResolvers(); if (isUpdate) { @@ -99,15 +131,11 @@ export const EnterprisePolicyTesting = { this.resolveOnceAllPoliciesApplied(resolve); } - const { ConsoleClient } = ChromeUtils.importESModule( - "resource:///modules/enterprise/ConsoleClient.sys.mjs" - ); - if (this.remotePoliciesStub) { this.remotePoliciesStub.restore(); } this.remotePoliciesStub = lazy.sinon.stub( - ConsoleClient, + lazy.ConsoleClient, "getRemotePolicies" ); diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js index bcb8702ebe0c2..16332746f29c9 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js @@ -179,7 +179,7 @@ add_task(async function test_policy_update_no_changes() { policyValue = POLICY_PARAM_STATE.DEFAULT; // Wait for next policy update to complete - await EnterprisePolicyTesting.nextPolicyUpdatesApplied(); + await EnterprisePolicyTesting.awaitNextPolicyUpdate(); // Verify that the policy's callback wasn't called a second time. Assert.deepEqual( From ce4e88e1f34ead6821c2920bea3d0e1cc39740a0 Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 22 Jan 2026 15:57:08 +0100 Subject: [PATCH 04/33] Test merging local and remote policies --- .../tests/browser/remote/browser.toml | 2 + ...owser_remote_and_local_policies_merging.js | 149 ++++++++++++++++++ .../tests/browser/remote/head.js | 33 ++++ 3 files changed, 184 insertions(+) create mode 100644 toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml index c6e3bbc07d920..7fd6c63b955fe 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml @@ -11,6 +11,8 @@ environment = [ "MOZ_AUTOMATION=1", ] +["browser_remote_and_local_policies_merging.js"] + ["browser_remote_policies_basic.js"] ["browser_remote_policies_diffing.js"] diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js new file mode 100644 index 0000000000000..08e036e020040 --- /dev/null +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js @@ -0,0 +1,149 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const customSchema = { + properties: { + simple_policy0: { + type: "string", + }, + + simple_policy1: { + type: "string", + }, + }, +} + +let policy_value0 = POLICY_PARAM_STATE.DEFAULT; +let policy_value1 = POLICY_PARAM_STATE.DEFAULT; + +const simple_policy0 = { + onBeforeUIStartup : (_manager, param) => { + policy_value0 = param; + }, + onRemove : (_manager, _oldParam) => { + policy_value0 = POLICY_PARAM_STATE.REMOVED; + } +} + +const simple_policy1 = { + onBeforeUIStartup : (_manager, param) => { + policy_value1 = param; + }, + onRemove : (_manager, _oldParam) => { + policy_value1 = POLICY_PARAM_STATE.REMOVED; + } +} + +add_setup(() => { + Policies.simple_policy0 = simple_policy0; + Policies.simple_policy1 = simple_policy1; + + registerCleanupFunction(async () => { + delete Policies.simple_policy0; + delete Policies.simple_policy1; + await EnterprisePolicyTesting.setupPolicyEngineWithJson(""); + assert_policy_cleanup(); + }); +}); + +add_task(async function test_remote_policy_overrides_local_policy() { + policy_value0 = POLICY_PARAM_STATE.DEFAULT; + + const localPolicies = { + policies: { + simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + }, + } + let remotePolicies = { + policies: {}, + }; + + await setupPolicyEngineWithCombinedPolicyProvider( + localPolicies, + remotePolicies, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY }, + "Expected local policy simple_policy0 to be set." + ); + Assert.equal( + policy_value0, + POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + "Expected local policy simple_policy0 to be set." + ); + + remotePolicies = { + policies: { + simple_policy0: POLICY_PARAM_STATE.UPDATED, + }, + }; + + await EnterprisePolicyTesting.applyRemotePolicies(remotePolicies); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { simple_policy0: POLICY_PARAM_STATE.UPDATED }, + "Expected remote policy update to override local policies." + ); + Assert.equal( + policy_value0, + POLICY_PARAM_STATE.UPDATED, + "Expected remote policy update to override local policies." + ); + + // Remove remote policies, re-apply local policy + await EnterprisePolicyTesting.applyRemotePolicies({ policies: {} }); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY }, + "Expected local policy to be re-applied if the remote policy is removed." + ); + Assert.equal( + policy_value0, + POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + "Expected local policy to be re-applied if the remote policy is removed." + ); +}); + +add_task(async function test_remote_and_local_policy_merged() { + policy_value1 = POLICY_PARAM_STATE.DEFAULT; + + const localPolicies = { + policies: { + simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + }, + } + let remotePolicies = { + policies: { + simple_policy1: POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY, + }, + }; + + await setupPolicyEngineWithCombinedPolicyProvider( + localPolicies, + remotePolicies, + customSchema + ); + + Assert.deepEqual( + Services.policies.getActivePolicies(), + { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, simple_policy1: POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY }, + "Expected local and remote policy to be merged." + ); + Assert.equal( + policy_value0, + POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + "Expected local policy to be applied." + ); + Assert.equal( + policy_value1, + POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY, + "Expected local policy to be applied." + ); +}); diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/head.js b/toolkit/components/enterprisepolicies/tests/browser/remote/head.js index 30f91d300c188..f0d3b39939067 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/head.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/head.js @@ -28,3 +28,36 @@ add_setup(async () => { } }); }); + +/** + * Set up a policy engine that combined local and regularly fetched remote policies. + * + * @param {object} localPolicies Policies to be read from a local policies.json + * @param {object} remotePolicies Policies to be fetched from a stubed ConsoleClient endpoint + * @param {object} customSchema + * + * @returns {Promise} Promise that resolves once local and remote policies are applied after a policy engine restart. + */ +async function setupPolicyEngineWithCombinedPolicyProvider( + localPolicies, + remotePolicies, + customSchema +) { + PoliciesPrefTracker.restoreDefaultValues(); + + // Stub remote policies endpoint + const remotePoliciesAppliedPromise = + EnterprisePolicyTesting.applyRemotePolicies(remotePolicies, false); + + // Put local policies in place (local policies.json file) + const localPoliciesAppliedPromise = setupPolicyWithJsonFile( + localPolicies, + customSchema + ); + + // Waiting for the "EnterprisePolicies:PolicyUpdatesApplied" notification + return Promise.all([ + localPoliciesAppliedPromise, + remotePoliciesAppliedPromise, + ]); +} From ed9a580562a4f06e98275ba91f44dc12f8453a61 Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 22 Jan 2026 16:03:19 +0100 Subject: [PATCH 05/33] Spin event loop until remote policies are fetched --- .../EnterprisePoliciesParent.sys.mjs | 46 +++++++++++++++++-- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index d05d31924713f..f061fe661a7c8 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -117,7 +117,7 @@ EnterprisePoliciesManager.prototype = { } }, - async _initialize() { + _initialize() { this._cleanupPolicies(); this._policiesSchema = ChromeUtils.importESModule( @@ -132,7 +132,8 @@ EnterprisePoliciesManager.prototype = { const remoteProvider = RemotePoliciesProvider.getInstance(); try { // Poll and ingest initial set of policies - await remoteProvider.ingestPolicies(); + const remotePoliciesPromise = remoteProvider.ingestPolicies(); + this.spinResolve(remotePoliciesPromise); // Will apply policy updates once policies manager is initialized remoteProvider.startPolling(); } catch (e) { @@ -541,14 +542,14 @@ EnterprisePoliciesManager.prototype = { }, // nsIObserver implementation - async observe(aSubject, aTopic) { + observe(aSubject, aTopic) { this._topicsObserved.add(aTopic); switch (aTopic) { case "policies-startup": // Before the first set of policy callbacks runs, we must // initialize the service. - await this._initialize(); + this._initialize(); this._runPoliciesCallbacks("onBeforeAddons"); break; @@ -765,6 +766,41 @@ EnterprisePoliciesManager.prototype = { return isEnterprise; }, + + /** + * Spin the event loop until the passed promise resolves. + * + * @param {Promise} promise + * @returns {any} Result of the resolved promise + */ + spinResolve(promise) { + if (!(promise instanceof Promise)) { + return promise; + } + let done = false; + let result = null; + let error = null; + promise + .catch(e => { + error = e; + }) + .then(r => { + result = r; + done = true; + }); + + Services.tm.spinEventLoopUntil( + "BrowserContentHandler.sys.mjs:BCH_spinResolve", + () => done + ); + if (!done) { + throw new Error("Forcefully exited event loop."); + } else if (error) { + throw error; + } else { + return result; + } + }, }; let DisallowedFeatures = {}; @@ -1035,7 +1071,7 @@ class RemotePoliciesProvider extends PoliciesProvider { if (!this._isPollingEnabled) { return; } - + const res = await lazy.ConsoleClient.getRemotePolicies(); if (!res.policies) { this._policies = {}; From b0686c7b1ff032dfaa81f242d5096f28c3a7bfbf Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 22 Jan 2026 16:07:26 +0100 Subject: [PATCH 06/33] Fix lint issues --- .../tests/EnterprisePolicyTesting.sys.mjs | 24 ++++++++--------- ...owser_remote_and_local_policies_merging.js | 27 ++++++++++--------- .../remote/browser_remote_policies_diffing.js | 3 --- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs index f7874ed5bd47d..292fb2185edb4 100644 --- a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs +++ b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs @@ -17,7 +17,6 @@ ChromeUtils.defineESModuleGetters(lazy, { export const REMOTE_POLICIES_TESTING_PREF = "browser.policies.remote.enabled"; export const EnterprisePolicyTesting = { - /* The stub wrapping ConsoleClient.getRemotePolicies to control which remote policies are fetched */ get remotePoliciesStub() { return this._remotePoliciesStub; @@ -28,9 +27,9 @@ export const EnterprisePolicyTesting = { }, /** - * Observe for all policies to be applied. This notification + * Observe for all policies to be applied. This notification * is sent when the policy engine is started up or reseted. - * + * * @param {Promise} resolve Promise that resolves once all policies are applied. */ resolveOnceAllPoliciesApplied(resolve) { @@ -44,9 +43,9 @@ export const EnterprisePolicyTesting = { }, /** - * Observe for a policy update. This notification is sent once + * Observe for a policy update. This notification is sent once * we check the console for updated policies. - * + * * @param {Promise} resolve Promise that resolves once the policy update is handled. */ resolveOnceAllPolicyUpdatesApplied(resolve) { @@ -88,7 +87,6 @@ export const EnterprisePolicyTesting = { return promise; }, - awaitNextPolicyUpdate() { const { promise, resolve } = Promise.withResolvers(); this.resolveOnceAllPolicyUpdatesApplied(resolve); @@ -96,11 +94,11 @@ export const EnterprisePolicyTesting = { }, /** - * Apply the custom schema, setup the remote policies stub and + * Apply the custom schema, setup the remote policies stub and * trigger a restart of the policy engine. - * - * @param {object} policies - * @param {object} customSchema + * + * @param {object} policies + * @param {object} customSchema * @returns {Promise} Promise that resolves once the set of policies are applied */ async servePolicyWithRemoteJson(policies, customSchema) { @@ -115,9 +113,9 @@ export const EnterprisePolicyTesting = { /** * Listen for the policies to be applied and stub the remote policies. - * - * @param {object} policies - * @param {boolean} isUpdate Whether the promise resolves once all policies are + * + * @param {object} policies + * @param {boolean} isUpdate Whether the promise resolves once all policies are * applied on startup or once the policy update is complete * @returns {Promise} Promise that resolves once the set of policies are applied */ diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js index 08e036e020040..f9f5aa39c66e4 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_and_local_policies_merging.js @@ -13,28 +13,28 @@ const customSchema = { type: "string", }, }, -} +}; let policy_value0 = POLICY_PARAM_STATE.DEFAULT; let policy_value1 = POLICY_PARAM_STATE.DEFAULT; const simple_policy0 = { - onBeforeUIStartup : (_manager, param) => { + onBeforeUIStartup: (_manager, param) => { policy_value0 = param; }, - onRemove : (_manager, _oldParam) => { + onRemove: (_manager, _oldParam) => { policy_value0 = POLICY_PARAM_STATE.REMOVED; - } -} + }, +}; const simple_policy1 = { - onBeforeUIStartup : (_manager, param) => { + onBeforeUIStartup: (_manager, param) => { policy_value1 = param; }, - onRemove : (_manager, _oldParam) => { + onRemove: (_manager, _oldParam) => { policy_value1 = POLICY_PARAM_STATE.REMOVED; - } -} + }, +}; add_setup(() => { Policies.simple_policy0 = simple_policy0; @@ -55,7 +55,7 @@ add_task(async function test_remote_policy_overrides_local_policy() { policies: { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, }, - } + }; let remotePolicies = { policies: {}, }; @@ -118,7 +118,7 @@ add_task(async function test_remote_and_local_policy_merged() { policies: { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, }, - } + }; let remotePolicies = { policies: { simple_policy1: POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY, @@ -133,7 +133,10 @@ add_task(async function test_remote_and_local_policy_merged() { Assert.deepEqual( Services.policies.getActivePolicies(), - { simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, simple_policy1: POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY }, + { + simple_policy0: POLICY_PARAM_STATE.APPLIED_LOCAL_POLICY, + simple_policy1: POLICY_PARAM_STATE.APPLIED_REMOTE_POLICY, + }, "Expected local and remote policy to be merged." ); Assert.equal( diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js index 16332746f29c9..0bd26b2a10c3c 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js @@ -22,12 +22,10 @@ const TestPolicy = { }, }; - add_setup(async () => { Policies.TestPolicy = TestPolicy; registerCleanupFunction(async () => { - dump("In cleanup") delete Policies.TestPolicy; }); }); @@ -66,7 +64,6 @@ add_task(async function test_policy_update_apply_new_policy() { { TestPolicy: POLICY_PARAM_STATE.APPLIED }, "Expected remote policy TestPolicy with parameter APPLIED." ); - }); add_task(async function test_policy_update_apply_policy_param_update() { From d3de4b6ff9b7c562070fd94442560eac97fb5acb Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 22 Jan 2026 16:23:20 +0100 Subject: [PATCH 07/33] Correct setupPolicyEngineWithJson call --- .../backgroundtasks/tests/BackgroundTask_policies.sys.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs b/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs index 67687ee5b0d19..90bb71db8e0d7 100644 --- a/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs +++ b/toolkit/components/backgroundtasks/tests/BackgroundTask_policies.sys.mjs @@ -7,7 +7,7 @@ import { EnterprisePolicyTesting } from "resource://testing-common/EnterprisePol export async function runBackgroundTask(commandLine) { let filePath = commandLine.getArgument(0); - await EnterprisePolicyTesting.setupPolicyEngineWithJsonFile(filePath); + await EnterprisePolicyTesting.setupPolicyEngineWithJson(filePath); let checker = Cc["@mozilla.org/updates/update-checker;1"].getService( Ci.nsIUpdateChecker From 093e5dc4775f209a37940b73c656311937dd7f64 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 6 Feb 2026 14:34:42 +0100 Subject: [PATCH 08/33] Remove EnterprisePoliciesParent from prettierignore --- .prettierignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 09e7633d4804d..d86975cbf15f4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1240,7 +1240,6 @@ toolkit/modules/AppConstants.sys.mjs # Files with MOZ_ENTERPRISE preprocessor directives browser/components/enterprisepolicies/helpers/WebsiteFilter.sys.mjs -browser/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs services/fxaccounts/FxAccounts.sys.mjs services/fxaccounts/FxAccountsClient.sys.mjs toolkit/components/downloads/DownloadCore.sys.mjs From 50288a96fb59ae1698af81531b2ff781ed56af1e Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 6 Feb 2026 14:43:00 +0100 Subject: [PATCH 09/33] Only set status inactive once --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index f061fe661a7c8..9769d16de75b3 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -154,7 +154,6 @@ EnterprisePoliciesManager.prototype = { } if (!this._provider.hasPolicies) { - this._status = Ci.nsIEnterprisePolicies.INACTIVE; return; } @@ -171,7 +170,6 @@ EnterprisePoliciesManager.prototype = { (policies.Certificates.ImportEnterpriseRoots === true || policies.Certificates.ImportEnterpriseRoots === 1) ) { - this._status = Ci.nsIEnterprisePolicies.INACTIVE; return; } From 99058f7738d9540c2ace1ae7b8ba10c2f394a47b Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 6 Feb 2026 14:58:35 +0100 Subject: [PATCH 10/33] Set policy status after parsing policies --- .../EnterprisePoliciesParent.sys.mjs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 9769d16de75b3..6f05e45691f58 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -204,10 +204,7 @@ EnterprisePoliciesManager.prototype = { * Activates the policies that are provided during initialization. */ _activatePolicies() { - this._status = Ci.nsIEnterprisePolicies.ACTIVE; - - lazy.log.debug(this._provider); - + for (const [policyName, policyParams] of Object.entries( this._provider.policies || {} )) { @@ -215,17 +212,21 @@ EnterprisePoliciesManager.prototype = { policyName, policyParams ); - + if (!isValid) { console.warn(`Parameters for policy ${policyName} are invalid`); continue; } - + this._parsedPolicies[policyName] = parsedParams; - + const policyImpl = lazy.Policies[policyName]; this._scheduleActivationPolicyCallbacks(policyImpl, parsedParams); } + + if (this.hasActivePolicies()) { + this._status = Ci.nsIEnterprisePolicies.ACTIVE; + } }, /** @@ -273,6 +274,12 @@ EnterprisePoliciesManager.prototype = { } this._runPoliciesCallbacks(timing); } + + if (this.hasActivePolicies()) { + this._status = Ci.nsIEnterprisePolicies.ACTIVE; + } else { + this._status = Ci.nsIEnterprisePolicies.INACTIVE; + } }, /** @@ -643,6 +650,10 @@ EnterprisePoliciesManager.prototype = { return this._parsedPolicies; }, + hasActivePolicies() { + return !!Object.keys(this._parsedPolicies || {}).length + }, + setSupportMenu(supportMenu) { SupportMenu = supportMenu; }, From d3e874ef55b4faf0289f6caf23e92ecd1167dadd Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 6 Feb 2026 15:02:47 +0100 Subject: [PATCH 11/33] Correct this.isRemotePoliciesSupported() call --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 6f05e45691f58..1daa2f7466109 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -508,7 +508,7 @@ EnterprisePoliciesManager.prototype = { this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED; this._parsedPolicies = {}; - if (this.isRemotePoliciesSupported) { + if (this.isRemotePoliciesSupported()) { RemotePoliciesProvider.dropInstance(); } this._provider = null; From f872f21524a2d27b6670e92540a8a6842d05ced8 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 6 Feb 2026 16:39:24 +0100 Subject: [PATCH 12/33] Lint changes --- .../EnterprisePoliciesParent.sys.mjs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 1daa2f7466109..5399810b2212e 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -204,7 +204,6 @@ EnterprisePoliciesManager.prototype = { * Activates the policies that are provided during initialization. */ _activatePolicies() { - for (const [policyName, policyParams] of Object.entries( this._provider.policies || {} )) { @@ -212,18 +211,18 @@ EnterprisePoliciesManager.prototype = { policyName, policyParams ); - + if (!isValid) { console.warn(`Parameters for policy ${policyName} are invalid`); continue; } - + this._parsedPolicies[policyName] = parsedParams; - + const policyImpl = lazy.Policies[policyName]; this._scheduleActivationPolicyCallbacks(policyImpl, parsedParams); } - + if (this.hasActivePolicies()) { this._status = Ci.nsIEnterprisePolicies.ACTIVE; } @@ -651,7 +650,7 @@ EnterprisePoliciesManager.prototype = { }, hasActivePolicies() { - return !!Object.keys(this._parsedPolicies || {}).length + return !!Object.keys(this._parsedPolicies || {}).length; }, setSupportMenu(supportMenu) { From 1ecc8be083090b038b6ddbc2686d83be6fbea91a Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 12:09:45 +0100 Subject: [PATCH 13/33] Add DisableRemoteImprovements to set of "not live" policies --- .../browser/remote/browser_remote_policies_missing_live.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js index 5b935a1fb4709..8bfa0827e6b08 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js @@ -32,6 +32,7 @@ add_task(async function check_all_policies_are_live() { "DisableDefaultBrowserAgent", "DisableForgetButton", "DisableFormHistory", + "DisableRemoteImprovements", "DisableMasterPasswordCreation", "DisablePasswordReveal", "DisableProfileImport", @@ -122,7 +123,7 @@ add_task(async function check_all_policies_are_live() { const livePoliciesNotAppliable = livePolicies.difference(policiesAppliable) if (livePoliciesNotAppliable.size) { - console.debug(`Live policies that are not appliable because of missing remove functions ${JSON.stringify(livePoliciesNotAppliable)}`); + console.debug(`Live policies that are not appliable because of missing remove functions ${livePoliciesNotAppliable}`); } Assert.equal(livePoliciesNotAppliable.size, 0, "Not all policies are live. Work better."); From dd3fffb729a0073bb315a9ffc43feb5fae55867c Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 12:11:23 +0100 Subject: [PATCH 14/33] Undo pref duplication --- .../tests/EnterprisePolicyTesting.sys.mjs | 2 -- .../enterprisepolicies/tests/browser/head.js | 11 +++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs index 292fb2185edb4..bf795fc87efd4 100644 --- a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs +++ b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs @@ -14,8 +14,6 @@ ChromeUtils.defineESModuleGetters(lazy, { ConsoleClient: "resource:///modules/enterprise/ConsoleClient.sys.mjs", }); -export const REMOTE_POLICIES_TESTING_PREF = "browser.policies.remote.enabled"; - export const EnterprisePolicyTesting = { /* The stub wrapping ConsoleClient.getRemotePolicies to control which remote policies are fetched */ get remotePoliciesStub() { diff --git a/toolkit/components/enterprisepolicies/tests/browser/head.js b/toolkit/components/enterprisepolicies/tests/browser/head.js index e64e2b7f340ea..710d17a9c3bc3 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/head.js +++ b/toolkit/components/enterprisepolicies/tests/browser/head.js @@ -7,7 +7,6 @@ const { EnterprisePolicyTesting, PoliciesPrefTracker, - REMOTE_POLICIES_TESTING_PREF, } = ChromeUtils.importESModule( "resource://testing-common/EnterprisePolicyTesting.sys.mjs" ); @@ -16,6 +15,10 @@ const { Policies } = ChromeUtils.importESModule( "resource:///modules/policies/Policies.sys.mjs" ); +const { PREF_REMOTE_POLICIES_ENABLED } = ChromeUtils.importESModule( + "resource://gre/modules/EnterprisePoliciesParent.sys.mjs" +); + PoliciesPrefTracker.start(); registerCleanupFunction(() => { PoliciesPrefTracker.stop(); @@ -23,11 +26,11 @@ registerCleanupFunction(() => { async function setupPolicyEngineWithJson(json, customSchema) { PoliciesPrefTracker.restoreDefaultValues(); - const isRemotePoliciesTesting = Services.prefs.getBoolPref( - REMOTE_POLICIES_TESTING_PREF, + const isRemotePoliciesEnabled = Services.prefs.getBoolPref( + PREF_REMOTE_POLICIES_ENABLED, false ); - if (isRemotePoliciesTesting) { + if (isRemotePoliciesEnabled) { return servePolicyWithRemoteJson(json, customSchema); } return setupPolicyWithJsonFile(json, customSchema); From eeedb7ffa54400318e34e5cc6105275b6e51f0a0 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 12:22:18 +0100 Subject: [PATCH 15/33] Let felt set pref to disable local policies --- .../felt/content/FeltProcessParent.sys.mjs | 9 ++++++++- .../EnterprisePoliciesParent.sys.mjs | 15 +++++++++++---- .../enterprisepolicies/tests/browser/browser.toml | 1 + .../tests/browser/remote/browser.toml | 1 + 4 files changed, 21 insertions(+), 5 deletions(-) diff --git a/browser/extensions/felt/content/FeltProcessParent.sys.mjs b/browser/extensions/felt/content/FeltProcessParent.sys.mjs index 63bfd45d9e981..882e1f6593ffe 100644 --- a/browser/extensions/felt/content/FeltProcessParent.sys.mjs +++ b/browser/extensions/felt/content/FeltProcessParent.sys.mjs @@ -11,6 +11,8 @@ ChromeUtils.defineESModuleGetters(lazy, { isTesting: "resource:///modules/enterprise/EnterpriseCommon.sys.mjs", FeltCommon: "chrome://felt/content/FeltCommon.sys.mjs", FeltStorage: "resource:///modules/FeltStorage.sys.mjs", + PREF_REMOTE_POLICIES_ENABLED: "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", + PREF_LOCAL_POLICIES_ENABLED: "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", }); console.debug(`FeltExtension: FeltParentProcess.sys.mjs`); @@ -236,8 +238,13 @@ export class FeltProcessParent extends JSProcessActorParent { "identity.fxaccounts.auth.uri", lazy.ConsoleClient.fxAccountsAuth ); + + // Enables remote policies + Services.felt.sendBoolPreference(lazy.PREF_REMOTE_POLICIES_ENABLED, true); + // Disables local policies + Services.felt.sendBoolPreference(lazy.PREF_LOCAL_POLICIES_ENABLED, false); - // Enables remote policy polling + // Enables live policy polling Services.felt.sendBoolPreference( "browser.policies.live_polling.enabled", true diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 5399810b2212e..b6808fa50d2cb 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -46,7 +46,8 @@ const PREF_LOGLEVEL = "browser.policies.loglevel"; // To allow for cleaning up old policies const PREF_POLICIES_APPLIED = "browser.policies.applied"; -const PREF_REMOTE_POLICIES_ENABLED = "browser.policies.remote.enabled"; +export const PREF_REMOTE_POLICIES_ENABLED = "browser.policies.remote.enabled"; +export const PREF_LOCAL_POLICIES_ENABLED = "browser.policies.local.enabled"; ChromeUtils.defineLazyGetter(lazy, "log", () => { let { ConsoleAPI } = ChromeUtils.importESModule( @@ -107,6 +108,12 @@ EnterprisePoliciesManager.prototype = { ); }, + isLocalPoliciesSupported() { + // If remote policies are enabled, + // we ignore local ones for now. + return !AppConstants.MOZ_ENTERPRISE || Services.prefs.getBoolPref(PREF_LOCAL_POLICIES_ENABLED, true); + }, + _cleanupPolicies() { if (Services.prefs.getBoolPref(PREF_POLICIES_APPLIED, false)) { if ("_cleanup" in lazy.Policies) { @@ -139,7 +146,7 @@ EnterprisePoliciesManager.prototype = { } catch (e) { console.error("Unable to find policies in payload."); } - if (localProvider.hasPolicies) { + if (this.isLocalPoliciesSupported() && localProvider.hasPolicies) { this._provider = new CombinedProvider(remoteProvider, localProvider); } else { this._provider = remoteProvider; @@ -1122,8 +1129,8 @@ class WindowsGPOPoliciesProvider extends PoliciesProvider { lazy.log.debug( `root = ${ root == wrk.ROOT_KEY_CURRENT_USER - ? "HKEY_CURRENT_USER" - : "HKEY_LOCAL_MACHINE" + ? "HKEY_CURRENT_USER" + : "HKEY_LOCAL_MACHINE" }` ); this._policies = lazy.WindowsGPOParser.readPolicies( diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/browser.toml index b680d961d6914..0caa8b45e84fe 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/browser.toml @@ -3,6 +3,7 @@ head = "head.js" support-files = ["config_broken_json.json"] prefs = [ "browser.policies.remote.enabled=false", + "browser.policies.local.enabled=true", ] environment = [ "MOZ_BYPASS_FELT=1", diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml index 7fd6c63b955fe..6d3a66bd4efaf 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml @@ -3,6 +3,7 @@ head = "head.js" support-files = ["../head.js"] prefs = [ "browser.policies.remote.enabled=true", + "browser.policies.local.enabled=true", "browser.policies.live_polling.enabled=true", "browser.policies.live_polling.frequency=500", ] From 76bb370bfc05d7d9e768703a3f175bb30f1383e2 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 14:39:22 +0100 Subject: [PATCH 16/33] Call spinResolve from observer --- .../EnterprisePoliciesParent.sys.mjs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index b6808fa50d2cb..33e0163525440 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -124,7 +124,7 @@ EnterprisePoliciesManager.prototype = { } }, - _initialize() { + async _initialize() { this._cleanupPolicies(); this._policiesSchema = ChromeUtils.importESModule( @@ -139,8 +139,7 @@ EnterprisePoliciesManager.prototype = { const remoteProvider = RemotePoliciesProvider.getInstance(); try { // Poll and ingest initial set of policies - const remotePoliciesPromise = remoteProvider.ingestPolicies(); - this.spinResolve(remotePoliciesPromise); + await remoteProvider.ingestPolicies(); // Will apply policy updates once policies manager is initialized remoteProvider.startPolling(); } catch (e) { @@ -560,11 +559,12 @@ EnterprisePoliciesManager.prototype = { case "policies-startup": // Before the first set of policy callbacks runs, we must // initialize the service. - this._initialize(); - - this._runPoliciesCallbacks("onBeforeAddons"); - break; - + { + const initializedPromise = this._initialize(); + this.spinResolve(initializedPromise); + this._runPoliciesCallbacks("onBeforeAddons"); + break; + } case "profile-after-change": this._runPoliciesCallbacks("onProfileAfterChange"); break; @@ -805,7 +805,7 @@ EnterprisePoliciesManager.prototype = { }); Services.tm.spinEventLoopUntil( - "BrowserContentHandler.sys.mjs:BCH_spinResolve", + "EnterprisePoliciesManager.sys.mjs:_initialize", () => done ); if (!done) { @@ -1113,7 +1113,7 @@ class WindowsGPOPoliciesProvider extends PoliciesProvider { // We don't access machine policies in testing if (!Cu.isInAutomation && !isXpcshell) { this._readData(wrk, wrk.ROOT_KEY_LOCAL_MACHINE); - } + } } _readData(wrk, root) { From 6be51897614c501632686adbb96ad7e36a4dc089 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 14:45:45 +0100 Subject: [PATCH 17/33] Correct policies engine status --- .../EnterprisePoliciesParent.sys.mjs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 33e0163525440..a46acfee9c6f9 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -131,7 +131,6 @@ EnterprisePoliciesManager.prototype = { "resource:///modules/policies/schema.sys.mjs" ).schema; - this._status = Ci.nsIEnterprisePolicies.INACTIVE; Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, false); const localProvider = this._chooseProvider(); @@ -155,11 +154,12 @@ EnterprisePoliciesManager.prototype = { } if (this._provider.failed) { - this._status = Ci.nsIEnterprisePolicies.FAILED; + this.status = Ci.nsIEnterprisePolicies.FAILED; return; } if (!this._provider.hasPolicies) { + this.status = Ci.nsIEnterprisePolicies.INACTIVE; return; } @@ -176,6 +176,7 @@ EnterprisePoliciesManager.prototype = { (policies.Certificates.ImportEnterpriseRoots === true || policies.Certificates.ImportEnterpriseRoots === 1) ) { + this.status = Ci.nsIEnterprisePolicies.INACTIVE; return; } @@ -206,6 +207,17 @@ EnterprisePoliciesManager.prototype = { return jsonProvider; }, + /** + * Update engine status after parsing a new set of policies + */ + _updateStatus() { + if (this.hasActivePolicies()) { + this.status = Ci.nsIEnterprisePolicies.ACTIVE; + } else { + this.status = Ci.nsIEnterprisePolicies.INACTIVE; + } + }, + /** * Activates the policies that are provided during initialization. */ @@ -229,9 +241,7 @@ EnterprisePoliciesManager.prototype = { this._scheduleActivationPolicyCallbacks(policyImpl, parsedParams); } - if (this.hasActivePolicies()) { - this._status = Ci.nsIEnterprisePolicies.ACTIVE; - } + this._updateStatus(); }, /** @@ -244,7 +254,7 @@ EnterprisePoliciesManager.prototype = { * - Remove a policy if it's missing in the updated set. */ _updatePolicies() { - if (this._status === Ci.nsIEnterprisePolicies.UNINITIALIZED) { + if (this.status === Ci.nsIEnterprisePolicies.UNINITIALIZED) { // Abort if we are still initializing or restarting the policy engine. return; } @@ -280,11 +290,7 @@ EnterprisePoliciesManager.prototype = { this._runPoliciesCallbacks(timing); } - if (this.hasActivePolicies()) { - this._status = Ci.nsIEnterprisePolicies.ACTIVE; - } else { - this._status = Ci.nsIEnterprisePolicies.INACTIVE; - } + this._updateStatus(); }, /** @@ -511,7 +517,7 @@ EnterprisePoliciesManager.prototype = { Services.ppmm.sharedData.delete("EnterprisePolicies:Status"); Services.ppmm.sharedData.delete("EnterprisePolicies:DisallowedFeatures"); - this._status = Ci.nsIEnterprisePolicies.UNINITIALIZED; + this.status = Ci.nsIEnterprisePolicies.UNINITIALIZED; this._parsedPolicies = {}; if (this.isRemotePoliciesSupported()) { RemotePoliciesProvider.dropInstance(); @@ -1113,7 +1119,7 @@ class WindowsGPOPoliciesProvider extends PoliciesProvider { // We don't access machine policies in testing if (!Cu.isInAutomation && !isXpcshell) { this._readData(wrk, wrk.ROOT_KEY_LOCAL_MACHINE); - } + } } _readData(wrk, root) { From 063260df20e5da3fa7db262d0c02343db6badc46 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 9 Feb 2026 14:46:27 +0100 Subject: [PATCH 18/33] Lint changes --- .../felt/content/FeltProcessParent.sys.mjs | 8 ++++-- .../EnterprisePoliciesParent.sys.mjs | 28 ++++++++++--------- .../enterprisepolicies/tests/browser/head.js | 10 +++---- .../browser_remote_policies_missing_live.js | 14 +++++++--- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/browser/extensions/felt/content/FeltProcessParent.sys.mjs b/browser/extensions/felt/content/FeltProcessParent.sys.mjs index 882e1f6593ffe..0915c9ebcb560 100644 --- a/browser/extensions/felt/content/FeltProcessParent.sys.mjs +++ b/browser/extensions/felt/content/FeltProcessParent.sys.mjs @@ -11,8 +11,10 @@ ChromeUtils.defineESModuleGetters(lazy, { isTesting: "resource:///modules/enterprise/EnterpriseCommon.sys.mjs", FeltCommon: "chrome://felt/content/FeltCommon.sys.mjs", FeltStorage: "resource:///modules/FeltStorage.sys.mjs", - PREF_REMOTE_POLICIES_ENABLED: "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", - PREF_LOCAL_POLICIES_ENABLED: "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", + PREF_REMOTE_POLICIES_ENABLED: + "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", + PREF_LOCAL_POLICIES_ENABLED: + "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", }); console.debug(`FeltExtension: FeltParentProcess.sys.mjs`); @@ -238,7 +240,7 @@ export class FeltProcessParent extends JSProcessActorParent { "identity.fxaccounts.auth.uri", lazy.ConsoleClient.fxAccountsAuth ); - + // Enables remote policies Services.felt.sendBoolPreference(lazy.PREF_REMOTE_POLICIES_ENABLED, true); // Disables local policies diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index a46acfee9c6f9..20378be712101 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -109,9 +109,12 @@ EnterprisePoliciesManager.prototype = { }, isLocalPoliciesSupported() { - // If remote policies are enabled, + // If remote policies are enabled, // we ignore local ones for now. - return !AppConstants.MOZ_ENTERPRISE || Services.prefs.getBoolPref(PREF_LOCAL_POLICIES_ENABLED, true); + return ( + !AppConstants.MOZ_ENTERPRISE || + Services.prefs.getBoolPref(PREF_LOCAL_POLICIES_ENABLED, true) + ); }, _cleanupPolicies() { @@ -562,15 +565,14 @@ EnterprisePoliciesManager.prototype = { this._topicsObserved.add(aTopic); switch (aTopic) { - case "policies-startup": - // Before the first set of policy callbacks runs, we must - // initialize the service. - { - const initializedPromise = this._initialize(); - this.spinResolve(initializedPromise); - this._runPoliciesCallbacks("onBeforeAddons"); - break; - } + case "policies-startup": // Before the first set of policy callbacks runs, we must + // initialize the service. + { + const initializedPromise = this._initialize(); + this.spinResolve(initializedPromise); + this._runPoliciesCallbacks("onBeforeAddons"); + break; + } case "profile-after-change": this._runPoliciesCallbacks("onProfileAfterChange"); break; @@ -1135,8 +1137,8 @@ class WindowsGPOPoliciesProvider extends PoliciesProvider { lazy.log.debug( `root = ${ root == wrk.ROOT_KEY_CURRENT_USER - ? "HKEY_CURRENT_USER" - : "HKEY_LOCAL_MACHINE" + ? "HKEY_CURRENT_USER" + : "HKEY_LOCAL_MACHINE" }` ); this._policies = lazy.WindowsGPOParser.readPolicies( diff --git a/toolkit/components/enterprisepolicies/tests/browser/head.js b/toolkit/components/enterprisepolicies/tests/browser/head.js index 710d17a9c3bc3..73bb207c3d297 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/head.js +++ b/toolkit/components/enterprisepolicies/tests/browser/head.js @@ -4,12 +4,10 @@ "use strict"; -const { - EnterprisePolicyTesting, - PoliciesPrefTracker, -} = ChromeUtils.importESModule( - "resource://testing-common/EnterprisePolicyTesting.sys.mjs" -); +const { EnterprisePolicyTesting, PoliciesPrefTracker } = + ChromeUtils.importESModule( + "resource://testing-common/EnterprisePolicyTesting.sys.mjs" + ); const { Policies } = ChromeUtils.importESModule( "resource:///modules/policies/Policies.sys.mjs" diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js index 8bfa0827e6b08..f9cea9882c828 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_missing_live.js @@ -121,14 +121,20 @@ add_task(async function check_all_policies_are_live() { } } - const livePoliciesNotAppliable = livePolicies.difference(policiesAppliable) + const livePoliciesNotAppliable = livePolicies.difference(policiesAppliable); if (livePoliciesNotAppliable.size) { - console.debug(`Live policies that are not appliable because of missing remove functions ${livePoliciesNotAppliable}`); + console.debug( + `Live policies that are not appliable because of missing remove functions ${livePoliciesNotAppliable}` + ); } - Assert.equal(livePoliciesNotAppliable.size, 0, "Not all policies are live. Work better."); + Assert.equal( + livePoliciesNotAppliable.size, + 0, + "Not all policies are live. Work better." + ); - const liveAndNotLive = policiesAppliable.intersection(notLivePolicies) + const liveAndNotLive = policiesAppliable.intersection(notLivePolicies); if (liveAndNotLive.size) { console.debug( `Inconsistent state: live and not live ${JSON.stringify(liveAndNotLive)}` From 5b4393a39477eab8bc4da2a5a7c8c4b7a9fdf32b Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 08:47:32 +0100 Subject: [PATCH 19/33] Remove MOZ_AUTOMATION test env --- toolkit/components/enterprisepolicies/tests/browser/browser.toml | 1 - .../enterprisepolicies/tests/browser/remote/browser.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/tests/browser/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/browser.toml index 0caa8b45e84fe..9057cb4344403 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/browser.toml @@ -7,7 +7,6 @@ prefs = [ ] environment = [ "MOZ_BYPASS_FELT=1", - "MOZ_AUTOMATION=1", ] ["browser_policies_basic_tests.js"] diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml index 6d3a66bd4efaf..6002fab33b38b 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml @@ -9,7 +9,6 @@ prefs = [ ] environment = [ "MOZ_BYPASS_FELT=1", - "MOZ_AUTOMATION=1", ] ["browser_remote_and_local_policies_merging.js"] From e1319086a206515d37a9466f704c62d824e063e7 Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 08:55:33 +0100 Subject: [PATCH 20/33] Remove isRemotePoliciesSupported from the policies xpcom interface --- .../enterprisepolicies/nsIEnterprisePolicies.idl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl b/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl index dba497fe2b670..62b1b568ce7e3 100644 --- a/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl +++ b/toolkit/components/enterprisepolicies/nsIEnterprisePolicies.idl @@ -21,13 +21,6 @@ interface nsIEnterprisePolicies : nsISupports boolean isAllowed(in ACString feature); - /** - * Whether remote policies are supported (limited to enterprise builds) - * - * @return A boolean value defining is remote policies are supported. - */ - boolean isRemotePoliciesSupported(); - /** * Get the active policies that have been successfully parsed. * From c1afb99e97603aa7ea24908a87e50d6706230079 Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 08:56:04 +0100 Subject: [PATCH 21/33] Only parse local policies if local policies supported --- .../EnterprisePoliciesParent.sys.mjs | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 20378be712101..8bd60a0fcb3cd 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -101,16 +101,14 @@ EnterprisePoliciesManager.prototype = { // Caches latest set of parsed policies _parsedPolicies: {}, - isRemotePoliciesSupported() { + _isRemotePoliciesSupported() { return ( AppConstants.MOZ_ENTERPRISE && Services.prefs.getBoolPref(PREF_REMOTE_POLICIES_ENABLED, false) ); }, - isLocalPoliciesSupported() { - // If remote policies are enabled, - // we ignore local ones for now. + _isLocalPoliciesSupported() { return ( !AppConstants.MOZ_ENTERPRISE || Services.prefs.getBoolPref(PREF_LOCAL_POLICIES_ENABLED, true) @@ -136,8 +134,12 @@ EnterprisePoliciesManager.prototype = { Services.prefs.setBoolPref(PREF_POLICIES_APPLIED, false); - const localProvider = this._chooseProvider(); - if (this.isRemotePoliciesSupported()) { + let localProvider; + if (this._isLocalPoliciesSupported()) { + localProvider = this._chooseProvider(); + } + + if (this._isRemotePoliciesSupported()) { const remoteProvider = RemotePoliciesProvider.getInstance(); try { // Poll and ingest initial set of policies @@ -147,7 +149,7 @@ EnterprisePoliciesManager.prototype = { } catch (e) { console.error("Unable to find policies in payload."); } - if (this.isLocalPoliciesSupported() && localProvider.hasPolicies) { + if (localProvider?.hasPolicies) { this._provider = new CombinedProvider(remoteProvider, localProvider); } else { this._provider = remoteProvider; @@ -156,6 +158,12 @@ EnterprisePoliciesManager.prototype = { this._provider = localProvider; } + if (!this._provider) { + // Both local and remote policy provision is disabled. + this.status = Ci.nsIEnterprisePolicies.INACTIVE; + return; + } + if (this._provider.failed) { this.status = Ci.nsIEnterprisePolicies.FAILED; return; @@ -522,7 +530,7 @@ EnterprisePoliciesManager.prototype = { this.status = Ci.nsIEnterprisePolicies.UNINITIALIZED; this._parsedPolicies = {}; - if (this.isRemotePoliciesSupported()) { + if (this._isRemotePoliciesSupported()) { RemotePoliciesProvider.dropInstance(); } this._provider = null; @@ -565,7 +573,8 @@ EnterprisePoliciesManager.prototype = { this._topicsObserved.add(aTopic); switch (aTopic) { - case "policies-startup": // Before the first set of policy callbacks runs, we must + case "policies-startup": + // Before the first set of policy callbacks runs, we must // initialize the service. { const initializedPromise = this._initialize(); From 015ae5ade3725cb8699cb87d6d78c804732d2e6d Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 09:20:30 +0100 Subject: [PATCH 22/33] Remove browser/**/enterprisepolicies/**/live --- .../tests/browser/live/browser.toml | 140 ------------------ .../enterprisepolicies/tests/moz.build | 1 - 2 files changed, 141 deletions(-) delete mode 100644 browser/components/enterprisepolicies/tests/browser/live/browser.toml diff --git a/browser/components/enterprisepolicies/tests/browser/live/browser.toml b/browser/components/enterprisepolicies/tests/browser/live/browser.toml deleted file mode 100644 index 62f98e857fe3f..0000000000000 --- a/browser/components/enterprisepolicies/tests/browser/live/browser.toml +++ /dev/null @@ -1,140 +0,0 @@ -[DEFAULT] -prefs = [ - "browser.policies.testUseHttp=true", - "browser.policies.live_polling.frequency=250" -] - -support-files = [ - "../favicon.svg", - "../head.js", - "../opensearch.html", - "../opensearchEngine.xml", - "../policytest_v0.1.xpi", - "../policytest_v0.2.xpi", - "../policy_websitefilter_block.html", - "../policy_websitefilter_exception.html", - "../policy_websitefilter_savelink.html", - "../../../../../../toolkit/components/antitracking/test/browser/page.html", - "../../../../../../toolkit/components/antitracking/test/browser/subResources.sjs", - "../extensionsettings.html", - "../301.sjs", - "../302.sjs", - "../404.sjs", -] - -["../browser_policies_getActivePolicies.js"] - -["../browser_policies_notice_in_aboutpreferences.js"] - -["../browser_policies_setAndLockPref_API.js"] - -["../browser_policy_allowfileselectiondialogs.js"] -skip-if = ["headless"] - -["../browser_policy_app_auto_update.js"] -run-if = [ - "!msix", # Updater is disabled in MSIX builds -] - -["../browser_policy_app_update.js"] -run-if = [ - "!msix", # Updater is disabled in MSIX builds -] - -["../browser_policy_background_app_update.js"] -run-if = [ - "!msix", # Updater is disabled in MSIX builds -] - -["../browser_policy_block_about.js"] - -["../browser_policy_block_about_support.js"] - -["../browser_policy_block_set_desktop_background.js"] - -["../browser_policy_bookmarks.js"] - -["../browser_policy_cookie_settings.js"] -https_first_disabled = true - -["../browser_policy_disable_feedback_commands.js"] - -["../browser_policy_disable_fxaccounts.js"] - -["../browser_policy_disable_masterpassword.js"] - -["../browser_policy_disable_password_reveal.js"] - -["../browser_policy_disable_popup_blocker.js"] - -["../browser_policy_disable_privatebrowsing.js"] - -["../browser_policy_disable_profile_import.js"] - -["../browser_policy_disable_profile_reset.js"] - -["../browser_policy_disable_safemode.js"] - -["../browser_policy_disable_shield.js"] - -["../browser_policy_disable_telemetry.js"] - -["../browser_policy_display_bookmarks.js"] - -["../browser_policy_display_menu.js"] - -["../browser_policy_downloads.js"] -support-files = [ - "!/browser/components/downloads/test/browser/foo.txt", - "!/browser/components/downloads/test/browser/foo.txt^headers^", -] - -["../browser_policy_extensions.js"] - -["../browser_policy_extensionsettings.js"] -https_first_disabled = true - -["../browser_policy_extensionsettings2.js"] - -["../browser_policy_firefoxhome.js"] - -["../browser_policy_firefoxsuggest.js"] - -["../browser_policy_handlers.js"] - -["../browser_policy_masterpassword.js"] - -["../browser_policy_masterpassword_aboutlogins.js"] - -["../browser_policy_masterpassword_doorhanger.js"] - -["../browser_policy_offertosavelogins.js"] - -["../browser_policy_override_postupdatepage.js"] - -["../browser_policy_pageinfo_permissions.js"] - -["../browser_policy_passwordmanager.js"] - -["../browser_policy_pdfviewer.js"] - -["../browser_policy_privatebrowsingmodeavailability.js"] - -["../browser_policy_search_engine.js"] -skip-if = [ - "os == 'win' && os_version == '11.26100' && arch == 'x86_64' && opt && verify-standalone", -] - -["../browser_policy_searchbar.js"] - -["../browser_policy_set_homepage.js"] - -["../browser_policy_set_startpage.js"] - -["../browser_policy_support_menu.js"] - -["../browser_policy_translateenabled.js"] - -["../browser_policy_usermessaging.js"] - -["../browser_policy_websitefilter.js"] diff --git a/browser/components/enterprisepolicies/tests/moz.build b/browser/components/enterprisepolicies/tests/moz.build index b62d7e92479d4..2d44eaaa762eb 100644 --- a/browser/components/enterprisepolicies/tests/moz.build +++ b/browser/components/enterprisepolicies/tests/moz.build @@ -13,7 +13,6 @@ BROWSER_CHROME_MANIFESTS += [ "browser/disable_fxscreenshots/browser.toml", "browser/hardware_acceleration/browser.toml", "browser/homepage_button/browser.toml", - "browser/live/browser.toml", "browser/managedbookmarks/browser.toml", "browser/show_home_button/browser.toml", "browser/trackingprotection/browser.toml", From ca18fa9c75d0abae644bbd9846353adb87f36e0a Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 09:22:32 +0100 Subject: [PATCH 23/33] Lint changes --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 8bd60a0fcb3cd..58db545e5b419 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -573,8 +573,7 @@ EnterprisePoliciesManager.prototype = { this._topicsObserved.add(aTopic); switch (aTopic) { - case "policies-startup": - // Before the first set of policy callbacks runs, we must + case "policies-startup": // Before the first set of policy callbacks runs, we must // initialize the service. { const initializedPromise = this._initialize(); From ab0eca57138422a6ef72e8cb6798cd8e8a4210d5 Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 09:33:23 +0100 Subject: [PATCH 24/33] Remove description update in firefox-enterprise.js --- browser/branding/enterprise/pref/firefox-enterprise.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/branding/enterprise/pref/firefox-enterprise.js b/browser/branding/enterprise/pref/firefox-enterprise.js index 523109f4e3ed5..8fbe25c9ae0fc 100644 --- a/browser/branding/enterprise/pref/firefox-enterprise.js +++ b/browser/branding/enterprise/pref/firefox-enterprise.js @@ -2,7 +2,7 @@ * 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/. */ -/* Preferences here are going to be applied both to Felt and to the launched Firefox. */ +/* global pref */ pref("enterprise.console.address", "https://console.enterfox.eu"); From 639ec2532dd32bd3af749911b8239d0a169bbd9c Mon Sep 17 00:00:00 2001 From: Janika Date: Thu, 12 Feb 2026 09:34:35 +0100 Subject: [PATCH 25/33] Undo setting local policy support from felt --- browser/extensions/felt/content/FeltProcessParent.sys.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/browser/extensions/felt/content/FeltProcessParent.sys.mjs b/browser/extensions/felt/content/FeltProcessParent.sys.mjs index 12cd571ee7967..07e1fe7715878 100644 --- a/browser/extensions/felt/content/FeltProcessParent.sys.mjs +++ b/browser/extensions/felt/content/FeltProcessParent.sys.mjs @@ -244,8 +244,6 @@ export class FeltProcessParent extends JSProcessActorParent { // Enables remote policies Services.felt.sendBoolPreference(lazy.PREF_REMOTE_POLICIES_ENABLED, true); - // Disables local policies - Services.felt.sendBoolPreference(lazy.PREF_LOCAL_POLICIES_ENABLED, false); // Enables live policy polling Services.felt.sendBoolPreference( From e24f4e26f4768382139f5be71a84e11eff4a8722 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 13 Feb 2026 11:23:58 +0100 Subject: [PATCH 26/33] Test policy updates are applied --- .../remote/browser_remote_policies_diffing.js | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js index 0bd26b2a10c3c..bc5abc157cac5 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_diffing.js @@ -64,6 +64,11 @@ add_task(async function test_policy_update_apply_new_policy() { { TestPolicy: POLICY_PARAM_STATE.APPLIED }, "Expected remote policy TestPolicy with parameter APPLIED." ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.APPLIED, + `Expected the policy parameter "applied".` + ); }); add_task(async function test_policy_update_apply_policy_param_update() { @@ -83,6 +88,11 @@ add_task(async function test_policy_update_apply_policy_param_update() { { TestPolicy: POLICY_PARAM_STATE.APPLIED }, "Expected remote policy TestPolicy with parameter APPLIED." ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.APPLIED, + `Expected the policy parameter "applied".` + ); policyValue = POLICY_PARAM_STATE.DEFAULT; @@ -99,6 +109,11 @@ add_task(async function test_policy_update_apply_policy_param_update() { { TestPolicy: POLICY_PARAM_STATE.UPDATED }, "Expected remote policy TestPolicy with parameter UPDATED." ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.UPDATED, + `Expected the policy parameter "updated".` + ); }); add_task(async function test_policy_update_remove_old_policy() { @@ -118,6 +133,11 @@ add_task(async function test_policy_update_remove_old_policy() { { TestPolicy: POLICY_PARAM_STATE.APPLIED }, "Expected remote policy TestPolicy with parameter APPLIED." ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.APPLIED, + `Expected the policy parameter "applied".` + ); const policies = { policies: {}, @@ -154,6 +174,11 @@ add_task(async function test_policy_update_no_changes() { { TestPolicy: POLICY_PARAM_STATE.APPLIED }, "Expected remote policy TestPolicy with parameter APPLIED." ); + Assert.equal( + policyValue, + POLICY_PARAM_STATE.APPLIED, + `Expected the policy parameter "applied".` + ); // This is not really representative of how things can happen but rather to // verify that the policy's callback was not called a second time. @@ -172,7 +197,7 @@ add_task(async function test_policy_update_no_changes() { // incorrect WRT policy at the moment) // - // Revert back to DEFAULT + // Revert back to DEFAULT (pref is unlocked) policyValue = POLICY_PARAM_STATE.DEFAULT; // Wait for next policy update to complete From 6222ab72d82e2cd5c75671dfe3f234d59b15860b Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 13 Feb 2026 11:34:56 +0100 Subject: [PATCH 27/33] Use Object.assign to merge local and remote policies --- .../EnterprisePoliciesParent.sys.mjs | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 58db545e5b419..4bdd6467fe754 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -574,13 +574,13 @@ EnterprisePoliciesManager.prototype = { switch (aTopic) { case "policies-startup": // Before the first set of policy callbacks runs, we must - // initialize the service. - { - const initializedPromise = this._initialize(); - this.spinResolve(initializedPromise); - this._runPoliciesCallbacks("onBeforeAddons"); - break; - } + // initialize the service. + { + const initializedPromise = this._initialize(); + this.spinResolve(initializedPromise); + this._runPoliciesCallbacks("onBeforeAddons"); + break; + } case "profile-after-change": this._runPoliciesCallbacks("onProfileAfterChange"); break; @@ -1145,8 +1145,8 @@ class WindowsGPOPoliciesProvider extends PoliciesProvider { lazy.log.debug( `root = ${ root == wrk.ROOT_KEY_CURRENT_USER - ? "HKEY_CURRENT_USER" - : "HKEY_LOCAL_MACHINE" + ? "HKEY_CURRENT_USER" + : "HKEY_LOCAL_MACHINE" }` ); this._policies = lazy.WindowsGPOParser.readPolicies( @@ -1185,14 +1185,7 @@ class CombinedProvider extends PoliciesProvider { mergePolicies() { // Combine policies with primaryProvider taking precedence. // We only do this for top level policies. - this._policies = structuredClone(this._primaryProvider.policies); - for (let [policyName, policyParams] of Object.entries( - this._secondaryProvider.policies || {} - )) { - if (!(policyName in this._policies)) { - this._policies[policyName] = policyParams; - } - } + this._policies = Object.assign({}, this._secondaryProvider.policies ?? {}, this._primaryProvider.policies); } get failed() { From 0d67df322a1bace8f714a5768477b92427f4da98 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 13 Feb 2026 12:10:34 +0100 Subject: [PATCH 28/33] Log on restart and reset error --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 4bdd6467fe754..659f191ce4be5 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -594,11 +594,11 @@ EnterprisePoliciesManager.prototype = { break; case "EnterprisePolicies:Reset": - this._resetEngine().then(null, console.error); + this._resetEngine().catch(console.error); break; case "EnterprisePolicies:Restart": - this._restart().then(null, console.error); + this._restart().catch(console.error); break; case "EnterprisePolicies:Update": { From 959f1b6881fe8db25ced24a3652d773b754423c8 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 13 Feb 2026 16:53:12 +0100 Subject: [PATCH 29/33] Update engine status on failure --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 659f191ce4be5..9415d839df93e 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -219,10 +219,12 @@ EnterprisePoliciesManager.prototype = { }, /** - * Update engine status after parsing a new set of policies + * Update engine status after parsing policies */ _updateStatus() { - if (this.hasActivePolicies()) { + if (this._provider.failed) { + this.status = Ci.nsIEnterprisePolicies.FAILED; + } else if (this.hasActivePolicies()) { this.status = Ci.nsIEnterprisePolicies.ACTIVE; } else { this.status = Ci.nsIEnterprisePolicies.INACTIVE; From c532175305573eea8f7dfdb749d8cc57e73e7c93 Mon Sep 17 00:00:00 2001 From: Janika Date: Fri, 13 Feb 2026 16:53:52 +0100 Subject: [PATCH 30/33] Undo clearing policies on failure --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 2 -- 1 file changed, 2 deletions(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 9415d839df93e..ee3da46d8d616 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -956,7 +956,6 @@ class JSONPoliciesProvider extends PoliciesProvider { if (!policies) { lazy.log.error("Policies file doesn't contain a 'policies' object"); - this._policies = {}; this._failed = true; } else { this._policies = policies; @@ -1107,7 +1106,6 @@ class RemotePoliciesProvider extends PoliciesProvider { const res = await lazy.ConsoleClient.getRemotePolicies(); if (!res.policies) { - this._policies = {}; console.error( `Clearing remote policies because no policies were found in the response: ${JSON.stringify(res)}.` ); From 8761391b06b00ea140c5da530f79568e17615943 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 16 Feb 2026 13:30:08 +0100 Subject: [PATCH 31/33] Refactor test to restore preferences state with unsetAndUnlockPref --- .../tests/EnterprisePolicyTesting.sys.mjs | 4 + .../tests/browser/remote/browser.toml | 2 +- ...ser_remote_policies_unsetAndUnlockPref.js} | 103 ++++++++---------- 3 files changed, 51 insertions(+), 58 deletions(-) rename toolkit/components/enterprisepolicies/tests/browser/remote/{browser_remote_policies_prefs.js => browser_remote_policies_unsetAndUnlockPref.js} (52%) diff --git a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs index bf795fc87efd4..ef3cb9a874f85 100644 --- a/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs +++ b/toolkit/components/enterprisepolicies/tests/EnterprisePolicyTesting.sys.mjs @@ -240,6 +240,10 @@ export var PoliciesPrefTracker = { let defaults = new Preferences({ defaultBranch: true }); for (let [prefName, stored] of this._originalValues) { + if (!Preferences.get(prefName)) { + // Pref might have been removed by the test. + continue; + } // If a pref was used through setDefaultPref instead // of setAndLockPref, it wasn't locked, but calling // unlockPref is harmless diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml index 6002fab33b38b..b873de6b68e57 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser.toml @@ -21,4 +21,4 @@ environment = [ ["browser_remote_policies_missing_live.js"] -["browser_remote_policies_prefs.js"] +["browser_remote_policies_unsetAndUnlockPref.js"] diff --git a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_unsetAndUnlockPref.js similarity index 52% rename from toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js rename to toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_unsetAndUnlockPref.js index 25701c3db1e92..56e336ddd5bd6 100644 --- a/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_prefs.js +++ b/toolkit/components/enterprisepolicies/tests/browser/remote/browser_remote_policies_unsetAndUnlockPref.js @@ -14,10 +14,30 @@ const PREF_VALUE = { POLICY_CHANGED: "policy-changed", } -add_task(async function test_simple_policy_pref_setAndLock() { - - const prefName = "browser.tests.some_random_pref"; - +const prefName = "browser.tests.some_random_pref"; + +add_setup(async () => { + registerCleanupFunction(() => { + Services.prefs.deleteBranch(prefName); + }); +}) + +/** + * Tests that the initial preference state (default and user value) is restored + * when unsetting and unlocking a preference via unsetAndUnlockPref + * + * Test flow: + * + * 0. Verify that we are starting with a clean preference state. + * 1. Setting user and default values + * 2. Changing the user value + * 3. Calling setAndUnlockPref which overriddes the preference's default and user value. + * 4. Calling setAndUnlockPref again with a different value. + * 5. Call unsetAndUnlockPref which should restore the preference's initial state. + */ +add_task(async function test_unsetAndUnlockPref() { + + // 0. Verify that we are starting with a clean preference state. try { Services.prefs.getStringPref(prefName); ok(false, `Pref ${prefName} exists, this should not happen`); @@ -25,129 +45,98 @@ add_task(async function test_simple_policy_pref_setAndLock() { ok(true, `Pref ${prefName} does not exist`); } + // 1. Setting user and default values let defaults = Services.prefs.getDefaultBranch(""); defaults.setStringPref(prefName, PREF_VALUE.DEFAULT); - // Assert default pref value + info("Verify the preference's default value is set.") is( Services.prefs.getStringPref(prefName), PREF_VALUE.DEFAULT, "Correct default pref value returned via Services.prefs." ); + info("Verify the preference's user value is set.") is( defaults.getStringPref(prefName), PREF_VALUE.DEFAULT, "Correct default pref value returned via defaults." ); + // 2. Changing the user value Services.prefs.setStringPref(prefName, PREF_VALUE.USER_CHANGED); - // Assert user value works + info("Verify the preference's user value is changed.") is( Services.prefs.getStringPref(prefName), PREF_VALUE.USER_CHANGED, "user pref value returned via Services.prefs." ); + info("Verify the preference's default value has not changed.") is( defaults.getStringPref(prefName), PREF_VALUE.DEFAULT, "default pref value returned via defaults." ); - // Assert not locked + info("Verify the preference is not locked.") is( false, Services.prefs.prefIsLocked(prefName), "Pref reports as not locked" ); - const customSchema = { - properties: { - SetSomePref: { - type: "string", - }, - }, - }; - - Policies.SetSomePref = { - onBeforeUIStartup(manager, param) { - if (param) { - setAndLockPref(prefName, param); - } - }, - onRemove(manager, oldParams) { - if (oldParams) { - unsetAndUnlockPref(prefName); - } - }, - }; - - await setupPolicyEngineWithJson( - { - policies: { - SetSomePref: PREF_VALUE.POLICY_DEFAULT, - }, - }, - customSchema - ); + // 3. Calling setAndUnlockPref which overriddes a preference's default and user value once. + setAndLockPref(prefName, PREF_VALUE.POLICY_DEFAULT); - // Assert pref value set and locked, default value returned + info("Verify the preference's user value was overridden by the policy.") is( Services.prefs.getStringPref(prefName), PREF_VALUE.POLICY_DEFAULT, "new default pref value returned via Services.prefs." ); + info("Verify the preference's default value was overridden by the policy.") is( defaults.getStringPref(prefName), PREF_VALUE.POLICY_DEFAULT, "new default pref value returned via defaults." ); + info("Verify the preference is locked.") is(true, Services.prefs.prefIsLocked(prefName), "Pref reports as locked"); - await EnterprisePolicyTesting.applyRemotePolicies( - { - policies: { - SetSomePref: PREF_VALUE.POLICY_CHANGED, - }, - }, - ); + // 4. Calling setAndUnlockPref again with a different value. + setAndLockPref(prefName, PREF_VALUE.POLICY_CHANGED); - // Assert pref value set and locked, default value returned + info("Verify the preference's user value was overridden another time by the policy update.") is( Services.prefs.getStringPref(prefName), PREF_VALUE.POLICY_CHANGED, "new changed pref value returned via Services.prefs." ); + info("Verify the preference's default value was overridden another time by the policy update.") is( defaults.getStringPref(prefName), PREF_VALUE.POLICY_CHANGED, "new changed pref value returned via defaults." ); + info("Verify the preference remains locked.") is(true, Services.prefs.prefIsLocked(prefName), "Pref remains as locked"); - await EnterprisePolicyTesting.applyRemotePolicies( - { - policies: {}, - }, - ); + // 5. Call unsetAndUnlockPref which should restore the preference's initial state. + unsetAndUnlockPref(prefName) - // Assert original default pref and user value returned again + info("Verify the preference's initial changed user value is restored."); is( Services.prefs.getStringPref(prefName), PREF_VALUE.USER_CHANGED, "original user pref value returned via Services.prefs." ); + info("Verify the preference's initial default value is restored."); is( defaults.getStringPref(prefName), PREF_VALUE.DEFAULT, "original default pref value returned via defaults." ); - + info("Verify the preference is unlocked again.") is(false, Services.prefs.prefIsLocked(prefName), "Pref reports as unlocked"); - - delete Policies.SetSomePref; - - Services.prefs.deleteBranch(prefName); }); - \ No newline at end of file From c45264bf54620013d587d2045094659391948bfc Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 16 Feb 2026 13:36:27 +0100 Subject: [PATCH 32/33] Remove duplicate console warning on policy param validation --- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index ee3da46d8d616..45bb1589eaea2 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -244,7 +244,6 @@ EnterprisePoliciesManager.prototype = { ); if (!isValid) { - console.warn(`Parameters for policy ${policyName} are invalid`); continue; } From 51242dd2c549225fe711c20dd61feae095de9708 Mon Sep 17 00:00:00 2001 From: Janika Date: Mon, 16 Feb 2026 13:42:50 +0100 Subject: [PATCH 33/33] Remove unnecessary PREF_LOCAL_POLICIES_ENABLED import/export --- browser/extensions/felt/content/FeltProcessParent.sys.mjs | 2 -- .../enterprisepolicies/EnterprisePoliciesParent.sys.mjs | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/browser/extensions/felt/content/FeltProcessParent.sys.mjs b/browser/extensions/felt/content/FeltProcessParent.sys.mjs index 07e1fe7715878..3c5051ded686e 100644 --- a/browser/extensions/felt/content/FeltProcessParent.sys.mjs +++ b/browser/extensions/felt/content/FeltProcessParent.sys.mjs @@ -13,8 +13,6 @@ ChromeUtils.defineESModuleGetters(lazy, { FeltStorage: "resource:///modules/FeltStorage.sys.mjs", PREF_REMOTE_POLICIES_ENABLED: "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", - PREF_LOCAL_POLICIES_ENABLED: - "resource://gre/modules/EnterprisePoliciesParent.sys.mjs", }); console.debug(`FeltExtension: FeltParentProcess.sys.mjs`); diff --git a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs index 45bb1589eaea2..9a192777cd9b6 100644 --- a/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs +++ b/toolkit/components/enterprisepolicies/EnterprisePoliciesParent.sys.mjs @@ -47,7 +47,7 @@ const PREF_LOGLEVEL = "browser.policies.loglevel"; const PREF_POLICIES_APPLIED = "browser.policies.applied"; export const PREF_REMOTE_POLICIES_ENABLED = "browser.policies.remote.enabled"; -export const PREF_LOCAL_POLICIES_ENABLED = "browser.policies.local.enabled"; +const PREF_LOCAL_POLICIES_ENABLED = "browser.policies.local.enabled"; ChromeUtils.defineLazyGetter(lazy, "log", () => { let { ConsoleAPI } = ChromeUtils.importESModule(