diff --git a/dom/base/ChromeUtils.cpp b/dom/base/ChromeUtils.cpp index 48d0b768a8bc..a9c2b907d4d5 100644 --- a/dom/base/ChromeUtils.cpp +++ b/dom/base/ChromeUtils.cpp @@ -19,5 +19,14 @@ ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal, attrs.CookieJar(aCookieJar); } +/* static */ void +ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal, + const dom::OriginAttributesDictionary& aAttrs, + nsCString& aSuffix) + +{ + OriginAttributes attrs(aAttrs); + attrs.CreateSuffix(aSuffix); +} } // namespace dom } // namespace mozilla diff --git a/dom/base/ChromeUtils.h b/dom/base/ChromeUtils.h index a43d0b298b31..fcd02f379998 100644 --- a/dom/base/ChromeUtils.h +++ b/dom/base/ChromeUtils.h @@ -44,6 +44,11 @@ public: OriginAttributesToCookieJar(dom::GlobalObject& aGlobal, const dom::OriginAttributesDictionary& aAttrs, nsCString& aCookieJar); + + static void + OriginAttributesToSuffix(dom::GlobalObject& aGlobal, + const dom::OriginAttributesDictionary& aAttrs, + nsCString& aSuffix); }; } // namespace dom diff --git a/dom/interfaces/base/nsIServiceWorkerManager.idl b/dom/interfaces/base/nsIServiceWorkerManager.idl index 68ebd3dd62ff..aa6a1c0afd26 100644 --- a/dom/interfaces/base/nsIServiceWorkerManager.idl +++ b/dom/interfaces/base/nsIServiceWorkerManager.idl @@ -20,7 +20,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports void unregisterFailed(); }; -[scriptable, builtinclass, uuid(103763c8-53ba-42e4-8b26-e601d5bc4afe)] +[scriptable, builtinclass, uuid(e633b73b-a734-4d04-a09c-b7779a439f3f)] interface nsIServiceWorkerInfo : nsISupports { readonly attribute nsIPrincipal principal; @@ -33,7 +33,7 @@ interface nsIServiceWorkerInfo : nsISupports readonly attribute DOMString waitingCacheName; }; -[scriptable, builtinclass, uuid(aee94712-9adb-4c0b-80a7-a8df34dfa2e8)] +[scriptable, builtinclass, uuid(e9abb123-0099-4d9e-85db-c8cd0aff19e6)] interface nsIServiceWorkerManager : nsISupports { /** @@ -126,11 +126,11 @@ interface nsIServiceWorkerManager : nsISupports in nsIServiceWorkerUnregisterCallback aCallback, in DOMString aScope); - [implicit_jscontext] void sendPushEvent(in jsval aOriginAttributes, - in ACString aScope, - in DOMString aData); - [implicit_jscontext] void sendPushSubscriptionChangeEvent(in jsval aOriginAttributes, - in ACString scope); + void sendPushEvent(in ACString aOriginAttributes, + in ACString aScope, + in DOMString aData); + void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes, + in ACString scope); void updateAllRegistrations(); }; diff --git a/dom/interfaces/push/nsIPushNotificationService.idl b/dom/interfaces/push/nsIPushNotificationService.idl index 19619ce0f0a7..7dd1ac51b0ea 100644 --- a/dom/interfaces/push/nsIPushNotificationService.idl +++ b/dom/interfaces/push/nsIPushNotificationService.idl @@ -11,7 +11,7 @@ * uses service workers to notify applications. This interface exists to allow * privileged code to receive messages without migrating to service workers. */ -[scriptable, uuid(3da6a16c-69f8-4843-9149-1e89d58a53e2)] +[scriptable, uuid(abde228b-7d14-4cab-b1f9-9f87750ede0f)] interface nsIPushNotificationService : nsISupports { /** @@ -32,20 +32,20 @@ interface nsIPushNotificationService : nsISupports * Servers may drop subscriptions at any time, so callers should recreate * subscriptions if desired. */ - jsval register(in string scope, [optional] in string pageURL); + jsval register(in string scope, in jsval originAttributes); /** * 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); + jsval unregister(in string scope, in jsval originAttributes); /** * 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); + jsval registration(in string scope, in jsval originAttributes); /** * Clear all subscriptions diff --git a/dom/push/Push.js b/dom/push/Push.js index 2a5c2c559d07..cdfb0bdfca9b 100644 --- a/dom/push/Push.js +++ b/dom/push/Push.js @@ -23,11 +23,11 @@ Cu.import("resource://gre/modules/AppsUtils.jsm"); const PUSH_SUBSCRIPTION_CID = Components.ID("{CA86B665-BEDA-4212-8D0F-5C9F65270B58}"); -function PushSubscription(pushEndpoint, scope, pageURL) { +function PushSubscription(pushEndpoint, scope, principal) { debug("PushSubscription Constructor"); this._pushEndpoint = pushEndpoint; this._scope = scope; - this._pageURL = pageURL; + this._principal = principal; } PushSubscription.prototype = { @@ -53,10 +53,10 @@ PushSubscription.prototype = { .getService(Ci.nsISyncMessageSender); }, - __init: function(endpoint, scope, pageURL) { + __init: function(endpoint, scope, principal) { this._pushEndpoint = endpoint; this._scope = scope; - this._pageURL = pageURL; + this._principal = principal; }, get endpoint() { @@ -71,11 +71,10 @@ PushSubscription.prototype = { reject: reject }); this._cpmm.sendAsyncMessage("Push:Unregister", { - pageURL: this._pageURL, scope: this._scope, pushEndpoint: this._pushEndpoint, requestID: resolverId - }); + }, null, this._principal); }.bind(this); return this.createPromise(promiseInit); @@ -133,7 +132,6 @@ Push.prototype = { gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug"); debug("init()"); - this._pageURL = aWindow.document.nodePrincipal.URI; this._window = aWindow; this.initDOMRequestHelper(aWindow, [ @@ -145,6 +143,7 @@ Push.prototype = { this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender); + this._principal = aWindow.document.nodePrincipal; }, setScope: function(scope){ @@ -160,8 +159,6 @@ Push.prototype = { let permValue = Services.perms.testExactPermissionFromPrincipal(principal, type); - debug("Existing permission " + permValue); - if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) { aAllowCallback(); return; @@ -219,9 +216,9 @@ Push.prototype = { switch (aMessage.name) { case "PushService:Register:OK": { - let subscription = new this._window.PushSubscription(json.pushEndpoint, - this._scope, - this._pageURL.spec); + let subscription = + new this._window.PushSubscription(json.pushEndpoint, this._scope, + this._principal); resolver.resolve(subscription); break; } @@ -232,8 +229,9 @@ Push.prototype = { { let subscription = null; try { - subscription = new this._window.PushSubscription(json.registration.pushEndpoint, - this._scope, this._pageURL.spec); + subscription = + new this._window.PushSubscription(json.registration.pushEndpoint, + this._scope, this._principal); } catch(error) { } resolver.resolve(subscription); @@ -255,10 +253,9 @@ Push.prototype = { this.askPermission( function() { this._cpmm.sendAsyncMessage("Push:Register", { - pageURL: this._pageURL.spec, scope: this._scope, requestID: resolverId - }); + }, null, this._principal); }.bind(this), function() { @@ -279,10 +276,9 @@ Push.prototype = { this.askPermission( function() { this._cpmm.sendAsyncMessage("Push:Registration", { - pageURL: this._pageURL.spec, scope: this._scope, requestID: resolverId - }); + }, null, this._principal); }.bind(this), function() { @@ -294,11 +290,14 @@ Push.prototype = { }, hasPermission: function() { - debug("getSubscription()" + this._scope); + debug("hasPermission()" + this._scope); let p = this.createPromise(function(resolve, reject) { - let permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); - let permission = permissionManager.testExactPermission(this._pageURL, "push"); + let permissionManager = Cc["@mozilla.org/permissionmanager;1"] + .getService(Ci.nsIPermissionManager); + let permission = + permissionManager.testExactPermissionFromPrincipal(this._principal, + "push"); let pushPermissionStatus = "default"; if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) { diff --git a/dom/push/PushDB.jsm b/dom/push/PushDB.jsm index 61a82269f13b..f9ae365c5cd1 100644 --- a/dom/push/PushDB.jsm +++ b/dom/push/PushDB.jsm @@ -147,9 +147,15 @@ this.PushDB.prototype = { ); }, - - getByScope: function(aScope) { - debug("getByScope() " + aScope); + // Perform a unique match against { scope, originAttributes } + getByIdentifiers: function(aPageRecord) { + debug("getByIdentifiers() { " + aPageRecord.scope + ", " + + JSON.stringify(aPageRecord.originAttributes) + " }"); + if (!aPageRecord.scope || aPageRecord.originAttributes == undefined) { + return Promise.reject( + new TypeError("Scope and originAttributes are required! " + + JSON.stringify(aPageRecord))); + } return new Promise((resolve, reject) => this.newTxn( @@ -158,11 +164,11 @@ this.PushDB.prototype = { function txnCb(aTxn, aStore) { aTxn.result = undefined; - let index = aStore.index("scope"); - index.get(aScope).onsuccess = function setTxnResult(aEvent) { + let index = aStore.index("identifiers"); + let request = index.get(IDBKeyRange.only([aPageRecord.scope, aPageRecord.originAttributes])); + request.onsuccess = function setTxnResult(aEvent) { aTxn.result = aEvent.target.result; - debug("Fetch successful " + aEvent.target.result); - }; + } }, resolve, reject @@ -170,6 +176,37 @@ this.PushDB.prototype = { ); }, + _getAllByKey: function(aKeyName, aKeyValue) { + return new Promise((resolve, reject) => + this.newTxn( + "readonly", + this._dbStoreName, + function txnCb(aTxn, aStore) { + aTxn.result = undefined; + + let index = aStore.index(aKeyName); + // It seems ok to use getAll here, since unlike contacts or other + // high storage APIs, we don't expect more than a handful of + // registrations per domain, and usually only one. + let getAllReq = index.mozGetAll(aKeyValue); + getAllReq.onsuccess = function setTxnResult(aEvent) { + aTxn.result = aEvent.target.result; + } + }, + resolve, + reject + ) + ); + }, + + // aOriginAttributes must be a string! + getAllByOriginAttributes: function(aOriginAttributes) { + if (typeof aOriginAttributes !== "string") { + return Promise.reject("Expected string!"); + } + return this._getAllByKey("originAttributes", aOriginAttributes); + }, + getAllKeyIDs: function() { debug("getAllKeyIDs()"); @@ -178,6 +215,7 @@ this.PushDB.prototype = { "readonly", this._dbStoreName, function txnCb(aTxn, aStore) { + aTxn.result = undefined; aStore.mozGetAll().onsuccess = function(event) { aTxn.result = event.target.result; }; diff --git a/dom/push/PushNotificationService.js b/dom/push/PushNotificationService.js index 9229ef6c6bc4..9c7f472cce04 100644 --- a/dom/push/PushNotificationService.js +++ b/dom/push/PushNotificationService.js @@ -38,16 +38,16 @@ PushNotificationService.prototype = { Ci.nsISupportsWeakReference, Ci.nsIPushNotificationService]), - register: function register(scope, pageURL) { - return PushService._register({scope, pageURL}); + register: function register(scope, originAttributes) { + return PushService._register({scope, originAttributes}); }, - unregister: function unregister(scope) { - return PushService._unregister({scope}); + unregister: function unregister(scope, originAttributes) { + return PushService._unregister({scope, originAttributes}); }, - registration: function registration(scope) { - return PushService._registration({scope}); + registration: function registration(scope, originAttributes) { + return PushService._registration({scope, originAttributes}); }, clearAll: function clearAll() { diff --git a/dom/push/PushService.jsm b/dom/push/PushService.jsm index 0f13f85b85ab..f6f5746ad407 100644 --- a/dom/push/PushService.jsm +++ b/dom/push/PushService.jsm @@ -116,6 +116,26 @@ this.PushService = { } }, + _makePendingKey: function(aPageRecord) { + return aPageRecord.scope + "|" + aPageRecord.originAttributes; + }, + + _lookupOrPutPendingRequest: function(aPageRecord) { + let key = this._makePendingKey(aPageRecord); + if (this._pendingRegisterRequest[key]) { + return this._pendingRegisterRequest[key]; + } + + return this._pendingRegisterRequest[key] = this._registerWithServer(aPageRecord); + }, + + _deletePendingRequest: function(aPageRecord) { + let key = this._makePendingKey(aPageRecord); + if (this._pendingRegisterRequest[key]) { + delete this._pendingRegisterRequest[key]; + } + }, + _setState: function(aNewState) { debug("new state: " + aNewState + " old state: " + this._state); @@ -233,24 +253,41 @@ this.PushService = { return; } - // TODO 1149274. We should support site permissions as well as a way to - // go from manifest url to 'all scopes registered for push in this app' - let appsService = Cc["@mozilla.org/AppsService;1"] - .getService(Ci.nsIAppsService); - let scope = appsService.getScopeByLocalId(data.appId); - if (!scope) { - debug("webapps-clear-data: No scope found for " + data.appId); - return; - } + var originAttributes = + ChromeUtils.originAttributesToSuffix({ appId: data.appId, + inBrowser: data.browserOnly }); + this._db.getAllByOriginAttributes(originAttributes) + .then(records => { + records.forEach(record => { + this._db.delete(this._service.getKeyFromRecord(record)) + .then(_ => { + // courtesy, but don't establish a connection + // just for it + if (this._ws) { + debug("Had a connection, so telling the server"); + this._sendRequest("unregister", {channelID: records.channelID}) + .catch(function(e) { + debug("Unregister errored " + e); + }); + } + }, err => { + debug("webapps-clear-data: " + scope + + " Could not delete entry " + records.channelID); - this._db.getByScope(scope) - .then(record => - Promise.all([ - this._db.delete(this._service.getKeyFromRecord(record)), - this._sendRequest("unregister", record) - ]) - ).catch(_ => { - debug("webapps-clear-data: Error in getByScope(" + scope + ")"); + // courtesy, but don't establish a connection + // just for it + if (this._ws) { + debug("Had a connection, so telling the server"); + this._sendRequest("unregister", {channelID: records.channelID}) + .catch(function(e) { + debug("Unregister errored " + e); + }); + } + throw "Database error"; + }); + }); + }, _ => { + debug("webapps-clear-data: Error in getAllByOriginAttributes(" + originAttributes + ")"); }); break; @@ -623,13 +660,9 @@ this.PushService = { // records are objects describing the registration as stored in IndexedDB. return this._db.getAllKeyIDs() .then(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) { + for (let record of records) { // Notify XPCOM observers. Services.obs.notifyObservers( null, @@ -638,8 +671,8 @@ this.PushService = { ); let data = { - originAttributes: {}, // TODO bug 1166350 - scope: scope + originAttributes: record.originAttributes, + scope: record.scope }; globalMM.broadcastAsyncMessage('pushsubscriptionchange', data); @@ -659,7 +692,7 @@ this.PushService = { ); let data = { - originAttributes: {}, // TODO bug 1166350 + originAttributes: record.originAttributes, scope: record.scope }; @@ -681,7 +714,7 @@ this.PushService = { ); let data = { - originAttributes: {}, // TODO bug 1166350 + originAttributes: record.originAttributes, scope: record.scope }; @@ -695,7 +728,8 @@ this.PushService = { }, _notifyApp: function(aPushRecord, message) { - if (!aPushRecord || !aPushRecord.scope) { + if (!aPushRecord || !aPushRecord.scope || + aPushRecord.originAttributes === undefined) { debug("notifyApp() something is undefined. Dropping notification: " + JSON.stringify(aPushRecord) ); return; @@ -728,7 +762,7 @@ this.PushService = { // TODO data. let data = { payload: message, - originAttributes: {}, // TODO bug 1166350 + originAttributes: aPushRecord.originAttributes, scope: aPushRecord.scope }; @@ -745,7 +779,7 @@ this.PushService = { return this._db.getAllKeyIDs(); }, - _sendRequest(action, aRecord) { + _sendRequest: function(action, aRecord) { if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) { return Promise.reject({state: 0, error: "Service not active"}); } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { @@ -759,37 +793,35 @@ this.PushService = { * navigator.push, identifying the sending page and other fields. */ _registerWithServer: function(aPageRecord) { - debug("registerWithServer()"); + debug("registerWithServer()" + JSON.stringify(aPageRecord)); return this._sendRequest("register", aPageRecord) .then(pushRecord => this._onRegisterSuccess(pushRecord), err => this._onRegisterError(err)) .then(pushRecord => { - if (this._pendingRegisterRequest[aPageRecord.scope]) { - delete this._pendingRegisterRequest[aPageRecord.scope]; - } + this._deletePendingRequest(aPageRecord); return pushRecord; }, err => { - if (this._pendingRegisterRequest[aPageRecord.scope]) { - delete this._pendingRegisterRequest[aPageRecord.scope]; - } + this._deletePendingRequest(aPageRecord); throw err; }); }, _register: function(aPageRecord) { + debug("_register()"); + if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { + return Promise.reject({state: 0, error: "NotFoundError"}); + } + return this._checkActivated() - .then(_ => this._db.getByScope(aPageRecord.scope)) + .then(_ => this._db.getByIdentifiers(aPageRecord)) .then(pushRecord => { if (pushRecord === undefined) { - if (this._pendingRegisterRequest[aPageRecord.scope]) { - return this._pendingRegisterRequest[aPageRecord.scope]; - } - return this._pendingRegisterRequest[aPageRecord.scope] = this._registerWithServer(aPageRecord); + return this._lookupOrPutPendingRequest(aPageRecord); } return pushRecord; }, error => { - debug("getByScope failed"); + debug("getByIdentifiers failed"); throw error; }); }, @@ -833,10 +865,39 @@ this.PushService = { return; } - let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); - let json = aMessage.data; + if (!aMessage.target.assertPermission("push")) { + debug("Got message from a child process that does not have 'push' permission."); + return null; + } - this[aMessage.name.slice("Push:".length).toLowerCase()](json, mm); + let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); + let pageRecord = aMessage.data; + + let principal = aMessage.principal; + if (!principal) { + debug("No principal passed!"); + let message = { + requestID: aPageRecord.requestID, + error: "SecurityError" + }; + mm.sendAsyncMessage("PushService:Register:KO", message); + return; + } + + pageRecord.originAttributes = + ChromeUtils.originAttributesToSuffix(principal.originAttributes); + + if (!pageRecord.scope || pageRecord.originAttributes === undefined) { + debug("Incorrect identifier values set! " + JSON.stringify(pageRecord)); + let message = { + requestID: aPageRecord.requestID, + error: "SecurityError" + }; + mm.sendAsyncMessage("PushService:Register:KO", message); + return; + } + + this[aMessage.name.slice("Push:".length).toLowerCase()](pageRecord, mm); }, register: function(aPageRecord, aMessageManager) { @@ -883,13 +944,12 @@ this.PushService = { */ _unregister: function(aPageRecord) { debug("_unregister()"); - - if (!aPageRecord.scope) { + if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { return Promise.reject({state: 0, error: "NotFoundError"}); } return this._checkActivated() - .then(_ => this._db.getByScope(aPageRecord.scope)) + .then(_ => this._db.getByIdentifiers(aPageRecord)) .then(record => { // If the endpoint didn't exist, let's just fail. if (record === undefined) { @@ -932,12 +992,12 @@ this.PushService = { */ _registration: function(aPageRecord) { debug("_registration()"); - if (!aPageRecord.scope) { - return Promise.reject({state: 0, error: "Database error"}); + if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) { + return Promise.reject({state: 0, error: "NotFoundError"}); } return this._checkActivated() - .then(_ => this._db.getByScope(aPageRecord.scope)) + .then(_ => this._db.getByIdentifiers(aPageRecord)) .then(pushRecord => { if (!pushRecord) { return null; diff --git a/dom/push/PushServiceHttp2.jsm b/dom/push/PushServiceHttp2.jsm index 266212987220..9a9c93cac9f2 100644 --- a/dom/push/PushServiceHttp2.jsm +++ b/dom/push/PushServiceHttp2.jsm @@ -33,7 +33,7 @@ function debug(s) { } const kPUSHHTTP2DB_DB_NAME = "pushHttp2"; -const kPUSHHTTP2DB_DB_VERSION = 1; // Change this if the IndexedDB format changes +const kPUSHHTTP2DB_DB_VERSION = 3; // Change this if the IndexedDB format changes const kPUSHHTTP2DB_STORE_NAME = "pushHttp2"; /** @@ -291,6 +291,7 @@ SubscriptionListener.prototype = { pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint, pageURL: this._subInfo.record.pageURL, scope: this._subInfo.record.scope, + originAttributes: this._subInfo.record.originAttributes, pushCount: 0, lastPush: 0 }; @@ -385,15 +386,33 @@ this.PushServiceHttp2 = { aDbInstance) { debug("upgradeSchemaHttp2()"); + //XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old + //registrations away without even informing the app. + if (aNewVersion != aOldVersion) { + try { + aDb.deleteObjectStore(aDbInstance._dbStoreName); + } catch (e) { + if (e.name === "NotFoundError") { + debug("No existing object store found"); + } else { + throw e; + } + } + } + let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName, { keyPath: "subscriptionUri" }); // index to fetch records based on endpoints. used by unregister objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true }); - // index to fetch records per scope, so we can identify endpoints - // associated with an app. - objectStore.createIndex("scope", "scope", { unique: true }); + // index to fetch records by identifiers. + // In the current security model, the originAttributes distinguish between + // different 'apps' on the same origin. Since ServiceWorkers are + // same-origin to the scope they are registered for, the attributes and + // scope are enough to reconstruct a valid principal. + objectStore.createIndex("identifiers", ["scope", "originAttributes"], { unique: true }); + objectStore.createIndex("originAttributes", "originAttributes", { unique: false }); }, getKeyFromRecord: function(aRecord) { diff --git a/dom/push/PushServiceWebSocket.jsm b/dom/push/PushServiceWebSocket.jsm index aa270ce49f2e..315c87708f6e 100644 --- a/dom/push/PushServiceWebSocket.jsm +++ b/dom/push/PushServiceWebSocket.jsm @@ -32,7 +32,7 @@ var threadManager = Cc["@mozilla.org/thread-manager;1"] .getService(Ci.nsIThreadManager); const kPUSHWSDB_DB_NAME = "pushapi"; -const kPUSHWSDB_DB_VERSION = 1; // Change this if the IndexedDB format changes +const kPUSHWSDB_DB_VERSION = 3; // Change this if the IndexedDB format changes const kPUSHWSDB_STORE_NAME = "pushapi"; const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent @@ -132,15 +132,33 @@ this.PushServiceWebSocket = { aDbInstance) { debug("upgradeSchemaWS()"); + //XXXnsm We haven't shipped Push during this upgrade, so I'm just going to throw old + //registrations away without even informing the app. + if (aNewVersion != aOldVersion) { + try { + aDb.deleteObjectStore(aDbInstance._dbStoreName); + } catch (e) { + if (e.name === "NotFoundError") { + debug("No existing object store found"); + } else { + throw e; + } + } + } + let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName, { keyPath: "channelID" }); // index to fetch records based on endpoints. used by unregister objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true }); - // index to fetch records per scope, so we can identify endpoints - // associated with an app. - objectStore.createIndex("scope", "scope", { unique: true }); + // index to fetch records by identifiers. + // In the current security model, the originAttributes distinguish between + // different 'apps' on the same origin. Since ServiceWorkers are + // same-origin to the scope they are registered for, the attributes and + // scope are enough to reconstruct a valid principal. + objectStore.createIndex("identifiers", ["scope", "originAttributes"], { unique: true }); + objectStore.createIndex("originAttributes", "originAttributes", { unique: false }); }, getKeyFromRecord: function(aRecord) { @@ -870,10 +888,12 @@ this.PushServiceWebSocket = { pushEndpoint: reply.pushEndpoint, pageURL: tmp.record.pageURL, scope: tmp.record.scope, + originAttributes: tmp.record.originAttributes, pushCount: 0, lastPush: 0, version: null }; + dump("PushWebSocket " + JSON.stringify(record)); tmp.resolve(record); } else { tmp.reject(reply); diff --git a/dom/push/test/test_multiple_register.html b/dom/push/test/test_multiple_register.html index 4557d0abd1f5..be7bb574a87a 100644 --- a/dom/push/test/test_multiple_register.html +++ b/dom/push/test/test_multiple_register.html @@ -120,6 +120,7 @@ http://creativecommons.org/licenses/publicdomain/ SpecialPowers.pushPrefEnv({"set": [ ["dom.push.enabled", true], + ["dom.push.debug", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.testing.enabled", true] diff --git a/dom/push/test/xpcshell/test_notification_ack.js b/dom/push/test/xpcshell/test_notification_ack.js index 300e8491a33e..4570244d685b 100644 --- a/dom/push/test/xpcshell/test_notification_ack.js +++ b/dom/push/test/xpcshell/test_notification_ack.js @@ -25,16 +25,19 @@ add_task(function* test_notification_ack() { channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c', pushEndpoint: 'https://example.com/update/1', scope: 'https://example.org/1', + originAttributes: '', version: 1 }, { channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305', pushEndpoint: 'https://example.com/update/2', scope: 'https://example.org/2', + originAttributes: '', version: 2 }, { channelID: '5477bfda-22db-45d4-9614-fee369630260', pushEndpoint: 'https://example.com/update/3', scope: 'https://example.org/3', + originAttributes: '', version: 3 }]; for (let record of records) { diff --git a/dom/push/test/xpcshell/test_notification_duplicate.js b/dom/push/test/xpcshell/test_notification_duplicate.js index 9a043b729333..0fcb66c1b41b 100644 --- a/dom/push/test/xpcshell/test_notification_duplicate.js +++ b/dom/push/test/xpcshell/test_notification_duplicate.js @@ -23,11 +23,13 @@ add_task(function* test_notification_duplicate() { channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc', pushEndpoint: 'https://example.org/update/1', scope: 'https://example.com/1', + originAttributes: "", version: 2 }, { channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0', pushEndpoint: 'https://example.org/update/2', scope: 'https://example.com/2', + originAttributes: "", version: 2 }]; for (let record of records) { diff --git a/dom/push/test/xpcshell/test_notification_error.js b/dom/push/test/xpcshell/test_notification_error.js index db69d023b8ed..3bd764b0bc56 100644 --- a/dom/push/test/xpcshell/test_notification_error.js +++ b/dom/push/test/xpcshell/test_notification_error.js @@ -19,20 +19,25 @@ function run_test() { add_task(function* test_notification_error() { let db = PushServiceWebSocket.newPushDB(); do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + let originAttributes = ''; let records = [{ channelID: 'f04f1e46-9139-4826-b2d1-9411b0821283', pushEndpoint: 'https://example.org/update/success-1', scope: 'https://example.com/a', + originAttributes: originAttributes, version: 1 }, { channelID: '3c3930ba-44de-40dc-a7ca-8a133ec1a866', pushEndpoint: 'https://example.org/update/error', scope: 'https://example.com/b', + originAttributes: originAttributes, version: 2 }, { channelID: 'b63f7bef-0a0d-4236-b41e-086a69dfd316', pushEndpoint: 'https://example.org/update/success-2', scope: 'https://example.com/c', + originAttributes: originAttributes, version: 3 }]; for (let record of records) { @@ -107,19 +112,22 @@ add_task(function* test_notification_error() { yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, 'Timed out waiting for acknowledgements'); - let aRecord = yield db.getByScope('https://example.com/a'); + let aRecord = yield db.getByIdentifiers({scope: 'https://example.com/a', + originAttributes: originAttributes }); 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 db.getByScope('https://example.com/b'); + let bRecord = yield db.getByIdentifiers({scope: 'https://example.com/b', + originAttributes: originAttributes }); 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 db.getByScope('https://example.com/c'); + let cRecord = yield db.getByIdentifiers({scope: 'https://example.com/c', + originAttributes: originAttributes }); equal(cRecord.channelID, 'b63f7bef-0a0d-4236-b41e-086a69dfd316', 'Wrong channel ID for record C'); strictEqual(cRecord.version, 4, diff --git a/dom/push/test/xpcshell/test_notification_version_string.js b/dom/push/test/xpcshell/test_notification_version_string.js index 5b59bbc046dc..1008ad676d58 100644 --- a/dom/push/test/xpcshell/test_notification_version_string.js +++ b/dom/push/test/xpcshell/test_notification_version_string.js @@ -21,6 +21,7 @@ add_task(function* test_notification_version_string() { channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b', pushEndpoint: 'https://example.org/updates/1', scope: 'https://example.com/page/1', + originAttributes: '', version: 2 }); diff --git a/dom/push/test/xpcshell/test_register_5xxCode_http2.js b/dom/push/test/xpcshell/test_register_5xxCode_http2.js index 9bac9b2de9c1..bc944013f72f 100644 --- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js +++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js @@ -84,7 +84,8 @@ add_task(function* test1() { }); let newRecord = yield PushNotificationService.register( - 'https://example.com/retry5xxCode' + 'https://example.com/retry5xxCode', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false } ); var subscriptionUri = serverURL + '/subscription'; diff --git a/dom/push/test/xpcshell/test_register_case.js b/dom/push/test/xpcshell/test_register_case.js index 14092de1376a..1ec779c485a0 100644 --- a/dom/push/test/xpcshell/test_register_case.js +++ b/dom/push/test/xpcshell/test_register_case.js @@ -47,7 +47,8 @@ add_task(function* test_register_case() { }); let newRecord = yield waitForPromise( - PushNotificationService.register('https://example.net/case'), + PushNotificationService.register('https://example.net/case', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), DEFAULT_TIMEOUT, 'Mixed-case register response timed out' ); diff --git a/dom/push/test/xpcshell/test_register_flush.js b/dom/push/test/xpcshell/test_register_flush.js index b5b3653694ae..5db81ee29262 100644 --- a/dom/push/test/xpcshell/test_register_flush.js +++ b/dom/push/test/xpcshell/test_register_flush.js @@ -29,6 +29,7 @@ add_task(function* test_register_flush() { channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74', pushEndpoint: 'https://example.org/update/1', scope: 'https://example.com/page/1', + originAttributes: '', version: 2 }; yield db.put(record); @@ -75,8 +76,7 @@ add_task(function* test_register_flush() { }); let newRecord = yield PushNotificationService.register( - 'https://example.com/page/2' - ); + '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', diff --git a/dom/push/test/xpcshell/test_register_invalid_channel.js b/dom/push/test/xpcshell/test_register_invalid_channel.js index 797a029bce2d..537d4525d519 100644 --- a/dom/push/test/xpcshell/test_register_invalid_channel.js +++ b/dom/push/test/xpcshell/test_register_invalid_channel.js @@ -48,7 +48,8 @@ add_task(function* test_register_invalid_channel() { }); yield rejects( - PushNotificationService.register('https://example.com/invalid-channel'), + PushNotificationService.register('https://example.com/invalid-channel', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'Invalid channel ID'; }, diff --git a/dom/push/test/xpcshell/test_register_invalid_endpoint.js b/dom/push/test/xpcshell/test_register_invalid_endpoint.js index c53c81b23829..2656734af76e 100644 --- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js +++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js @@ -50,7 +50,8 @@ add_task(function* test_register_invalid_endpoint() { yield rejects( PushNotificationService.register( - 'https://example.net/page/invalid-endpoint'), + 'https://example.net/page/invalid-endpoint', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error && error.includes('Invalid pushEndpoint'); }, diff --git a/dom/push/test/xpcshell/test_register_invalid_json.js b/dom/push/test/xpcshell/test_register_invalid_json.js index f5c2e967bc91..b326bc1e7ea1 100644 --- a/dom/push/test/xpcshell/test_register_invalid_json.js +++ b/dom/push/test/xpcshell/test_register_invalid_json.js @@ -49,7 +49,8 @@ add_task(function* test_register_invalid_json() { }); yield rejects( - PushNotificationService.register('https://example.net/page/invalid-json'), + PushNotificationService.register('https://example.net/page/invalid-json', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'TimeoutError'; }, diff --git a/dom/push/test/xpcshell/test_register_no_id.js b/dom/push/test/xpcshell/test_register_no_id.js index 21b0ff8054ef..5c5a844fbe71 100644 --- a/dom/push/test/xpcshell/test_register_no_id.js +++ b/dom/push/test/xpcshell/test_register_no_id.js @@ -53,7 +53,8 @@ add_task(function* test_register_no_id() { }); yield rejects( - PushNotificationService.register('https://example.com/incomplete'), + PushNotificationService.register('https://example.com/incomplete', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'TimeoutError'; }, diff --git a/dom/push/test/xpcshell/test_register_request_queue.js b/dom/push/test/xpcshell/test_register_request_queue.js index 50c542534cdf..7bebe305458c 100644 --- a/dom/push/test/xpcshell/test_register_request_queue.js +++ b/dom/push/test/xpcshell/test_register_request_queue.js @@ -45,10 +45,12 @@ add_task(function* test_register_request_queue() { }); let firstRegister = PushNotificationService.register( - 'https://example.com/page/1' + 'https://example.com/page/1', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false } ); let secondRegister = PushNotificationService.register( - 'https://example.com/page/1' + 'https://example.com/page/1', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false } ); yield waitForPromise(Promise.all([ diff --git a/dom/push/test/xpcshell/test_register_rollback.js b/dom/push/test/xpcshell/test_register_rollback.js index 274cc65ee6a0..c45a41a625f3 100644 --- a/dom/push/test/xpcshell/test_register_rollback.js +++ b/dom/push/test/xpcshell/test_register_rollback.js @@ -74,7 +74,8 @@ add_task(function* test_register_rollback() { // Should return a rejected promise if storage fails. yield rejects( - PushNotificationService.register('https://example.com/storage-error'), + PushNotificationService.register('https://example.com/storage-error', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'universe has imploded'; }, diff --git a/dom/push/test/xpcshell/test_register_success.js b/dom/push/test/xpcshell/test_register_success.js index 0fb56a2a9621..c56aa0937b0d 100644 --- a/dom/push/test/xpcshell/test_register_success.js +++ b/dom/push/test/xpcshell/test_register_success.js @@ -57,7 +57,8 @@ add_task(function* test_register_success() { }); let newRecord = yield PushNotificationService.register( - 'https://example.org/1' + 'https://example.org/1', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false } ); equal(newRecord.channelID, channelID, 'Wrong channel ID in registration record'); diff --git a/dom/push/test/xpcshell/test_register_timeout.js b/dom/push/test/xpcshell/test_register_timeout.js index 97e362ffc83d..606ecd3b544b 100644 --- a/dom/push/test/xpcshell/test_register_timeout.js +++ b/dom/push/test/xpcshell/test_register_timeout.js @@ -83,7 +83,8 @@ add_task(function* test_register_timeout() { }); yield rejects( - PushNotificationService.register('https://example.net/page/timeout'), + PushNotificationService.register('https://example.net/page/timeout', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'TimeoutError'; }, diff --git a/dom/push/test/xpcshell/test_register_wrong_id.js b/dom/push/test/xpcshell/test_register_wrong_id.js index 418384f34ed0..1385de5b6566 100644 --- a/dom/push/test/xpcshell/test_register_wrong_id.js +++ b/dom/push/test/xpcshell/test_register_wrong_id.js @@ -59,7 +59,8 @@ add_task(function* test_register_wrong_id() { }); yield rejects( - PushNotificationService.register('https://example.com/mismatched'), + PushNotificationService.register('https://example.com/mismatched', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'TimeoutError'; }, diff --git a/dom/push/test/xpcshell/test_register_wrong_type.js b/dom/push/test/xpcshell/test_register_wrong_type.js index 00405aaff1d4..09281fd6ad45 100644 --- a/dom/push/test/xpcshell/test_register_wrong_type.js +++ b/dom/push/test/xpcshell/test_register_wrong_type.js @@ -55,7 +55,8 @@ add_task(function* test_register_wrong_type() { let promise = yield rejects( - PushNotificationService.register('https://example.com/mistyped'), + PushNotificationService.register('https://example.com/mistyped', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'TimeoutError'; }, diff --git a/dom/push/test/xpcshell/test_registration_error.js b/dom/push/test/xpcshell/test_registration_error.js index 89db285ef89b..42aa601157cf 100644 --- a/dom/push/test/xpcshell/test_registration_error.js +++ b/dom/push/test/xpcshell/test_registration_error.js @@ -21,7 +21,7 @@ add_task(function* test_registrations_error() { serverURI: "wss://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), db: makeStub(db, { - getByScope(prev, scope) { + getByIdentifiers(prev, scope) { return Promise.reject('Database error'); } }), @@ -31,7 +31,8 @@ add_task(function* test_registrations_error() { }); yield rejects( - PushNotificationService.registration('https://example.net/1'), + PushNotificationService.registration('https://example.net/1', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error == 'Database error'; }, diff --git a/dom/push/test/xpcshell/test_registration_error_http2.js b/dom/push/test/xpcshell/test_registration_error_http2.js index cf12c719f354..3e2e9de1bd72 100644 --- a/dom/push/test/xpcshell/test_registration_error_http2.js +++ b/dom/push/test/xpcshell/test_registration_error_http2.js @@ -18,7 +18,7 @@ add_task(function* test_registrations_error() { serverURI: "https://push.example.org/", networkInfo: new MockDesktopNetworkInfo(), db: makeStub(db, { - getByScope(prev, scope) { + getByIdentifiers() { return Promise.reject('Database error'); } }), diff --git a/dom/push/test/xpcshell/test_registration_missing_scope.js b/dom/push/test/xpcshell/test_registration_missing_scope.js index ccead3fea913..c32955aead13 100644 --- a/dom/push/test/xpcshell/test_registration_missing_scope.js +++ b/dom/push/test/xpcshell/test_registration_missing_scope.js @@ -20,9 +20,9 @@ add_task(function* test_registration_missing_scope() { } }); yield rejects( - PushNotificationService.registration(''), + PushNotificationService.registration('', ''), function(error) { - return error.error == 'Database error'; + return error.error == 'NotFoundError'; }, 'Record missing page and manifest URLs' ); diff --git a/dom/push/test/xpcshell/test_registration_none.js b/dom/push/test/xpcshell/test_registration_none.js index 35a33dbbcd47..ad2fd9a8cb48 100644 --- a/dom/push/test/xpcshell/test_registration_none.js +++ b/dom/push/test/xpcshell/test_registration_none.js @@ -24,6 +24,7 @@ add_task(function* test_registration_none() { }); let registration = yield PushNotificationService.registration( - 'https://example.net/1'); + 'https://example.net/1', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }); ok(!registration, 'Should not open a connection without registration'); }); diff --git a/dom/push/test/xpcshell/test_registration_success.js b/dom/push/test/xpcshell/test_registration_success.js index eaf6081ca42e..800e0eb658a1 100644 --- a/dom/push/test/xpcshell/test_registration_success.js +++ b/dom/push/test/xpcshell/test_registration_success.js @@ -20,16 +20,19 @@ add_task(function* test_registration_success() { channelID: 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b', pushEndpoint: 'https://example.com/update/same-manifest/1', scope: 'https://example.net/a', + originAttributes: '', version: 5 }, { channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f', pushEndpoint: 'https://example.com/update/same-manifest/2', scope: 'https://example.net/b', + originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42 }), version: 10 }, { channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728', pushEndpoint: 'https://example.org/update/different-manifest', scope: 'https://example.org/c', + originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42, inBrowser: true }), version: 15 }]; for (let record of records) { @@ -59,7 +62,7 @@ add_task(function* test_registration_success() { }); let registration = yield PushNotificationService.registration( - 'https://example.net/a'); + 'https://example.net/a', ''); equal( registration.pushEndpoint, 'https://example.com/update/same-manifest/1', diff --git a/dom/push/test/xpcshell/test_unregister_empty_scope.js b/dom/push/test/xpcshell/test_unregister_empty_scope.js index 00cad2392e26..6c715b668d9b 100644 --- a/dom/push/test/xpcshell/test_unregister_empty_scope.js +++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js @@ -29,7 +29,8 @@ add_task(function* test_unregister_empty_scope() { }); yield rejects( - PushNotificationService.unregister(''), + PushNotificationService.unregister('', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }), function(error) { return error.error == 'NotFoundError'; }, diff --git a/dom/push/test/xpcshell/test_unregister_error.js b/dom/push/test/xpcshell/test_unregister_error.js index 7d99b5ed33bb..2fd5d03ed5f6 100644 --- a/dom/push/test/xpcshell/test_unregister_error.js +++ b/dom/push/test/xpcshell/test_unregister_error.js @@ -20,6 +20,7 @@ add_task(function* test_unregister_error() { channelID: channelID, pushEndpoint: 'https://example.org/update/failure', scope: 'https://example.net/page/failure', + originAttributes: '', version: 1 }); @@ -54,7 +55,7 @@ add_task(function* test_unregister_error() { }); yield PushNotificationService.unregister( - 'https://example.net/page/failure'); + 'https://example.net/page/failure', ''); let result = yield db.getByKeyID(channelID); ok(!result, 'Deleted push record exists'); diff --git a/dom/push/test/xpcshell/test_unregister_invalid_json.js b/dom/push/test/xpcshell/test_unregister_invalid_json.js index 0279cb7ae134..ca12f1732842 100644 --- a/dom/push/test/xpcshell/test_unregister_invalid_json.js +++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js @@ -24,11 +24,13 @@ add_task(function* test_unregister_invalid_json() { channelID: '87902e90-c57e-4d18-8354-013f4a556559', pushEndpoint: 'https://example.org/update/1', scope: 'https://example.edu/page/1', + originAttributes: '', version: 1 }, { channelID: '057caa8f-9b99-47ff-891c-adad18ce603e', pushEndpoint: 'https://example.com/update/2', scope: 'https://example.net/page/1', + originAttributes: '', version: 1 }]; for (let record of records) { @@ -61,13 +63,13 @@ add_task(function* test_unregister_invalid_json() { // "unregister" is fire-and-forget: it's sent via _send(), not // _sendRequest(). yield PushNotificationService.unregister( - 'https://example.edu/page/1'); + 'https://example.edu/page/1', ''); let record = yield db.getByKeyID( '87902e90-c57e-4d18-8354-013f4a556559'); ok(!record, 'Failed to delete unregistered record'); yield PushNotificationService.unregister( - 'https://example.net/page/1'); + 'https://example.net/page/1', ''); record = yield db.getByKeyID( '057caa8f-9b99-47ff-891c-adad18ce603e'); ok(!record, diff --git a/dom/push/test/xpcshell/test_unregister_not_found.js b/dom/push/test/xpcshell/test_unregister_not_found.js index b45c6328f28f..184ac0760db7 100644 --- a/dom/push/test/xpcshell/test_unregister_not_found.js +++ b/dom/push/test/xpcshell/test_unregister_not_found.js @@ -29,7 +29,8 @@ add_task(function* test_unregister_not_found() { }); let promise = PushNotificationService.unregister( - 'https://example.net/nonexistent'); + 'https://example.net/nonexistent', + { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }); yield rejects(promise, function(error) { return error == 'NotFoundError'; }, 'Wrong error for nonexistent scope'); diff --git a/dom/push/test/xpcshell/test_unregister_success.js b/dom/push/test/xpcshell/test_unregister_success.js index 95a797944758..4d3e603c9327 100644 --- a/dom/push/test/xpcshell/test_unregister_success.js +++ b/dom/push/test/xpcshell/test_unregister_success.js @@ -20,6 +20,7 @@ add_task(function* test_unregister_success() { channelID, pushEndpoint: 'https://example.org/update/unregister-success', scope: 'https://example.com/page/unregister-success', + originAttributes: '', version: 1 }); @@ -51,7 +52,7 @@ add_task(function* test_unregister_success() { }); yield PushNotificationService.unregister( - 'https://example.com/page/unregister-success'); + 'https://example.com/page/unregister-success', ''); let record = yield db.getByKeyID(channelID); ok(!record, 'Unregister did not remove record'); diff --git a/dom/push/test/xpcshell/test_webapps_cleardata.js b/dom/push/test/xpcshell/test_webapps_cleardata.js new file mode 100644 index 000000000000..567ab4298cfe --- /dev/null +++ b/dom/push/test/xpcshell/test_webapps_cleardata.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const {PushDB, PushService, PushServiceWebSocket} = serviceExports; + +const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f'; +const channelIDs = ['0ef2ad4a-6c49-41ad-af6e-95d2425276bf', '4818b54a-97c5-4277-ad5d-0bfe630e4e50']; +var channelIDCounter = 0; + +function run_test() { + do_get_profile(); + setPrefs({ + userAgentID, + requestTimeout: 1000, + retryBaseInterval: 150 + }); + disableServiceWorkerEvents( + 'https://example.org/1' + ); + run_next_test(); +} + +add_task(function* test_webapps_cleardata() { + let db = PushServiceWebSocket.newPushDB(); + do_register_cleanup(() => {return db.drop().then(_ => db.close());}); + + PushService._generateID = () => channelID; + PushService.init({ + serverURI: "wss://push.example.org", + 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'); + this.serverSendMsg(JSON.stringify({ + messageType: 'register', + status: 200, + channelID: data.channelID, + uaid: userAgentID, + pushEndpoint: 'https://example.com/update/' + Math.random(), + })); + } + }); + } + }); + + let registers = yield Promise.all([ + PushNotificationService.register( + 'https://example.org/1', + ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })), + PushNotificationService.register( + 'https://example.org/1', + ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })), + ]); + + Services.obs.notifyObservers( + { appId: 1, browserOnly: false, + QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])}, + "webapps-clear-data", ""); + + let waitAWhile = new Promise(function(res) { + setTimeout(res, 2000); + }); + yield waitAWhile; + + let registration; + registration = yield PushNotificationService.registration( + 'https://example.org/1', + ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: false })); + ok(!registration, 'Registration for { 1, false } should not exist.'); + + registration = yield PushNotificationService.registration( + 'https://example.org/1', + ChromeUtils.originAttributesToSuffix({ appId: 1, inBrowser: true })); + ok(registration, 'Registration for { 1, true } should still exist.'); +}); + diff --git a/dom/push/test/xpcshell/xpcshell.ini b/dom/push/test/xpcshell/xpcshell.ini index 39a5e341784e..c26d7f842c13 100644 --- a/dom/push/test/xpcshell/xpcshell.ini +++ b/dom/push/test/xpcshell/xpcshell.ini @@ -30,6 +30,7 @@ skip-if = toolkit == 'android' [test_unregister_invalid_json.js] [test_unregister_not_found.js] [test_unregister_success.js] +[test_webapps_cleardata.js] #http2 test [test_resubscribe_4xxCode_http2.js] [test_resubscribe_5xxCode_http2.js] diff --git a/dom/webidl/ChromeUtils.webidl b/dom/webidl/ChromeUtils.webidl index 35b8aead9055..23ba32fffe6a 100644 --- a/dom/webidl/ChromeUtils.webidl +++ b/dom/webidl/ChromeUtils.webidl @@ -18,6 +18,14 @@ interface ChromeUtils : ThreadSafeChromeUtils { */ static ByteString originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs); + + /** + * A helper that converts OriginAttributesDictionary to a opaque suffix string. + * + * @param originAttrs The originAttributes from the caller. + */ + static ByteString + originAttributesToSuffix(optional OriginAttributesDictionary originAttrs); }; /** diff --git a/dom/webidl/PushSubscription.webidl b/dom/webidl/PushSubscription.webidl index 42423798f0fc..d97acd2cd867 100644 --- a/dom/webidl/PushSubscription.webidl +++ b/dom/webidl/PushSubscription.webidl @@ -7,8 +7,10 @@ * https://w3c.github.io/push-api/ */ +interface Principal; + [JSImplementation="@mozilla.org/push/PushSubscription;1", - Constructor(DOMString pushEndpoint, DOMString scope, DOMString pageURL), ChromeOnly] + Constructor(DOMString pushEndpoint, DOMString scope, Principal principal), ChromeOnly] interface PushSubscription { readonly attribute USVString endpoint; diff --git a/dom/workers/ServiceWorkerManager.cpp b/dom/workers/ServiceWorkerManager.cpp index 2bd1525f091b..e8e82489922d 100644 --- a/dom/workers/ServiceWorkerManager.cpp +++ b/dom/workers/ServiceWorkerManager.cpp @@ -2200,16 +2200,15 @@ public: #endif /* ! MOZ_SIMPLEPUSH */ NS_IMETHODIMP -ServiceWorkerManager::SendPushEvent(JS::Handle aOriginAttributes, +ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes, const nsACString& aScope, - const nsAString& aData, - JSContext* aCx) + const nsAString& aData) { #ifdef MOZ_SIMPLEPUSH return NS_ERROR_NOT_AVAILABLE; #else OriginAttributes attrs; - if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + if (!attrs.PopulateFromSuffix(aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -2226,7 +2225,9 @@ ServiceWorkerManager::SendPushEvent(JS::Handle aOriginAttributes, new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData, serviceWorkerHandle); - if (NS_WARN_IF(!r->Dispatch(aCx))) { + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { return NS_ERROR_FAILURE; } @@ -2235,15 +2236,14 @@ ServiceWorkerManager::SendPushEvent(JS::Handle aOriginAttributes, } NS_IMETHODIMP -ServiceWorkerManager::SendPushSubscriptionChangeEvent(JS::Handle aOriginAttributes, - const nsACString& aScope, - JSContext* aCx) +ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes, + const nsACString& aScope) { #ifdef MOZ_SIMPLEPUSH return NS_ERROR_NOT_AVAILABLE; #else OriginAttributes attrs; - if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + if (!attrs.PopulateFromSuffix(aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } @@ -2259,7 +2259,9 @@ ServiceWorkerManager::SendPushSubscriptionChangeEvent(JS::Handle aOri new SendPushSubscriptionChangeEventRunnable( serviceWorker->GetWorkerPrivate(), serviceWorkerHandle); - if (NS_WARN_IF(!r->Dispatch(aCx))) { + AutoJSAPI jsapi; + jsapi.Init(); + if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) { return NS_ERROR_FAILURE; } diff --git a/toolkit/content/aboutServiceWorkers.js b/toolkit/content/aboutServiceWorkers.js index 14ab0ce57dcc..b56b7716b664 100644 --- a/toolkit/content/aboutServiceWorkers.js +++ b/toolkit/content/aboutServiceWorkers.js @@ -120,7 +120,7 @@ function display(info) { createItem(bundle.GetStringFromName('waitingCacheName'), info.waitingCacheName); let pushItem = createItem(bundle.GetStringFromName('pushEndpoint'), bundle.GetStringFromName('waiting')); - PushNotificationService.registration(info.scope).then( + PushNotificationService.registration(info.scope, info.principal.originAttributes).then( pushRecord => { pushItem.data = JSON.stringify(pushRecord); },