tor-browser

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

commit b8c619fb2ad8493a245dbfef76732e240bb4ffa0
parent 7addb3e109535c9af6873ff2d4f160e299f57038
Author: Beth Rennie <beth@brennie.ca>
Date:   Mon, 27 Oct 2025 19:27:52 +0000

Bug 1972647 - Unenroll from Firefox labs opt-ins during Nimbus initialization if Labs is disabled by policy r=nimbus-reviewers,relud

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

Diffstat:
Mtoolkit/components/nimbus/lib/ExperimentManager.sys.mjs | 30++++++++++++++++++++++++++++--
Mtoolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs | 2+-
Mtoolkit/components/nimbus/test/unit/test_policy.js | 28++++++++++++++++++++++++++++
3 files changed, 57 insertions(+), 3 deletions(-)

diff --git a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs @@ -261,6 +261,10 @@ export class ExperimentManager { this._handleStudiesOptOut(); } + if (!lazy.ExperimentAPI.labsEnabled) { + this._handleLabsDisabled(); + } + lazy.NimbusFeatures.nimbusTelemetry.onUpdate(() => { // Providing default values ensure we disable metrics when unenrolling. const cfg = { @@ -938,9 +942,11 @@ export class ExperimentManager { * Unenroll from all active studies if user opts out. */ _handleStudiesOptOut() { - for (const enrollment of this.store + const enrollments = this.store .getAll() - .filter(e => e.active && !e.isFirefoxLabsOptIn)) { + .filter(e => e.active && !e.isFirefoxLabsOptIn); + + for (const enrollment of enrollments) { this._unenroll( enrollment, UnenrollmentCause.fromReason( @@ -951,6 +957,26 @@ export class ExperimentManager { } /** + * Unenroll from all active Firefox Labs opt-ins if Labs becomes disabled. + */ + _handleLabsDisabled() { + const enrollments = this.store + .getAll() + .filter(e => e.active && e.isFirefoxLabsOptIn); + + for (const enrollment of enrollments) { + this._unenroll( + enrollment, + UnenrollmentCause.fromReason( + lazy.NimbusTelemetry.UnenrollReason.LABS_DISABLED + ) + ); + } + + this.optinRecipes = []; + } + + /** * Generate Normandy UserId respective to a branch * for a given experiment. * diff --git a/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs b/toolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs @@ -821,7 +821,7 @@ export class RemoteSettingsExperimentLoader { * Resolves when the RemoteSettingsExperimentLoader has updated at least once * and is not in the middle of an update. * - * If studies are disabled or the RemoteSettingsExperimentLoader has been + * If Nimbus is disabled or the RemoteSettingsExperimentLoader has been * disabled (i.e., during shutdown), then this will always resolve * immediately. */ diff --git a/toolkit/components/nimbus/test/unit/test_policy.js b/toolkit/components/nimbus/test/unit/test_policy.js @@ -40,6 +40,7 @@ async function doTest({ policies, labsEnabled, studiesEnabled, + existingEnrollments = [], expectedEnrollments, expectedOptIns, }) { @@ -53,10 +54,26 @@ async function doTest({ "Policy engine is active" ); + let storePath = undefined; + if (existingEnrollments) { + const store = NimbusTestUtils.stubs.store(); + await store.init(); + + for (const slug of existingEnrollments) { + NimbusTestUtils.addEnrollmentForRecipe( + RECIPES.find(e => e.slug === slug), + { store } + ); + } + + storePath = await NimbusTestUtils.saveStore(store); + } + const { initExperimentAPI, cleanup, loader } = await NimbusTestUtils.setupTest({ init: false, experiments: RECIPES, + storePath, }); sinon.spy(loader, "updateRecipes"); @@ -145,3 +162,14 @@ add_task(async function testNimbusDisabled() { expectedOptIns: [], }); }); + +add_task(async function testDisableLabsPolicyCausesUnenrollments() { + await doTest({ + policies: { UserMessaging: { FirefoxLabs: false } }, + labsEnabled: false, + studiesEnabled: true, + expectedEnrollments: ["experiment", "rollout"], + existingEnrollments: ["optin"], + expectedOptIns: [], + }); +});