Bug 1166350 - Push fixes for principals. r=kitcambridge,bholley

Fix xpcshell tests.
Add support for webapps-clear-data.
Trash old regs on idb version upgrade.
Use principal for permission check.
use principal in PushSubscription.

--HG--
extra : source : d7554019b424327a3271e2c0debda995fff36cb5
extra : intermediate-source : be40dea6534771bdeedc9f7c6ccd8bbddb6e41c2
This commit is contained in:
Nikhil Marathe 2015-06-24 13:34:54 -07:00
Родитель 552b7cd770
Коммит b8eea4d16b
44 изменённых файлов: 438 добавлений и 147 удалений

Просмотреть файл

@ -19,5 +19,14 @@ ChromeUtils::OriginAttributesToCookieJar(GlobalObject& aGlobal,
attrs.CookieJar(aCookieJar); 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 dom
} // namespace mozilla } // namespace mozilla

Просмотреть файл

@ -44,6 +44,11 @@ public:
OriginAttributesToCookieJar(dom::GlobalObject& aGlobal, OriginAttributesToCookieJar(dom::GlobalObject& aGlobal,
const dom::OriginAttributesDictionary& aAttrs, const dom::OriginAttributesDictionary& aAttrs,
nsCString& aCookieJar); nsCString& aCookieJar);
static void
OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
const dom::OriginAttributesDictionary& aAttrs,
nsCString& aSuffix);
}; };
} // namespace dom } // namespace dom

Просмотреть файл

@ -20,7 +20,7 @@ interface nsIServiceWorkerUnregisterCallback : nsISupports
void unregisterFailed(); void unregisterFailed();
}; };
[scriptable, builtinclass, uuid(103763c8-53ba-42e4-8b26-e601d5bc4afe)] [scriptable, builtinclass, uuid(e633b73b-a734-4d04-a09c-b7779a439f3f)]
interface nsIServiceWorkerInfo : nsISupports interface nsIServiceWorkerInfo : nsISupports
{ {
readonly attribute nsIPrincipal principal; readonly attribute nsIPrincipal principal;
@ -33,7 +33,7 @@ interface nsIServiceWorkerInfo : nsISupports
readonly attribute DOMString waitingCacheName; readonly attribute DOMString waitingCacheName;
}; };
[scriptable, builtinclass, uuid(aee94712-9adb-4c0b-80a7-a8df34dfa2e8)] [scriptable, builtinclass, uuid(e9abb123-0099-4d9e-85db-c8cd0aff19e6)]
interface nsIServiceWorkerManager : nsISupports interface nsIServiceWorkerManager : nsISupports
{ {
/** /**
@ -126,11 +126,11 @@ interface nsIServiceWorkerManager : nsISupports
in nsIServiceWorkerUnregisterCallback aCallback, in nsIServiceWorkerUnregisterCallback aCallback,
in DOMString aScope); in DOMString aScope);
[implicit_jscontext] void sendPushEvent(in jsval aOriginAttributes, void sendPushEvent(in ACString aOriginAttributes,
in ACString aScope, in ACString aScope,
in DOMString aData); in DOMString aData);
[implicit_jscontext] void sendPushSubscriptionChangeEvent(in jsval aOriginAttributes, void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
in ACString scope); in ACString scope);
void updateAllRegistrations(); void updateAllRegistrations();
}; };

Просмотреть файл

@ -11,7 +11,7 @@
* uses service workers to notify applications. This interface exists to allow * uses service workers to notify applications. This interface exists to allow
* privileged code to receive messages without migrating to service workers. * 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 interface nsIPushNotificationService : nsISupports
{ {
/** /**
@ -32,20 +32,20 @@ interface nsIPushNotificationService : nsISupports
* Servers may drop subscriptions at any time, so callers should recreate * Servers may drop subscriptions at any time, so callers should recreate
* subscriptions if desired. * 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 * Revokes a push subscription for the given |scope|. Returns a promise
* for the revoked subscription record, or `null` if the |scope| is not * for the revoked subscription record, or `null` if the |scope| is not
* subscribed to receive notifications. * 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 * Returns a promise for the subscription record associated with the
* given |scope|, or `null` if the |scope| does not have a subscription. * 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 * Clear all subscriptions

Просмотреть файл

@ -23,11 +23,11 @@ Cu.import("resource://gre/modules/AppsUtils.jsm");
const PUSH_SUBSCRIPTION_CID = Components.ID("{CA86B665-BEDA-4212-8D0F-5C9F65270B58}"); const PUSH_SUBSCRIPTION_CID = Components.ID("{CA86B665-BEDA-4212-8D0F-5C9F65270B58}");
function PushSubscription(pushEndpoint, scope, pageURL) { function PushSubscription(pushEndpoint, scope, principal) {
debug("PushSubscription Constructor"); debug("PushSubscription Constructor");
this._pushEndpoint = pushEndpoint; this._pushEndpoint = pushEndpoint;
this._scope = scope; this._scope = scope;
this._pageURL = pageURL; this._principal = principal;
} }
PushSubscription.prototype = { PushSubscription.prototype = {
@ -53,10 +53,10 @@ PushSubscription.prototype = {
.getService(Ci.nsISyncMessageSender); .getService(Ci.nsISyncMessageSender);
}, },
__init: function(endpoint, scope, pageURL) { __init: function(endpoint, scope, principal) {
this._pushEndpoint = endpoint; this._pushEndpoint = endpoint;
this._scope = scope; this._scope = scope;
this._pageURL = pageURL; this._principal = principal;
}, },
get endpoint() { get endpoint() {
@ -71,11 +71,10 @@ PushSubscription.prototype = {
reject: reject }); reject: reject });
this._cpmm.sendAsyncMessage("Push:Unregister", { this._cpmm.sendAsyncMessage("Push:Unregister", {
pageURL: this._pageURL,
scope: this._scope, scope: this._scope,
pushEndpoint: this._pushEndpoint, pushEndpoint: this._pushEndpoint,
requestID: resolverId requestID: resolverId
}); }, null, this._principal);
}.bind(this); }.bind(this);
return this.createPromise(promiseInit); return this.createPromise(promiseInit);
@ -133,7 +132,6 @@ Push.prototype = {
gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug"); gDebuggingEnabled = Services.prefs.getBoolPref("dom.push.debug");
debug("init()"); debug("init()");
this._pageURL = aWindow.document.nodePrincipal.URI;
this._window = aWindow; this._window = aWindow;
this.initDOMRequestHelper(aWindow, [ this.initDOMRequestHelper(aWindow, [
@ -145,6 +143,7 @@ Push.prototype = {
this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
.getService(Ci.nsISyncMessageSender); .getService(Ci.nsISyncMessageSender);
this._principal = aWindow.document.nodePrincipal;
}, },
setScope: function(scope){ setScope: function(scope){
@ -160,8 +159,6 @@ Push.prototype = {
let permValue = let permValue =
Services.perms.testExactPermissionFromPrincipal(principal, type); Services.perms.testExactPermissionFromPrincipal(principal, type);
debug("Existing permission " + permValue);
if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) { if (permValue == Ci.nsIPermissionManager.ALLOW_ACTION) {
aAllowCallback(); aAllowCallback();
return; return;
@ -219,9 +216,9 @@ Push.prototype = {
switch (aMessage.name) { switch (aMessage.name) {
case "PushService:Register:OK": case "PushService:Register:OK":
{ {
let subscription = new this._window.PushSubscription(json.pushEndpoint, let subscription =
this._scope, new this._window.PushSubscription(json.pushEndpoint, this._scope,
this._pageURL.spec); this._principal);
resolver.resolve(subscription); resolver.resolve(subscription);
break; break;
} }
@ -232,8 +229,9 @@ Push.prototype = {
{ {
let subscription = null; let subscription = null;
try { try {
subscription = new this._window.PushSubscription(json.registration.pushEndpoint, subscription =
this._scope, this._pageURL.spec); new this._window.PushSubscription(json.registration.pushEndpoint,
this._scope, this._principal);
} catch(error) { } catch(error) {
} }
resolver.resolve(subscription); resolver.resolve(subscription);
@ -255,10 +253,9 @@ Push.prototype = {
this.askPermission( this.askPermission(
function() { function() {
this._cpmm.sendAsyncMessage("Push:Register", { this._cpmm.sendAsyncMessage("Push:Register", {
pageURL: this._pageURL.spec,
scope: this._scope, scope: this._scope,
requestID: resolverId requestID: resolverId
}); }, null, this._principal);
}.bind(this), }.bind(this),
function() { function() {
@ -279,10 +276,9 @@ Push.prototype = {
this.askPermission( this.askPermission(
function() { function() {
this._cpmm.sendAsyncMessage("Push:Registration", { this._cpmm.sendAsyncMessage("Push:Registration", {
pageURL: this._pageURL.spec,
scope: this._scope, scope: this._scope,
requestID: resolverId requestID: resolverId
}); }, null, this._principal);
}.bind(this), }.bind(this),
function() { function() {
@ -294,11 +290,14 @@ Push.prototype = {
}, },
hasPermission: function() { hasPermission: function() {
debug("getSubscription()" + this._scope); debug("hasPermission()" + this._scope);
let p = this.createPromise(function(resolve, reject) { let p = this.createPromise(function(resolve, reject) {
let permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); let permissionManager = Cc["@mozilla.org/permissionmanager;1"]
let permission = permissionManager.testExactPermission(this._pageURL, "push"); .getService(Ci.nsIPermissionManager);
let permission =
permissionManager.testExactPermissionFromPrincipal(this._principal,
"push");
let pushPermissionStatus = "default"; let pushPermissionStatus = "default";
if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) { if (permission == Ci.nsIPermissionManager.ALLOW_ACTION) {

Просмотреть файл

@ -147,9 +147,15 @@ this.PushDB.prototype = {
); );
}, },
// Perform a unique match against { scope, originAttributes }
getByScope: function(aScope) { getByIdentifiers: function(aPageRecord) {
debug("getByScope() " + aScope); 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) => return new Promise((resolve, reject) =>
this.newTxn( this.newTxn(
@ -158,11 +164,11 @@ this.PushDB.prototype = {
function txnCb(aTxn, aStore) { function txnCb(aTxn, aStore) {
aTxn.result = undefined; aTxn.result = undefined;
let index = aStore.index("scope"); let index = aStore.index("identifiers");
index.get(aScope).onsuccess = function setTxnResult(aEvent) { let request = index.get(IDBKeyRange.only([aPageRecord.scope, aPageRecord.originAttributes]));
request.onsuccess = function setTxnResult(aEvent) {
aTxn.result = aEvent.target.result; aTxn.result = aEvent.target.result;
debug("Fetch successful " + aEvent.target.result); }
};
}, },
resolve, resolve,
reject 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() { getAllKeyIDs: function() {
debug("getAllKeyIDs()"); debug("getAllKeyIDs()");
@ -178,6 +215,7 @@ this.PushDB.prototype = {
"readonly", "readonly",
this._dbStoreName, this._dbStoreName,
function txnCb(aTxn, aStore) { function txnCb(aTxn, aStore) {
aTxn.result = undefined;
aStore.mozGetAll().onsuccess = function(event) { aStore.mozGetAll().onsuccess = function(event) {
aTxn.result = event.target.result; aTxn.result = event.target.result;
}; };

Просмотреть файл

@ -38,16 +38,16 @@ PushNotificationService.prototype = {
Ci.nsISupportsWeakReference, Ci.nsISupportsWeakReference,
Ci.nsIPushNotificationService]), Ci.nsIPushNotificationService]),
register: function register(scope, pageURL) { register: function register(scope, originAttributes) {
return PushService._register({scope, pageURL}); return PushService._register({scope, originAttributes});
}, },
unregister: function unregister(scope) { unregister: function unregister(scope, originAttributes) {
return PushService._unregister({scope}); return PushService._unregister({scope, originAttributes});
}, },
registration: function registration(scope) { registration: function registration(scope, originAttributes) {
return PushService._registration({scope}); return PushService._registration({scope, originAttributes});
}, },
clearAll: function clearAll() { clearAll: function clearAll() {

Просмотреть файл

@ -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) { _setState: function(aNewState) {
debug("new state: " + aNewState + " old state: " + this._state); debug("new state: " + aNewState + " old state: " + this._state);
@ -233,24 +253,41 @@ this.PushService = {
return; return;
} }
// TODO 1149274. We should support site permissions as well as a way to var originAttributes =
// go from manifest url to 'all scopes registered for push in this app' ChromeUtils.originAttributesToSuffix({ appId: data.appId,
let appsService = Cc["@mozilla.org/AppsService;1"] inBrowser: data.browserOnly });
.getService(Ci.nsIAppsService); this._db.getAllByOriginAttributes(originAttributes)
let scope = appsService.getScopeByLocalId(data.appId); .then(records => {
if (!scope) { records.forEach(record => {
debug("webapps-clear-data: No scope found for " + data.appId); this._db.delete(this._service.getKeyFromRecord(record))
return; .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) // courtesy, but don't establish a connection
.then(record => // just for it
Promise.all([ if (this._ws) {
this._db.delete(this._service.getKeyFromRecord(record)), debug("Had a connection, so telling the server");
this._sendRequest("unregister", record) this._sendRequest("unregister", {channelID: records.channelID})
]) .catch(function(e) {
).catch(_ => { debug("Unregister errored " + e);
debug("webapps-clear-data: Error in getByScope(" + scope + ")"); });
}
throw "Database error";
});
});
}, _ => {
debug("webapps-clear-data: Error in getAllByOriginAttributes(" + originAttributes + ")");
}); });
break; break;
@ -623,13 +660,9 @@ this.PushService = {
// records are objects describing the registration as stored in IndexedDB. // records are objects describing the registration as stored in IndexedDB.
return this._db.getAllKeyIDs() return this._db.getAllKeyIDs()
.then(records => { .then(records => {
let scopes = new Set();
for (let record of records) {
scopes.add(record.scope);
}
let globalMM = Cc['@mozilla.org/globalmessagemanager;1'] let globalMM = Cc['@mozilla.org/globalmessagemanager;1']
.getService(Ci.nsIMessageListenerManager); .getService(Ci.nsIMessageListenerManager);
for (let scope of scopes) { for (let record of records) {
// Notify XPCOM observers. // Notify XPCOM observers.
Services.obs.notifyObservers( Services.obs.notifyObservers(
null, null,
@ -638,8 +671,8 @@ this.PushService = {
); );
let data = { let data = {
originAttributes: {}, // TODO bug 1166350 originAttributes: record.originAttributes,
scope: scope scope: record.scope
}; };
globalMM.broadcastAsyncMessage('pushsubscriptionchange', data); globalMM.broadcastAsyncMessage('pushsubscriptionchange', data);
@ -659,7 +692,7 @@ this.PushService = {
); );
let data = { let data = {
originAttributes: {}, // TODO bug 1166350 originAttributes: record.originAttributes,
scope: record.scope scope: record.scope
}; };
@ -681,7 +714,7 @@ this.PushService = {
); );
let data = { let data = {
originAttributes: {}, // TODO bug 1166350 originAttributes: record.originAttributes,
scope: record.scope scope: record.scope
}; };
@ -695,7 +728,8 @@ this.PushService = {
}, },
_notifyApp: function(aPushRecord, message) { _notifyApp: function(aPushRecord, message) {
if (!aPushRecord || !aPushRecord.scope) { if (!aPushRecord || !aPushRecord.scope ||
aPushRecord.originAttributes === undefined) {
debug("notifyApp() something is undefined. Dropping notification: " + debug("notifyApp() something is undefined. Dropping notification: " +
JSON.stringify(aPushRecord) ); JSON.stringify(aPushRecord) );
return; return;
@ -728,7 +762,7 @@ this.PushService = {
// TODO data. // TODO data.
let data = { let data = {
payload: message, payload: message,
originAttributes: {}, // TODO bug 1166350 originAttributes: aPushRecord.originAttributes,
scope: aPushRecord.scope scope: aPushRecord.scope
}; };
@ -745,7 +779,7 @@ this.PushService = {
return this._db.getAllKeyIDs(); return this._db.getAllKeyIDs();
}, },
_sendRequest(action, aRecord) { _sendRequest: function(action, aRecord) {
if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) { if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
return Promise.reject({state: 0, error: "Service not active"}); return Promise.reject({state: 0, error: "Service not active"});
} else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) { } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
@ -759,37 +793,35 @@ this.PushService = {
* navigator.push, identifying the sending page and other fields. * navigator.push, identifying the sending page and other fields.
*/ */
_registerWithServer: function(aPageRecord) { _registerWithServer: function(aPageRecord) {
debug("registerWithServer()"); debug("registerWithServer()" + JSON.stringify(aPageRecord));
return this._sendRequest("register", aPageRecord) return this._sendRequest("register", aPageRecord)
.then(pushRecord => this._onRegisterSuccess(pushRecord), .then(pushRecord => this._onRegisterSuccess(pushRecord),
err => this._onRegisterError(err)) err => this._onRegisterError(err))
.then(pushRecord => { .then(pushRecord => {
if (this._pendingRegisterRequest[aPageRecord.scope]) { this._deletePendingRequest(aPageRecord);
delete this._pendingRegisterRequest[aPageRecord.scope];
}
return pushRecord; return pushRecord;
}, err => { }, err => {
if (this._pendingRegisterRequest[aPageRecord.scope]) { this._deletePendingRequest(aPageRecord);
delete this._pendingRegisterRequest[aPageRecord.scope];
}
throw err; throw err;
}); });
}, },
_register: function(aPageRecord) { _register: function(aPageRecord) {
debug("_register()");
if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
return Promise.reject({state: 0, error: "NotFoundError"});
}
return this._checkActivated() return this._checkActivated()
.then(_ => this._db.getByScope(aPageRecord.scope)) .then(_ => this._db.getByIdentifiers(aPageRecord))
.then(pushRecord => { .then(pushRecord => {
if (pushRecord === undefined) { if (pushRecord === undefined) {
if (this._pendingRegisterRequest[aPageRecord.scope]) { return this._lookupOrPutPendingRequest(aPageRecord);
return this._pendingRegisterRequest[aPageRecord.scope];
}
return this._pendingRegisterRequest[aPageRecord.scope] = this._registerWithServer(aPageRecord);
} }
return pushRecord; return pushRecord;
}, error => { }, error => {
debug("getByScope failed"); debug("getByIdentifiers failed");
throw error; throw error;
}); });
}, },
@ -833,10 +865,39 @@ this.PushService = {
return; return;
} }
let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); if (!aMessage.target.assertPermission("push")) {
let json = aMessage.data; 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) { register: function(aPageRecord, aMessageManager) {
@ -883,13 +944,12 @@ this.PushService = {
*/ */
_unregister: function(aPageRecord) { _unregister: function(aPageRecord) {
debug("_unregister()"); debug("_unregister()");
if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
if (!aPageRecord.scope) {
return Promise.reject({state: 0, error: "NotFoundError"}); return Promise.reject({state: 0, error: "NotFoundError"});
} }
return this._checkActivated() return this._checkActivated()
.then(_ => this._db.getByScope(aPageRecord.scope)) .then(_ => this._db.getByIdentifiers(aPageRecord))
.then(record => { .then(record => {
// If the endpoint didn't exist, let's just fail. // If the endpoint didn't exist, let's just fail.
if (record === undefined) { if (record === undefined) {
@ -932,12 +992,12 @@ this.PushService = {
*/ */
_registration: function(aPageRecord) { _registration: function(aPageRecord) {
debug("_registration()"); debug("_registration()");
if (!aPageRecord.scope) { if (!aPageRecord.scope || aPageRecord.originAttributes === undefined) {
return Promise.reject({state: 0, error: "Database error"}); return Promise.reject({state: 0, error: "NotFoundError"});
} }
return this._checkActivated() return this._checkActivated()
.then(_ => this._db.getByScope(aPageRecord.scope)) .then(_ => this._db.getByIdentifiers(aPageRecord))
.then(pushRecord => { .then(pushRecord => {
if (!pushRecord) { if (!pushRecord) {
return null; return null;

Просмотреть файл

@ -33,7 +33,7 @@ function debug(s) {
} }
const kPUSHHTTP2DB_DB_NAME = "pushHttp2"; 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"; const kPUSHHTTP2DB_STORE_NAME = "pushHttp2";
/** /**
@ -291,6 +291,7 @@ SubscriptionListener.prototype = {
pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint, pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
pageURL: this._subInfo.record.pageURL, pageURL: this._subInfo.record.pageURL,
scope: this._subInfo.record.scope, scope: this._subInfo.record.scope,
originAttributes: this._subInfo.record.originAttributes,
pushCount: 0, pushCount: 0,
lastPush: 0 lastPush: 0
}; };
@ -385,15 +386,33 @@ this.PushServiceHttp2 = {
aDbInstance) { aDbInstance) {
debug("upgradeSchemaHttp2()"); 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, let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName,
{ keyPath: "subscriptionUri" }); { keyPath: "subscriptionUri" });
// index to fetch records based on endpoints. used by unregister // index to fetch records based on endpoints. used by unregister
objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true }); objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });
// index to fetch records per scope, so we can identify endpoints // index to fetch records by identifiers.
// associated with an app. // In the current security model, the originAttributes distinguish between
objectStore.createIndex("scope", "scope", { unique: true }); // 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) { getKeyFromRecord: function(aRecord) {

Просмотреть файл

@ -32,7 +32,7 @@ var threadManager = Cc["@mozilla.org/thread-manager;1"]
.getService(Ci.nsIThreadManager); .getService(Ci.nsIThreadManager);
const kPUSHWSDB_DB_NAME = "pushapi"; 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 kPUSHWSDB_STORE_NAME = "pushapi";
const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent const kUDP_WAKEUP_WS_STATUS_CODE = 4774; // WebSocket Close status code sent
@ -132,15 +132,33 @@ this.PushServiceWebSocket = {
aDbInstance) { aDbInstance) {
debug("upgradeSchemaWS()"); 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, let objectStore = aDb.createObjectStore(aDbInstance._dbStoreName,
{ keyPath: "channelID" }); { keyPath: "channelID" });
// index to fetch records based on endpoints. used by unregister // index to fetch records based on endpoints. used by unregister
objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true }); objectStore.createIndex("pushEndpoint", "pushEndpoint", { unique: true });
// index to fetch records per scope, so we can identify endpoints // index to fetch records by identifiers.
// associated with an app. // In the current security model, the originAttributes distinguish between
objectStore.createIndex("scope", "scope", { unique: true }); // 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) { getKeyFromRecord: function(aRecord) {
@ -870,10 +888,12 @@ this.PushServiceWebSocket = {
pushEndpoint: reply.pushEndpoint, pushEndpoint: reply.pushEndpoint,
pageURL: tmp.record.pageURL, pageURL: tmp.record.pageURL,
scope: tmp.record.scope, scope: tmp.record.scope,
originAttributes: tmp.record.originAttributes,
pushCount: 0, pushCount: 0,
lastPush: 0, lastPush: 0,
version: null version: null
}; };
dump("PushWebSocket " + JSON.stringify(record));
tmp.resolve(record); tmp.resolve(record);
} else { } else {
tmp.reject(reply); tmp.reject(reply);

Просмотреть файл

@ -120,6 +120,7 @@ http://creativecommons.org/licenses/publicdomain/
SpecialPowers.pushPrefEnv({"set": [ SpecialPowers.pushPrefEnv({"set": [
["dom.push.enabled", true], ["dom.push.enabled", true],
["dom.push.debug", true],
["dom.serviceWorkers.exemptFromPerDomainMax", true], ["dom.serviceWorkers.exemptFromPerDomainMax", true],
["dom.serviceWorkers.enabled", true], ["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true] ["dom.serviceWorkers.testing.enabled", true]

Просмотреть файл

@ -25,16 +25,19 @@ add_task(function* test_notification_ack() {
channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c', channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
pushEndpoint: 'https://example.com/update/1', pushEndpoint: 'https://example.com/update/1',
scope: 'https://example.org/1', scope: 'https://example.org/1',
originAttributes: '',
version: 1 version: 1
}, { }, {
channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305', channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
pushEndpoint: 'https://example.com/update/2', pushEndpoint: 'https://example.com/update/2',
scope: 'https://example.org/2', scope: 'https://example.org/2',
originAttributes: '',
version: 2 version: 2
}, { }, {
channelID: '5477bfda-22db-45d4-9614-fee369630260', channelID: '5477bfda-22db-45d4-9614-fee369630260',
pushEndpoint: 'https://example.com/update/3', pushEndpoint: 'https://example.com/update/3',
scope: 'https://example.org/3', scope: 'https://example.org/3',
originAttributes: '',
version: 3 version: 3
}]; }];
for (let record of records) { for (let record of records) {

Просмотреть файл

@ -23,11 +23,13 @@ add_task(function* test_notification_duplicate() {
channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc', channelID: '8d2d9400-3597-4c5a-8a38-c546b0043bcc',
pushEndpoint: 'https://example.org/update/1', pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.com/1', scope: 'https://example.com/1',
originAttributes: "",
version: 2 version: 2
}, { }, {
channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0', channelID: '27d1e393-03ef-4c72-a5e6-9e890dfccad0',
pushEndpoint: 'https://example.org/update/2', pushEndpoint: 'https://example.org/update/2',
scope: 'https://example.com/2', scope: 'https://example.com/2',
originAttributes: "",
version: 2 version: 2
}]; }];
for (let record of records) { for (let record of records) {

Просмотреть файл

@ -19,20 +19,25 @@ function run_test() {
add_task(function* test_notification_error() { add_task(function* test_notification_error() {
let db = PushServiceWebSocket.newPushDB(); let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());}); do_register_cleanup(() => {return db.drop().then(_ => db.close());});
let originAttributes = '';
let records = [{ let records = [{
channelID: 'f04f1e46-9139-4826-b2d1-9411b0821283', channelID: 'f04f1e46-9139-4826-b2d1-9411b0821283',
pushEndpoint: 'https://example.org/update/success-1', pushEndpoint: 'https://example.org/update/success-1',
scope: 'https://example.com/a', scope: 'https://example.com/a',
originAttributes: originAttributes,
version: 1 version: 1
}, { }, {
channelID: '3c3930ba-44de-40dc-a7ca-8a133ec1a866', channelID: '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
pushEndpoint: 'https://example.org/update/error', pushEndpoint: 'https://example.org/update/error',
scope: 'https://example.com/b', scope: 'https://example.com/b',
originAttributes: originAttributes,
version: 2 version: 2
}, { }, {
channelID: 'b63f7bef-0a0d-4236-b41e-086a69dfd316', channelID: 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
pushEndpoint: 'https://example.org/update/success-2', pushEndpoint: 'https://example.org/update/success-2',
scope: 'https://example.com/c', scope: 'https://example.com/c',
originAttributes: originAttributes,
version: 3 version: 3
}]; }];
for (let record of records) { for (let record of records) {
@ -107,19 +112,22 @@ add_task(function* test_notification_error() {
yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT, yield waitForPromise(ackDefer.promise, DEFAULT_TIMEOUT,
'Timed out waiting for acknowledgements'); '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', equal(aRecord.channelID, 'f04f1e46-9139-4826-b2d1-9411b0821283',
'Wrong channel ID for record A'); 'Wrong channel ID for record A');
strictEqual(aRecord.version, 2, strictEqual(aRecord.version, 2,
'Should return the new version for record A'); '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', equal(bRecord.channelID, '3c3930ba-44de-40dc-a7ca-8a133ec1a866',
'Wrong channel ID for record B'); 'Wrong channel ID for record B');
strictEqual(bRecord.version, 2, strictEqual(bRecord.version, 2,
'Should return the previous version for record B'); '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', equal(cRecord.channelID, 'b63f7bef-0a0d-4236-b41e-086a69dfd316',
'Wrong channel ID for record C'); 'Wrong channel ID for record C');
strictEqual(cRecord.version, 4, strictEqual(cRecord.version, 4,

Просмотреть файл

@ -21,6 +21,7 @@ add_task(function* test_notification_version_string() {
channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b', channelID: '6ff97d56-d0c0-43bc-8f5b-61b855e1d93b',
pushEndpoint: 'https://example.org/updates/1', pushEndpoint: 'https://example.org/updates/1',
scope: 'https://example.com/page/1', scope: 'https://example.com/page/1',
originAttributes: '',
version: 2 version: 2
}); });

Просмотреть файл

@ -84,7 +84,8 @@ add_task(function* test1() {
}); });
let newRecord = yield PushNotificationService.register( 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'; var subscriptionUri = serverURL + '/subscription';

Просмотреть файл

@ -47,7 +47,8 @@ add_task(function* test_register_case() {
}); });
let newRecord = yield waitForPromise( 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, DEFAULT_TIMEOUT,
'Mixed-case register response timed out' 'Mixed-case register response timed out'
); );

Просмотреть файл

@ -29,6 +29,7 @@ add_task(function* test_register_flush() {
channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74', channelID: '9bcc7efb-86c7-4457-93ea-e24e6eb59b74',
pushEndpoint: 'https://example.org/update/1', pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.com/page/1', scope: 'https://example.com/page/1',
originAttributes: '',
version: 2 version: 2
}; };
yield db.put(record); yield db.put(record);
@ -75,8 +76,7 @@ add_task(function* test_register_flush() {
}); });
let newRecord = yield PushNotificationService.register( let newRecord = yield PushNotificationService.register(
'https://example.com/page/2' 'https://example.com/page/2', '');
);
equal(newRecord.pushEndpoint, 'https://example.org/update/2', equal(newRecord.pushEndpoint, 'https://example.org/update/2',
'Wrong push endpoint in record'); 'Wrong push endpoint in record');
equal(newRecord.scope, 'https://example.com/page/2', equal(newRecord.scope, 'https://example.com/page/2',

Просмотреть файл

@ -48,7 +48,8 @@ add_task(function* test_register_invalid_channel() {
}); });
yield rejects( 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) { function(error) {
return error == 'Invalid channel ID'; return error == 'Invalid channel ID';
}, },

Просмотреть файл

@ -50,7 +50,8 @@ add_task(function* test_register_invalid_endpoint() {
yield rejects( yield rejects(
PushNotificationService.register( PushNotificationService.register(
'https://example.net/page/invalid-endpoint'), 'https://example.net/page/invalid-endpoint',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error && error.includes('Invalid pushEndpoint'); return error && error.includes('Invalid pushEndpoint');
}, },

Просмотреть файл

@ -49,7 +49,8 @@ add_task(function* test_register_invalid_json() {
}); });
yield rejects( 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) { function(error) {
return error == 'TimeoutError'; return error == 'TimeoutError';
}, },

Просмотреть файл

@ -53,7 +53,8 @@ add_task(function* test_register_no_id() {
}); });
yield rejects( yield rejects(
PushNotificationService.register('https://example.com/incomplete'), PushNotificationService.register('https://example.com/incomplete',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error == 'TimeoutError'; return error == 'TimeoutError';
}, },

Просмотреть файл

@ -45,10 +45,12 @@ add_task(function* test_register_request_queue() {
}); });
let firstRegister = PushNotificationService.register( 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( 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([ yield waitForPromise(Promise.all([

Просмотреть файл

@ -74,7 +74,8 @@ add_task(function* test_register_rollback() {
// Should return a rejected promise if storage fails. // Should return a rejected promise if storage fails.
yield rejects( 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) { function(error) {
return error == 'universe has imploded'; return error == 'universe has imploded';
}, },

Просмотреть файл

@ -57,7 +57,8 @@ add_task(function* test_register_success() {
}); });
let newRecord = yield PushNotificationService.register( 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, equal(newRecord.channelID, channelID,
'Wrong channel ID in registration record'); 'Wrong channel ID in registration record');

Просмотреть файл

@ -83,7 +83,8 @@ add_task(function* test_register_timeout() {
}); });
yield rejects( 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) { function(error) {
return error == 'TimeoutError'; return error == 'TimeoutError';
}, },

Просмотреть файл

@ -59,7 +59,8 @@ add_task(function* test_register_wrong_id() {
}); });
yield rejects( yield rejects(
PushNotificationService.register('https://example.com/mismatched'), PushNotificationService.register('https://example.com/mismatched',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error == 'TimeoutError'; return error == 'TimeoutError';
}, },

Просмотреть файл

@ -55,7 +55,8 @@ add_task(function* test_register_wrong_type() {
let promise = let promise =
yield rejects( yield rejects(
PushNotificationService.register('https://example.com/mistyped'), PushNotificationService.register('https://example.com/mistyped',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error == 'TimeoutError'; return error == 'TimeoutError';
}, },

Просмотреть файл

@ -21,7 +21,7 @@ add_task(function* test_registrations_error() {
serverURI: "wss://push.example.org/", serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(), networkInfo: new MockDesktopNetworkInfo(),
db: makeStub(db, { db: makeStub(db, {
getByScope(prev, scope) { getByIdentifiers(prev, scope) {
return Promise.reject('Database error'); return Promise.reject('Database error');
} }
}), }),
@ -31,7 +31,8 @@ add_task(function* test_registrations_error() {
}); });
yield rejects( yield rejects(
PushNotificationService.registration('https://example.net/1'), PushNotificationService.registration('https://example.net/1',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error == 'Database error'; return error == 'Database error';
}, },

Просмотреть файл

@ -18,7 +18,7 @@ add_task(function* test_registrations_error() {
serverURI: "https://push.example.org/", serverURI: "https://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(), networkInfo: new MockDesktopNetworkInfo(),
db: makeStub(db, { db: makeStub(db, {
getByScope(prev, scope) { getByIdentifiers() {
return Promise.reject('Database error'); return Promise.reject('Database error');
} }
}), }),

Просмотреть файл

@ -20,9 +20,9 @@ add_task(function* test_registration_missing_scope() {
} }
}); });
yield rejects( yield rejects(
PushNotificationService.registration(''), PushNotificationService.registration('', ''),
function(error) { function(error) {
return error.error == 'Database error'; return error.error == 'NotFoundError';
}, },
'Record missing page and manifest URLs' 'Record missing page and manifest URLs'
); );

Просмотреть файл

@ -24,6 +24,7 @@ add_task(function* test_registration_none() {
}); });
let registration = yield PushNotificationService.registration( 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'); ok(!registration, 'Should not open a connection without registration');
}); });

Просмотреть файл

@ -20,16 +20,19 @@ add_task(function* test_registration_success() {
channelID: 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b', channelID: 'bf001fe0-2684-42f2-bc4d-a3e14b11dd5b',
pushEndpoint: 'https://example.com/update/same-manifest/1', pushEndpoint: 'https://example.com/update/same-manifest/1',
scope: 'https://example.net/a', scope: 'https://example.net/a',
originAttributes: '',
version: 5 version: 5
}, { }, {
channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f', channelID: 'f6edfbcd-79d6-49b8-9766-48b9dcfeff0f',
pushEndpoint: 'https://example.com/update/same-manifest/2', pushEndpoint: 'https://example.com/update/same-manifest/2',
scope: 'https://example.net/b', scope: 'https://example.net/b',
originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42 }),
version: 10 version: 10
}, { }, {
channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728', channelID: 'b1cf38c9-6836-4d29-8a30-a3e98d59b728',
pushEndpoint: 'https://example.org/update/different-manifest', pushEndpoint: 'https://example.org/update/different-manifest',
scope: 'https://example.org/c', scope: 'https://example.org/c',
originAttributes: ChromeUtils.originAttributesToSuffix({ appId: 42, inBrowser: true }),
version: 15 version: 15
}]; }];
for (let record of records) { for (let record of records) {
@ -59,7 +62,7 @@ add_task(function* test_registration_success() {
}); });
let registration = yield PushNotificationService.registration( let registration = yield PushNotificationService.registration(
'https://example.net/a'); 'https://example.net/a', '');
equal( equal(
registration.pushEndpoint, registration.pushEndpoint,
'https://example.com/update/same-manifest/1', 'https://example.com/update/same-manifest/1',

Просмотреть файл

@ -29,7 +29,8 @@ add_task(function* test_unregister_empty_scope() {
}); });
yield rejects( yield rejects(
PushNotificationService.unregister(''), PushNotificationService.unregister('',
{ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }),
function(error) { function(error) {
return error.error == 'NotFoundError'; return error.error == 'NotFoundError';
}, },

Просмотреть файл

@ -20,6 +20,7 @@ add_task(function* test_unregister_error() {
channelID: channelID, channelID: channelID,
pushEndpoint: 'https://example.org/update/failure', pushEndpoint: 'https://example.org/update/failure',
scope: 'https://example.net/page/failure', scope: 'https://example.net/page/failure',
originAttributes: '',
version: 1 version: 1
}); });
@ -54,7 +55,7 @@ add_task(function* test_unregister_error() {
}); });
yield PushNotificationService.unregister( yield PushNotificationService.unregister(
'https://example.net/page/failure'); 'https://example.net/page/failure', '');
let result = yield db.getByKeyID(channelID); let result = yield db.getByKeyID(channelID);
ok(!result, 'Deleted push record exists'); ok(!result, 'Deleted push record exists');

Просмотреть файл

@ -24,11 +24,13 @@ add_task(function* test_unregister_invalid_json() {
channelID: '87902e90-c57e-4d18-8354-013f4a556559', channelID: '87902e90-c57e-4d18-8354-013f4a556559',
pushEndpoint: 'https://example.org/update/1', pushEndpoint: 'https://example.org/update/1',
scope: 'https://example.edu/page/1', scope: 'https://example.edu/page/1',
originAttributes: '',
version: 1 version: 1
}, { }, {
channelID: '057caa8f-9b99-47ff-891c-adad18ce603e', channelID: '057caa8f-9b99-47ff-891c-adad18ce603e',
pushEndpoint: 'https://example.com/update/2', pushEndpoint: 'https://example.com/update/2',
scope: 'https://example.net/page/1', scope: 'https://example.net/page/1',
originAttributes: '',
version: 1 version: 1
}]; }];
for (let record of records) { 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 // "unregister" is fire-and-forget: it's sent via _send(), not
// _sendRequest(). // _sendRequest().
yield PushNotificationService.unregister( yield PushNotificationService.unregister(
'https://example.edu/page/1'); 'https://example.edu/page/1', '');
let record = yield db.getByKeyID( let record = yield db.getByKeyID(
'87902e90-c57e-4d18-8354-013f4a556559'); '87902e90-c57e-4d18-8354-013f4a556559');
ok(!record, 'Failed to delete unregistered record'); ok(!record, 'Failed to delete unregistered record');
yield PushNotificationService.unregister( yield PushNotificationService.unregister(
'https://example.net/page/1'); 'https://example.net/page/1', '');
record = yield db.getByKeyID( record = yield db.getByKeyID(
'057caa8f-9b99-47ff-891c-adad18ce603e'); '057caa8f-9b99-47ff-891c-adad18ce603e');
ok(!record, ok(!record,

Просмотреть файл

@ -29,7 +29,8 @@ add_task(function* test_unregister_not_found() {
}); });
let promise = PushNotificationService.unregister( 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) { yield rejects(promise, function(error) {
return error == 'NotFoundError'; return error == 'NotFoundError';
}, 'Wrong error for nonexistent scope'); }, 'Wrong error for nonexistent scope');

Просмотреть файл

@ -20,6 +20,7 @@ add_task(function* test_unregister_success() {
channelID, channelID,
pushEndpoint: 'https://example.org/update/unregister-success', pushEndpoint: 'https://example.org/update/unregister-success',
scope: 'https://example.com/page/unregister-success', scope: 'https://example.com/page/unregister-success',
originAttributes: '',
version: 1 version: 1
}); });
@ -51,7 +52,7 @@ add_task(function* test_unregister_success() {
}); });
yield PushNotificationService.unregister( yield PushNotificationService.unregister(
'https://example.com/page/unregister-success'); 'https://example.com/page/unregister-success', '');
let record = yield db.getByKeyID(channelID); let record = yield db.getByKeyID(channelID);
ok(!record, 'Unregister did not remove record'); ok(!record, 'Unregister did not remove record');

Просмотреть файл

@ -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.');
});

Просмотреть файл

@ -30,6 +30,7 @@ skip-if = toolkit == 'android'
[test_unregister_invalid_json.js] [test_unregister_invalid_json.js]
[test_unregister_not_found.js] [test_unregister_not_found.js]
[test_unregister_success.js] [test_unregister_success.js]
[test_webapps_cleardata.js]
#http2 test #http2 test
[test_resubscribe_4xxCode_http2.js] [test_resubscribe_4xxCode_http2.js]
[test_resubscribe_5xxCode_http2.js] [test_resubscribe_5xxCode_http2.js]

Просмотреть файл

@ -18,6 +18,14 @@ interface ChromeUtils : ThreadSafeChromeUtils {
*/ */
static ByteString static ByteString
originAttributesToCookieJar(optional OriginAttributesDictionary originAttrs); 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);
}; };
/** /**

Просмотреть файл

@ -7,8 +7,10 @@
* https://w3c.github.io/push-api/ * https://w3c.github.io/push-api/
*/ */
interface Principal;
[JSImplementation="@mozilla.org/push/PushSubscription;1", [JSImplementation="@mozilla.org/push/PushSubscription;1",
Constructor(DOMString pushEndpoint, DOMString scope, DOMString pageURL), ChromeOnly] Constructor(DOMString pushEndpoint, DOMString scope, Principal principal), ChromeOnly]
interface PushSubscription interface PushSubscription
{ {
readonly attribute USVString endpoint; readonly attribute USVString endpoint;

Просмотреть файл

@ -2200,16 +2200,15 @@ public:
#endif /* ! MOZ_SIMPLEPUSH */ #endif /* ! MOZ_SIMPLEPUSH */
NS_IMETHODIMP NS_IMETHODIMP
ServiceWorkerManager::SendPushEvent(JS::Handle<JS::Value> aOriginAttributes, ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
const nsACString& aScope, const nsACString& aScope,
const nsAString& aData, const nsAString& aData)
JSContext* aCx)
{ {
#ifdef MOZ_SIMPLEPUSH #ifdef MOZ_SIMPLEPUSH
return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_AVAILABLE;
#else #else
OriginAttributes attrs; OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;
} }
@ -2226,7 +2225,9 @@ ServiceWorkerManager::SendPushEvent(JS::Handle<JS::Value> aOriginAttributes,
new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData, new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData,
serviceWorkerHandle); serviceWorkerHandle);
if (NS_WARN_IF(!r->Dispatch(aCx))) { AutoJSAPI jsapi;
jsapi.Init();
if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
return NS_ERROR_FAILURE; return NS_ERROR_FAILURE;
} }
@ -2235,15 +2236,14 @@ ServiceWorkerManager::SendPushEvent(JS::Handle<JS::Value> aOriginAttributes,
} }
NS_IMETHODIMP NS_IMETHODIMP
ServiceWorkerManager::SendPushSubscriptionChangeEvent(JS::Handle<JS::Value> aOriginAttributes, ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
const nsACString& aScope, const nsACString& aScope)
JSContext* aCx)
{ {
#ifdef MOZ_SIMPLEPUSH #ifdef MOZ_SIMPLEPUSH
return NS_ERROR_NOT_AVAILABLE; return NS_ERROR_NOT_AVAILABLE;
#else #else
OriginAttributes attrs; OriginAttributes attrs;
if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
return NS_ERROR_INVALID_ARG; return NS_ERROR_INVALID_ARG;
} }
@ -2259,7 +2259,9 @@ ServiceWorkerManager::SendPushSubscriptionChangeEvent(JS::Handle<JS::Value> aOri
new SendPushSubscriptionChangeEventRunnable( new SendPushSubscriptionChangeEventRunnable(
serviceWorker->GetWorkerPrivate(), serviceWorkerHandle); 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; return NS_ERROR_FAILURE;
} }

Просмотреть файл

@ -120,7 +120,7 @@ function display(info) {
createItem(bundle.GetStringFromName('waitingCacheName'), info.waitingCacheName); createItem(bundle.GetStringFromName('waitingCacheName'), info.waitingCacheName);
let pushItem = createItem(bundle.GetStringFromName('pushEndpoint'), bundle.GetStringFromName('waiting')); let pushItem = createItem(bundle.GetStringFromName('pushEndpoint'), bundle.GetStringFromName('waiting'));
PushNotificationService.registration(info.scope).then( PushNotificationService.registration(info.scope, info.principal.originAttributes).then(
pushRecord => { pushRecord => {
pushItem.data = JSON.stringify(pushRecord); pushItem.data = JSON.stringify(pushRecord);
}, },