Bug 1188686 - Clear push subscriptions when forgetting about site. r=kitcambridge

This commit is contained in:
Nikhil Marathe 2015-07-29 11:33:48 -07:00
parent e9d71f7375
commit 2c43ab2f64
9 changed files with 162 additions and 9 deletions

View File

@ -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(abde228b-7d14-4cab-b1f9-9f87750ede0f)]
[scriptable, uuid(74586476-d73f-4867-bece-87c1dea35750)]
interface nsIPushNotificationService : nsISupports
{
/**
@ -48,7 +48,12 @@ interface nsIPushNotificationService : nsISupports
jsval registration(in string scope, in jsval originAttributes);
/**
* Clear all subscriptions
* Clear all subscriptions.
*/
jsval clearAll();
jsval clearAll();
/**
* Clear subscriptions for a domain.
*/
jsval clearForDomain(in string domain);
};

View File

@ -154,6 +154,36 @@ this.PushDB.prototype = {
);
},
// testFn(record) is called with a database record and should return true if
// that record should be deleted.
clearIf: function(testFn) {
debug("clearIf()");
return new Promise((resolve, reject) =>
this.newTxn(
"readwrite",
this._dbStoreName,
(aTxn, aStore) => {
aTxn.result = undefined;
aStore.openCursor().onsuccess = event => {
let cursor = event.target.result;
if (cursor) {
if (testFn(this.toPushRecord(cursor.value))) {
let deleteRequest = cursor.delete();
deleteRequest.onerror = e => {
debug("Failed to delete entry even when test succeeded!");
}
}
cursor.continue();
}
}
},
resolve,
reject
)
);
},
getByPushEndpoint: function(aPushEndpoint) {
debug("getByPushEndpoint()");

View File

@ -58,6 +58,10 @@ PushNotificationService.prototype = {
return PushService._clearAll();
},
clearForDomain: function(domain) {
return PushService._clearForDomain(domain);
},
observe: function observe(subject, topic, data) {
switch (topic) {
case "app-startup":

View File

@ -1027,7 +1027,47 @@ this.PushService = {
_clearAll: function _clearAll() {
return this._checkActivated()
.then(_ => this._db.clearAll())
.catch(_ => {
.catch(_ => Promise.resolve());
},
_clearForDomain: function(domain) {
/**
* Copied from ForgetAboutSite.jsm.
*
* Returns true if the string passed in is part of the root domain of the
* current string. For example, if this is "www.mozilla.org", and we pass in
* "mozilla.org", this will return true. It would return false the other way
* around.
*/
function hasRootDomain(str, aDomain)
{
let index = str.indexOf(aDomain);
// If aDomain is not found, we know we do not have it as a root domain.
if (index == -1)
return false;
// If the strings are the same, we obviously have a match.
if (str == aDomain)
return true;
// Otherwise, we have aDomain as our root domain iff the index of aDomain is
// aDomain.length subtracted from our length and (since we do not have an
// exact match) the character before the index is a dot or slash.
let prevChar = str[index - 1];
return (index == (str.length - aDomain.length)) &&
(prevChar == "." || prevChar == "/");
}
let clear = (db, domain) => {
db.clearIf(record => {
return hasRootDomain(record.origin, domain);
});
}
return this._checkActivated()
.then(_ => clear(this._db, domain))
.catch(e => {
debug("Error forgetting about domain! " + e);
return Promise.resolve();
});
},

View File

@ -3,7 +3,7 @@
'use strict';
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
Cu.import('resource://gre/modules/Services.jsm');

View File

@ -181,6 +181,16 @@ this.ForgetAboutSite = {
let np = Cc["@mozilla.org/network/predictor;1"].
getService(Ci.nsINetworkPredictor);
np.reset();
// Push notifications.
try {
var push = Cc["@mozilla.org/push/NotificationService;1"]
.getService(Ci.nsIPushNotificationService);
push.clearForDomain(aDomain);
} catch (e) {
dump("Web Push may not be available.\n");
}
return Promise.all(promises);
}
};

View File

@ -2,9 +2,9 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
let Cc = Components.classes;
let Ci = Components.interfaces;
let Cu = Components.utils;
var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
var profileDir = do_get_profile();

View File

@ -462,6 +462,67 @@ function test_content_preferences_not_cleared_with_uri_contains_domain()
do_check_false(yield preference_exists(TEST_URI));
}
// Push
function test_push_cleared()
{
let ps;
try {
ps = Cc["@mozilla.org/push/NotificationService;1"].
getService(Ci.nsIPushNotificationService);
} catch(e) {
// No push service, skip test.
return;
}
do_get_profile();
setPrefs();
const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
const userAgentID = 'bd744428-f125-436a-b6d0-dd0c9845837f';
const channelID = '0ef2ad4a-6c49-41ad-af6e-95d2425276bf';
let db = PushServiceWebSocket.newPushDB();
do_register_cleanup(() => {return db.drop().then(_ => db.close());});
PushService.init({
serverURI: "wss://push.example.org/",
networkInfo: new MockDesktopNetworkInfo(),
db,
makeWebSocket(uri) {
return new MockWebSocket(uri, {
onHello(request) {
this.serverSendMsg(JSON.stringify({
messageType: 'hello',
status: 200,
uaid: userAgentID,
}));
},
});
}
});
function push_registration_exists(aURL, ps)
{
return ps.registration(aURL, ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false }))
.then(record => !!record)
.catch(_ => false);
}
const TEST_URL = "https://www.mozilla.org/scope/";
do_check_false(yield push_registration_exists(TEST_URL, ps));
yield db.put({
channelID,
pushEndpoint: 'https://example.org/update/clear-success',
scope: TEST_URL,
version: 1,
originAttributes: '',
quota: Infinity,
});
do_check_true(yield push_registration_exists(TEST_URL, ps));
ForgetAboutSite.removeDataFromDomain("mozilla.org");
yield waitForPurgeNotification();
do_check_false(yield push_registration_exists(TEST_URL, ps));
}
// Cache
function test_cache_cleared()
{
@ -555,6 +616,9 @@ let tests = [
test_content_preferences_cleared_with_subdomain,
test_content_preferences_not_cleared_with_uri_contains_domain,
// Push
test_push_cleared,
// Storage
test_storage_cleared,

View File

@ -1,5 +1,5 @@
[DEFAULT]
head = head_forgetaboutsite.js
head = head_forgetaboutsite.js ../../../../dom/push/test/xpcshell/head.js
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'