From 3e7f935a12170368325e1527ba583f6b5dce55de Mon Sep 17 00:00:00 2001 From: Janika Neuberger Date: Thu, 8 Jan 2026 10:30:45 +0100 Subject: [PATCH 1/7] 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 2/7] 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 3/7] 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 4/7] 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 5/7] 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 6/7] 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 7/7] 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