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
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
lazy,
"PushService",
"@mozilla.org/push/Service;1",
Ci.nsIPushService
);
ChromeUtils.defineLazyGetter(lazy, "Timer", () =>
ChromeUtils.importESModule("resource://gre/modules/Timer.sys.mjs")
);
/**
* Command-line handler for the --receive-push-messages argument.
*/
export class CommandLineHandler {
static classID = Components.ID("{10fc3d88-c2b2-4e3f-85e2-13dc355d0257}");
static contractID = "@mozilla.org/push/receive-push-messages-clh;1";
QueryInterface = ChromeUtils.generateQI([Ci.nsICommandLineHandler]);
/**
* Handle the --receive-push-messages argument, which opens Firefox without a
* window, receives any pending push messages, and then exits.
*
* @param {nsICommandLine} cmdLine The command line to handle.
*/
handle(cmdLine) {
if (!cmdLine.handleFlag("receive-push-messages", false)) {
return;
}
// Don't display a window
cmdLine.preventDefault = true;
// Firefox is already running and receiving push messages
if (cmdLine.state != Ci.nsICommandLine.STATE_INITIAL_LAUNCH) {
return;
}
// Keep Firefox alive while receiving push messages
Services.startup.enterLastWindowClosingSurvivalArea();
this.receivePushMessages()
.catch(e => {
console.error("Error receiving push messages:", e);
})
.finally(() => {
Services.startup.exitLastWindowClosingSurvivalArea();
});
}
/**
* Ensure the Push Service is ready.
*
* @returns {Promise<boolean>} Resolves to true if the Push Service is ready.
*/
async ensurePushServiceReady() {
try {
await lazy.PushService.wrappedJSObject.ensureReady();
} catch (e) {
if (e.result != Cr.NS_ERROR_NOT_AVAILABLE) {
throw e;
}
return false;
}
return true;
}
/**
* Receive push messages.
*
* Nothing tells us how many push messages are pending, so we cannot wait for
* the last one. Instead we listen and stop once they stop arriving.
*
* The per message timeout restarts on every push message, so a steady
* stream keeps receiving. The total timeout never restarts, so a stream that
* never ends cannot keep Firefox alive forever. Both come from prefs under
* app.backgroundNotifications.receivePushMessages.
*
* Does nothing if push is disabled.
*
* TODO: A push message arriving isn't the same as its service worker having
* finished processing it. Ideally, we'd wait until the service worker is
* this can still shut down before a service worker has responded properly.
*
* @returns {Promise<void>} Resolves when receiving stops.
*/
async receivePushMessages() {
if (!(await this.ensurePushServiceReady())) {
return;
}
await new Promise(resolve => {
let receiving = true;
let pushTopicObserver = null;
const pushTopic = lazy.PushService.pushTopic;
let perMessageTimer = null;
let totalTimer = null;
const stopReceiving = () => {
if (!receiving) {
return;
}
receiving = false;
Services.obs.removeObserver(pushTopicObserver, pushTopic);
lazy.Timer.clearTimeout(perMessageTimer);
lazy.Timer.clearTimeout(totalTimer);
resolve();
};
const perMessageTimeoutMs = Services.prefs.getIntPref(
"app.backgroundNotifications.receivePushMessages.perMessageTimeoutMs",
5000
);
const startPerMessageTimer = () => {
lazy.Timer.clearTimeout(perMessageTimer);
perMessageTimer = lazy.Timer.setTimeout(
stopReceiving,
perMessageTimeoutMs
);
};
const totalTimeoutMs = Services.prefs.getIntPref(
"app.backgroundNotifications.receivePushMessages.totalTimeoutMs",
60000
);
const startTotalTimer = () => {
totalTimer = lazy.Timer.setTimeout(stopReceiving, totalTimeoutMs);
};
pushTopicObserver = () => startPerMessageTimer();
Services.obs.addObserver(pushTopicObserver, pushTopic);
startPerMessageTimer();
startTotalTimer();
});
}
}