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:
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: [],
+ });
+});