150 lines
3.7 KiB
JavaScript
150 lines
3.7 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 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
|
|
*/
|
|
constructor(callback, epochMilliseconds) {
|
|
this.epochMilliseconds = epochMilliseconds;
|
|
this.armed = false;
|
|
this.timer = null;
|
|
this.callback = callback;
|
|
this.promise = Promise.resolve();
|
|
}
|
|
|
|
async _callbackHandler() {
|
|
try {
|
|
await this.callback();
|
|
this.resolve();
|
|
} catch (err) {
|
|
this.reject(err);
|
|
} finally {
|
|
this._disableTask();
|
|
}
|
|
}
|
|
|
|
_createTimer() {
|
|
const delay = this.epochMilliseconds - Date.now();
|
|
if (delay >= 0) {
|
|
const newTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
|
newTimer.initWithCallback(
|
|
() => {
|
|
this._callbackHandler();
|
|
},
|
|
delay,
|
|
Ci.nsITimer.TYPE_ONE_SHOT
|
|
);
|
|
return newTimer;
|
|
}
|
|
ChromeUtils.idleDispatch(() => {
|
|
this._callbackHandler();
|
|
});
|
|
return null;
|
|
}
|
|
|
|
_destroyTimer() {
|
|
if (this.timer) {
|
|
this.timer.cancel();
|
|
this.timer = null;
|
|
}
|
|
}
|
|
|
|
// Callback needed for Services.obs.addObserver
|
|
observe(_subject, topic, _data) {
|
|
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.armed && this.timer) {
|
|
this._destroyTimer();
|
|
}
|
|
break;
|
|
case "wake_notification":
|
|
// We're back! Create a timer.
|
|
if (this.armed && !this.timer) {
|
|
this.timer = this._createTimer();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
_enableObservers() {
|
|
topics.forEach(topic => {
|
|
Services.obs.addObserver(this, topic);
|
|
});
|
|
}
|
|
|
|
_disableObservers() {
|
|
topics.forEach(topic => {
|
|
Services.obs.removeObserver(this, topic);
|
|
});
|
|
}
|
|
|
|
_disableTask() {
|
|
if (this.armed) {
|
|
this._destroyTimer();
|
|
this._disableObservers();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
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) {
|
|
this.resolve();
|
|
this._disableTask();
|
|
this.armed = false;
|
|
}
|
|
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;
|
|
}
|
|
}
|