Backed out 4 changesets (bug 1150683) for Talos indexedDB crashes

CLOSED TREE

Backed out changeset 7953d3dd62ff (bug 1150683)
Backed out changeset c6805afff48c (bug 1150683)
Backed out changeset 186ed6bc887e (bug 1150683)
Backed out changeset 8e82f557f913 (bug 1150683)
This commit is contained in:
Phil Ringnalda 2015-04-19 07:20:24 -07:00
parent 50d7fd13e2
commit a614ceb8b3
39 changed files with 236 additions and 2810 deletions

View File

@ -209,7 +209,6 @@
@RESPATH@/components/dom_json.xpt
@RESPATH@/components/dom_messages.xpt
@RESPATH@/components/dom_power.xpt
@RESPATH@/components/dom_push.xpt
@RESPATH@/components/dom_quota.xpt
@RESPATH@/components/dom_range.xpt
@RESPATH@/components/dom_security.xpt
@ -630,7 +629,7 @@
@RESPATH@/components/AppsService.manifest
@RESPATH@/components/Push.js
@RESPATH@/components/Push.manifest
@RESPATH@/components/PushNotificationService.js
@RESPATH@/components/PushServiceLauncher.js
@RESPATH@/components/InterAppComm.manifest
@RESPATH@/components/InterAppCommService.js

View File

@ -218,7 +218,6 @@
@RESPATH@/components/dom_offline.xpt
@RESPATH@/components/dom_json.xpt
@RESPATH@/components/dom_power.xpt
@RESPATH@/components/dom_push.xpt
@RESPATH@/components/dom_quota.xpt
@RESPATH@/components/dom_range.xpt
@RESPATH@/components/dom_security.xpt
@ -557,7 +556,7 @@
@RESPATH@/components/AlarmsManager.manifest
@RESPATH@/components/Push.js
@RESPATH@/components/Push.manifest
@RESPATH@/components/PushNotificationService.js
@RESPATH@/components/PushServiceLauncher.js
@RESPATH@/components/SlowScriptDebug.manifest
@RESPATH@/components/SlowScriptDebug.js

View File

@ -1,12 +0,0 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
XPIDL_SOURCES += [
'nsIPushNotificationService.idl',
'nsIPushObserverNotification.idl',
]
XPIDL_MODULE = 'dom_push'

View File

@ -1,49 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "nsISupports.idl"
/**
* A service for components to subscribe and receive push messages from web
* services. This functionality is exposed to content via the Push API, which
* uses service workers to notify applications. This interface exists to allow
* privileged code to receive messages without migrating to service workers.
*/
[scriptable, uuid(32028e38-903b-4a64-a180-5857eb4cb3dd)]
interface nsIPushNotificationService : nsISupports
{
/**
* Creates a push subscription for the given |scope| URL and |pageURL|.
* Returns a promise for the new subscription record, or the existing
* record if this |scope| already has a subscription.
*
* The |pushEndpoint| property of the subscription record is a URL string
* that can be used to send push messages to subscribers. For details,
* please see the Simple Push protocol docs.
*
* Each incoming message fires a `push-notification` observer
* notification, with an `nsIPushObserverNotification` as the subject and
* the |scope| as the data.
*
* If the server drops a subscription, a `push-subscription-change` observer
* will be fired, with the subject set to `null` and the data set to |scope|.
* Servers may drop subscriptions at any time, so callers should recreate
* subscriptions if desired.
*/
jsval register(in string scope, [optional] in string pageURL);
/**
* Revokes a push subscription for the given |scope|. Returns a promise
* for the revoked subscription record, or `null` if the |scope| is not
* subscribed to receive notifications.
*/
jsval unregister(in string scope);
/**
* Returns a promise for the subscription record associated with the
* given |scope|, or `null` if the |scope| does not have a subscription.
*/
jsval registration(in string scope);
};

View File

@ -1,30 +0,0 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "nsISupports.idl"
/**
* A push message received by an `nsIPushNotificationService`, used as the
* subject of a `push-notification` observer notification.
*/
[scriptable, uuid(66a87970-6dc9-46e0-ac61-adb4a13791de)]
interface nsIPushObserverNotification : nsISupports
{
/* The URL that receives push messages from an application server. */
attribute string pushEndpoint;
/**
* The notification version sent by the application server. This is a
* monotonically increasing number.
*/
attribute long long version;
/**
* The notification payload. Delivery is not guaranteed; if the browser is
* offline when the application server sends the push message, the payload
* may be discarded.
*/
attribute string data;
};

View File

@ -26,7 +26,6 @@ interfaces = [
'storage',
'json',
'offline',
'push',
'geolocation',
'notification',
'permission',

View File

@ -5,10 +5,7 @@ contract @mozilla.org/push/PushManager;1 {cde1d019-fad8-4044-b141-65fb4fb7a245}
component {CA86B665-BEDA-4212-8D0F-5C9F65270B58} Push.js
contract @mozilla.org/push/PushSubscription;1 {CA86B665-BEDA-4212-8D0F-5C9F65270B58}
# XPCOM component; initializes the PushService on startup.
component {32028e38-903b-4a64-a180-5857eb4cb3dd} PushNotificationService.js
contract @mozilla.org/push/NotificationService;1 {32028e38-903b-4a64-a180-5857eb4cb3dd}
category app-startup PushNotificationService @mozilla.org/push/NotificationService;1
component {66a87970-6dc9-46e0-ac61-adb4a13791de} PushNotificationService.js
contract @mozilla.org/push/ObserverNotification;1 {66a87970-6dc9-46e0-ac61-adb4a13791de}
# Component to initialize PushService on startup.
component {4b8caa3b-3c58-4f3c-a7f5-7bd9cb24c11d} PushServiceLauncher.js
contract @mozilla.org/push/ServiceLauncher;1 {4b8caa3b-3c58-4f3c-a7f5-7bd9cb24c11d}
category app-startup PushServiceLauncher @mozilla.org/push/ServiceLauncher;1

View File

@ -1,81 +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/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
let isParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
XPCOMUtils.defineLazyGetter(this, "PushService", function() {
// Lazily initialize the PushService on
// `sessionstore-windows-restored` or first use.
const {PushService} = Cu.import("resource://gre/modules/PushService.jsm", {});
if (isParent) {
PushService.init();
}
return PushService;
});
this.PushNotificationService = function PushNotificationService() {};
PushNotificationService.prototype = {
classID: Components.ID("{32028e38-903b-4a64-a180-5857eb4cb3dd}"),
contractID: "@mozilla.org/push/NotificationService;1",
_xpcom_factory: XPCOMUtils.generateSingletonFactory(PushNotificationService),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference,
Ci.nsIPushNotificationService]),
register: function register(scope, pageURL) {
return PushService._register({scope, pageURL});
},
unregister: function unregister(scope) {
return PushService._unregister({scope});
},
registration: function registration(scope) {
return PushService._registration({scope});
},
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
Services.obs.addObserver(this, "sessionstore-windows-restored", true);
break;
case "sessionstore-windows-restored":
Services.obs.removeObserver(this, "sessionstore-windows-restored");
if (isParent) {
PushService.init();
}
break;
}
}
};
this.PushObserverNotification = function PushObserverNotification() {};
PushObserverNotification.prototype = {
classID: Components.ID("{66a87970-6dc9-46e0-ac61-adb4a13791de}"),
contractID: "@mozilla.org/push/ObserverNotification;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushObserverNotification])
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
PushNotificationService,
PushObserverNotification
]);

View File

@ -33,11 +33,9 @@ XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
"resource://gre/modules/AlarmService.jsm");
#ifdef MOZ_B2G
XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService",
"@mozilla.org/power/powermanagerservice;1",
"nsIPowerManagerService");
#endif
var threadManager = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
@ -219,8 +217,8 @@ this.PushDB.prototype = {
function txnCb(aTxn, aStore) {
aStore.clear();
},
aSuccessCb,
aErrorCb
aSuccessCb(),
aErrorCb()
);
}
};
@ -296,7 +294,6 @@ this.PushService = {
*/
case "xpcom-shutdown":
this.uninit();
break;
case "network-active-changed": /* On B2G. */
case "network:offline-status-changed": /* On desktop. */
// In case of network-active-changed, always disconnect existing
@ -351,12 +348,9 @@ this.PushService = {
.deferred.reject({status: 0, error: "TimeoutError"});
delete this._pendingRequests[channelID];
for (let i = this._requestQueue.length - 1; i >= 0; --i) {
let [, data] = this._requestQueue[i];
if (data && data.channelID == channelID) {
for (let i = this._requestQueue.length - 1; i >= 0; --i)
if (this._requestQueue[i].channelID == channelID)
this._requestQueue.splice(i, 1);
}
}
}
}
@ -497,42 +491,16 @@ this.PushService = {
this._ws.sendMsg(msg);
},
init: function(options = {}) {
init: function() {
debug("init()");
if (this._started) {
return;
}
// Override the backing store for testing.
this._db = options.db;
if (!this._db) {
this._db = new PushDB();
}
// Override the default WebSocket factory function. The returned object
// must be null or satisfy the nsIWebSocketChannel interface. Used by
// the tests to provide a mock WebSocket implementation.
if (options.makeWebSocket) {
this._makeWebSocket = options.makeWebSocket;
}
// Override the default UDP socket factory function. The returned object
// must be null or satisfy the nsIUDPSocket interface. Used by the
// UDP tests.
if (options.makeUDPSocket) {
this._makeUDPSocket = options.makeUDPSocket;
}
this._networkInfo = options.networkInfo;
if (!this._networkInfo) {
this._networkInfo = PushNetworkInfo;
}
var globalMM = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIFrameScriptLoader);
globalMM.loadFrameScript("chrome://global/content/PushServiceChildPreload.js", true);
this._db = new PushDB();
let ppmm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
@ -569,8 +537,7 @@ this.PushService = {
// On B2G both events fire, one after the other, when the network goes
// online, so we explicitly check for the presence of NetworkManager and
// don't add an observer for offline-status-changed on B2G.
this._networkStateChangeEventName = this._networkInfo.getNetworkStateChangeEventName();
Services.obs.addObserver(this, this._networkStateChangeEventName, false);
Services.obs.addObserver(this, this._getNetworkStateChangeEventName(), false);
// This is only used for testing. Different tests require connecting to
// slightly different URLs.
@ -597,7 +564,6 @@ this.PushService = {
this._waitingForPong = false;
this._stopAlarm();
this._cancelPendingRequests();
},
uninit: function() {
@ -609,7 +575,7 @@ this.PushService = {
prefs.ignore("debug", this);
prefs.ignore("connection.enabled", this);
prefs.ignore("serverURL", this);
Services.obs.removeObserver(this, this._networkStateChangeEventName);
Services.obs.removeObserver(this, this._getNetworkStateChangeEventName());
Services.obs.removeObserver(this, "webapps-clear-data", false);
Services.obs.removeObserver(this, "xpcom-shutdown", false);
@ -637,7 +603,6 @@ this.PushService = {
this._requestTimeoutTimer.cancel();
}
this._started = false;
debug("shutdown complete!");
},
@ -723,7 +688,7 @@ this.PushService = {
}
// Save actual state of the network
let ns = this._networkInfo.getNetworkInformation();
let ns = this._getNetworkInformation();
if (ns.ip) {
// mobile
@ -841,7 +806,27 @@ this.PushService = {
}
},
_getServerURI: function() {
_beginWSSetup: function() {
debug("beginWSSetup()");
if (this._currentState != STATE_SHUT_DOWN) {
debug("_beginWSSetup: Not in shutdown state! Current state " +
this._currentState);
return;
}
if (!prefs.get("connection.enabled")) {
debug("_beginWSSetup: connection.enabled is not set to true. Aborting.");
return;
}
// Stop any pending reconnects scheduled for the near future.
this._stopAlarm();
if (Services.io.offline) {
debug("Network is offline.");
return;
}
let serverURL = prefs.get("serverURL");
if (!serverURL) {
debug("No dom.push.serverURL found!");
@ -856,63 +841,26 @@ this.PushService = {
serverURL + ")");
return;
}
return uri;
},
_makeWebSocket: function(uri) {
if (!prefs.get("connection.enabled")) {
debug("_makeWebSocket: connection.enabled is not set to true. Aborting.");
return null;
}
if (Services.io.offline) {
debug("Network is offline.");
return null;
}
let socket;
if (uri.scheme === "wss") {
socket = Cc["@mozilla.org/network/protocol;1?name=wss"]
.createInstance(Ci.nsIWebSocketChannel);
this._ws = Cc["@mozilla.org/network/protocol;1?name=wss"]
.createInstance(Ci.nsIWebSocketChannel);
socket.initLoadInfo(null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_WEBSOCKET);
this._ws.initLoadInfo(null, // aLoadingNode
Services.scriptSecurityManager.getSystemPrincipal(),
null, // aTriggeringPrincipal
Ci.nsILoadInfo.SEC_NORMAL,
Ci.nsIContentPolicy.TYPE_WEBSOCKET);
}
else if (uri.scheme === "ws") {
debug("Push over an insecure connection (ws://) is not allowed!");
return null;
return;
}
else {
debug("Unsupported websocket scheme " + uri.scheme);
return null;
}
return socket;
},
_beginWSSetup: function() {
debug("beginWSSetup()");
if (this._currentState != STATE_SHUT_DOWN) {
debug("_beginWSSetup: Not in shutdown state! Current state " +
this._currentState);
return;
}
// Stop any pending reconnects scheduled for the near future.
this._stopAlarm();
let uri = this._getServerURI();
if (!uri) {
return;
}
let socket = this._makeWebSocket(uri);
if (!socket) {
return;
}
this._ws = socket.QueryInterface(Ci.nsIWebSocketChannel);
debug("serverURL: " + uri.spec);
this._wsListener = new PushWebSocketListener(this);
this._ws.protocol = "push-notification";
@ -920,7 +868,7 @@ this.PushService = {
try {
// Grab a wakelock before we open the socket to ensure we don't go to sleep
// before connection the is opened.
this._ws.asyncOpen(uri, uri.spec, this._wsListener, null);
this._ws.asyncOpen(uri, serverURL, this._wsListener, null);
this._acquireWakeLock();
this._currentState = STATE_WAITING_FOR_WS_START;
} catch(e) {
@ -932,10 +880,6 @@ this.PushService = {
_startListeningIfChannelsPresent: function() {
// Check to see if we need to do anything.
if (this._requestQueue.length > 0) {
this._beginWSSetup();
return;
}
this._db.getAllChannelIDs(function(channelIDs) {
if (channelIDs.length > 0) {
this._beginWSSetup();
@ -1055,8 +999,6 @@ this.PushService = {
},
_acquireWakeLock: function() {
#ifdef MOZ_B2G
// Disable the wake lock on non-B2G platforms to work around bug 1154492.
if (!this._socketWakeLock) {
debug("Acquiring Socket Wakelock");
this._socketWakeLock = gPowerManagerService.newWakeLock("cpu");
@ -1074,12 +1016,10 @@ this.PushService = {
// timers can be a little off and we don't want to go
// to sleep just as the socket connected.
this._requestTimeout + 1000,
Ci.nsITimer.TYPE_ONE_SHOT);
#endif
Ci.nsITimer.ONE_SHOT);
},
_releaseWakeLock: function() {
#ifdef MOZ_B2G
debug("Releasing Socket WakeLock");
if (this._socketWakeLockTimer) {
this._socketWakeLockTimer.cancel();
@ -1088,7 +1028,6 @@ this.PushService = {
this._socketWakeLock.unlock();
this._socketWakeLock = null;
}
#endif
},
/**
@ -1224,9 +1163,11 @@ this.PushService = {
debug("sendRequest() " + action);
if (typeof data.channelID !== "string") {
debug("Received non-string channelID");
return Promise.reject({error: "Received non-string channelID"});
return Promise.reject("Received non-string channelID");
}
let deferred = Promise.defer();
if (Object.keys(this._pendingRequests).length == 0) {
// start the timer since we now have at least one request
if (!this._requestTimeoutTimer)
@ -1237,18 +1178,8 @@ this.PushService = {
Ci.nsITimer.TYPE_REPEATING_SLACK);
}
let deferred;
let request = this._pendingRequests[data.channelID];
if (request) {
// If a request is already pending for this channel ID, assume it's a
// retry. Use the existing deferred, but update the send time and re-send
// the request.
deferred = request.deferred;
} else {
deferred = Promise.defer();
request = this._pendingRequests[data.channelID] = {deferred};
}
request.ctime = Date.now();
this._pendingRequests[data.channelID] = { deferred: deferred,
ctime: Date.now() };
this._send(action, data);
return deferred.promise;
@ -1270,11 +1201,6 @@ this.PushService = {
}
if (this._currentState != STATE_READY) {
if (!this._started) {
// The component hasn't been initialized yet. Return early; init()
// will dequeue all pending requests.
return;
}
if (!this._ws) {
// This will end up calling processNextRequestInQueue().
this._beginWSSetup();
@ -1343,49 +1269,52 @@ this.PushService = {
// registration.
_notifyAllAppsRegister: function() {
debug("notifyAllAppsRegister()");
return new Promise((resolve, reject) => {
// records are objects describing the registration as stored in IndexedDB.
this._db.getAllChannelIDs(records => {
let scopes = new Set();
for (let record of records) {
scopes.add(record.scope);
}
let globalMM = Cc['@mozilla.org/globalmessagemanager;1'].getService(Ci.nsIMessageListenerManager);
for (let scope of scopes) {
// Notify XPCOM observers.
Services.obs.notifyObservers(
null,
"push-subscription-change",
scope
);
// TODO -- test needed. E10s support needed.
globalMM.broadcastAsyncMessage('pushsubscriptionchanged', scope);
}
resolve();
}, reject);
});
let deferred = Promise.defer();
// records are objects describing the registration as stored in IndexedDB.
function wakeupRegisteredApps(records) {
// Pages to be notified.
// wakeupTable[scope] -> [ pageURL ]
let wakeupTable = {};
for (let i = 0; i < records.length; i++) {
let record = records[i];
if (!(record.scope in wakeupTable))
wakeupTable[record.scope] = [];
wakeupTable[record.scope].push(record.pageURL);
}
// TODO -- test needed. E10s support needed.
let globalMM = Cc['@mozilla.org/globalmessagemanager;1'].getService(Ci.nsIMessageListenerManager);
for (let scope in wakeupTable) {
wakeupTable[scope].forEach(function(pageURL) {
globalMM.broadcastAsyncMessage('pushsubscriptionchanged', aPushRecord.scope);
});
}
deferred.resolve();
}
this._db.getAllChannelIDs(wakeupRegisteredApps, deferred.reject);
return deferred.promise;
},
_notifyApp: function(aPushRecord) {
if (!aPushRecord || !aPushRecord.scope) {
if (!aPushRecord || !aPushRecord.pageURL || !aPushRecord.scope) {
debug("notifyApp() something is undefined. Dropping notification: "
+ JSON.stringify(aPushRecord) );
return;
}
debug("notifyApp() " + aPushRecord.scope);
debug("notifyApp() " + aPushRecord.pageURL +
" " + aPushRecord.scope);
let pageURI = Services.io.newURI(aPushRecord.pageURL, null, null);
let scopeURI = Services.io.newURI(aPushRecord.scope, null, null);
// Notify XPCOM observers.
let notification = Cc["@mozilla.org/push/ObserverNotification;1"]
.createInstance(Ci.nsIPushObserverNotification);
notification.pushEndpoint = aPushRecord.pushEndpoint;
notification.version = aPushRecord.version;
notification.data = "";
Services.obs.notifyObservers(
notification,
"push-notification",
aPushRecord.scope
);
let message = {
pushEndpoint: aPushRecord.pushEndpoint,
version: aPushRecord.version
};
// If permission has been revoked, trash the message.
if(Services.perms.testExactPermission(scopeURI, "push") != Ci.nsIPermissionManager.ALLOW_ACTION) {
@ -1434,38 +1363,24 @@ this.PushService = {
* Called on message from the child process. aPageRecord is an object sent by
* navigator.push, identifying the sending page and other fields.
*/
_registerWithServer: function(channelID, aPageRecord) {
debug("registerWithServer()");
return this._sendRequest("register", {channelID: channelID})
.then(
this._onRegisterSuccess.bind(this, aPageRecord, channelID),
this._onRegisterError.bind(this)
);
},
_generateID: function() {
_registerWithServer: function(aPageRecord, aMessageManager) {
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
// generateUUID() gives a UUID surrounded by {...}, slice them off.
return uuidGenerator.generateUUID().toString().slice(1, -1);
},
let channelID = uuidGenerator.generateUUID().toString().slice(1, -1);
_register: function(aPageRecord) {
let recordPromise = new Promise((resolve, reject) =>
this._db.getByScope(aPageRecord.scope, resolve, reject));
return recordPromise.then(
pushRecord => {
if (pushRecord == null) {
let channelID = this._generateID();
return this._registerWithServer(channelID, aPageRecord);
}
return pushRecord;
},
error => {
debug("getByScope failed");
throw "Database error";
this._sendRequest("register", {channelID: channelID})
.then(
this._onRegisterSuccess.bind(this, aPageRecord, channelID),
this._onRegisterError.bind(this, aPageRecord, aMessageManager)
)
.then(
function(message) {
aMessageManager.sendAsyncMessage("PushService:Register:OK", message);
},
function(message) {
aMessageManager.sendAsyncMessage("PushService:Register:KO", message);
}
);
},
@ -1473,20 +1388,17 @@ this.PushService = {
register: function(aPageRecord, aMessageManager) {
debug("register(): " + JSON.stringify(aPageRecord));
this._register(aPageRecord).then(
this._db.getByScope(aPageRecord.scope,
function(aPageRecord, aMessageManager, pushRecord) {
let message = {
requestID: aPageRecord.requestID,
pushEndpoint: pushRecord.pushEndpoint
};
aMessageManager.sendAsyncMessage("PushService:Register:OK", message);
if (pushRecord == null) {
this._registerWithServer(aPageRecord, aMessageManager);
}
else {
this._onRegistrationSuccess(aPageRecord, aMessageManager, pushRecord);
}
}.bind(this, aPageRecord, aMessageManager),
function(error) {
let message = {
requestID: aPageRecord.requestID,
error
};
aMessageManager.sendAsyncMessage("PushService:Register:KO", message);
function () {
debug("getByScope failed");
}
);
},
@ -1497,15 +1409,19 @@ this.PushService = {
*/
_onRegisterSuccess: function(aPageRecord, generatedChannelID, data) {
debug("_onRegisterSuccess()");
let deferred = Promise.defer();
let message = { requestID: aPageRecord.requestID };
if (typeof data.channelID !== "string") {
debug("Invalid channelID " + data.channelID);
throw "Invalid channelID received";
debug("Invalid channelID " + message);
message["error"] = "Invalid channelID received";
throw message;
}
else if (data.channelID != generatedChannelID) {
debug("Server replied with different channelID " + data.channelID +
" than what UA generated " + generatedChannelID);
throw "Server sent 200 status code but different channelID";
message["error"] = "Server sent 200 status code but different channelID";
throw message;
}
try {
@ -1513,7 +1429,8 @@ this.PushService = {
}
catch (e) {
debug("Invalid pushEndpoint " + data.pushEndpoint);
throw "Invalid pushEndpoint " + data.pushEndpoint;
message["error"] = "Invalid pushEndpoint " + data.pushEndpoint;
throw message;
}
let record = {
@ -1526,30 +1443,33 @@ this.PushService = {
debug("scope in _onRegisterSuccess: " + aPageRecord.scope)
return this._updatePushRecord(record)
this._updatePushRecord(record)
.then(
function() {
return record;
message["pushEndpoint"] = data.pushEndpoint;
deferred.resolve(message);
},
function(error) {
// Unable to save.
this._send("unregister", {channelID: record.channelID});
throw error;
message["error"] = error;
deferred.reject(message);
}.bind(this)
);
return deferred.promise;
},
/**
* Exceptions thrown in _onRegisterError are caught by the promise obtained
* from _sendRequest, causing the promise to be rejected instead.
*/
_onRegisterError: function(reply) {
_onRegisterError: function(aPageRecord, aMessageManager, reply) {
debug("_onRegisterError()");
if (!reply.error) {
debug("Called without valid error message!");
throw "Registration error";
}
throw reply.error;
throw { requestID: aPageRecord.requestID, error: reply.error };
},
/**
@ -1576,99 +1496,78 @@ this.PushService = {
* messages from the server, and have the client acknowledge. On a server,
* data is cheap, reliable notification is not.
*/
_unregister: function(aPageRecord) {
debug("unregisterWithServer()");
unregister: function(aPageRecord, aMessageManager) {
debug("unregister() " + JSON.stringify(aPageRecord));
let deferred = Promise.defer();
let fail = function(error) {
debug("unregister() fail() error " + error);
deferred.reject(error);
};
if (!aPageRecord.scope) {
fail("NotFoundError");
return deferred.promise;
let message = {requestID: aPageRecord.requestID, error: error};
aMessageManager.sendAsyncMessage("PushService:Unregister:KO", message);
}
this._db.getByScope(aPageRecord.scope, function(record) {
this._db.getByPushEndpoint(aPageRecord.pushEndpoint, function(record) {
// If the endpoint didn't exist, let's just fail.
if (record === undefined) {
fail("NotFoundError");
return;
}
// Non-owner tried to unregister, say success, but don't do anything.
if (record.scope !== aPageRecord.scope) {
aMessageManager.sendAsyncMessage("PushService:Unregister:OK", {
requestID: aPageRecord.requestID,
pushEndpoint: aPageRecord.pushEndpoint
});
return;
}
this._db.delete(record.channelID, function() {
// Let's be nice to the server and try to inform it, but we don't care
// about the reply.
this._send("unregister", {channelID: record.channelID});
deferred.resolve();
}.bind(this), fail);
}.bind(this), fail);
return deferred.promise;
},
unregister: function(aPageRecord, aMessageManager) {
debug("unregister() " + JSON.stringify(aPageRecord));
this._unregister(aPageRecord).then(
() => {
aMessageManager.sendAsyncMessage("PushService:Unregister:OK", {
requestID: aPageRecord.requestID,
pushEndpoint: aPageRecord.pushEndpoint
});
},
error => {
aMessageManager.sendAsyncMessage("PushService:Unregister:KO", {
requestID: aPageRecord.requestID,
error
});
}
);
}.bind(this), fail);
}.bind(this), fail);
},
/**
* Called on message from the child process
*/
_registration: function(aPageRecord) {
return new Promise((resolve, reject) => {
if (!aPageRecord.scope) {
reject("Database error");
return;
}
this._db.getByScope(aPageRecord.scope,
pushRecord => {
let registration = null;
if (pushRecord) {
registration = {
pushEndpoint: pushRecord.pushEndpoint,
version: pushRecord.version
};
}
resolve(registration);
},
() => reject("Database error")
);
registration: function(aPageRecord, aMessageManager) {
debug("registration()");
this._db.getByScope(aPageRecord.scope,
this._onRegistrationSuccess.bind(this, aPageRecord, aMessageManager),
this._onRegistrationError.bind(this, aPageRecord, aMessageManager));
},
_onRegistrationSuccess: function(aPageRecord,
aMessageManager,
pushRecord) {
let registration = null;
if (pushRecord) {
registration = {
pushEndpoint: pushRecord.pushEndpoint,
version: pushRecord.version
};
}
aMessageManager.sendAsyncMessage("PushService:Registration:OK", {
requestID: aPageRecord.requestID,
registration: registration
});
},
registration: function(aPageRecord, aMessageManager) {
debug("registration()");
return this._registration(aPageRecord).then(
registration => {
aMessageManager.sendAsyncMessage("PushService:Registration:OK", {
requestID: aPageRecord.requestID,
registration
});
},
error => {
aMessageManager.sendAsyncMessage("PushService:Registration:KO", {
requestID: aPageRecord.requestID,
error
});
}
);
_onRegistrationError: function(aPageRecord, aMessageManager) {
aMessageManager.sendAsyncMessage("PushService:Registration:KO", {
requestID: aPageRecord.requestID,
error: "Database error"
});
},
// begin Push protocol handshake
@ -1700,7 +1599,7 @@ this.PushService = {
this._currentState = STATE_WAITING_FOR_HELLO;
}
this._networkInfo.getNetworkState((networkState) => {
this._getNetworkState((networkState) => {
if (networkState.ip) {
// Opening an available UDP port.
this._listenForUDPWakeup();
@ -1831,22 +1730,6 @@ this.PushService = {
}
},
/**
* Rejects all pending requests with errors.
*/
_cancelPendingRequests: function() {
for (let channelID in this._pendingRequests) {
let request = this._pendingRequests[channelID];
delete this._pendingRequests[channelID];
request.deferred.reject({status: 0, error: "AbortError"});
}
},
_makeUDPSocket: function() {
return Cc["@mozilla.org/network/udp-socket;1"]
.createInstance(Ci.nsIUDPSocket);
},
/**
* This method should be called only if the device is on a mobile network!
*/
@ -1863,12 +1746,9 @@ this.PushService = {
return;
}
let socket = this._makeUDPSocket();
if (!socket) {
return;
}
this._udpServer = socket.QueryInterface(Ci.nsIUDPSocket);
this._udpServer.init(-1, false, Services.scriptSecurityManager.getSystemPrincipal());
this._udpServer = Cc["@mozilla.org/network/udp-socket;1"]
.createInstance(Ci.nsIUDPSocket);
this._udpServer.init(-1, false);
this._udpServer.asyncListen(this);
debug("listenForUDPWakeup listening on " + this._udpServer.port);
@ -1894,14 +1774,12 @@ this.PushService = {
debug("UDP Server socket was shutdown. Status: " + aStatus);
this._udpServer = undefined;
this._beginWSSetup();
}
};
},
let PushNetworkInfo = {
/**
* Returns information about MCC-MNC and the IP of the current connection.
*/
getNetworkInformation: function() {
_getNetworkInformation: function() {
debug("getNetworkInformation()");
try {
@ -1952,14 +1830,14 @@ let PushNetworkInfo = {
* woken up by UDP (which currently just means having an mcc and mnc along
* with an IP, and optionally a netid).
*/
getNetworkState: function(callback) {
_getNetworkState: function(callback) {
debug("getNetworkState()");
if (typeof callback !== 'function') {
throw new Error("No callback method. Aborting push agent !");
}
var networkInfo = this.getNetworkInformation();
var networkInfo = this._getNetworkInformation();
if (networkInfo.ip) {
this._getMobileNetworkId(networkInfo, function(netid) {
@ -1977,7 +1855,7 @@ let PushNetworkInfo = {
},
// utility function used to add/remove observers in init() and shutdown()
getNetworkStateChangeEventName: function() {
_getNetworkStateChangeEventName: function() {
try {
Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
return "network-active-changed";
@ -2026,4 +1904,4 @@ let PushNetworkInfo = {
".mcc" + ("00" + networkInfo.mcc).slice(-3) + ".3gppnetwork.org";
queryDNSForDomain(netidAddress, callback);
}
};
}

View File

@ -0,0 +1,50 @@
/* 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/. */
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
function PushServiceLauncher() {
};
PushServiceLauncher.prototype = {
classID: Components.ID("{4b8caa3b-3c58-4f3c-a7f5-7bd9cb24c11d}"),
contractID: "@mozilla.org/push/ServiceLauncher;1",
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
Ci.nsISupportsWeakReference]),
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":
Services.obs.addObserver(this, "final-ui-startup", true);
break;
case "final-ui-startup":
Services.obs.removeObserver(this, "final-ui-startup");
if (!Services.prefs.getBoolPref("dom.push.enabled")) {
return;
}
let isParent = Cc["@mozilla.org/xre/runtime;1"]
.getService(Ci.nsIXULRuntime)
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
if (isParent) {
Cu.import("resource://gre/modules/PushService.jsm");
PushService.init();
}
break;
}
}
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PushServiceLauncher]);

View File

@ -6,17 +6,13 @@
EXTRA_COMPONENTS += [
'Push.js',
'Push.manifest',
'PushNotificationService.js',
'PushServiceLauncher.js',
]
EXTRA_PP_JS_MODULES += [
EXTRA_JS_MODULES += [
'PushService.jsm',
]
MOCHITEST_MANIFESTS += [
'test/mochitest.ini',
]
XPCSHELL_TESTS_MANIFESTS += [
'test/xpcshell/xpcshell.ini',
]

View File

@ -1,450 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');
Cu.import('resource://gre/modules/Timer.jsm');
Cu.import('resource://gre/modules/Promise.jsm');
Cu.import('resource://gre/modules/Preferences.jsm');
const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
const servicePrefs = new Preferences('dom.push.');
XPCOMUtils.defineLazyServiceGetter(
this,
"PushNotificationService",
"@mozilla.org/push/NotificationService;1",
"nsIPushNotificationService"
);
const DEFAULT_TIMEOUT = 5000;
const WEBSOCKET_CLOSE_GOING_AWAY = 1001;
// Stop and clean up after the PushService.
Services.obs.addObserver(function observe(subject, topic, data) {
Services.obs.removeObserver(observe, topic, false);
serviceExports.PushService.uninit();
// Occasionally, `profile-change-teardown` and `xpcom-shutdown` will fire
// before the PushService and AlarmService finish writing to IndexedDB. This
// causes spurious errors and crashes, so we spin the event loop to let the
// writes finish.
let done = false;
setTimeout(() => done = true, 1000);
let thread = Services.tm.mainThread;
while (!done) {
try {
thread.processNextEvent(true);
} catch (e) {
Cu.reportError(e);
}
}
}, 'profile-change-net-teardown', false);
/**
* Gates a function so that it is called only after the wrapper is called a
* given number of times.
*
* @param {Number} times The number of wrapper calls before |func| is called.
* @param {Function} func The function to gate.
* @returns {Function} The gated function wrapper.
*/
function after(times, func) {
return function afterFunc() {
if (--times <= 0) {
return func.apply(this, arguments);
}
};
}
/**
* Wraps a Push database in a proxy that returns promises for all asynchronous
* methods. This makes it easier to test the database code with Task.jsm.
*
* @param {PushDB} db A Push database.
* @returns {Proxy} A proxy that traps function property gets and returns
* promisified functions.
*/
function promisifyDatabase(db) {
return new Proxy(db, {
get(target, property) {
let method = target[property];
if (typeof method != 'function') {
return method;
}
return function(...params) {
return new Promise((resolve, reject) => {
method.call(target, ...params, resolve, reject);
});
};
}
});
}
/**
* Clears and closes an open Push database.
*
* @param {PushDB} db A Push database.
* @returns {Promise} A promise that fulfills when the database is closed.
*/
function cleanupDatabase(db) {
return new Promise(resolve => {
function close() {
db.close();
resolve();
}
db.drop(close, close);
});
}
/**
* Defers one or more callbacks until the next turn of the event loop. Multiple
* callbacks are executed in order.
*
* @param {Function[]} callbacks The callbacks to execute. One callback will be
* executed per tick.
*/
function waterfall(...callbacks) {
callbacks.reduce((promise, callback) => promise.then(() => {
callback();
}), Promise.resolve()).catch(Cu.reportError);
}
/**
* Waits for an observer notification to fire.
*
* @param {String} topic The notification topic.
* @returns {Promise} A promise that fulfills when the notification is fired.
*/
function promiseObserverNotification(topic, matchFunc) {
return new Promise((resolve, reject) => {
Services.obs.addObserver(function observe(subject, topic, data) {
let matches = typeof matchFunc != 'function' || matchFunc(subject, data);
if (!matches) {
return;
}
Services.obs.removeObserver(observe, topic, false);
resolve({subject, data});
}, topic, false);
});
}
/**
* Waits for a promise to settle. Returns a rejected promise if the promise
* is not resolved or rejected within the given delay.
*
* @param {Promise} promise The pending promise.
* @param {Number} delay The time to wait before rejecting the promise.
* @param {String} [message] The rejection message if the promise times out.
* @returns {Promise} A promise that settles with the value of the pending
* promise, or rejects if the pending promise times out.
*/
function waitForPromise(promise, delay, message = 'Timed out waiting on promise') {
let timeoutDefer = Promise.defer();
let id = setTimeout(() => timeoutDefer.reject(new Error(message)), delay);
return Promise.race([
promise.then(value => {
clearTimeout(id);
return value;
}, error => {
clearTimeout(id);
throw error;
}),
timeoutDefer.promise
]);
}
/**
* Wraps an object in a proxy that traps property gets and returns stubs. If
* the stub is a function, the original value will be passed as the first
* argument. If the original value is a function, the proxy returns a wrapper
* that calls the stub; otherwise, the stub is called as a getter.
*
* @param {Object} target The object to wrap.
* @param {Object} stubs An object containing stubbed values and functions.
* @returns {Proxy} A proxy that returns stubs for property gets.
*/
function makeStub(target, stubs) {
return new Proxy(target, {
get(target, property) {
if (!stubs || typeof stubs != 'object' || !(property in stubs)) {
return target[property];
}
let stub = stubs[property];
if (typeof stub != 'function') {
return stub;
}
let original = target[property];
if (typeof original != 'function') {
return stub.call(this, original);
}
return function callStub(...params) {
return stub.call(this, original, ...params);
};
}
});
}
/**
* Disables `push` and `pushsubscriptionchange` service worker events for the
* given scopes. These events cause crashes in xpcshell, so we disable them
* for testing nsIPushNotificationService.
*
* @param {String[]} scopes A list of scope URLs.
*/
function disableServiceWorkerEvents(...scopes) {
for (let scope of scopes) {
Services.perms.add(
Services.io.newURI(scope, null, null),
'push',
Ci.nsIPermissionManager.DENY_ACTION
);
}
}
/**
* Sets default PushService preferences. All pref names are prefixed with
* `dom.push.`; any additional preferences will override the defaults.
*
* @param {Object} [prefs] Additional preferences to set.
*/
function setPrefs(prefs = {}) {
let defaultPrefs = Object.assign({
debug: true,
serverURL: 'wss://push.example.org',
'connection.enabled': true,
userAgentID: '',
enabled: true,
// Disable adaptive pings and UDP wake-up by default; these are
// tested separately.
'adaptive.enabled': false,
'udp.wakeupEnabled': false,
// Defaults taken from /b2g/app/b2g.js.
requestTimeout: 10000,
retryBaseInterval: 5000,
pingInterval: 30 * 60 * 1000,
'pingInterval.default': 3 * 60 * 1000,
'pingInterval.mobile': 3 * 60 * 1000,
'pingInterval.wifi': 3 * 60 * 1000,
'adaptive.lastGoodPingInterval': 3 * 60 * 1000,
'adaptive.lastGoodPingInterval.mobile': 3 * 60 * 1000,
'adaptive.lastGoodPingInterval.wifi': 3 * 60 * 1000,
'adaptive.gap': 60000,
'adaptive.upperLimit': 29 * 60 * 1000,
// Misc. defaults.
'adaptive.mobile': ''
}, prefs);
for (let pref in defaultPrefs) {
servicePrefs.set(pref, defaultPrefs[pref]);
}
}
function compareAscending(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
}
/**
* Creates a mock WebSocket object that implements a subset of the
* nsIWebSocketChannel interface used by the PushService.
*
* The given protocol handlers are invoked for each Simple Push command sent
* by the PushService. The ping handler is optional; all others will throw if
* the PushService sends a command for which no handler is registered.
*
* All nsIWebSocketListener methods will be called asynchronously.
* serverSendMsg() and serverClose() can be used to respond to client messages
* and close the "server" end of the connection, respectively.
*
* @param {nsIURI} originalURI The original WebSocket URL.
* @param {Function} options.onHello The "hello" handshake command handler.
* @param {Function} options.onRegister The "register" command handler.
* @param {Function} options.onUnregister The "unregister" command handler.
* @param {Function} options.onACK The "ack" command handler.
* @param {Function} [options.onPing] An optional ping handler.
*/
function MockWebSocket(originalURI, handlers = {}) {
this._originalURI = originalURI;
this._onHello = handlers.onHello;
this._onRegister = handlers.onRegister;
this._onUnregister = handlers.onUnregister;
this._onACK = handlers.onACK;
this._onPing = handlers.onPing;
}
MockWebSocket.prototype = {
_originalURI: null,
_onHello: null,
_onRegister: null,
_onUnregister: null,
_onACK: null,
_onPing: null,
_listener: null,
_context: null,
QueryInterface: XPCOMUtils.generateQI([
Ci.nsISupports,
Ci.nsIWebSocketChannel
]),
get originalURI() {
return this._originalURI;
},
asyncOpen(uri, origin, listener, context) {
this._listener = listener;
this._context = context;
waterfall(() => this._listener.onStart(this._context));
},
_handleMessage(msg) {
let messageType, request;
if (msg == '{}') {
request = {};
messageType = 'ping';
} else {
request = JSON.parse(msg);
messageType = request.messageType;
}
switch (messageType) {
case 'hello':
if (typeof this._onHello != 'function') {
throw new Error('Unexpected handshake request');
}
this._onHello(request);
break;
case 'register':
if (typeof this._onRegister != 'function') {
throw new Error('Unexpected register request');
}
this._onRegister(request);
break;
case 'unregister':
if (typeof this._onUnregister != 'function') {
throw new Error('Unexpected unregister request');
}
this._onUnregister(request);
break;
case 'ack':
if (typeof this._onACK != 'function') {
throw new Error('Unexpected acknowledgement');
}
this._onACK(request);
break;
case 'ping':
if (typeof this._onPing == 'function') {
this._onPing(request);
} else {
// Echo ping packets.
this.serverSendMsg('{}');
}
break;
default:
throw new Error('Unexpected message: ' + messageType);
}
},
sendMsg(msg) {
this._handleMessage(msg);
},
close(code, reason) {
waterfall(() => this._listener.onStop(this._context, Cr.NS_OK));
},
/**
* Responds with the given message, calling onMessageAvailable() and
* onAcknowledge() synchronously. Throws if the message is not a string.
* Used by the tests to respond to client commands.
*
* @param {String} msg The message to send to the client.
*/
serverSendMsg(msg) {
if (typeof msg != 'string') {
throw new Error('Invalid response message');
}
waterfall(
() => this._listener.onMessageAvailable(this._context, msg),
() => this._listener.onAcknowledge(this._context, 0)
);
},
/**
* Closes the server end of the connection, calling onServerClose()
* followed by onStop(). Used to test abrupt connection termination
* and UDP wake-up.
*
* @param {Number} [statusCode] The WebSocket connection close code.
* @param {String} [reason] The connection close reason.
*/
serverClose(statusCode, reason = '') {
if (!isFinite(statusCode)) {
statusCode = WEBSOCKET_CLOSE_GOING_AWAY;
}
waterfall(
() => this._listener.onServerClose(this._context, statusCode, reason),
() => this._listener.onStop(this._context, Cr.NS_BASE_STREAM_CLOSED)
);
}
};
/**
* Creates an object that exposes the same interface as NetworkInfo, used
* to simulate network status changes on Desktop. All methods returns empty
* carrier data.
*/
function MockDesktopNetworkInfo() {}
MockDesktopNetworkInfo.prototype = {
getNetworkInformation() {
return {mcc: '', mnc: '', ip: ''};
},
getNetworkState(callback) {
callback({mcc: '', mnc: '', ip: '', netid: ''});
},
getNetworkStateChangeEventName() {
return 'network:offline-status-changed';
}
};
/**
* Creates an object that exposes the same interface as NetworkInfo, used
* to simulate network status changes on B2G.
*
* @param {String} [info.mcc] The mobile country code.
* @param {String} [info.mnc] The mobile network code.
* @param {String} [info.ip] The carrier IP address.
* @param {String} [info.netid] The resolved network ID for UDP wake-up.
*/
function MockMobileNetworkInfo(info = {}) {
this._info = info;
}
MockMobileNetworkInfo.prototype = {
_info: null,
getNetworkInformation() {
let {mcc, mnc, ip} = this._info;
return {mcc, mnc, ip};
},
getNetworkState(callback) {
let {mcc, mnc, ip, netid} = this._info;
callback({mcc, mnc, ip, netid});
},
getNetworkStateChangeEventName() {
return 'network-active-changed';
}
};

View File

@ -1,127 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
let userAgentID = '5ab1d1df-7a3d-4024-a469-b9e1bb399fad';
function run_test() {
do_get_profile();
setPrefs({userAgentID});
disableServiceWorkerEvents(
'https://example.org/1',
'https://example.org/2',
'https://example.org/3'
);
run_next_test();
}
add_task(function* test_notification_ack() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
pushEndpoint: 'https://example.com/update/1',
scope: 'https://example.org/1',
version: 1
}, {
channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
pushEndpoint: 'https://example.com/update/2',
scope: 'https://example.org/2',
version: 2
}, {
channelID: '5477bfda-22db-45d4-9614-fee369630260',
pushEndpoint: 'https://example.com/update/3',
scope: 'https://example.org/3',
version: 3
}];
for (let record of records) {
yield promiseDB.put(record);
}
let notifyPromise = Promise.all([
promiseObserverNotification('push-notification'),
promiseObserverNotification('push-notification'),
promiseObserverNotification('push-notification')
]);
let acks = 0;
let ackDefer = Promise.defer();
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
equal(request.uaid, userAgentID,
'Should send matching device IDs in handshake');
deepEqual(request.channelIDs.sort(), [
'21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
'5477bfda-22db-45d4-9614-fee369630260',
'9a5ff87f-47c9-4215-b2b8-0bdd38b4b305'
], 'Should send matching channel IDs in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
uaid: userAgentID,
status: 200
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
version: 2
}]
}));
},
onACK(request) {
equal(request.messageType, 'ack', 'Should send acknowledgements');
let updates = request.updates;
ok(Array.isArray(updates),
'Should send an array of acknowledged updates');
equal(updates.length, 1,
'Should send one acknowledged update per packet');
switch (++acks) {
case 1:
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
version: 4
}, {
channelID: '5477bfda-22db-45d4-9614-fee369630260',
version: 6
}]
}));
break;
case 2:
deepEqual([{
channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
version: 4
}], updates, 'Wrong updates for acknowledgement 2');
break;
case 3:
deepEqual([{
channelID: '5477bfda-22db-45d4-9614-fee369630260',
version: 6
}], updates, 'Wrong updates for acknowledgement 3');
ackDefer.resolve();
break;
default:
ok(false, 'Unexpected acknowledgement ' + acks);
}
}
});
}
});
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for multiple acknowledgements');
});

View File

@ -1,82 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.com/1',
'https://example.com/2'
);
run_next_test();
}
// Should acknowledge duplicate notifications, but not notify apps.
add_task(function* test_notification_duplicate() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc',
pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.com/1',
version: 2
}, {
channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0',
pushEndpoint: 'https://example.org/update/2',
scope: 'https://example.com/2',
version: 2
}];
for (let record of records) {
yield promiseDB.put(record);
}
let notifyPromise = promiseObserverNotification('push-notification');
let acks = 0;
let ackDefer = Promise.defer();
let ackDone = after(2, ackDefer.resolve);
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '1500e7d9-8cbe-4ee6-98da-7fa5d6a39852'
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc',
version: 2
}, {
channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0',
version: 3
}]
}));
},
onACK: ackDone
});
}
});
yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notifications');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for stale acknowledgement');
let staleRecord = yield promiseDB.getByChannelID(
'8d2d9400-3597-4c5a-8a38-c546b0043bcc');
strictEqual(staleRecord.version, 2, 'Wrong stale record version');
let updatedRecord = yield promiseDB.getByChannelID(
'27d1e393-03ef-4c72-a5e6-9e890dfccad0');
strictEqual(updatedRecord.version, 3, 'Wrong updated record version');
});

View File

@ -1,127 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.com/a',
'https://example.com/b',
'https://example.com/c'
);
run_next_test();
}
add_task(function* test_notification_error() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: 'f04f1e46-9139-4826-b2d1-9411b0821283',
pushEndpoint: 'https://example.org/update/success-1',
scope: 'https://example.com/a',
version: 1
}, {
channelID: '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
pushEndpoint: 'https://example.org/update/error',
scope: 'https://example.com/b',
version: 2
}, {
channelID: 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
pushEndpoint: 'https://example.org/update/success-2',
scope: 'https://example.com/c',
version: 3
}];
for (let record of records) {
yield promiseDB.put(record);
}
let notifyPromise = Promise.all([
promiseObserverNotification(
'push-notification',
(subject, data) => data == 'https://example.com/a'
),
promiseObserverNotification(
'push-notification',
(subject, data) => data == 'https://example.com/c'
)
]);
let ackDefer = Promise.defer();
let ackDone = after(records.length, ackDefer.resolve);
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db: makeStub(db, {
getByChannelID(prev, channelID, successCb, failureCb) {
if (channelID == '3c3930ba-44de-40dc-a7ca-8a133ec1a866') {
return failureCb('splines not reticulated');
}
return prev.call(this, channelID, successCb, failureCb);
}
}),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
deepEqual(request.channelIDs.sort(), [
'3c3930ba-44de-40dc-a7ca-8a133ec1a866',
'b63f7bef-0a0d-4236-b41e-086a69dfd316',
'f04f1e46-9139-4826-b2d1-9411b0821283'
], 'Wrong channel list');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '3c7462fc-270f-45be-a459-b9d631b0d093'
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: records.map(({channelID, version}) =>
({channelID, version: ++version}))
}));
},
// Should acknowledge all received updates, even if updating
// IndexedDB fails.
onACK: ackDone
});
}
});
let [a, c] = yield waitForPromise(
notifyPromise,
DEFAULT_TIMEOUT,
'Timed out waiting for notifications'
);
let aPush = a.subject.QueryInterface(Ci.nsIPushObserverNotification);
equal(aPush.pushEndpoint, 'https://example.org/update/success-1',
'Wrong endpoint for notification A');
equal(aPush.version, 2, 'Wrong version for notification A');
let cPush = c.subject.QueryInterface(Ci.nsIPushObserverNotification);
equal(cPush.pushEndpoint, 'https://example.org/update/success-2',
'Wrong endpoint for notification C');
equal(cPush.version, 4, 'Wrong version for notification C');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for acknowledgements');
let aRecord = yield promiseDB.getByScope('https://example.com/a');
equal(aRecord.channelID, 'f04f1e46-9139-4826-b2d1-9411b0821283',
'Wrong channel ID for record A');
strictEqual(aRecord.version, 2,
'Should return the new version for record A');
let bRecord = yield promiseDB.getByScope('https://example.com/b');
equal(bRecord.channelID, '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
'Wrong channel ID for record B');
strictEqual(bRecord.version, 2,
'Should return the previous version for record B');
let cRecord = yield promiseDB.getByScope('https://example.com/c');
equal(cRecord.channelID, 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
'Wrong channel ID for record C');
strictEqual(cRecord.version, 4,
'Should return the new version for record C');
});

View File

@ -1,109 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.com/page/1',
'https://example.com/page/2',
'https://example.com/page/3',
'https://example.com/page/4'
);
run_next_test();
}
add_task(function* test_notification_incomplete() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: '123',
pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.com/page/1',
version: 1
}, {
channelID: '3ad1ed95-d37a-4d88-950f-22cbe2e240d7',
pushEndpoint: 'https://example.org/update/2',
scope: 'https://example.com/page/2',
version: 1
}, {
channelID: 'd239498b-1c85-4486-b99b-205866e82d1f',
pushEndpoint: 'https://example.org/update/3',
scope: 'https://example.com/page/3',
version: 3
}, {
channelID: 'a50de97d-b496-43ce-8b53-05522feb78db',
pushEndpoint: 'https://example.org/update/4',
scope: 'https://example.com/page/4',
version: 10
}];
for (let record of records) {
promiseDB.put(record);
}
Services.obs.addObserver(function observe(subject, topic, data) {
ok(false, 'Should not deliver malformed updates');
}, 'push-notification', false);
let notificationDefer = Promise.defer();
let notificationDone = after(2, notificationDefer.resolve);
let prevHandler = PushService._handleNotificationReply;
PushService._handleNotificationReply = function _handleNotificationReply() {
notificationDone();
return prevHandler.apply(this, arguments);
};
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90'
}));
this.serverSendMsg(JSON.stringify({
// Missing "updates" field; should ignore message.
messageType: 'notification'
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
// Wrong channel ID field type.
channelID: 123,
version: 3
}, {
// Missing version field.
channelID: '3ad1ed95-d37a-4d88-950f-22cbe2e240d7'
}, {
// Wrong version field type.
channelID: 'd239498b-1c85-4486-b99b-205866e82d1f',
version: true
}, {
// Negative versions should be ignored.
channelID: 'a50de97d-b496-43ce-8b53-05522feb78db',
version: -5
}]
}));
},
onACK() {
ok(false, 'Should not acknowledge malformed updates');
}
});
}
});
yield waitForPromise(notificationDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for incomplete notifications');
let storeRecords = yield promiseDB.getAllChannelIDs();
storeRecords.sort(({pushEndpoint: a}, {pushEndpoint: b}) =>
compareAscending(a, b));
deepEqual(records, storeRecords, 'Should not update malformed records');
});

View File

@ -1,72 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.net/case'
);
run_next_test();
}
add_task(function* test_notification_version_string() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
yield promiseDB.put({
channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b',
pushEndpoint: 'https://example.org/updates/1',
scope: 'https://example.com/page/1',
version: 2
});
let notifyPromise = promiseObserverNotification('push-notification');
let ackDefer = Promise.defer();
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: 'ba31ac13-88d4-4984-8e6b-8731315a7cf8'
}));
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b',
version: '4'
}]
}));
},
onACK: ackDefer.resolve
});
}
});
let {subject: notification, data: scope} = yield waitForPromise(
notifyPromise,
DEFAULT_TIMEOUT,
'Timed out waiting for string notification'
);
let message = notification.QueryInterface(Ci.nsIPushObserverNotification);
equal(scope, 'https://example.com/page/1', 'Wrong scope');
equal(message.pushEndpoint, 'https://example.org/updates/1',
'Wrong push endpoint');
strictEqual(message.version, 4, 'Wrong version');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for string acknowledgement');
let storeRecord = yield promiseDB.getByChannelID(
'6ff97d56-d0c0-43bc-8f5b-61b855e1d93b');
strictEqual(storeRecord.version, 4, 'Wrong record version');
});

View File

@ -1,64 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '1760b1f5-c3ba-40e3-9344-adef7c18ab12';
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.net/case'
);
run_next_test();
}
add_task(function* test_register_case() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'HELLO',
uaid: userAgentID,
status: 200
}));
},
onRegister(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'ReGiStEr',
uaid: userAgentID,
channelID: request.channelID,
status: 200,
pushEndpoint: 'https://example.com/update/case'
}));
}
});
}
});
let newRecord = yield waitForPromise(
PushNotificationService.register('https://example.net/case'),
DEFAULT_TIMEOUT,
'Mixed-case register response timed out'
);
equal(newRecord.pushEndpoint, 'https://example.com/update/case',
'Wrong push endpoint in registration record');
equal(newRecord.scope, 'https://example.net/case',
'Wrong scope in registration record');
let record = yield promiseDB.getByChannelID(newRecord.channelID);
equal(record.pushEndpoint, 'https://example.com/update/case',
'Wrong push endpoint in database record');
equal(record.scope, 'https://example.net/case',
'Wrong scope in database record');
});

View File

@ -1,103 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '9ce1e6d3-7bdb-4fe9-90a5-def1d64716f1';
const channelID = 'c26892c5-6e08-4c16-9f0c-0044697b4d85';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/page/1',
'https://example.com/page/2'
);
run_next_test();
}
add_task(function* test_register_flush() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let record = {
channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74',
pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.com/page/1',
version: 2
};
yield promiseDB.put(record);
let notifyPromise = promiseObserverNotification('push-notification');
let ackDefer = Promise.defer();
let ackDone = after(2, ackDefer.resolve);
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onRegister(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'notification',
updates: [{
channelID: request.channelID,
version: 2
}, {
channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74',
version: 3
}]
}));
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: request.channelID,
uaid: userAgentID,
pushEndpoint: 'https://example.org/update/2'
}));
},
onACK: ackDone
});
}
});
let newRecord = yield PushNotificationService.register(
'https://example.com/page/2'
);
equal(newRecord.pushEndpoint, 'https://example.org/update/2',
'Wrong push endpoint in record');
equal(newRecord.scope, 'https://example.com/page/2',
'Wrong scope in record');
let {data: scope} = yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
'Timed out waiting for notification');
equal(scope, 'https://example.com/page/1', 'Wrong notification scope');
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for acknowledgements');
let prevRecord = yield promiseDB.getByChannelID(
'9bcc7efb-86c7-4457-93ea-e24e6eb59b74');
equal(prevRecord.pushEndpoint, 'https://example.org/update/1',
'Wrong existing push endpoint');
strictEqual(prevRecord.version, 3,
'Should record version updates sent before register responses');
let registeredRecord = yield promiseDB.getByChannelID(newRecord.channelID);
equal(registeredRecord.pushEndpoint, 'https://example.org/update/2',
'Wrong new push endpoint');
ok(!registeredRecord.version, 'Should not record premature updates');
});

View File

@ -1,60 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '52b2b04c-b6cc-42c6-abdf-bef9cbdbea00';
const channelID = 'cafed00d';
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.com/invalid-channel'
);
run_next_test();
}
add_task(function* test_register_invalid_channel() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
uaid: userAgentID,
status: 200
}));
},
onRegister(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 403,
channelID,
error: 'Invalid channel ID'
}));
}
});
}
});
yield rejects(
PushNotificationService.register('https://example.com/invalid-channel'),
function(error) {
return error == 'Invalid channel ID';
},
'Wrong error for invalid channel ID'
);
let record = yield promiseDB.getByChannelID(channelID);
ok(!record, 'Should not store records for error responses');
});

View File

@ -1,62 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'c9a12e81-ea5e-40f9-8bf4-acee34621671';
const channelID = 'c0660af8-b532-4931-81f0-9fd27a12d6ab';
function run_test() {
do_get_profile();
setPrefs();
disableServiceWorkerEvents(
'https://example.net/page/invalid-endpoint'
);
run_next_test();
}
add_task(function* test_register_invalid_endpoint() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
},
onRegister(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID,
uaid: userAgentID,
pushEndpoint: '!@#$%^&*'
}));
}
});
}
});
yield rejects(
PushNotificationService.register(
'https://example.net/page/invalid-endpoint'),
function(error) {
return error && error.contains('Invalid pushEndpoint');
},
'Wrong error for invalid endpoint'
);
let record = yield promiseDB.getByChannelID(channelID);
ok(!record, 'Should not store records with invalid endpoints');
});

View File

@ -1,61 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '8271186b-8073-43a3-adf6-225bd44a8b0a';
const channelID = '2d08571e-feab-48a0-9f05-8254c3c7e61f';
function run_test() {
do_get_profile();
setPrefs({
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.net/page/invalid-json'
);
run_next_test();
}
add_task(function* test_register_invalid_json() {
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
let registers = 0;
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
helloDone();
},
onRegister(request) {
equal(request.channelID, channelID, 'Register: wrong channel ID');
this.serverSendMsg(');alert(1);(');
registers++;
}
});
}
});
yield rejects(
PushNotificationService.register('https://example.net/page/invalid-json'),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for invalid JSON response'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
'Reconnect after invalid JSON response timed out');
equal(registers, 1, 'Wrong register count');
});

View File

@ -1,65 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
let userAgentID = '9a2f9efe-2ebb-4bcb-a5d9-9e2b73d30afe';
let channelID = '264c2ba0-f6db-4e84-acdb-bd225b62d9e3';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/incomplete'
);
run_next_test();
}
add_task(function* test_register_no_id() {
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
helloDone();
},
onRegister(request) {
registers++;
equal(request.channelID, channelID, 'Register: wrong channel ID');
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200
}));
}
});
}
});
yield rejects(
PushNotificationService.register('https://example.com/incomplete'),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for incomplete register response'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
'Reconnect after incomplete register response timed out');
equal(registers, 1, 'Wrong register count');
});

View File

@ -1,65 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs({
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/page/1'
);
run_next_test();
}
add_task(function* test_register_request_queue() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let helloDefer = Promise.defer();
let onHello = after(2, function onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '54b08a9e-59c6-4ed7-bb54-f4fd60d6f606'
}));
helloDefer.resolve();
});
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello,
onRegister() {
ok(false, 'Should cancel timed-out requests');
}
});
}
});
let firstRegister = PushNotificationService.register(
'https://example.com/page/1'
);
let secondRegister = PushNotificationService.register(
'https://example.com/page/1'
);
yield waitForPromise(Promise.all([
rejects(firstRegister, function(error) {
return error == 'TimeoutError';
}, 'Should time out the first request'),
rejects(secondRegister, function(error) {
return error == 'TimeoutError';
}, 'Should time out the second request')
]), DEFAULT_TIMEOUT, 'Queued requests did not time out');
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for reconnect');
});

View File

@ -1,88 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'b2546987-4f63-49b1-99f7-739cd3c40e44';
const channelID = '35a820f7-d7dd-43b3-af21-d65352212ae3';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/storage-error'
);
run_next_test();
}
add_task(function* test_register_rollback() {
let db = new PushDB();
do_register_cleanup(() => cleanupDatabase(db));
let handshakes = 0;
let registers = 0;
let unregisterDefer = Promise.defer();
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db: makeStub(db, {
put(prev, record, successCb, failureCb) {
failureCb('universe has imploded');
}
}),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
handshakes++;
equal(request.uaid, userAgentID, 'Handshake: wrong device ID');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onRegister(request) {
equal(request.channelID, channelID, 'Register: wrong channel ID');
registers++;
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
uaid: userAgentID,
channelID,
pushEndpoint: 'https://example.com/update/rollback'
}));
},
onUnregister(request) {
equal(request.channelID, channelID, 'Unregister: wrong channel ID');
this.serverSendMsg(JSON.stringify({
messageType: 'unregister',
status: 200,
channelID
}));
unregisterDefer.resolve();
}
});
}
});
// Should return a rejected promise if storage fails.
yield rejects(
PushNotificationService.register('https://example.com/storage-error'),
function(error) {
return error == 'universe has imploded';
},
'Wrong error for unregister database failure'
);
// Should send an out-of-band unregister request.
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
'Unregister request timed out');
equal(handshakes, 1, 'Wrong handshake count');
equal(registers, 1, 'Wrong register count');
});

View File

@ -1,76 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
const channelID = '0ef2ad4a-6c49-41ad-af6e-95d2425276bf';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.org/1'
);
run_next_test();
}
add_task(function* test_register_success() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(data) {
equal(data.messageType, 'hello', 'Handshake: wrong message type');
equal(data.uaid, userAgentID, 'Handshake: wrong device ID');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onRegister(data) {
equal(data.messageType, 'register', 'Register: wrong message type');
equal(data.channelID, channelID, 'Register: wrong channel ID');
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: channelID,
uaid: userAgentID,
pushEndpoint: 'https://example.com/update/1',
}));
}
});
}
});
let newRecord = yield PushNotificationService.register(
'https://example.org/1'
);
equal(newRecord.channelID, channelID,
'Wrong channel ID in registration record');
equal(newRecord.pushEndpoint, 'https://example.com/update/1',
'Wrong push endpoint in registration record');
equal(newRecord.scope, 'https://example.org/1',
'Wrong scope in registration record');
let record = yield promiseDB.getByChannelID(channelID);
equal(record.channelID, channelID,
'Wrong channel ID in database record');
equal(record.pushEndpoint, 'https://example.com/update/1',
'Wrong push endpoint in database record');
equal(record.scope, 'https://example.org/1',
'Wrong scope in database record');
});

View File

@ -1,102 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'a4be0df9-b16d-4b5f-8f58-0f93b6f1e23d';
const channelID = 'e1944e0b-48df-45e7-bdc0-d1fbaa7986d3';
function run_test() {
do_get_profile();
setPrefs({
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.net/page/timeout'
);
run_next_test();
}
add_task(function* test_register_timeout() {
let handshakes = 0;
let timeoutDefer = Promise.defer();
let registers = 0;
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
PushService._generateID = () => channelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
switch (handshakes) {
case 0:
equal(request.uaid, null, 'Should not include device ID');
deepEqual(request.channelIDs, [],
'Should include empty channel list');
break;
case 1:
// Should use the previously-issued device ID when reconnecting,
// but should not include the timed-out channel ID.
equal(request.uaid, userAgentID,
'Should include device ID on reconnect');
deepEqual(request.channelIDs, [],
'Should not include failed channel ID');
break;
default:
ok(false, 'Unexpected reconnect attempt ' + handshakes);
}
handshakes++;
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
},
onRegister(request) {
equal(request.channelID, channelID,
'Wrong channel ID in register request');
setTimeout(() => {
// Should ignore replies for timed-out requests.
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: channelID,
uaid: userAgentID,
pushEndpoint: 'https://example.com/update/timeout',
}));
timeoutDefer.resolve();
}, 2000);
registers++;
}
});
}
});
yield rejects(
PushNotificationService.register('https://example.net/page/timeout'),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for request timeout'
);
let record = yield promiseDB.getByChannelID(channelID);
ok(!record, 'Should not store records for timed-out responses');
yield waitForPromise(
timeoutDefer.promise,
DEFAULT_TIMEOUT,
'Reconnect timed out'
);
equal(registers, 1, 'Should not handle timed-out register requests');
});

View File

@ -1,71 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '84afc774-6995-40d1-9c90-8c34ddcd0cb4';
const clientChannelID = '4b42a681c99e4dfbbb166a7e01a09b8b';
const serverChannelID = '3f5aeb89c6e8405a9569619522783436';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/mismatched'
);
run_next_test();
}
add_task(function* test_register_wrong_id() {
// Should reconnect after the register request times out.
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
PushService._generateID = () => clientChannelID;
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
helloDone();
},
onRegister(request) {
equal(request.channelID, clientChannelID,
'Register: wrong channel ID');
registers++;
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
// Reply with a different channel ID. Since the ID is used as a
// nonce, the registration request will time out.
channelID: serverChannelID
}));
}
});
}
});
yield rejects(
PushNotificationService.register('https://example.com/mismatched'),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for mismatched register reply'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
'Reconnect after mismatched register reply timed out');
equal(registers, 1, 'Wrong register count');
});

View File

@ -1,67 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'c293fdc5-a75e-4eb1-af88-a203991c0787';
function run_test() {
do_get_profile();
setPrefs({
requestTimeout: 1000,
retryBaseInterval: 150
});
disableServiceWorkerEvents(
'https://example.com/mistyped'
);
run_next_test();
}
add_task(function* test_register_wrong_type() {
let registers = 0;
let helloDefer = Promise.defer();
let helloDone = after(2, helloDefer.resolve);
PushService._generateID = () => '1234';
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
helloDone();
},
onRegister(request) {
registers++;
this.serverSendMsg(JSON.stringify({
messageType: 'register',
status: 200,
channelID: 1234,
uaid: userAgentID,
pushEndpoint: 'https://example.org/update/wrong-type'
}));
}
});
}
});
let promise =
yield rejects(
PushNotificationService.register('https://example.com/mistyped'),
function(error) {
return error == 'TimeoutError';
},
'Wrong error for non-string channel ID'
);
yield waitForPromise(helloDefer.promise, DEFAULT_TIMEOUT,
'Reconnect after sending non-string channel ID timed out');
equal(registers, 1, 'Wrong register count');
});

View File

@ -1,39 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs({
userAgentID: '6faed1f0-1439-4aac-a978-db21c81cd5eb'
});
run_next_test();
}
add_task(function* test_registrations_error() {
let db = new PushDB();
do_register_cleanup(() => cleanupDatabase(db));
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db: makeStub(db, {
getByScope(prev, scope, successCb, failureCb) {
failureCb('oops');
}
}),
makeWebSocket(uri) {
return new MockWebSocket(uri);
}
});
yield rejects(
PushNotificationService.registration('https://example.net/1'),
function(error) {
return error == 'Database error';
},
'Wrong message'
);
});

View File

@ -1,28 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
run_next_test();
}
add_task(function* test_registration_missing_scope() {
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri);
}
});
yield rejects(
PushNotificationService.registration(''),
function(error) {
return error == 'Database error';
},
'Record missing page and manifest URLs'
);
});

View File

@ -1,28 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = 'a722e448-c481-4c48-aea0-fc411cb7c9ed';
function run_test() {
do_get_profile();
setPrefs({userAgentID});
run_next_test();
}
// Should not open a connection if the client has no registrations.
add_task(function* test_registration_none() {
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri);
}
});
let registration = yield PushNotificationService.registration(
'https://example.net/1');
ok(!registration, 'Should not open a connection without registration');
});

View File

@ -1,67 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '997ee7ba-36b1-4526-ae9e-2d3f38d6efe8';
function run_test() {
do_get_profile();
setPrefs({userAgentID});
run_next_test();
}
add_task(function* test_registration_success() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b',
pushEndpoint: 'https://example.com/update/same-manifest/1',
scope: 'https://example.net/a',
version: 5
}, {
channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
pushEndpoint: 'https://example.com/update/same-manifest/2',
scope: 'https://example.net/b',
version: 10
}, {
channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
pushEndpoint: 'https://example.org/update/different-manifest',
scope: 'https://example.org/c',
version: 15
}];
for (let record of records) {
yield promiseDB.put(record);
}
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
equal(request.uaid, userAgentID, 'Wrong device ID in handshake');
deepEqual(request.channelIDs.sort(), [
'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b',
'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
], 'Wrong channel list in handshake');
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
}
});
}
});
let registration = yield PushNotificationService.registration(
'https://example.net/a');
deepEqual(registration, {
pushEndpoint: 'https://example.com/update/same-manifest/1',
version: 5
}, 'Should include registrations for all pages with this manifest');
});

View File

@ -1,37 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
run_next_test();
}
add_task(function* test_unregister_empty_scope() {
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '5619557c-86fe-4711-8078-d1fd6987aef7'
}));
}
});
}
});
yield rejects(
PushNotificationService.unregister(''),
function(error) {
return error == 'NotFoundError';
},
'Wrong error for empty endpoint'
);
});

View File

@ -1,65 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const channelID = '00c7fa13-7b71-447d-bd27-a91abc09d1b2';
function run_test() {
do_get_profile();
setPrefs();
run_next_test();
}
add_task(function* test_unregister_error() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
yield promiseDB.put({
channelID: channelID,
pushEndpoint: 'https://example.org/update/failure',
scope: 'https://example.net/page/failure',
version: 1
});
let unregisterDefer = Promise.defer();
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: '083e6c17-1063-4677-8638-ab705aebebc2'
}));
},
onUnregister(request) {
// The server is notified out-of-band. Since channels may be pruned,
// any failures are swallowed.
equal(request.channelID, channelID, 'Unregister: wrong channel ID');
this.serverSendMsg(JSON.stringify({
messageType: 'unregister',
status: 500,
error: 'omg, everything is exploding',
channelID
}));
unregisterDefer.resolve();
}
});
}
});
yield PushNotificationService.unregister(
'https://example.net/page/failure');
let result = yield promiseDB.getByChannelID(channelID);
ok(!result, 'Deleted push record exists');
// Make sure we send a request to the server.
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});

View File

@ -1,78 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const userAgentID = '7f0af1bb-7e1f-4fb8-8e4a-e8de434abde3';
function run_test() {
do_get_profile();
setPrefs({
userAgentID,
requestTimeout: 150,
retryBaseInterval: 150
});
run_next_test();
}
add_task(function* test_unregister_invalid_json() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
let records = [{
channelID: '87902e90-c57e-4d18-8354-013f4a556559',
pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.edu/page/1',
version: 1
}, {
channelID: '057caa8f-9b99-47ff-891c-adad18ce603e',
pushEndpoint: 'https://example.com/update/2',
scope: 'https://example.net/page/1',
version: 1
}];
for (let record of records) {
yield promiseDB.put(record);
}
let unregisterDefer = Promise.defer();
let unregisterDone = after(2, unregisterDefer.resolve);
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID
}));
},
onUnregister(request) {
this.serverSendMsg(');alert(1);(');
unregisterDone();
}
});
}
});
// "unregister" is fire-and-forget: it's sent via _send(), not
// _sendRequest().
yield PushNotificationService.unregister(
'https://example.edu/page/1');
let record = yield promiseDB.getByChannelID(
'87902e90-c57e-4d18-8354-013f4a556559');
ok(!record, 'Failed to delete unregistered record');
yield PushNotificationService.unregister(
'https://example.net/page/1');
record = yield promiseDB.getByChannelID(
'057caa8f-9b99-47ff-891c-adad18ce603e');
ok(!record,
'Failed to delete unregistered record after receiving invalid JSON');
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});

View File

@ -1,35 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
function run_test() {
do_get_profile();
setPrefs();
run_next_test();
}
add_task(function* test_unregister_not_found() {
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: 'f074ed80-d479-44fa-ba65-792104a79ea9'
}));
}
});
}
});
let promise = PushNotificationService.unregister(
'https://example.net/nonexistent');
yield rejects(promise, function(error) {
return error == 'NotFoundError';
}, 'Wrong error for nonexistent scope');
});

View File

@ -1,60 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
'use strict';
const {PushDB, PushService} = serviceExports;
const channelID = 'db0a7021-ec2d-4bd3-8802-7a6966f10ed8';
function run_test() {
do_get_profile();
setPrefs();
run_next_test();
}
add_task(function* test_unregister_success() {
let db = new PushDB();
let promiseDB = promisifyDatabase(db);
do_register_cleanup(() => cleanupDatabase(db));
yield promiseDB.put({
channelID,
pushEndpoint: 'https://example.org/update/unregister-success',
scope: 'https://example.com/page/unregister-success',
version: 1
});
let unregisterDefer = Promise.defer();
PushService.init({
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: 'fbe865a6-aeb8-446f-873c-aeebdb8d493c'
}));
},
onUnregister(request) {
equal(request.channelID, channelID, 'Should include the channel ID');
this.serverSendMsg(JSON.stringify({
messageType: 'unregister',
status: 200,
channelID
}));
unregisterDefer.resolve();
}
});
}
});
yield PushNotificationService.unregister(
'https://example.com/page/unregister-success');
let record = yield promiseDB.getByChannelID(channelID);
ok(!record, 'Unregister did not remove record');
yield waitForPromise(unregisterDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for unregister');
});

View File

@ -1,32 +0,0 @@
[DEFAULT]
head = head.js
tail =
# Push notifications and alarms are currently disabled on Android.
skip-if = toolkit == 'android'
[test_notification_ack.js]
[test_notification_duplicate.js]
[test_notification_error.js]
[test_notification_incomplete.js]
[test_notification_version_string.js]
[test_register_case.js]
[test_register_flush.js]
[test_register_invalid_channel.js]
[test_register_invalid_endpoint.js]
[test_register_invalid_json.js]
[test_register_no_id.js]
[test_register_request_queue.js]
[test_register_rollback.js]
[test_register_success.js]
[test_register_timeout.js]
[test_register_wrong_id.js]
[test_register_wrong_type.js]
[test_registration_error.js]
[test_registration_missing_scope.js]
[test_registration_none.js]
[test_registration_success.js]
[test_unregister_empty_scope.js]
[test_unregister_error.js]
[test_unregister_invalid_json.js]
[test_unregister_not_found.js]
[test_unregister_success.js]