tor-browser

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

commit cd1586e5cf72d8c948d8d74d66a8a696ec07eb40
parent 73fb4a228ffb514b21a86ec0391ab52a51a152ff
Author: Beth Rennie <beth@brennie.ca>
Date:   Sat,  1 Nov 2025 12:50:15 +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 | 32+++++++++++++++++++++++++++++---
Mtoolkit/components/nimbus/lib/RemoteSettingsExperimentLoader.sys.mjs | 2+-
Mtoolkit/components/nimbus/test/unit/test_policy.js | 28++++++++++++++++++++++++++++
3 files changed, 58 insertions(+), 4 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 = { @@ -755,7 +759,7 @@ export class ExperimentManager { if (result.ok) { // Unenrollment due to studies becoming disabled is handled in // `_handleStudiesOptOut`. Firefox Labs can only be disabled by policy and - // thus its enabled state cannot change at runtime. + // thus its enabled state cannot change after Nimbus is initialized. if (result.status === lazy.MatchStatus.DISABLED) { return false; } @@ -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 @@ -815,7 +815,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: [], + }); +});