261 lines
8.4 KiB
JavaScript
261 lines
8.4 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/. */
|
|
|
|
import { Log } from "resource://gre/modules/Log.sys.mjs";
|
|
import { setTimeout } from "resource://gre/modules/Timer.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonRollouts: "resource://normandy/lib/AddonRollouts.sys.mjs",
|
|
AddonStudies: "resource://normandy/lib/AddonStudies.sys.mjs",
|
|
CleanupManager: "resource://normandy/lib/CleanupManager.sys.mjs",
|
|
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
|
|
LogManager: "resource://normandy/lib/LogManager.sys.mjs",
|
|
NormandyMigrations: "resource://normandy/NormandyMigrations.sys.mjs",
|
|
PreferenceExperiments:
|
|
"resource://normandy/lib/PreferenceExperiments.sys.mjs",
|
|
PreferenceRollouts: "resource://normandy/lib/PreferenceRollouts.sys.mjs",
|
|
RecipeRunner: "resource://normandy/lib/RecipeRunner.sys.mjs",
|
|
ShieldPreferences: "resource://normandy/lib/ShieldPreferences.sys.mjs",
|
|
});
|
|
|
|
const UI_AVAILABLE_NOTIFICATION = "sessionstore-windows-restored";
|
|
const BOOTSTRAP_LOGGER_NAME = "app.normandy.bootstrap";
|
|
const SHIELD_INIT_NOTIFICATION = "shield-init-complete";
|
|
|
|
const STARTUP_EXPERIMENT_PREFS_BRANCH = "app.normandy.startupExperimentPrefs.";
|
|
const STARTUP_ROLLOUT_PREFS_BRANCH = "app.normandy.startupRolloutPrefs.";
|
|
const PREF_LOGGING_LEVEL = "app.normandy.logging.level";
|
|
|
|
// Logging
|
|
const log = Log.repository.getLogger(BOOTSTRAP_LOGGER_NAME);
|
|
log.addAppender(new Log.ConsoleAppender(new Log.BasicFormatter()));
|
|
log.level = Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn);
|
|
|
|
export var Normandy = {
|
|
studyPrefsChanged: {},
|
|
rolloutPrefsChanged: {},
|
|
defaultPrefsHaveBeenApplied: Promise.withResolvers(),
|
|
uiAvailableNotificationObserved: Promise.withResolvers(),
|
|
|
|
/** Initialization that needs to happen before the first paint on startup. */
|
|
async init({ runAsync = true } = {}) {
|
|
// It is important to register the listener for the UI before the first
|
|
// await, to avoid missing it.
|
|
Services.obs.addObserver(this, UI_AVAILABLE_NOTIFICATION);
|
|
|
|
// It is important this happens before the first `await`. Note that this
|
|
// also happens before migrations are applied.
|
|
this.rolloutPrefsChanged = this.applyStartupPrefs(
|
|
STARTUP_ROLLOUT_PREFS_BRANCH
|
|
);
|
|
this.studyPrefsChanged = this.applyStartupPrefs(
|
|
STARTUP_EXPERIMENT_PREFS_BRANCH
|
|
);
|
|
this.defaultPrefsHaveBeenApplied.resolve();
|
|
|
|
await lazy.NormandyMigrations.applyAll();
|
|
|
|
// Wait for the UI to be ready, or time out after 5 minutes.
|
|
if (runAsync) {
|
|
await Promise.race([
|
|
this.uiAvailableNotificationObserved.promise,
|
|
new Promise(resolve => setTimeout(resolve, 5 * 60 * 1000)),
|
|
]);
|
|
}
|
|
|
|
// Remove observer for UI notifications. It will error if the notification
|
|
// was already removed, which is fine.
|
|
try {
|
|
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
|
|
} catch (e) {}
|
|
|
|
await this.finishInit();
|
|
},
|
|
|
|
async observe(subject, topic) {
|
|
if (topic === UI_AVAILABLE_NOTIFICATION) {
|
|
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
|
|
this.uiAvailableNotificationObserved.resolve();
|
|
}
|
|
},
|
|
|
|
async finishInit() {
|
|
await lazy.PreferenceRollouts.recordOriginalValues(
|
|
this.rolloutPrefsChanged
|
|
);
|
|
await lazy.PreferenceExperiments.recordOriginalValues(
|
|
this.studyPrefsChanged
|
|
);
|
|
|
|
// Setup logging and listen for changes to logging prefs
|
|
lazy.LogManager.configure(
|
|
Services.prefs.getIntPref(PREF_LOGGING_LEVEL, Log.Level.Warn)
|
|
);
|
|
Services.prefs.addObserver(PREF_LOGGING_LEVEL, lazy.LogManager.configure);
|
|
lazy.CleanupManager.addCleanupHandler(() =>
|
|
Services.prefs.removeObserver(
|
|
PREF_LOGGING_LEVEL,
|
|
lazy.LogManager.configure
|
|
)
|
|
);
|
|
|
|
await lazy.ExperimentAPI.init();
|
|
|
|
try {
|
|
await lazy.AddonStudies.init();
|
|
} catch (err) {
|
|
log.error("Failed to initialize addon studies:", err);
|
|
}
|
|
|
|
try {
|
|
await lazy.PreferenceRollouts.init();
|
|
} catch (err) {
|
|
log.error("Failed to initialize preference rollouts:", err);
|
|
}
|
|
|
|
try {
|
|
await lazy.AddonRollouts.init();
|
|
} catch (err) {
|
|
log.error("Failed to initialize addon rollouts:", err);
|
|
}
|
|
|
|
try {
|
|
await lazy.PreferenceExperiments.init();
|
|
} catch (err) {
|
|
log.error("Failed to initialize preference experiments:", err);
|
|
}
|
|
|
|
try {
|
|
lazy.ShieldPreferences.init();
|
|
} catch (err) {
|
|
log.error("Failed to initialize preferences UI:", err);
|
|
}
|
|
|
|
await lazy.RecipeRunner.init();
|
|
Services.obs.notifyObservers(null, SHIELD_INIT_NOTIFICATION);
|
|
},
|
|
|
|
async uninit() {
|
|
await lazy.CleanupManager.cleanup();
|
|
// Note that Service.pref.removeObserver and Service.obs.removeObserver have
|
|
// oppositely ordered parameters.
|
|
Services.prefs.removeObserver(
|
|
PREF_LOGGING_LEVEL,
|
|
lazy.LogManager.configure
|
|
);
|
|
|
|
try {
|
|
Services.obs.removeObserver(this, UI_AVAILABLE_NOTIFICATION);
|
|
} catch (e) {
|
|
// topic must have already been removed or never added
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Copy a preference subtree from one branch to another, being careful about
|
|
* types, and return the values the target branch originally had. Prefs will
|
|
* be read from the user branch and applied to the default branch.
|
|
*
|
|
* @param sourcePrefix
|
|
* The pref prefix to read prefs from.
|
|
* @returns
|
|
* The original values that each pref had on the default branch.
|
|
*/
|
|
applyStartupPrefs(sourcePrefix) {
|
|
// Note that this is called before Normandy's migrations are applied. This
|
|
// currently has no effect, but future changes should be careful to be
|
|
// backwards compatible.
|
|
const originalValues = {};
|
|
const sourceBranch = Services.prefs.getBranch(sourcePrefix);
|
|
const targetBranch = Services.prefs.getDefaultBranch("");
|
|
|
|
for (const prefName of sourceBranch.getChildList("")) {
|
|
const sourcePrefType = sourceBranch.getPrefType(prefName);
|
|
const targetPrefType = targetBranch.getPrefType(prefName);
|
|
|
|
if (
|
|
targetPrefType !== Services.prefs.PREF_INVALID &&
|
|
targetPrefType !== sourcePrefType
|
|
) {
|
|
console.error(
|
|
new Error(
|
|
`Error setting startup pref ${prefName}; pref type does not match.`
|
|
)
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// record the value of the default branch before setting it
|
|
try {
|
|
switch (targetPrefType) {
|
|
case Services.prefs.PREF_STRING: {
|
|
originalValues[prefName] = targetBranch.getCharPref(prefName);
|
|
break;
|
|
}
|
|
case Services.prefs.PREF_INT: {
|
|
originalValues[prefName] = targetBranch.getIntPref(prefName);
|
|
break;
|
|
}
|
|
case Services.prefs.PREF_BOOL: {
|
|
originalValues[prefName] = targetBranch.getBoolPref(prefName);
|
|
break;
|
|
}
|
|
case Services.prefs.PREF_INVALID: {
|
|
originalValues[prefName] = null;
|
|
break;
|
|
}
|
|
default: {
|
|
// This should never happen
|
|
log.error(
|
|
`Error getting startup pref ${prefName}; unknown value type ${sourcePrefType}.`
|
|
);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e.result === Cr.NS_ERROR_UNEXPECTED) {
|
|
// There is a value for the pref on the user branch but not on the default branch. This is ok.
|
|
originalValues[prefName] = null;
|
|
} else {
|
|
// Unexpected error, report it and move on
|
|
console.error(e);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// now set the new default value
|
|
switch (sourcePrefType) {
|
|
case Services.prefs.PREF_STRING: {
|
|
targetBranch.setCharPref(
|
|
prefName,
|
|
sourceBranch.getCharPref(prefName)
|
|
);
|
|
break;
|
|
}
|
|
case Services.prefs.PREF_INT: {
|
|
targetBranch.setIntPref(prefName, sourceBranch.getIntPref(prefName));
|
|
break;
|
|
}
|
|
case Services.prefs.PREF_BOOL: {
|
|
targetBranch.setBoolPref(
|
|
prefName,
|
|
sourceBranch.getBoolPref(prefName)
|
|
);
|
|
break;
|
|
}
|
|
default: {
|
|
// This should never happen.
|
|
console.error(
|
|
new Error(
|
|
`Error getting startup pref ${prefName}; unexpected value type ${sourcePrefType}.`
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return originalValues;
|
|
},
|
|
};
|