tor-browser

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

commit 174d199de0f6af71b9b63944e8723e0d5a6805b4
parent 4b5cf1138eb0e074690b9fae0ffbfb87e26fd1db
Author: Beth Rennie <beth@brennie.ca>
Date:   Mon,  1 Dec 2025 21:40:58 +0000

Bug 1997467 - Handle paused enrollment in ExperimentManager.canEnroll r=nimbus-reviewers,relud

Normally, `isEnrollmentPaused` is handled in `ExperimentManager.onRecipe`,
which prevents enrollment in regular experiments and rollouts when
enrollment is paused. However, now that we want to supposed ending
enrollment for Firefox Labs recipes, we also need to handle this
condition when computing the list of available Firefox Labs opt-ins.

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

Diffstat:
Mtoolkit/components/nimbus/lib/ExperimentManager.sys.mjs | 24++++++++++++++++--------
Mtoolkit/components/nimbus/test/unit/test_FirefoxLabs.js | 41++++++++++++++++++++++++++++++++++++++++-
2 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs b/toolkit/components/nimbus/lib/ExperimentManager.sys.mjs @@ -42,6 +42,11 @@ const CannotEnrollFeatureReason = Object.freeze({ * feature does not support co-enrollment. */ ENROLLED_IN_FEATURE: "enrolled-in-feature", + + /** + * The enrollment is paused. + */ + ENROLLMENT_PAUSED: "enrollment-paused", }); /** @@ -562,22 +567,25 @@ export class ExperimentManager { } /** - * Determine if enrollment in the given recipe is possible based on its - * features. + * Determine if enrollment in the given recipe is possible. * * @param {object} recipe The recipe in question. - * @param {string[]} recipe.featureIds The list of featureIds that the recipe - * uses. - * @param {boolean} recipe.isRollout Whether or not the recipe is a rollout. * * @returns {CanEnrollResult} Whether or not we can enroll into a given recipe. */ - canEnroll({ featureIds, isRollout }) { - const storeLookupByFeature = isRollout + canEnroll(recipe) { + const storeLookupByFeature = recipe.isRollout ? this.store.getRolloutForFeature.bind(this.store) : this.store.getExperimentForFeature.bind(this.store); - for (const featureId of featureIds) { + if (recipe.isEnrollmentPaused) { + return { + ok: false, + reason: CannotEnrollFeatureReason.ENROLLMENT_PAUSED, + }; + } + + for (const featureId of recipe.featureIds) { const feature = lazy.NimbusFeatures[featureId]; if (!feature) { diff --git a/toolkit/components/nimbus/test/unit/test_FirefoxLabs.js b/toolkit/components/nimbus/test/unit/test_FirefoxLabs.js @@ -35,11 +35,29 @@ add_task(async function test_all() { } ); + const preexistingPaused = NimbusTestUtils.factories.recipe.withFeatureConfig( + "preexisting-paused", + { featureId: "no-feature-firefox-desktop" }, + { + isRollout: true, + isEnrollmentPaused: true, + isFirefoxLabsOptIn: true, + firefoxLabsTitle: "true", + firefoxLabsDescription: "description", + firefoxLabsDescriptionLinks: null, + firefoxLabsGroup: "group", + requiresRestart: false, + } + ); + const { initExperimentAPI, cleanup } = await setupTest({ init: false, storePath: await NimbusTestUtils.createStoreWith(async store => { await NimbusTestUtils.addEnrollmentForRecipe(preexisting, { store }); await NimbusTestUtils.addEnrollmentForRecipe(alreadyEnrolled, { store }); + await NimbusTestUtils.addEnrollmentForRecipe(preexistingPaused, { + store, + }); }), experiments: [ NimbusTestUtils.factories.recipe("opt-in-rollout", { @@ -113,7 +131,22 @@ add_task(async function test_all() { { featureId: "no-feature-firefox-desktop" }, { isRollout: true } ), + NimbusTestUtils.factories.recipe.withFeatureConfig( + "paused", + { featureId: "no-feature-firefox-desktop" }, + { + isRollout: true, + isEnrollmentPaused: true, + isFirefoxLabsOptIn: true, + firefoxLabsTitle: "title", + firefoxLabsDescription: "description", + firefoxLabsDescriptionLinks: null, + firefoxLabsGroup: "group", + requiresRestart: false, + } + ), preexisting, // Prevent unenrollment. + preexistingPaused, alreadyEnrolled, ], migrationState: NimbusTestUtils.migrationState.LATEST, @@ -126,7 +159,12 @@ add_task(async function test_all() { Assert.deepEqual( availableSlugs, - ["opt-in-rollout", "opt-in-experiment", "already-enrolled-opt-in"].sort(), + [ + "opt-in-rollout", + "opt-in-experiment", + "already-enrolled-opt-in", + "preexisting-paused", + ].sort(), "Should return all opt in recipes that match targeting and bucketing" ); @@ -135,6 +173,7 @@ add_task(async function test_all() { "experiment", "rollout", "preexisting-rollout", + "preexisting-paused", ]); await cleanup();