Files
Chris DuPuis 9bd452db34 Bug 2034669 - Give extra time after waking for compulsory restart r=aborondo,gerard-majax
Add a parameter to ScheduledTask to allow a delay between waking from sleep and executing the scheduled task.

Differential Revision: https://phabricator.services.mozilla.com/D296267
2026-09-03 18:40:34 +00:00

183 lines
5.1 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/. */
/**
* Schedule a task to be run no sooner than a specified instant in time.
* If the computer goes to sleep, the task will be run as soon as possible
* after the computer wakes again.
*/
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "logConsole", () =>
console.createInstance({
prefix: "ScheduledTask",
maxLogLevelPref: "app.update.compulsory_restart_log",
})
);
const topics = ["wake_notification", "sleep_notification"];
export class ScheduledTask {
/**
* Constructor for a ScheduledTask. Created the task in a disarmed state. Call arm()
* to activate the task.
*
* @param {Function} callback
* Function to execute at or after the specified time
* @param {number} epochMilliseconds
* The time (in milliseconds since the Unix epoch) to execute the specified function
* @param {number} postWakeDeferralMilliseconds
* (optional) If the process goes to sleep and then wakes up after the specified
* epochMilliseconds, wait for the specified interval before executing the task
*/
constructor(callback, epochMilliseconds, postWakeDeferralMilliseconds = 0) {
this.epochMilliseconds = epochMilliseconds;
this.armed = false;
this.timer = null;
this.callback = callback;
this.postWakeDeferralMilliseconds = postWakeDeferralMilliseconds;
this.promise = Promise.resolve();
this.observedTopics = [];
lazy.logConsole.debug(
`Created task to execute at ${epochMilliseconds} with ${postWakeDeferralMilliseconds} post-wake deferreal`
);
}
async _callbackHandler() {
try {
lazy.logConsole.debug("_callbackHandler invoked");
await this.callback();
lazy.logConsole.debug("callback succeeded");
this.resolve();
} catch (err) {
lazy.logConsole.debug("callback failed");
this.reject(err);
} finally {
this._disableTask();
}
}
_createTimer() {
let delay = this.epochMilliseconds - Date.now();
if (delay < 0) {
// We're already past the scheduled time.
delay = this.postWakeDeferralMilliseconds;
this.postWakeDeferralMilliseconds = 0;
}
lazy.logConsole.debug(
`_createTimer: requested delay of ${delay}, calculated delay of ${delay} milliseconds`
);
const newTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
newTimer.initWithCallback(
() => {
this._callbackHandler();
},
delay,
Ci.nsITimer.TYPE_ONE_SHOT
);
return newTimer;
}
_destroyTimer() {
if (this.timer) {
lazy.logConsole.debug("cancelling timer");
this.timer.cancel();
this.timer = null;
}
}
// Callback needed for Services.obs.addObserver
observe(_subject, topic, _data) {
lazy.logConsole.debug(`Received message on topic ${topic}`);
if (this.armed) {
switch (topic) {
case "sleep_notification":
// Going to sleep now.
// Apparently nsITimer (and anything that uses it directly) doesn't count milliseconds during
// sleep as part of the time. So the existing timer is no longer useful when going to sleep.
// Destroy it.
if (this.timer) {
this._destroyTimer();
}
break;
case "wake_notification":
// We're back! Create a timer.
if (!this.timer) {
this.timer = this._createTimer();
}
break;
}
}
}
_enableObservers() {
topics.forEach(topic => {
Services.obs.addObserver(this, topic);
this.observedTopics.push(topic);
});
}
_disableObservers() {
const iter = this.observedTopics.values();
this.observedTopics = [];
for (const topic of iter) {
Services.obs.removeObserver(this, topic);
}
}
_disableTask() {
if (this.armed) {
lazy.logConsole.debug(`Disabling task`);
this._destroyTimer();
this._disableObservers();
this.armed = false;
}
}
/**
* Arming the task means that, when the computer is not sleeping, there is a timer
* set to execute the callback after an appropriate number of milliseconds. If the
* computer wakes from sleep, if the time period has passed, the callback is
* executed immediately.
*/
arm() {
if (!this.armed) {
lazy.logConsole.debug(`Arming task`);
const { promise, resolve, reject } = Promise.withResolvers();
this.promise = promise;
this.resolve = resolve;
this.reject = reject;
this._enableObservers();
this.armed = true;
this.timer = this._createTimer();
}
return this; // Enable fluent chaining
}
/**
* Disarm the task.
*/
disarm() {
if (this.armed) {
lazy.logConsole.debug(`Disarming task`);
this.resolve();
this._disableTask();
}
return this; // Enable fluent chaining
}
get isArmed() {
return this.armed;
}
/**
* Returns a promise that resolves or rejects when the callback is invoked
*/
asPromise() {
return this.promise;
}
}