summaryrefslogtreecommitdiff
path: root/toolkit/forgetaboutsite
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/forgetaboutsite')
-rw-r--r--toolkit/forgetaboutsite/ForgetAboutSite.jsm219
-rw-r--r--toolkit/forgetaboutsite/moz.build15
-rw-r--r--toolkit/forgetaboutsite/test/browser/.eslintrc.js7
-rw-r--r--toolkit/forgetaboutsite/test/browser/browser.ini4
-rw-r--r--toolkit/forgetaboutsite/test/browser/browser_clearplugindata.html30
-rw-r--r--toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js91
-rw-r--r--toolkit/forgetaboutsite/test/unit/.eslintrc.js7
-rw-r--r--toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js31
-rw-r--r--toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js647
-rw-r--r--toolkit/forgetaboutsite/test/unit/xpcshell.ini8
10 files changed, 1059 insertions, 0 deletions
diff --git a/toolkit/forgetaboutsite/ForgetAboutSite.jsm b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
new file mode 100644
index 0000000000..b8cd31ad9c
--- /dev/null
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+
+this.EXPORTED_SYMBOLS = ["ForgetAboutSite"];
+
+/**
+ * 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 == "/");
+}
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+this.ForgetAboutSite = {
+ removeDataFromDomain: function CRH_removeDataFromDomain(aDomain)
+ {
+ PlacesUtils.history.removePagesFromHost(aDomain, true);
+
+ // Cache
+ let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(Ci.nsICacheStorageService);
+ // NOTE: there is no way to clear just that domain, so we clear out
+ // everything)
+ try {
+ cs.clear();
+ } catch (ex) {
+ Cu.reportError("Exception thrown while clearing the cache: " +
+ ex.toString());
+ }
+
+ // Image Cache
+ let imageCache = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools).getImgCacheForDocument(null);
+ try {
+ imageCache.clearCache(false); // true=chrome, false=content
+ } catch (ex) {
+ Cu.reportError("Exception thrown while clearing the image cache: " +
+ ex.toString());
+ }
+
+ // Cookies
+ let cm = Cc["@mozilla.org/cookiemanager;1"].
+ getService(Ci.nsICookieManager2);
+ let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
+ cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+ }
+
+ // EME
+ let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+ getService(Ci.mozIGeckoMediaPluginChromeService);
+ mps.forgetThisSite(aDomain, JSON.stringify({}));
+
+ // Plugin data
+ const phInterface = Ci.nsIPluginHost;
+ const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
+ let tags = ph.getPluginTags();
+ let promises = [];
+ for (let i = 0; i < tags.length; i++) {
+ let promise = new Promise(resolve => {
+ let tag = tags[i];
+ try {
+ ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, function(rv) {
+ resolve();
+ });
+ } catch (e) {
+ // Ignore errors from the plugin, but resolve the promise
+ resolve();
+ }
+ });
+ promises.push(promise);
+ }
+
+ // Downloads
+ Task.spawn(function*() {
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.removeFinished(download => hasRootDomain(
+ NetUtil.newURI(download.source.url).host, aDomain));
+ }).then(null, Cu.reportError);
+
+ // Passwords
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ // Clear all passwords for domain
+ try {
+ let logins = lm.getAllLogins();
+ for (let i = 0; i < logins.length; i++)
+ if (hasRootDomain(logins[i].hostname, aDomain))
+ lm.removeLogin(logins[i]);
+ }
+ // XXXehsan: is there a better way to do this rather than this
+ // hacky comparison?
+ catch (ex) {
+ if (ex.message.indexOf("User canceled Master Password entry") == -1) {
+ throw ex;
+ }
+ }
+
+ // Permissions
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+ // Enumerate all of the permissions, and if one matches, remove it
+ enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ try {
+ if (hasRootDomain(perm.principal.URI.host, aDomain)) {
+ pm.removePermission(perm);
+ }
+ } catch (e) {
+ /* Ignore entry */
+ }
+ }
+
+ // Offline Storages
+ let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
+ getService(Ci.nsIQuotaManagerService);
+ // delete data from both HTTP and HTTPS sites
+ let caUtils = {};
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
+ caUtils);
+ let httpURI = caUtils.makeURI("http://" + aDomain);
+ let httpsURI = caUtils.makeURI("https://" + aDomain);
+ // Following code section has been reverted to the state before Bug 1238183,
+ // but added a new argument to clearStoragesForPrincipal() for indicating
+ // clear all storages under a given origin.
+ let httpPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(httpURI, {});
+ let httpsPrincipal = Services.scriptSecurityManager
+ .createCodebasePrincipal(httpsURI, {});
+ qms.clearStoragesForPrincipal(httpPrincipal, null, true);
+ qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
+
+
+ function onContentPrefsRemovalFinished() {
+ // Everybody else (including extensions)
+ Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
+ }
+
+ // Content Preferences
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ cps2.removeBySubdomain(aDomain, null, {
+ handleCompletion: () => onContentPrefsRemovalFinished(),
+ handleError: function() {}
+ });
+
+ // Predictive network data - like cache, no way to clear this per
+ // domain, so just trash it all
+ let np = Cc["@mozilla.org/network/predictor;1"].
+ getService(Ci.nsINetworkPredictor);
+ np.reset();
+
+ // Push notifications.
+ promises.push(new Promise(resolve => {
+ var push = Cc["@mozilla.org/push/Service;1"]
+ .getService(Ci.nsIPushService);
+ push.clearForDomain(aDomain, status => {
+ (Components.isSuccessCode(status) ? resolve : reject)(status);
+ });
+ }).catch(e => {
+ Cu.reportError("Exception thrown while clearing Push notifications: " +
+ e.toString());
+ }));
+
+ // HSTS and HPKP
+ // TODO (bug 1290529): also remove HSTS/HPKP information for subdomains.
+ // Since we can't enumerate the information in the site security service
+ // (bug 1115712), we can't implement this right now.
+ try {
+ let sss = Cc["@mozilla.org/ssservice;1"].
+ getService(Ci.nsISiteSecurityService);
+ sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, httpsURI, 0);
+ sss.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, httpsURI, 0);
+ } catch (e) {
+ Cu.reportError("Exception thrown while clearing HSTS/HPKP: " +
+ e.toString());
+ }
+
+ return Promise.all(promises);
+ }
+};
diff --git a/toolkit/forgetaboutsite/moz.build b/toolkit/forgetaboutsite/moz.build
new file mode 100644
index 0000000000..73bb42b0b5
--- /dev/null
+++ b/toolkit/forgetaboutsite/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+EXTRA_JS_MODULES += [
+ 'ForgetAboutSite.jsm',
+]
+
+with Files('**'):
+ BUG_COMPONENT = ('Toolkit', 'Forget About Site')
diff --git a/toolkit/forgetaboutsite/test/browser/.eslintrc.js b/toolkit/forgetaboutsite/test/browser/.eslintrc.js
new file mode 100644
index 0000000000..c764b133dc
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/browser/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/mochitest/browser.eslintrc.js"
+ ]
+};
diff --git a/toolkit/forgetaboutsite/test/browser/browser.ini b/toolkit/forgetaboutsite/test/browser/browser.ini
new file mode 100644
index 0000000000..517d89d78a
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/browser/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+support-files = browser_clearplugindata.html
+
+[browser_clearplugindata.js]
diff --git a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.html b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.html
new file mode 100644
index 0000000000..b2bba12ce8
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.html
@@ -0,0 +1,30 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data clear by domain test</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ var p = document.getElementById("plugin1");
+
+ p.setSitesWithData(
+ "foo.com:0:0," +
+ "bar.foo.com:0:0," +
+ "baz.foo.com:0:0," +
+ "bar.com:1:0," +
+ "[192.168.1.1]:0:0," +
+ "localhost:0:0"
+ );
+ }
+ </script>
+ </head>
+
+ <body></body>
+
+</html>
diff --git a/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js
new file mode 100644
index 0000000000..ec0a70228a
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.js
@@ -0,0 +1,91 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
+
+// Test clearing plugin data by domain using ForgetAboutSite.
+const testURL = "http://mochi.test:8888/browser/toolkit/forgetaboutsite/test/browser/browser_clearplugindata.html";
+
+const pluginHostIface = Ci.nsIPluginHost;
+var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+pluginHost.QueryInterface(pluginHostIface);
+
+var pluginTag;
+
+function stored(needles) {
+ var something = pluginHost.siteHasData(this.pluginTag, null);
+ if (!needles)
+ return something;
+
+ if (!something)
+ return false;
+
+ for (var i = 0; i < needles.length; ++i) {
+ if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
+ return false;
+ }
+ return true;
+}
+
+function setTestPluginEnabledState(newEnabledState, plugin) {
+ var oldEnabledState = plugin.enabledState;
+ plugin.enabledState = newEnabledState;
+ SimpleTest.registerCleanupFunction(function() {
+ plugin.enabledState = oldEnabledState;
+ });
+}
+
+add_task(function* setup() {
+ var tags = pluginHost.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++)
+ {
+ if (tags[i].name == "Test Plug-in")
+ {
+ pluginTag = tags[i];
+ }
+ }
+ if (!pluginTag) {
+ ok(false, "Test Plug-in not available, can't run test");
+ finish();
+ }
+ setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, pluginTag);
+});
+
+add_task(function* () {
+ yield BrowserTestUtils.openNewForegroundTab(gBrowser, testURL);
+
+ // Set data for the plugin after the page load.
+ yield ContentTask.spawn(gBrowser.selectedBrowser, null, function*() {
+ content.wrappedJSObject.testSteps();
+ });
+
+ ok(stored(["192.168.1.1", "foo.com", "nonexistent.foo.com", "bar.com", "localhost"]),
+ "Data stored for sites");
+
+ // Clear data for "foo.com" and its subdomains.
+ yield ForgetAboutSite.removeDataFromDomain("foo.com");
+
+ ok(stored(["bar.com", "192.168.1.1", "localhost"]), "Data stored for sites");
+ ok(!stored(["foo.com"]), "Data cleared for foo.com");
+ ok(!stored(["bar.foo.com"]), "Data cleared for subdomains of foo.com");
+
+ // Clear data for "bar.com" using a subdomain.
+ yield ForgetAboutSite.removeDataFromDomain("foo.bar.com");
+ ok(!stored(["bar.com"]), "Data cleared for bar.com");
+
+ // Clear data for "192.168.1.1".
+ yield ForgetAboutSite.removeDataFromDomain("192.168.1.1");
+ ok(!stored(["192.168.1.1"]), "Data cleared for 192.168.1.1");
+
+ // Clear data for "localhost".
+ yield ForgetAboutSite.removeDataFromDomain("localhost");
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+});
+
+
diff --git a/toolkit/forgetaboutsite/test/unit/.eslintrc.js b/toolkit/forgetaboutsite/test/unit/.eslintrc.js
new file mode 100644
index 0000000000..fee088c179
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "../../../../testing/xpcshell/xpcshell.eslintrc.js"
+ ]
+};
diff --git a/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js b/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js
new file mode 100644
index 0000000000..d3828d5d8f
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+var profileDir = do_get_profile();
+
+/**
+ * Removes any files that could make our tests fail.
+ */
+function cleanUp()
+{
+ let files = [
+ "places.sqlite",
+ "cookies.sqlite",
+ "signons.sqlite",
+ "permissions.sqlite"
+ ];
+
+ for (let i = 0; i < files.length; i++) {
+ let file = dirSvc.get("ProfD", Ci.nsIFile);
+ file.append(files[i]);
+ if (file.exists())
+ file.remove(false);
+ }
+}
+cleanUp();
diff --git a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
new file mode 100644
index 0000000000..f6ace1e649
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -0,0 +1,647 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test added with bug 460086 to test the behavior of the new API that was added
+ * to remove all traces of visiting a site.
+ */
+
+// Globals
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm");
+
+const COOKIE_EXPIRY = Math.round(Date.now() / 1000) + 60;
+const COOKIE_NAME = "testcookie";
+const COOKIE_PATH = "/";
+
+const LOGIN_USERNAME = "username";
+const LOGIN_PASSWORD = "password";
+const LOGIN_USERNAME_FIELD = "username_field";
+const LOGIN_PASSWORD_FIELD = "password_field";
+
+const PERMISSION_TYPE = "test-perm";
+const PERMISSION_VALUE = Ci.nsIPermissionManager.ALLOW_ACTION;
+
+const PREFERENCE_NAME = "test-pref";
+
+// Utility Functions
+
+/**
+ * Creates an nsIURI object for the given string representation of a URI.
+ *
+ * @param aURIString
+ * The spec of the URI to create.
+ * @returns an nsIURI representing aURIString.
+ */
+function uri(aURIString)
+{
+ return Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newURI(aURIString, null, null);
+}
+
+/**
+ * Asynchronously check a url is visited.
+ *
+ * @param aURI
+ * The URI.
+ *
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI)
+{
+ let deferred = Promise.defer();
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(unused, aIsVisited) {
+ deferred.resolve(aIsVisited);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Add a cookie to the cookie service.
+ *
+ * @param aDomain
+ */
+function add_cookie(aDomain)
+{
+ check_cookie_exists(aDomain, false);
+ let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ cm.add(aDomain, COOKIE_PATH, COOKIE_NAME, "", false, false, false,
+ COOKIE_EXPIRY, {});
+ check_cookie_exists(aDomain, true);
+}
+
+/**
+ * Checks to ensure that a cookie exists or not for a domain.
+ *
+ * @param aDomain
+ * The domain to check for the cookie.
+ * @param aExists
+ * True if the cookie should exist, false otherwise.
+ */
+function check_cookie_exists(aDomain, aExists)
+{
+ let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+ let cookie = {
+ host: aDomain,
+ name: COOKIE_NAME,
+ path: COOKIE_PATH
+ }
+ let checker = aExists ? do_check_true : do_check_false;
+ checker(cm.cookieExists(cookie));
+}
+
+/**
+ * Adds a disabled host to the login manager.
+ *
+ * @param aHost
+ * The host to add to the list of disabled hosts.
+ */
+function add_disabled_host(aHost)
+{
+ check_disabled_host(aHost, false);
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ lm.setLoginSavingEnabled(aHost, false);
+ check_disabled_host(aHost, true);
+}
+
+/**
+ * Checks to see if a host is disabled for storing logins or not.
+ *
+ * @param aHost
+ * The host to check if it is disabled.
+ * @param aIsDisabled
+ * True if the host should be disabled, false otherwise.
+ */
+function check_disabled_host(aHost, aIsDisabled)
+{
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ let checker = aIsDisabled ? do_check_false : do_check_true;
+ checker(lm.getLoginSavingEnabled(aHost));
+}
+
+/**
+ * Adds a login for the specified host to the login manager.
+ *
+ * @param aHost
+ * The host to add the login for.
+ */
+function add_login(aHost)
+{
+ check_login_exists(aHost, false);
+ let login = Cc["@mozilla.org/login-manager/loginInfo;1"].
+ createInstance(Ci.nsILoginInfo);
+ login.init(aHost, "", null, LOGIN_USERNAME, LOGIN_PASSWORD,
+ LOGIN_USERNAME_FIELD, LOGIN_PASSWORD_FIELD);
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ lm.addLogin(login);
+ check_login_exists(aHost, true);
+}
+
+/**
+ * Checks to see if a login exists for a host.
+ *
+ * @param aHost
+ * The host to check for the test login.
+ * @param aExists
+ * True if the login should exist, false otherwise.
+ */
+function check_login_exists(aHost, aExists)
+{
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ let count = { value: 0 };
+ lm.findLogins(count, aHost, "", null);
+ do_check_eq(count.value, aExists ? 1 : 0);
+}
+
+/**
+ * Adds a permission for the specified URI to the permission manager.
+ *
+ * @param aURI
+ * The URI to add the test permission for.
+ */
+function add_permission(aURI)
+{
+ check_permission_exists(aURI, false);
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(aURI, {});
+
+ pm.addFromPrincipal(principal, PERMISSION_TYPE, PERMISSION_VALUE);
+ check_permission_exists(aURI, true);
+}
+
+/**
+ * Checks to see if a permission exists for the given URI.
+ *
+ * @param aURI
+ * The URI to check if a permission exists.
+ * @param aExists
+ * True if the permission should exist, false otherwise.
+ */
+function check_permission_exists(aURI, aExists)
+{
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(aURI, {});
+
+ let perm = pm.testExactPermissionFromPrincipal(principal, PERMISSION_TYPE);
+ let checker = aExists ? do_check_eq : do_check_neq;
+ checker(perm, PERMISSION_VALUE);
+}
+
+/**
+ * Adds a content preference for the specified URI.
+ *
+ * @param aURI
+ * The URI to add a preference for.
+ */
+function add_preference(aURI)
+{
+ let deferred = Promise.defer();
+ let cp = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ cp.set(aURI.spec, PREFERENCE_NAME, "foo", null, {
+ handleCompletion: () => deferred.resolve()
+ });
+ return deferred.promise;
+}
+
+/**
+ * Checks to see if a content preference exists for the given URI.
+ *
+ * @param aURI
+ * The URI to check if a preference exists.
+ */
+function preference_exists(aURI)
+{
+ let deferred = Promise.defer();
+ let cp = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ let exists = false;
+ cp.getByDomainAndName(aURI.spec, PREFERENCE_NAME, null, {
+ handleResult: () => exists = true,
+ handleCompletion: () => deferred.resolve(exists)
+ });
+ return deferred.promise;
+}
+
+// Test Functions
+
+// History
+function* test_history_cleared_with_direct_match()
+{
+ const TEST_URI = uri("http://mozilla.org/foo");
+ do_check_false(yield promiseIsURIVisited(TEST_URI));
+ yield PlacesTestUtils.addVisits(TEST_URI);
+ do_check_true(yield promiseIsURIVisited(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ do_check_false(yield promiseIsURIVisited(TEST_URI));
+}
+
+function* test_history_cleared_with_subdomain()
+{
+ const TEST_URI = uri("http://www.mozilla.org/foo");
+ do_check_false(yield promiseIsURIVisited(TEST_URI));
+ yield PlacesTestUtils.addVisits(TEST_URI);
+ do_check_true(yield promiseIsURIVisited(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ do_check_false(yield promiseIsURIVisited(TEST_URI));
+}
+
+function* test_history_not_cleared_with_uri_contains_domain()
+{
+ const TEST_URI = uri("http://ilovemozilla.org/foo");
+ do_check_false(yield promiseIsURIVisited(TEST_URI));
+ yield PlacesTestUtils.addVisits(TEST_URI);
+ do_check_true(yield promiseIsURIVisited(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ do_check_true(yield promiseIsURIVisited(TEST_URI));
+
+ // Clear history since we left something there from this test.
+ yield PlacesTestUtils.clearHistory();
+}
+
+// Cookie Service
+function test_cookie_cleared_with_direct_match()
+{
+ const TEST_DOMAIN = "mozilla.org";
+ add_cookie(TEST_DOMAIN);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, false);
+}
+
+function test_cookie_cleared_with_subdomain()
+{
+ const TEST_DOMAIN = "www.mozilla.org";
+ add_cookie(TEST_DOMAIN);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, false);
+}
+
+function test_cookie_not_cleared_with_uri_contains_domain()
+{
+ const TEST_DOMAIN = "ilovemozilla.org";
+ add_cookie(TEST_DOMAIN);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, true);
+}
+
+// Login Manager
+function test_login_manager_disabled_hosts_cleared_with_direct_match()
+{
+ const TEST_HOST = "http://mozilla.org";
+ add_disabled_host(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, false);
+}
+
+function test_login_manager_disabled_hosts_cleared_with_subdomain()
+{
+ const TEST_HOST = "http://www.mozilla.org";
+ add_disabled_host(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, false);
+}
+
+function test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain()
+{
+ const TEST_HOST = "http://ilovemozilla.org";
+ add_disabled_host(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, true);
+
+ // Reset state
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ lm.setLoginSavingEnabled(TEST_HOST, true);
+ check_disabled_host(TEST_HOST, false);
+}
+
+function test_login_manager_logins_cleared_with_direct_match()
+{
+ const TEST_HOST = "http://mozilla.org";
+ add_login(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_login_exists(TEST_HOST, false);
+}
+
+function test_login_manager_logins_cleared_with_subdomain()
+{
+ const TEST_HOST = "http://www.mozilla.org";
+ add_login(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_login_exists(TEST_HOST, false);
+}
+
+function test_login_manager_logins_not_cleared_with_uri_contains_domain()
+{
+ const TEST_HOST = "http://ilovemozilla.org";
+ add_login(TEST_HOST);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_login_exists(TEST_HOST, true);
+
+ let lm = Cc["@mozilla.org/login-manager;1"].
+ getService(Ci.nsILoginManager);
+ lm.removeAllLogins();
+ check_login_exists(TEST_HOST, false);
+}
+
+// Permission Manager
+function test_permission_manager_cleared_with_direct_match()
+{
+ const TEST_URI = uri("http://mozilla.org");
+ add_permission(TEST_URI);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, false);
+}
+
+function test_permission_manager_cleared_with_subdomain()
+{
+ const TEST_URI = uri("http://www.mozilla.org");
+ add_permission(TEST_URI);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, false);
+}
+
+function test_permission_manager_not_cleared_with_uri_contains_domain()
+{
+ const TEST_URI = uri("http://ilovemozilla.org");
+ add_permission(TEST_URI);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, true);
+
+ // Reset state
+ let pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(Ci.nsIPermissionManager);
+ pm.removeAll();
+ check_permission_exists(TEST_URI, false);
+}
+
+function waitForPurgeNotification() {
+ let deferred = Promise.defer();
+
+ let observer = {
+ observe: function(aSubject, aTopic, aData)
+ {
+ Services.obs.removeObserver(observer, "browser:purge-domain-data");
+ // test_storage_cleared needs this extra executeSoon because
+ // the DOMStorage clean-up is also listening to this same observer
+ // which is run synchronously.
+ Services.tm.mainThread.dispatch(function() {
+ deferred.resolve();
+ }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ }
+ };
+ Services.obs.addObserver(observer, "browser:purge-domain-data", false);
+
+ return deferred.promise;
+}
+
+// Content Preferences
+function* test_content_preferences_cleared_with_direct_match()
+{
+ const TEST_URI = uri("http://mozilla.org");
+ do_check_false(yield preference_exists(TEST_URI));
+ yield add_preference(TEST_URI);
+ do_check_true(yield preference_exists(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield waitForPurgeNotification();
+ do_check_false(yield preference_exists(TEST_URI));
+}
+
+function* test_content_preferences_cleared_with_subdomain()
+{
+ const TEST_URI = uri("http://www.mozilla.org");
+ do_check_false(yield preference_exists(TEST_URI));
+ yield add_preference(TEST_URI);
+ do_check_true(yield preference_exists(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield waitForPurgeNotification();
+ do_check_false(yield preference_exists(TEST_URI));
+}
+
+function* test_content_preferences_not_cleared_with_uri_contains_domain()
+{
+ const TEST_URI = uri("http://ilovemozilla.org");
+ do_check_false(yield preference_exists(TEST_URI));
+ yield add_preference(TEST_URI);
+ do_check_true(yield preference_exists(TEST_URI));
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield waitForPurgeNotification();
+ do_check_true(yield preference_exists(TEST_URI));
+
+ // Reset state
+ ForgetAboutSite.removeDataFromDomain("ilovemozilla.org");
+ yield waitForPurgeNotification();
+ do_check_false(yield preference_exists(TEST_URI));
+}
+
+function push_registration_exists(aURL, ps)
+{
+ return new Promise(resolve => {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipalFromOrigin(aURL);
+ return ps.getSubscription(aURL, principal, (status, record) => {
+ if (!Components.isSuccessCode(status)) {
+ resolve(false);
+ } else {
+ resolve(!!record);
+ }
+ });
+ });
+}
+
+// Push
+function* test_push_cleared()
+{
+ let ps;
+ try {
+ ps = Cc["@mozilla.org/push/Service;1"].
+ getService(Ci.nsIPushService);
+ } 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();
+
+ try {
+ PushService.init({
+ serverURI: "wss://push.example.org/",
+ db,
+ makeWebSocket(uriObj) {
+ return new MockWebSocket(uriObj, {
+ onHello(request) {
+ this.serverSendMsg(JSON.stringify({
+ messageType: 'hello',
+ status: 200,
+ uaid: userAgentID,
+ }));
+ },
+ });
+ }
+ });
+
+ 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));
+
+ let promisePurgeNotification = waitForPurgeNotification();
+ yield ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield promisePurgeNotification;
+
+ do_check_false(yield push_registration_exists(TEST_URL, ps));
+ } finally {
+ yield PushService._shutdownService();
+ }
+}
+
+// Cache
+function test_cache_cleared()
+{
+ // Because this test is asynchronous, it should be the last test
+ do_check_true(tests[tests.length - 1] == arguments.callee);
+
+ // NOTE: We could be more extensive with this test and actually add an entry
+ // to the cache, and then make sure it is gone. However, we trust that
+ // the API is well tested, and that when we get the observer
+ // notification, we have actually cleared the cache.
+ // This seems to happen asynchronously...
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ let observer = {
+ observe: function(aSubject, aTopic, aData)
+ {
+ os.removeObserver(observer, "cacheservice:empty-cache");
+ // Shutdown the download manager.
+ Services.obs.notifyObservers(null, "quit-application", null);
+ do_test_finished();
+ }
+ };
+ os.addObserver(observer, "cacheservice:empty-cache", false);
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ do_test_pending();
+}
+
+function* test_storage_cleared()
+{
+ function getStorageForURI(aURI)
+ {
+ let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ let principal = ssm.createCodebasePrincipal(aURI, {});
+
+ let dsm = Cc["@mozilla.org/dom/localStorage-manager;1"].
+ getService(Ci.nsIDOMStorageManager);
+ return dsm.createStorage(null, principal, "");
+ }
+
+ let s = [
+ getStorageForURI(uri("http://mozilla.org")),
+ getStorageForURI(uri("http://my.mozilla.org")),
+ getStorageForURI(uri("http://ilovemozilla.org")),
+ ];
+
+ for (let i = 0; i < s.length; ++i) {
+ let storage = s[i];
+ storage.setItem("test", "value" + i);
+ do_check_eq(storage.length, 1);
+ do_check_eq(storage.key(0), "test");
+ do_check_eq(storage.getItem("test"), "value" + i);
+ }
+
+ ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ yield waitForPurgeNotification();
+
+ do_check_eq(s[0].getItem("test"), null);
+ do_check_eq(s[0].length, 0);
+ do_check_eq(s[1].getItem("test"), null);
+ do_check_eq(s[1].length, 0);
+ do_check_eq(s[2].getItem("test"), "value2");
+ do_check_eq(s[2].length, 1);
+}
+
+var tests = [
+ // History
+ test_history_cleared_with_direct_match,
+ test_history_cleared_with_subdomain,
+ test_history_not_cleared_with_uri_contains_domain,
+
+ // Cookie Service
+ test_cookie_cleared_with_direct_match,
+ test_cookie_cleared_with_subdomain,
+ test_cookie_not_cleared_with_uri_contains_domain,
+
+ // Login Manager
+ test_login_manager_disabled_hosts_cleared_with_direct_match,
+ test_login_manager_disabled_hosts_cleared_with_subdomain,
+ test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain,
+ test_login_manager_logins_cleared_with_direct_match,
+ test_login_manager_logins_cleared_with_subdomain,
+ test_login_manager_logins_not_cleared_with_uri_contains_domain,
+
+ // Permission Manager
+ test_permission_manager_cleared_with_direct_match,
+ test_permission_manager_cleared_with_subdomain,
+ test_permission_manager_not_cleared_with_uri_contains_domain,
+
+ // Content Preferences
+ test_content_preferences_cleared_with_direct_match,
+ test_content_preferences_cleared_with_subdomain,
+ test_content_preferences_not_cleared_with_uri_contains_domain,
+
+ // Push
+ test_push_cleared,
+
+ // Storage
+ test_storage_cleared,
+
+ // Cache
+ test_cache_cleared,
+];
+
+function run_test()
+{
+ for (let i = 0; i < tests.length; i++)
+ add_task(tests[i]);
+
+ run_next_test();
+}
diff --git a/toolkit/forgetaboutsite/test/unit/xpcshell.ini b/toolkit/forgetaboutsite/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..a18fa718a3
--- /dev/null
+++ b/toolkit/forgetaboutsite/test/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head = head_forgetaboutsite.js ../../../../dom/push/test/xpcshell/head.js
+tail =
+skip-if = toolkit == 'android'
+support-files =
+ !/dom/push/test/xpcshell/head.js
+
+[test_removeDataFromDomain.js]