Bug 1874195 - Remove PushServiceHttp2 r=asuth

Differential Revision: https://phabricator.services.mozilla.com/D227265
This commit is contained in:
Kagami Sascha Rosylight 2024-11-18 14:16:16 +00:00
parent 8ea4bdb3a3
commit 5dd76590be
15 changed files with 4 additions and 1670 deletions

View File

@ -2,14 +2,9 @@
* 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";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
export var PushServiceWebSocket;
export var PushServiceHttp2;
const lazy = {};
XPCOMUtils.defineLazyServiceGetter(
@ -20,22 +15,10 @@ XPCOMUtils.defineLazyServiceGetter(
);
ChromeUtils.defineESModuleGetters(lazy, {
PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs",
PushServiceWebSocket: "resource://gre/modules/PushServiceWebSocket.sys.mjs",
pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs",
});
const CONNECTION_PROTOCOLS = (function () {
if ("android" != AppConstants.MOZ_WIDGET_TOOLKIT) {
({ PushServiceWebSocket } = ChromeUtils.importESModule(
"resource://gre/modules/PushServiceWebSocket.sys.mjs"
));
({ PushServiceHttp2 } = ChromeUtils.importESModule(
"resource://gre/modules/PushServiceHttp2.sys.mjs"
));
return [PushServiceWebSocket, PushServiceHttp2];
}
return [];
})();
ChromeUtils.defineLazyGetter(lazy, "console", () => {
let { ConsoleAPI } = ChromeUtils.importESModule(
"resource://gre/modules/Console.sys.mjs"
@ -84,17 +67,8 @@ function getServiceForServerURI(uri) {
"testing.allowInsecureServerURL",
false
);
if (AppConstants.MOZ_WIDGET_TOOLKIT == "android") {
if (uri.scheme == "https" || (allowInsecure && uri.scheme == "http")) {
return CONNECTION_PROTOCOLS;
}
return null;
}
if (uri.scheme == "wss" || (allowInsecure && uri.scheme == "ws")) {
return PushServiceWebSocket;
}
if (uri.scheme == "https" || (allowInsecure && uri.scheme == "http")) {
return PushServiceHttp2;
return lazy.PushServiceWebSocket;
}
return null;
}
@ -706,17 +680,6 @@ export var PushService = {
.then(record => this._notifySubscriptionChangeObservers(record));
},
/**
* Replaces an existing registration and notifies the associated service
* worker.
*
* @param {String} aOldKey The registration ID to replace.
* @param {PushRecord} aNewRecord The new record.
* @returns {Promise} Resolves once the worker has been notified.
*/
updateRegistrationAndNotifyApp(aOldKey, aNewRecord) {
return this.updateRecordAndNotifyApp(aOldKey, _ => aNewRecord);
},
/**
* Updates a registration and notifies the associated service worker.
*

View File

@ -1,824 +0,0 @@
/* 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 { PushDB } from "resource://gre/modules/PushDB.sys.mjs";
import { PushRecord } from "resource://gre/modules/PushRecord.sys.mjs";
import { NetUtil } from "resource://gre/modules/NetUtil.sys.mjs";
import { clearTimeout, setTimeout } from "resource://gre/modules/Timer.sys.mjs";
import { PushCrypto } from "resource://gre/modules/PushCrypto.sys.mjs";
const lazy = {};
ChromeUtils.defineLazyGetter(lazy, "console", () => {
let { ConsoleAPI } = ChromeUtils.importESModule(
"resource://gre/modules/Console.sys.mjs"
);
return new ConsoleAPI({
maxLogLevelPref: "dom.push.loglevel",
prefix: "PushServiceHttp2",
});
});
const prefs = Services.prefs.getBranch("dom.push.");
const kPUSHHTTP2DB_DB_NAME = "pushHttp2";
const kPUSHHTTP2DB_DB_VERSION = 5; // Change this if the IndexedDB format changes
const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";
/**
* A proxy between the PushService and connections listening for incoming push
* messages. The PushService can silence messages from the connections by
* setting PushSubscriptionListener._pushService to null. This is required
* because it can happen that there is an outstanding push message that will
* be send on OnStopRequest but the PushService may not be interested in these.
* It's easier to stop listening than to have checks at specific points.
*/
var PushSubscriptionListener = function (pushService, uri) {
lazy.console.debug("PushSubscriptionListener()");
this._pushService = pushService;
this.uri = uri;
};
PushSubscriptionListener.prototype = {
QueryInterface: ChromeUtils.generateQI([
"nsIHttpPushListener",
"nsIStreamListener",
]),
getInterface(aIID) {
return this.QueryInterface(aIID);
},
onStartRequest() {
lazy.console.debug("PushSubscriptionListener: onStartRequest()");
// We do not do anything here.
},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
lazy.console.debug("PushSubscriptionListener: onDataAvailable()");
// Nobody should send data, but just to be sure, otherwise necko will
// complain.
if (aCount === 0) {
return;
}
let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
inputStream.init(aStream);
inputStream.read(aCount);
},
onStopRequest(aRequest, aStatusCode) {
lazy.console.debug("PushSubscriptionListener: onStopRequest()");
if (!this._pushService) {
return;
}
this._pushService.connOnStop(
aRequest,
Components.isSuccessCode(aStatusCode),
this.uri
);
},
onPush(associatedChannel, pushChannel) {
lazy.console.debug("PushSubscriptionListener: onPush()");
var pushChannelListener = new PushChannelListener(this);
pushChannel.asyncOpen(pushChannelListener);
},
disconnect() {
this._pushService = null;
},
};
/**
* The listener for pushed messages. The message data is collected in
* OnDataAvailable and send to the app in OnStopRequest.
*/
var PushChannelListener = function (pushSubscriptionListener) {
lazy.console.debug("PushChannelListener()");
this._mainListener = pushSubscriptionListener;
this._message = [];
this._ackUri = null;
};
PushChannelListener.prototype = {
onStartRequest(aRequest) {
this._ackUri = aRequest.URI.spec;
},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
lazy.console.debug("PushChannelListener: onDataAvailable()");
if (aCount === 0) {
return;
}
let inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
Ci.nsIBinaryInputStream
);
inputStream.setInputStream(aStream);
let chunk = new ArrayBuffer(aCount);
inputStream.readArrayBuffer(aCount, chunk);
this._message.push(chunk);
},
onStopRequest(aRequest, aStatusCode) {
lazy.console.debug(
"PushChannelListener: onStopRequest()",
"status code",
aStatusCode
);
if (
Components.isSuccessCode(aStatusCode) &&
this._mainListener &&
this._mainListener._pushService
) {
let headers = {
encryption_key: getHeaderField(aRequest, "Encryption-Key"),
crypto_key: getHeaderField(aRequest, "Crypto-Key"),
encryption: getHeaderField(aRequest, "Encryption"),
encoding: getHeaderField(aRequest, "Content-Encoding"),
};
let msg = PushCrypto.concatArray(this._message);
this._mainListener._pushService._pushChannelOnStop(
this._mainListener.uri,
this._ackUri,
headers,
msg
);
}
},
};
function getHeaderField(aRequest, name) {
try {
return aRequest.getRequestHeader(name);
} catch (e) {
// getRequestHeader can throw.
return null;
}
}
var PushServiceDelete = function (resolve, reject) {
this._resolve = resolve;
this._reject = reject;
};
PushServiceDelete.prototype = {
onStartRequest() {},
onDataAvailable(aRequest, aStream, aOffset, aCount) {
// Nobody should send data, but just to be sure, otherwise necko will
// complain.
if (aCount === 0) {
return;
}
let inputStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
Ci.nsIScriptableInputStream
);
inputStream.init(aStream);
inputStream.read(aCount);
},
onStopRequest(aRequest, aStatusCode) {
if (Components.isSuccessCode(aStatusCode)) {
this._resolve();
} else {
this._reject(new Error("Error removing subscription: " + aStatusCode));
}
},
};
var SubscriptionListener = function (
aSubInfo,
aResolve,
aReject,
aServerURI,
aPushServiceHttp2
) {
lazy.console.debug("SubscriptionListener()");
this._subInfo = aSubInfo;
this._resolve = aResolve;
this._reject = aReject;
this._serverURI = aServerURI;
this._service = aPushServiceHttp2;
this._ctime = Date.now();
this._retryTimeoutID = null;
};
SubscriptionListener.prototype = {
onStartRequest() {},
onDataAvailable() {},
onStopRequest(aRequest, aStatus) {
lazy.console.debug("SubscriptionListener: onStopRequest()");
// Check if pushService is still active.
if (!this._service.hasmainPushService()) {
this._reject(new Error("Push service unavailable"));
return;
}
if (!Components.isSuccessCode(aStatus)) {
this._reject(new Error("Error listening for messages: " + aStatus));
return;
}
var statusCode = aRequest.QueryInterface(Ci.nsIHttpChannel).responseStatus;
if (Math.floor(statusCode / 100) == 5) {
if (this._subInfo.retries < prefs.getIntPref("http2.maxRetries")) {
this._subInfo.retries++;
var retryAfter = retryAfterParser(aRequest);
this._retryTimeoutID = setTimeout(_ => {
this._reject({
retry: true,
subInfo: this._subInfo,
});
this._service.removeListenerPendingRetry(this);
this._retryTimeoutID = null;
}, retryAfter);
this._service.addListenerPendingRetry(this);
} else {
this._reject(new Error("Unexpected server response: " + statusCode));
}
return;
} else if (statusCode != 201) {
this._reject(new Error("Unexpected server response: " + statusCode));
return;
}
var subscriptionUri;
try {
subscriptionUri = aRequest.getResponseHeader("location");
} catch (err) {
this._reject(new Error("Missing Location header"));
return;
}
lazy.console.debug("onStopRequest: subscriptionUri", subscriptionUri);
var linkList;
try {
linkList = aRequest.getResponseHeader("link");
} catch (err) {
this._reject(new Error("Missing Link header"));
return;
}
var linkParserResult;
try {
linkParserResult = linkParser(linkList, this._serverURI);
} catch (e) {
this._reject(e);
return;
}
if (!subscriptionUri) {
this._reject(new Error("Invalid Location header"));
return;
}
try {
Services.io.newURI(subscriptionUri);
} catch (e) {
lazy.console.error(
"onStopRequest: Invalid subscription URI",
subscriptionUri
);
this._reject(
new Error("Invalid subscription endpoint: " + subscriptionUri)
);
return;
}
let reply = new PushRecordHttp2({
subscriptionUri,
pushEndpoint: linkParserResult.pushEndpoint,
pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
scope: this._subInfo.record.scope,
originAttributes: this._subInfo.record.originAttributes,
systemRecord: this._subInfo.record.systemRecord,
appServerKey: this._subInfo.record.appServerKey,
ctime: Date.now(),
});
this._resolve(reply);
},
abortRetry() {
if (this._retryTimeoutID != null) {
clearTimeout(this._retryTimeoutID);
this._retryTimeoutID = null;
} else {
lazy.console.debug(
"SubscriptionListener.abortRetry: aborting non-existent retry?"
);
}
},
};
function retryAfterParser(aRequest) {
let retryAfter = 0;
try {
let retryField = aRequest.getResponseHeader("retry-after");
if (isNaN(retryField)) {
retryAfter = Date.parse(retryField) - new Date().getTime();
} else {
retryAfter = parseInt(retryField, 10) * 1000;
}
retryAfter = retryAfter > 0 ? retryAfter : 0;
} catch (e) {}
return retryAfter;
}
function linkParser(linkHeader, serverURI) {
let linkList = linkHeader.split(",");
if (linkList.length < 1) {
throw new Error("Invalid Link header");
}
let pushEndpoint;
let pushReceiptEndpoint;
linkList.forEach(link => {
let linkElems = link.split(";");
if (linkElems.length == 2) {
if (linkElems[1].trim() === 'rel="urn:ietf:params:push"') {
pushEndpoint = linkElems[0].substring(
linkElems[0].indexOf("<") + 1,
linkElems[0].indexOf(">")
);
} else if (linkElems[1].trim() === 'rel="urn:ietf:params:push:receipt"') {
pushReceiptEndpoint = linkElems[0].substring(
linkElems[0].indexOf("<") + 1,
linkElems[0].indexOf(">")
);
}
}
});
lazy.console.debug("linkParser: pushEndpoint", pushEndpoint);
lazy.console.debug("linkParser: pushReceiptEndpoint", pushReceiptEndpoint);
// Missing pushReceiptEndpoint is allowed.
if (!pushEndpoint) {
throw new Error("Missing push endpoint");
}
const pushURI = Services.io.newURI(pushEndpoint, null, serverURI);
let pushReceiptURI;
if (pushReceiptEndpoint) {
pushReceiptURI = Services.io.newURI(pushReceiptEndpoint, null, serverURI);
}
return {
pushEndpoint: pushURI.spec,
pushReceiptEndpoint: pushReceiptURI ? pushReceiptURI.spec : "",
};
}
/**
* The implementation of the WebPush.
*/
export var PushServiceHttp2 = {
_mainPushService: null,
_serverURI: null,
// Keep information about all connections, e.g. the channel, listener...
_conns: {},
_started: false,
// Set of SubscriptionListeners that are pending a subscription retry attempt.
_listenersPendingRetry: new Set(),
newPushDB() {
return new PushDB(
kPUSHHTTP2DB_DB_NAME,
kPUSHHTTP2DB_DB_VERSION,
kPUSHHTTP2DB_STORE_NAME,
"subscriptionUri",
PushRecordHttp2
);
},
hasmainPushService() {
return this._mainPushService !== null;
},
async connect() {
let subscriptions = await this._mainPushService.getAllUnexpired();
this.startConnections(subscriptions);
},
async sendSubscribeBroadcast() {
// Not implemented yet
},
isConnected() {
return this._mainPushService != null;
},
disconnect() {
this._shutdownConnections(false);
},
_makeChannel(aUri) {
var chan = NetUtil.newChannel({
uri: aUri,
loadUsingSystemPrincipal: true,
}).QueryInterface(Ci.nsIHttpChannel);
var loadGroup = Cc["@mozilla.org/network/load-group;1"].createInstance(
Ci.nsILoadGroup
);
chan.loadGroup = loadGroup;
return chan;
},
/**
* Subscribe new resource.
*/
register(aRecord) {
lazy.console.debug("subscribeResource()");
return this._subscribeResourceInternal({
record: aRecord,
retries: 0,
}).then(result =>
PushCrypto.generateKeys().then(([publicKey, privateKey]) => {
result.p256dhPublicKey = publicKey;
result.p256dhPrivateKey = privateKey;
result.authenticationSecret = PushCrypto.generateAuthenticationSecret();
this._conns[result.subscriptionUri] = {
channel: null,
listener: null,
countUnableToConnect: 0,
lastStartListening: 0,
retryTimerID: 0,
};
this._listenForMsgs(result.subscriptionUri);
return result;
})
);
},
_subscribeResourceInternal(aSubInfo) {
lazy.console.debug("subscribeResourceInternal()");
return new Promise((resolve, reject) => {
var listener = new SubscriptionListener(
aSubInfo,
resolve,
reject,
this._serverURI,
this
);
var chan = this._makeChannel(this._serverURI.spec);
chan.requestMethod = "POST";
chan.asyncOpen(listener);
}).catch(err => {
if ("retry" in err) {
return this._subscribeResourceInternal(err.subInfo);
}
throw err;
});
},
_deleteResource(aUri) {
return new Promise((resolve, reject) => {
var chan = this._makeChannel(aUri);
chan.requestMethod = "DELETE";
chan.asyncOpen(new PushServiceDelete(resolve, reject));
});
},
/**
* Unsubscribe the resource with a subscription uri aSubscriptionUri.
* We can't do anything about it if it fails, so we don't listen for response.
*/
_unsubscribeResource(aSubscriptionUri) {
lazy.console.debug("unsubscribeResource()");
return this._deleteResource(aSubscriptionUri);
},
/**
* Start listening for messages.
*/
_listenForMsgs(aSubscriptionUri) {
lazy.console.debug("listenForMsgs()", aSubscriptionUri);
if (!this._conns[aSubscriptionUri]) {
lazy.console.warn(
"listenForMsgs: We do not have this subscription",
aSubscriptionUri
);
return;
}
var chan = this._makeChannel(aSubscriptionUri);
var conn = {};
conn.channel = chan;
var listener = new PushSubscriptionListener(this, aSubscriptionUri);
conn.listener = listener;
chan.notificationCallbacks = listener;
try {
chan.asyncOpen(listener);
} catch (e) {
lazy.console.error(
"listenForMsgs: Error connecting to push server.",
"asyncOpen failed",
e
);
conn.listener.disconnect();
chan.cancel(Cr.NS_ERROR_ABORT);
this._retryAfterBackoff(aSubscriptionUri, -1);
return;
}
this._conns[aSubscriptionUri].lastStartListening = Date.now();
this._conns[aSubscriptionUri].channel = conn.channel;
this._conns[aSubscriptionUri].listener = conn.listener;
},
_ackMsgRecv(aAckUri) {
lazy.console.debug("ackMsgRecv()", aAckUri);
return this._deleteResource(aAckUri);
},
init(aOptions, aMainPushService, aServerURL) {
lazy.console.debug("init()");
this._mainPushService = aMainPushService;
this._serverURI = aServerURL;
return Promise.resolve();
},
_retryAfterBackoff(aSubscriptionUri, retryAfter) {
lazy.console.debug("retryAfterBackoff()");
var resetRetryCount = prefs.getIntPref("http2.reset_retry_count_after_ms");
// If it was running for some time, reset retry counter.
if (
Date.now() - this._conns[aSubscriptionUri].lastStartListening >
resetRetryCount
) {
this._conns[aSubscriptionUri].countUnableToConnect = 0;
}
let maxRetries = prefs.getIntPref("http2.maxRetries");
if (this._conns[aSubscriptionUri].countUnableToConnect >= maxRetries) {
this._shutdownSubscription(aSubscriptionUri);
this._resubscribe(aSubscriptionUri);
return;
}
if (retryAfter !== -1) {
// This is a 5xx response.
this._conns[aSubscriptionUri].countUnableToConnect++;
this._conns[aSubscriptionUri].retryTimerID = setTimeout(
_ => this._listenForMsgs(aSubscriptionUri),
retryAfter
);
return;
}
retryAfter =
prefs.getIntPref("http2.retryInterval") *
Math.pow(2, this._conns[aSubscriptionUri].countUnableToConnect);
retryAfter = retryAfter * (0.8 + Math.random() * 0.4); // add +/-20%.
this._conns[aSubscriptionUri].countUnableToConnect++;
this._conns[aSubscriptionUri].retryTimerID = setTimeout(
_ => this._listenForMsgs(aSubscriptionUri),
retryAfter
);
lazy.console.debug("retryAfterBackoff: Retry in", retryAfter);
},
// Close connections.
_shutdownConnections(deleteInfo) {
lazy.console.debug("shutdownConnections()");
for (let subscriptionUri in this._conns) {
if (this._conns[subscriptionUri]) {
if (this._conns[subscriptionUri].listener) {
this._conns[subscriptionUri].listener._pushService = null;
}
if (this._conns[subscriptionUri].channel) {
try {
this._conns[subscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
} catch (e) {}
}
this._conns[subscriptionUri].listener = null;
this._conns[subscriptionUri].channel = null;
if (this._conns[subscriptionUri].retryTimerID > 0) {
clearTimeout(this._conns[subscriptionUri].retryTimerID);
}
if (deleteInfo) {
delete this._conns[subscriptionUri];
}
}
}
},
// Start listening if subscriptions present.
startConnections(aSubscriptions) {
lazy.console.debug("startConnections()", aSubscriptions.length);
for (let i = 0; i < aSubscriptions.length; i++) {
let record = aSubscriptions[i];
this._mainPushService.ensureCrypto(record).then(
record => {
this._startSingleConnection(record);
},
error => {
lazy.console.error(
"startConnections: Error updating record",
record.keyID,
error
);
}
);
}
},
_startSingleConnection(record) {
lazy.console.debug("_startSingleConnection()");
if (typeof this._conns[record.subscriptionUri] != "object") {
this._conns[record.subscriptionUri] = {
channel: null,
listener: null,
countUnableToConnect: 0,
retryTimerID: 0,
};
}
if (!this._conns[record.subscriptionUri].conn) {
this._listenForMsgs(record.subscriptionUri);
}
},
// Close connection and notify apps that subscription are gone.
_shutdownSubscription(aSubscriptionUri) {
lazy.console.debug("shutdownSubscriptions()");
if (typeof this._conns[aSubscriptionUri] == "object") {
if (this._conns[aSubscriptionUri].listener) {
this._conns[aSubscriptionUri].listener._pushService = null;
}
if (this._conns[aSubscriptionUri].channel) {
try {
this._conns[aSubscriptionUri].channel.cancel(Cr.NS_ERROR_ABORT);
} catch (e) {}
}
delete this._conns[aSubscriptionUri];
}
},
uninit() {
lazy.console.debug("uninit()");
this._abortPendingSubscriptionRetries();
this._shutdownConnections(true);
this._mainPushService = null;
},
_abortPendingSubscriptionRetries() {
this._listenersPendingRetry.forEach(listener => listener.abortRetry());
this._listenersPendingRetry.clear();
},
unregister(aRecord) {
this._shutdownSubscription(aRecord.subscriptionUri);
return this._unsubscribeResource(aRecord.subscriptionUri);
},
reportDeliveryError(messageID, reason) {
lazy.console.warn(
"reportDeliveryError: Ignoring message delivery error",
messageID,
reason
);
},
/** Push server has deleted subscription.
* Re-subscribe - if it succeeds send update db record and send
* pushsubscriptionchange,
* - on error delete record and send pushsubscriptionchange
* TODO: maybe pushsubscriptionerror will be included.
*/
_resubscribe(aSubscriptionUri) {
this._mainPushService.getByKeyID(aSubscriptionUri).then(record =>
this.register(record).then(
recordNew => {
if (this._mainPushService) {
this._mainPushService
.updateRegistrationAndNotifyApp(aSubscriptionUri, recordNew)
.catch(console.error);
}
},
() => {
if (this._mainPushService) {
this._mainPushService
.dropRegistrationAndNotifyApp(aSubscriptionUri)
.catch(console.error);
}
}
)
);
},
connOnStop(aRequest, aSuccess, aSubscriptionUri) {
lazy.console.debug("connOnStop() succeeded", aSuccess);
var conn = this._conns[aSubscriptionUri];
if (!conn) {
// there is no connection description that means that we closed
// connection, so do nothing. But we should have already deleted
// the listener.
return;
}
conn.channel = null;
conn.listener = null;
if (!aSuccess) {
this._retryAfterBackoff(aSubscriptionUri, -1);
} else if (Math.floor(aRequest.responseStatus / 100) == 5) {
var retryAfter = retryAfterParser(aRequest);
this._retryAfterBackoff(aSubscriptionUri, retryAfter);
} else if (Math.floor(aRequest.responseStatus / 100) == 4) {
this._shutdownSubscription(aSubscriptionUri);
this._resubscribe(aSubscriptionUri);
} else if (Math.floor(aRequest.responseStatus / 100) == 2) {
// This should be 204
setTimeout(_ => this._listenForMsgs(aSubscriptionUri), 0);
} else {
this._retryAfterBackoff(aSubscriptionUri, -1);
}
},
addListenerPendingRetry(aListener) {
this._listenersPendingRetry.add(aListener);
},
removeListenerPendingRetry(aListener) {
if (!this._listenersPendingRetry.remove(aListener)) {
lazy.console.debug("removeListenerPendingRetry: listener not in list?");
}
},
_pushChannelOnStop(aUri, aAckUri, aHeaders, aMessage) {
lazy.console.debug("pushChannelOnStop()");
this._mainPushService
.receivedPushMessage(aUri, "", aHeaders, aMessage, record => {
// Always update the stored record.
return record;
})
.then(_ => this._ackMsgRecv(aAckUri))
.catch(err => {
lazy.console.error("pushChannelOnStop: Error receiving message", err);
});
},
};
function PushRecordHttp2(record) {
PushRecord.call(this, record);
this.subscriptionUri = record.subscriptionUri;
this.pushReceiptEndpoint = record.pushReceiptEndpoint;
}
PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
keyID: {
get() {
return this.subscriptionUri;
},
},
});
PushRecordHttp2.prototype.toSubscription = function () {
let subscription = PushRecord.prototype.toSubscription.call(this);
subscription.pushReceiptEndpoint = this.pushReceiptEndpoint;
return subscription;
};

View File

@ -23,7 +23,6 @@ EXTRA_JS_MODULES += [
if CONFIG["MOZ_BUILD_APP"] != "mobile/android":
# Everything but GeckoView.
EXTRA_JS_MODULES += [
"PushServiceHttp2.sys.mjs",
"PushServiceWebSocket.sys.mjs",
]

View File

@ -1,44 +0,0 @@
const { NetUtil } = ChromeUtils.importESModule(
"resource://gre/modules/NetUtil.sys.mjs"
);
// Returns the test H/2 server port, throwing if it's missing or invalid.
function getTestServerPort() {
let portEnv = Services.env.get("MOZHTTP2_PORT");
let port = parseInt(portEnv, 10);
if (!Number.isFinite(port) || port < 1 || port > 65535) {
throw new Error(`Invalid port in MOZHTTP2_PORT env var: ${portEnv}`);
}
info(`Using HTTP/2 server on port ${port}`);
return port;
}
function readFile(file) {
let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
Ci.nsIFileInputStream
);
fstream.init(file, -1, 0, 0);
let data = NetUtil.readInputStreamToString(fstream, fstream.available());
fstream.close();
return data;
}
function addCertFromFile(certdb, filename, trustString) {
let certFile = do_get_file(filename, false);
let pem = readFile(certFile)
.replace(/-----BEGIN CERTIFICATE-----/, "")
.replace(/-----END CERTIFICATE-----/, "")
.replace(/[\r\n]/g, "");
certdb.addCertFromBase64(pem, trustString);
}
function trustHttp2CA() {
let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
Ci.nsIX509CertDB
);
addCertFromFile(
certdb,
"../../../../netwerk/test/unit/http2-ca.pem",
"CTu,u,u"
);
}

View File

@ -15,8 +15,7 @@ ChromeUtils.defineESModuleGetters(this, {
Preferences: "resource://gre/modules/Preferences.sys.mjs",
PushCrypto: "resource://gre/modules/PushCrypto.sys.mjs",
PushService: "resource://gre/modules/PushService.sys.mjs",
PushServiceHttp2: "resource://gre/modules/PushService.sys.mjs",
PushServiceWebSocket: "resource://gre/modules/PushService.sys.mjs",
PushServiceWebSocket: "resource://gre/modules/PushServiceWebSocket.sys.mjs",
pushBroadcastService: "resource://gre/modules/PushBroadcastService.sys.mjs",
});
@ -169,9 +168,6 @@ function setPrefs(prefs = {}) {
retryBaseInterval: 5000,
pingInterval: 30 * 60 * 1000,
// Misc. defaults.
"http2.maxRetries": 2,
"http2.retryInterval": 500,
"http2.reset_retry_count_after_ms": 60000,
maxQuotaPerSubscription: 16,
quotaUpdateDelay: 3000,
"testing.notifyWorkers": false,

View File

@ -1,129 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpServer = null;
ChromeUtils.defineLazyGetter(this, "serverPort", function () {
return httpServer.identity.primaryPort;
});
var retries = 0;
function subscribe5xxCodeHandler(metadata, response) {
if (retries == 0) {
ok(true, "Subscribe 5xx code");
do_test_finished();
response.setHeader("Retry-After", "1");
response.setStatusLine(metadata.httpVersion, 500, "Retry");
} else {
ok(true, "Subscribed");
do_test_finished();
response.setHeader(
"Location",
"http://localhost:" + serverPort + "/subscription"
);
response.setHeader(
"Link",
'</pushEndpoint>; rel="urn:ietf:params:push", ' +
'</receiptPushEndpoint>; rel="urn:ietf:params:push:receipt"'
);
response.setStatusLine(metadata.httpVersion, 201, "OK");
}
retries++;
}
function listenSuccessHandler(metadata, response) {
Assert.ok(true, "New listener point");
Assert.equal(retries, 2, "Should try 2 times.");
do_test_finished();
response.setHeader("Retry-After", "10");
response.setStatusLine(metadata.httpVersion, 500, "Retry");
}
httpServer = new HttpServer();
httpServer.registerPathHandler("/subscribe5xxCode", subscribe5xxCodeHandler);
httpServer.registerPathHandler("/subscription", listenSuccessHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
setPrefs({
"testing.allowInsecureServerURL": true,
"http2.retryInterval": 1000,
"http2.maxRetries": 2,
});
run_next_test();
}
add_task(async function test1() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
do_test_pending();
do_test_pending();
do_test_pending();
do_test_pending();
var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
PushService.init({
serverURI: serverURL + "/subscribe5xxCode",
db,
});
let originAttributes = ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
});
let newRecord = await PushService.register({
scope: "https://example.com/retry5xxCode",
originAttributes,
});
var subscriptionUri = serverURL + "/subscription";
var pushEndpoint = serverURL + "/pushEndpoint";
var pushReceiptEndpoint = serverURL + "/receiptPushEndpoint";
equal(
newRecord.endpoint,
pushEndpoint,
"Wrong push endpoint in registration record"
);
equal(
newRecord.pushReceiptEndpoint,
pushReceiptEndpoint,
"Wrong push endpoint receipt in registration record"
);
let record = await db.getByKeyID(subscriptionUri);
equal(
record.subscriptionUri,
subscriptionUri,
"Wrong subscription ID in database record"
);
equal(
record.pushEndpoint,
pushEndpoint,
"Wrong push endpoint in database record"
);
equal(
record.pushReceiptEndpoint,
pushReceiptEndpoint,
"Wrong push endpoint receipt in database record"
);
equal(
record.scope,
"https://example.com/retry5xxCode",
"Wrong scope in database record"
);
httpServer.stop(do_test_finished);
});

View File

@ -1,38 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function run_test() {
do_get_profile();
run_next_test();
}
add_task(async function test_registrations_error() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
PushService.init({
serverURI: "https://push.example.org/",
db: makeStub(db, {
getByIdentifiers() {
return Promise.reject("Database error");
},
}),
});
await Assert.rejects(
PushService.registration({
scope: "https://example.net/1",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
}),
function (error) {
return error == "Database error";
},
"Wrong message"
);
});

View File

@ -1,77 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var serverPort = -1;
function run_test() {
serverPort = getTestServerPort();
do_get_profile();
run_next_test();
}
add_task(async function test_pushNotifications() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
var serverURL = "https://localhost:" + serverPort;
let records = [
{
subscriptionUri: serverURL + "/subscriptionA",
pushEndpoint: serverURL + "/pushEndpointA",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpointA",
scope: "https://example.net/a",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
quota: Infinity,
},
{
subscriptionUri: serverURL + "/subscriptionB",
pushEndpoint: serverURL + "/pushEndpointB",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpointB",
scope: "https://example.net/b",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
quota: Infinity,
},
{
subscriptionUri: serverURL + "/subscriptionC",
pushEndpoint: serverURL + "/pushEndpointC",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpointC",
scope: "https://example.net/c",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
quota: Infinity,
},
];
for (let record of records) {
await db.put(record);
}
PushService.init({
serverURI: serverURL,
db,
});
let registration = await PushService.registration({
scope: "https://example.net/a",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
});
equal(
registration.endpoint,
serverURL + "/pushEndpointA",
"Wrong push endpoint for scope"
);
});

View File

@ -1,113 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpServer = null;
ChromeUtils.defineLazyGetter(this, "serverPort", function () {
return httpServer.identity.primaryPort;
});
var handlerDone;
var handlerPromise = new Promise(r => (handlerDone = after(3, r)));
function listen4xxCodeHandler(metadata, response) {
ok(true, "Listener point error");
handlerDone();
response.setStatusLine(metadata.httpVersion, 410, "GONE");
}
function resubscribeHandler(metadata, response) {
ok(true, "Ask for new subscription");
handlerDone();
response.setHeader(
"Location",
"http://localhost:" + serverPort + "/newSubscription"
);
response.setHeader(
"Link",
'</newPushEndpoint>; rel="urn:ietf:params:push", ' +
'</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"'
);
response.setStatusLine(metadata.httpVersion, 201, "OK");
}
function listenSuccessHandler(metadata, response) {
Assert.ok(true, "New listener point");
httpServer.stop(handlerDone);
response.setStatusLine(metadata.httpVersion, 204, "Try again");
}
httpServer = new HttpServer();
httpServer.registerPathHandler("/subscription4xxCode", listen4xxCodeHandler);
httpServer.registerPathHandler("/subscribe", resubscribeHandler);
httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
setPrefs({
"testing.allowInsecureServerURL": true,
"testing.notifyWorkers": false,
"testing.notifyAllObservers": true,
});
run_next_test();
}
add_task(async function test1() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
let records = [
{
subscriptionUri: serverURL + "/subscription4xxCode",
pushEndpoint: serverURL + "/pushEndpoint",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint",
scope: "https://example.com/page",
originAttributes: "",
quota: Infinity,
},
];
for (let record of records) {
await db.put(record);
}
PushService.init({
serverURI: serverURL + "/subscribe",
db,
});
await handlerPromise;
let record = await db.getByIdentifiers({
scope: "https://example.com/page",
originAttributes: "",
});
equal(
record.keyID,
serverURL + "/newSubscription",
"Should update subscription URL"
);
equal(
record.pushEndpoint,
serverURL + "/newPushEndpoint",
"Should update push endpoint"
);
equal(
record.pushReceiptEndpoint,
serverURL + "/newReceiptPushEndpoint",
"Should update push receipt endpoint"
);
});

View File

@ -1,116 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpServer = null;
ChromeUtils.defineLazyGetter(this, "serverPort", function () {
return httpServer.identity.primaryPort;
});
var retries = 0;
var handlerDone;
var handlerPromise = new Promise(r => (handlerDone = after(5, r)));
function listen5xxCodeHandler(metadata, response) {
ok(true, "Listener 5xx code");
handlerDone();
retries++;
response.setHeader("Retry-After", "1");
response.setStatusLine(metadata.httpVersion, 500, "Retry");
}
function resubscribeHandler(metadata, response) {
ok(true, "Ask for new subscription");
Assert.equal(retries, 3, "Should retry 2 times.");
handlerDone();
response.setHeader(
"Location",
"http://localhost:" + serverPort + "/newSubscription"
);
response.setHeader(
"Link",
'</newPushEndpoint>; rel="urn:ietf:params:push", ' +
'</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"'
);
response.setStatusLine(metadata.httpVersion, 201, "OK");
}
function listenSuccessHandler(metadata, response) {
Assert.ok(true, "New listener point");
httpServer.stop(handlerDone);
response.setStatusLine(metadata.httpVersion, 204, "Try again");
}
httpServer = new HttpServer();
httpServer.registerPathHandler("/subscription5xxCode", listen5xxCodeHandler);
httpServer.registerPathHandler("/subscribe", resubscribeHandler);
httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
setPrefs({
"testing.allowInsecureServerURL": true,
"http2.retryInterval": 1000,
"http2.maxRetries": 2,
});
run_next_test();
}
add_task(async function test1() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
let records = [
{
subscriptionUri: serverURL + "/subscription5xxCode",
pushEndpoint: serverURL + "/pushEndpoint",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint",
scope: "https://example.com/page",
originAttributes: "",
quota: Infinity,
},
];
for (let record of records) {
await db.put(record);
}
PushService.init({
serverURI: serverURL + "/subscribe",
db,
});
await handlerPromise;
let record = await db.getByIdentifiers({
scope: "https://example.com/page",
originAttributes: "",
});
equal(
record.keyID,
serverURL + "/newSubscription",
"Should update subscription URL"
);
equal(
record.pushEndpoint,
serverURL + "/newPushEndpoint",
"Should update push endpoint"
);
equal(
record.pushReceiptEndpoint,
serverURL + "/newReceiptPushEndpoint",
"Should update push receipt endpoint"
);
});

View File

@ -1,116 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpServer = null;
ChromeUtils.defineLazyGetter(this, "serverPort", function () {
return httpServer.identity.primaryPort;
});
var handlerDone;
var handlerPromise = new Promise(r => (handlerDone = after(2, r)));
function resubscribeHandler(metadata, response) {
ok(true, "Ask for new subscription");
handlerDone();
response.setHeader(
"Location",
"http://localhost:" + serverPort + "/newSubscription"
);
response.setHeader(
"Link",
'</newPushEndpoint>; rel="urn:ietf:params:push", ' +
'</newReceiptPushEndpoint>; rel="urn:ietf:params:push:receipt"'
);
response.setStatusLine(metadata.httpVersion, 201, "OK");
}
function listenSuccessHandler(metadata, response) {
Assert.ok(true, "New listener point");
httpServer.stop(handlerDone);
response.setStatusLine(metadata.httpVersion, 204, "Try again");
}
httpServer = new HttpServer();
httpServer.registerPathHandler("/subscribe", resubscribeHandler);
httpServer.registerPathHandler("/newSubscription", listenSuccessHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
setPrefs({
"testing.allowInsecureServerURL": true,
"http2.retryInterval": 1000,
"http2.maxRetries": 2,
});
run_next_test();
}
add_task(async function test1() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
let records = [
{
subscriptionUri: "http://localhost/subscriptionNotExist",
pushEndpoint: serverURL + "/pushEndpoint",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint",
scope: "https://example.com/page",
p256dhPublicKey:
"BPCd4gNQkjwRah61LpdALdzZKLLnU5UAwDztQ5_h0QsT26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA",
p256dhPrivateKey: {
crv: "P-256",
d: "1jUPhzVsRkzV0vIzwL4ZEsOlKdNOWm7TmaTfzitJkgM",
ext: true,
key_ops: ["deriveBits"],
kty: "EC",
x: "8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM",
y: "26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA",
},
originAttributes: "",
quota: Infinity,
},
];
for (let record of records) {
await db.put(record);
}
PushService.init({
serverURI: serverURL + "/subscribe",
db,
});
await handlerPromise;
let record = await db.getByIdentifiers({
scope: "https://example.com/page",
originAttributes: "",
});
equal(
record.keyID,
serverURL + "/newSubscription",
"Should update subscription URL"
);
equal(
record.pushEndpoint,
serverURL + "/newPushEndpoint",
"Should update push endpoint"
);
equal(
record.pushReceiptEndpoint,
serverURL + "/newReceiptPushEndpoint",
"Should update push receipt endpoint"
);
});

View File

@ -1,78 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
var pushEnabled;
var pushConnectionEnabled;
var serverPort = -1;
function run_test() {
serverPort = getTestServerPort();
do_get_profile();
pushEnabled = Services.prefs.getBoolPref("dom.push.enabled");
pushConnectionEnabled = Services.prefs.getBoolPref(
"dom.push.connection.enabled"
);
// Set to allow the cert presented by our H2 server
var oldPref = Services.prefs.getIntPref(
"network.http.speculative-parallel-limit"
);
Services.prefs.setIntPref("network.http.speculative-parallel-limit", 0);
Services.prefs.setBoolPref("dom.push.enabled", true);
Services.prefs.setBoolPref("dom.push.connection.enabled", true);
trustHttp2CA();
Services.prefs.setIntPref("network.http.speculative-parallel-limit", oldPref);
run_next_test();
}
add_task(async function test_pushUnsubscriptionSuccess() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(_ => db.close());
});
var serverURL = "https://localhost:" + serverPort;
await db.put({
subscriptionUri: serverURL + "/subscriptionUnsubscriptionSuccess",
pushEndpoint: serverURL + "/pushEndpointUnsubscriptionSuccess",
pushReceiptEndpoint:
serverURL + "/receiptPushEndpointUnsubscriptionSuccess",
scope: "https://example.com/page/unregister-success",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
quota: Infinity,
});
PushService.init({
serverURI: serverURL,
db,
});
await PushService.unregister({
scope: "https://example.com/page/unregister-success",
originAttributes: ChromeUtils.originAttributesToSuffix({
inIsolatedMozBrowser: false,
}),
});
let record = await db.getByKeyID(
serverURL + "/subscriptionUnsubscriptionSuccess"
);
ok(!record, "Unregister did not remove record");
});
add_task(async function test_complete() {
Services.prefs.setBoolPref("dom.push.enabled", pushEnabled);
Services.prefs.setBoolPref(
"dom.push.connection.enabled",
pushConnectionEnabled
);
});

View File

@ -1,72 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const { HttpServer } = ChromeUtils.importESModule(
"resource://testing-common/httpd.sys.mjs"
);
var httpServer = null;
function listenHandler(metadata, response) {
Assert.ok(true, "Start listening");
httpServer.stop(do_test_finished);
response.setHeader("Retry-After", "10");
response.setStatusLine(metadata.httpVersion, 500, "Retry");
}
httpServer = new HttpServer();
httpServer.registerPathHandler("/subscriptionNoKey", listenHandler);
httpServer.start(-1);
function run_test() {
do_get_profile();
setPrefs({
"testing.allowInsecureServerURL": true,
"http2.retryInterval": 1000,
"http2.maxRetries": 2,
});
run_next_test();
}
add_task(async function test1() {
let db = PushServiceHttp2.newPushDB();
registerCleanupFunction(() => {
return db.drop().then(() => db.close());
});
do_test_pending();
var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
let record = {
subscriptionUri: serverURL + "/subscriptionNoKey",
pushEndpoint: serverURL + "/pushEndpoint",
pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint",
scope: "https://example.com/page",
originAttributes: "",
quota: Infinity,
systemRecord: true,
};
await db.put(record);
let notifyPromise = promiseObserverNotification(
PushServiceComponent.subscriptionChangeTopic,
_ => true
);
PushService.init({
serverURI: serverURL + "/subscribe",
db,
});
await notifyPromise;
let aRecord = await db.getByKeyID(serverURL + "/subscriptionNoKey");
ok(aRecord, "The record should still be there");
ok(aRecord.p256dhPublicKey, "There should be a public key");
ok(aRecord.p256dhPrivateKey, "There should be a private key");
});

View File

@ -1,5 +1,5 @@
[DEFAULT]
head = "head.js head-http2.js"
head = "head.js"
# Push notifications and alarms are currently disabled on Android.
skip-if = ["os == 'android'"]
support-files = ["broadcast_handler.sys.mjs"]
@ -55,8 +55,6 @@ run-sequentially = "This will delete all existing push subscriptions."
["test_record.js"]
["test_register_5xxCode_http2.js"]
["test_register_case.js"]
["test_register_flush.js"]
@ -89,18 +87,10 @@ run-sequentially = "This will delete all existing push subscriptions."
["test_registration_success.js"]
["test_resubscribe_4xxCode_http2.js"]
["test_resubscribe_5xxCode_http2.js"]
["test_resubscribe_listening_for_msg_error_http2.js"]
["test_retry_ws.js"]
["test_service_child.js"]
#http2 test
["test_service_parent.js"]
["test_unregister_empty_scope.js"]
@ -115,7 +105,5 @@ run-sequentially = "very high failure rate in parallel"
["test_unregister_success.js"]
["test_updateRecordNoEncryptionKeys_http2.js"]
["test_updateRecordNoEncryptionKeys_ws.js"]
skip-if = ["os == 'linux'"] # Bug 1265233

View File

@ -3333,11 +3333,6 @@ pref("dom.push.pingInterval", 1800000); // 30 minutes
// How long before we timeout
pref("dom.push.requestTimeout", 10000);
// WebPush prefs:
pref("dom.push.http2.reset_retry_count_after_ms", 60000);
pref("dom.push.http2.maxRetries", 2);
pref("dom.push.http2.retryInterval", 5000);
// How long must we wait before declaring that a window is a "ghost" (i.e., a
// likely leak)? This should be longer than it usually takes for an eligible
// window to be collected via the GC/CC.