Source code

Revision control

Copy as Markdown

Other Tools

/* 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 { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
Normandy: "resource://normandy/Normandy.sys.mjs",
TaskScheduler: "resource://gre/modules/TaskScheduler.sys.mjs",
});
const PREF_TIMEOUT = "first-startup.timeout";
/**
* Service for blocking application startup, to be used on the first install. The intended
* use case is for `FirstStartup` to be invoked when the application is called by an installer,
* such as the Windows Stub Installer, to allow the application to do some first-install tasks
* such as performance tuning and downloading critical data.
*
* In this scenario, the installer does not exit until the first application window appears,
* which gives the user experience of the application starting up quickly on first install.
*/
export var FirstStartup = {
NOT_STARTED: 0,
IN_PROGRESS: 1,
TIMED_OUT: 2,
SUCCESS: 3,
UNSUPPORTED: 4,
_state: 0, // NOT_STARTED,
/**
* Initialize and run first-startup services. This will always run synchronously
* and spin the event loop until either all required services have
* completed, or until a timeout is reached.
*
* In the latter case, services are expected to run post-UI instead as usual.
*
* @param {boolean} newProfile
* True if a new profile was just created, false otherwise.
*/
init(newProfile) {
if (!newProfile) {
// In this case, we actually don't want to do any FirstStartup work,
// since a pre-existing profile was detected (presumably, we entered here
// because a user re-installed via the stub installer when there existed
// previous user profiles on the file system). We do, however, want to
// measure how often this occurs.
Glean.firstStartup.statusCode.set(this.NOT_STARTED);
Glean.firstStartup.newProfile.set(false);
GleanPings.firstStartup.submit();
return;
}
Glean.firstStartup.newProfile.set(true);
this._state = this.IN_PROGRESS;
const timeout = Services.prefs.getIntPref(PREF_TIMEOUT, 30000); // default to 30 seconds
let startingTime = Cu.now();
let initialized = false;
let promises = [];
let normandyInitEndTime = null;
if (AppConstants.MOZ_NORMANDY) {
promises.push(
lazy.Normandy.init({ runAsync: false }).finally(() => {
normandyInitEndTime = Cu.now();
})
);
}
let deleteTasksEndTime = null;
if (AppConstants.MOZ_UPDATE_AGENT) {
// It's technically possible for a previous installation to leave an old
// OS-level scheduled task around. Start fresh.
promises.push(
lazy.TaskScheduler.deleteAllTasks()
.catch(() => {})
.finally(() => {
deleteTasksEndTime = Cu.now();
})
);
}
if (promises.length) {
Promise.all(promises).then(() => (initialized = true));
this.elapsed = 0;
Services.tm.spinEventLoopUntil("FirstStartup.sys.mjs:init", () => {
this.elapsed = Math.round(Cu.now() - startingTime);
if (this.elapsed >= timeout) {
this._state = this.TIMED_OUT;
return true;
} else if (initialized) {
this._state = this.SUCCESS;
return true;
}
return false;
});
} else {
this._state = this.UNSUPPORTED;
}
if (AppConstants.MOZ_NORMANDY) {
Glean.firstStartup.normandyInitTime.set(
Math.ceil(normandyInitEndTime || Cu.now() - startingTime)
);
}
if (AppConstants.MOZ_UPDATE_AGENT) {
Glean.firstStartup.deleteTasksTime.set(
Math.ceil(deleteTasksEndTime || Cu.now() - startingTime)
);
}
Glean.firstStartup.statusCode.set(this._state);
Glean.firstStartup.elapsed.set(this.elapsed);
GleanPings.firstStartup.submit();
},
get state() {
return this._state;
},
/**
* For testing only. This puts us back into the initial NOT_STARTED state.
*/
resetForTesting() {
this._state = this.NOT_STARTED;
},
};