This reverts commit53b0e55e9e. Revert "Bug 1956080 - Write enrollment updates to the NimbusEnrollments table r=jhirsch,nimbus-reviewers,relud,nalexander" This reverts commit128370986e. Revert "Bug 1956080 - Add a migration to copy existing enrollments to the NimbusEnrollments table r=jhirsch,nimbus-reviewers,relud,Gijs" This reverts commit0bbb1c3d7a.
261 lines
7.3 KiB
JavaScript
261 lines
7.3 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* 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/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
|
FirefoxLabs: "resource://nimbus/FirefoxLabs.sys.mjs",
|
|
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
|
NimbusTelemetry: "resource://nimbus/lib/Telemetry.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "log", () => {
|
|
const { Logger } = ChromeUtils.importESModule(
|
|
"resource://messaging-system/lib/Logger.sys.mjs"
|
|
);
|
|
return new Logger("NimbusMigrations");
|
|
});
|
|
|
|
/**
|
|
* A named migration.
|
|
*
|
|
* @typedef {object} Migration
|
|
*
|
|
* @property {string} name The name of the migration. This will be reported in
|
|
* telemetry.
|
|
*
|
|
* @property {function(): void} fn The migration implementation.
|
|
*/
|
|
|
|
/**
|
|
* Construct a {@link Migration} with a specific name.
|
|
*
|
|
* @param {string} name The name of the migration.
|
|
* @param {function(): void} fn The migration function.
|
|
*
|
|
* @returns {Migration} The migration.
|
|
*/
|
|
function migration(name, fn) {
|
|
return { name, fn };
|
|
}
|
|
|
|
const Phase = Object.freeze({
|
|
INIT_STARTED: "init-started",
|
|
AFTER_STORE_INITIALIZED: "after-store-initialized",
|
|
AFTER_REMOTE_SETTINGS_UPDATE: "after-remote-settings-update",
|
|
});
|
|
|
|
/**
|
|
* An initialization phase.
|
|
*
|
|
* @typedef {typeof Phase[keyof typeof Phase]} Phase
|
|
*/
|
|
|
|
export const LEGACY_NIMBUS_MIGRATION_PREF = "nimbus.migrations.latest";
|
|
|
|
/** @type {Record<Phase, string>} */
|
|
export const NIMBUS_MIGRATION_PREFS = Object.fromEntries(
|
|
Object.entries(Phase).map(([, v]) => [v, `nimbus.migrations.${v}`])
|
|
);
|
|
|
|
export const LABS_MIGRATION_FEATURE_MAP = {
|
|
"auto-pip": "firefox-labs-auto-pip",
|
|
"urlbar-ime-search": "firefox-labs-urlbar-ime-search",
|
|
"jpeg-xl": "firefox-labs-jpeg-xl",
|
|
};
|
|
|
|
/**
|
|
* Migrate from the legacy migration state to multi-phase migration state.
|
|
*
|
|
* Previously there was only a single set of migrations that ran at the end of
|
|
* `ExperimentAPI.init()`, which is now the "after-remote-settings-update" phase.
|
|
*/
|
|
function migrateMultiphase() {
|
|
const latestMigration = Services.prefs.getIntPref(
|
|
LEGACY_NIMBUS_MIGRATION_PREF,
|
|
-1
|
|
);
|
|
if (latestMigration >= 0) {
|
|
Services.prefs.setIntPref(
|
|
NIMBUS_MIGRATION_PREFS[Phase.AFTER_REMOTE_SETTINGS_UPDATE],
|
|
latestMigration
|
|
);
|
|
Services.prefs.clearUserPref(LEGACY_NIMBUS_MIGRATION_PREF);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Migrate the pre-Nimbus Firefox Labs experiences into Nimbus enrollments.
|
|
*
|
|
* Previously Firefox Labs had a one-to-one correlation between Labs Experiments
|
|
* and prefs being set. If any of those prefs are set, attempt to enroll in the
|
|
* corresponding live Nimbus rollout.
|
|
*
|
|
* Once these rollouts end (i.e., because the features are generally available
|
|
* and no longer in Labs) they can be removed from {@link
|
|
* LABS_MIGRATION_FEATURE_MAP} and once that map is empty this migration can be
|
|
* replaced with a no-op.
|
|
*/
|
|
async function migrateFirefoxLabsEnrollments() {
|
|
const bts = Cc["@mozilla.org/backgroundtasks;1"]?.getService(
|
|
Ci.nsIBackgroundTasks
|
|
);
|
|
|
|
if (bts?.isBackgroundTaskMode) {
|
|
// This migration does not apply to background task mode.
|
|
return;
|
|
}
|
|
|
|
await lazy.ExperimentAPI._rsLoader.finishedUpdating();
|
|
await lazy.ExperimentAPI._rsLoader.withUpdateLock(
|
|
async () => {
|
|
const labs = await lazy.FirefoxLabs.create();
|
|
|
|
let didEnroll = false;
|
|
|
|
for (const [feature, slug] of Object.entries(
|
|
LABS_MIGRATION_FEATURE_MAP
|
|
)) {
|
|
const pref =
|
|
lazy.NimbusFeatures[feature].manifest.variables.enabled.setPref.pref;
|
|
|
|
if (!labs.get(slug)) {
|
|
// If the recipe is not available then either it is no longer live or
|
|
// the targeting did not match.
|
|
continue;
|
|
}
|
|
|
|
if (!Services.prefs.getBoolPref(pref, false)) {
|
|
// Only enroll if the migration pref is set.
|
|
continue;
|
|
}
|
|
|
|
await labs.enroll(slug, "control");
|
|
|
|
// We need to overwrite the original pref value stored in the
|
|
// ExperimentStore so that unenrolling will disable the feature.
|
|
const enrollment = lazy.ExperimentAPI.manager.store.get(slug);
|
|
if (!enrollment) {
|
|
lazy.log.error(`Enrollment with ${slug} should exist but does not`);
|
|
continue;
|
|
}
|
|
if (!enrollment.active) {
|
|
lazy.log.error(
|
|
`Enrollment with slug ${slug} should be active but is not.`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const prefEntry = enrollment.prefs?.find(entry => entry.name === pref);
|
|
if (!prefEntry) {
|
|
lazy.log.error(
|
|
`Enrollment with slug ${slug} does not set pref ${pref}`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
didEnroll = true;
|
|
prefEntry.originalValue = false;
|
|
}
|
|
|
|
if (didEnroll) {
|
|
// Trigger a save of the ExperimentStore since we've changed some data
|
|
// structures without using set().
|
|
// We do not have to sync these changes to child processes because the
|
|
// data is only used in the parent process.
|
|
lazy.ExperimentAPI.manager.store._store.saveSoon();
|
|
}
|
|
},
|
|
{ mode: "shared" }
|
|
);
|
|
}
|
|
|
|
export class MigrationError extends Error {
|
|
static Reason = Object.freeze({
|
|
UNKNOWN: "unknown",
|
|
});
|
|
|
|
constructor(reason) {
|
|
super(`Migration error: ${reason}`);
|
|
this.reason = reason;
|
|
}
|
|
}
|
|
|
|
export const NimbusMigrations = {
|
|
Phase,
|
|
migration,
|
|
|
|
/**
|
|
* Apply any outstanding migrations for the given phase.
|
|
*
|
|
* The first migration in the phase to report an error will halt the
|
|
* application of further migrations in the phase.
|
|
*
|
|
* @param {Phase} phase The phase of migrations to apply.
|
|
*
|
|
*/
|
|
async applyMigrations(phase) {
|
|
const phasePref = NIMBUS_MIGRATION_PREFS[phase];
|
|
const latestMigration = Services.prefs.getIntPref(phasePref, -1);
|
|
let lastSuccess = latestMigration;
|
|
|
|
lazy.log.debug(
|
|
`applyMigrations ${phase}: latestMigration = ${latestMigration}`
|
|
);
|
|
|
|
for (let i = latestMigration + 1; i < this.MIGRATIONS[phase].length; i++) {
|
|
const migration = this.MIGRATIONS[phase][i];
|
|
|
|
lazy.log.debug(
|
|
`applyMigrations ${phase}: applying migration ${i}: ${migration.name}`
|
|
);
|
|
|
|
try {
|
|
await migration.fn();
|
|
} catch (e) {
|
|
lazy.log.error(
|
|
`applyMigrations: error running migration ${i} (${migration.name}): ${e}`
|
|
);
|
|
|
|
let reason = MigrationError.Reason.UNKNOWN;
|
|
if (e instanceof MigrationError) {
|
|
reason = e.reason;
|
|
}
|
|
|
|
lazy.NimbusTelemetry.recordMigration(migration.name, reason);
|
|
|
|
break;
|
|
}
|
|
|
|
lastSuccess = i;
|
|
|
|
lazy.log.debug(
|
|
`applyMigrations: applied migration ${i}: ${migration.name}`
|
|
);
|
|
|
|
lazy.NimbusTelemetry.recordMigration(migration.name);
|
|
}
|
|
|
|
if (latestMigration != lastSuccess) {
|
|
Services.prefs.setIntPref(phasePref, lastSuccess);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @type {Record<Phase, Migration[]>}
|
|
*/
|
|
MIGRATIONS: {
|
|
[Phase.INIT_STARTED]: [
|
|
migration("multi-phase-migrations", migrateMultiphase),
|
|
],
|
|
|
|
[Phase.AFTER_STORE_INITIALIZED]: [],
|
|
|
|
[Phase.AFTER_REMOTE_SETTINGS_UPDATE]: [
|
|
migration("firefox-labs-enrollments", migrateFirefoxLabsEnrollments),
|
|
],
|
|
},
|
|
};
|