summaryrefslogtreecommitdiffstats
path: root/toolkit/components/forgetaboutsite
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/forgetaboutsite
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/forgetaboutsite')
-rw-r--r--toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs127
-rw-r--r--toolkit/components/forgetaboutsite/moz.build15
-rw-r--r--toolkit/components/forgetaboutsite/test/browser/browser.toml3
-rw-r--r--toolkit/components/forgetaboutsite/test/browser/browser_cookieDomain.js63
-rw-r--r--toolkit/components/forgetaboutsite/test/unit/head_forgetaboutsite.js28
-rw-r--r--toolkit/components/forgetaboutsite/test/unit/test_removeDataFromDomain.js574
-rw-r--r--toolkit/components/forgetaboutsite/test/unit/xpcshell.toml5
7 files changed, 815 insertions, 0 deletions
diff --git a/toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs b/toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs
new file mode 100644
index 0000000000..603d121403
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/ForgetAboutSite.sys.mjs
@@ -0,0 +1,127 @@
+/* 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/. */
+
+export var ForgetAboutSite = {
+ /**
+ * Clear data associated with a base domain. This includes partitioned storage
+ * associated with the domain. If a base domain can not be computed from
+ * aDomainOrHost, data will be cleared by host instead.
+ *
+ * @param {string} aDomainOrHost - Domain or host to clear data for. Will be
+ * converted to base domain if needed.
+ * @returns {Promise} - Resolves once all matching data has been cleared.
+ * Throws if any of the internal cleaners fail.
+ */
+ async removeDataFromBaseDomain(aDomainOrHost) {
+ if (!aDomainOrHost) {
+ throw new Error("aDomainOrHost can not be empty.");
+ }
+
+ let baseDomain;
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(aDomainOrHost);
+ } catch (e) {}
+
+ let errorCount;
+ if (baseDomain) {
+ errorCount = await new Promise(resolve => {
+ Services.clearData.deleteDataFromBaseDomain(
+ baseDomain,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
+ errorCode => resolve(bitCounting(errorCode))
+ );
+ });
+ } else {
+ // If we can't get a valid base domain for aDomainOrHost, fall back to
+ // delete by host.
+ errorCount = await new Promise(resolve => {
+ Services.clearData.deleteDataFromHost(
+ aDomainOrHost,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
+ errorCode => resolve(bitCounting(errorCode))
+ );
+ });
+ }
+
+ if (errorCount !== 0) {
+ throw new Error(
+ `There were a total of ${errorCount} errors during removal`
+ );
+ }
+ },
+
+ /**
+ * @deprecated This is a legacy method which clears by host only. Also it does
+ * not clear all storage partitioned via dFPI. Use removeDataFromBaseDomain
+ * instead.
+ */
+ async removeDataFromDomain(aDomain) {
+ let promises = [
+ new Promise(resolve =>
+ Services.clearData.deleteDataFromHost(
+ aDomain,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_FORGET_ABOUT_SITE,
+ errorCode => resolve(bitCounting(errorCode))
+ )
+ ),
+ ];
+
+ try {
+ let baseDomain = Services.eTLD.getBaseDomainFromHost(aDomain);
+
+ let cookies = Services.cookies.cookies;
+ let hosts = new Set();
+ for (let cookie of cookies) {
+ if (Services.eTLD.hasRootDomain(cookie.rawHost, baseDomain)) {
+ hosts.add(cookie.rawHost);
+ }
+ }
+
+ for (let host of hosts) {
+ promises.push(
+ new Promise(resolve =>
+ Services.clearData.deleteDataFromHost(
+ host,
+ true /* user request */,
+ Ci.nsIClearDataService.CLEAR_COOKIES,
+ errorCode => resolve(bitCounting(errorCode))
+ )
+ )
+ );
+ }
+ } catch (e) {
+ // - NS_ERROR_HOST_IS_IP_ADDRESS: the host is in ipv4/ipv6.
+ // - NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS: not enough domain parts to extract,
+ // i.e. the host is on the PSL.
+ // In both these cases we should probably not try to use the host as a base
+ // domain to remove more data, but we can still (try to) continue deleting the host.
+ if (
+ e.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS &&
+ e.result != Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS
+ ) {
+ throw e;
+ }
+ }
+
+ let errorCount = (await Promise.all(promises)).reduce((a, b) => a + b);
+
+ if (errorCount !== 0) {
+ throw new Error(
+ `There were a total of ${errorCount} errors during removal`
+ );
+ }
+ },
+};
+
+function bitCounting(value) {
+ // To know more about how to count bits set to 1 in a numeric value, see this
+ // interesting article:
+ // https://blogs.msdn.microsoft.com/jeuge/2005/06/08/bit-fiddling-3/
+ const count =
+ value - ((value >> 1) & 0o33333333333) - ((value >> 2) & 0o11111111111);
+ return ((count + (count >> 3)) & 0o30707070707) % 63;
+}
diff --git a/toolkit/components/forgetaboutsite/moz.build b/toolkit/components/forgetaboutsite/moz.build
new file mode 100644
index 0000000000..e8080540da
--- /dev/null
+++ b/toolkit/components/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.toml"]
+XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.toml"]
+
+EXTRA_JS_MODULES += [
+ "ForgetAboutSite.sys.mjs",
+]
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Data Sanitization")
diff --git a/toolkit/components/forgetaboutsite/test/browser/browser.toml b/toolkit/components/forgetaboutsite/test/browser/browser.toml
new file mode 100644
index 0000000000..a58397bba0
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/test/browser/browser.toml
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+["browser_cookieDomain.js"]
diff --git a/toolkit/components/forgetaboutsite/test/browser/browser_cookieDomain.js b/toolkit/components/forgetaboutsite/test/browser/browser_cookieDomain.js
new file mode 100644
index 0000000000..21dff6ada0
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/test/browser/browser_cookieDomain.js
@@ -0,0 +1,63 @@
+const { ForgetAboutSite } = ChromeUtils.importESModule(
+ "resource://gre/modules/ForgetAboutSite.sys.mjs"
+);
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+function checkCookie(host, originAttributes) {
+ for (let cookie of Services.cookies.cookies) {
+ if (
+ ChromeUtils.isOriginAttributesEqual(
+ originAttributes,
+ cookie.originAttributes
+ ) &&
+ cookie.host.includes(host)
+ ) {
+ return true;
+ }
+ }
+ return false;
+}
+
+add_task(async function test_singleDomain() {
+ info("Test single cookie domain");
+
+ // Let's clean up all the data.
+ await SiteDataTestUtils.clear();
+
+ SiteDataTestUtils.addToCookies({ origin: "https://example.com" });
+
+ // Cleaning up.
+ await ForgetAboutSite.removeDataFromDomain("example.com");
+
+ // All good.
+ ok(!checkCookie("example.com", {}), "No cookies");
+
+ // Clean up.
+ await SiteDataTestUtils.clear();
+});
+
+add_task(async function test_subDomain() {
+ info("Test cookies for sub domains");
+
+ // Let's clean up all the data.
+ await SiteDataTestUtils.clear();
+
+ SiteDataTestUtils.addToCookies({ origin: "https://example.com" });
+ SiteDataTestUtils.addToCookies({ origin: "https://sub.example.com" });
+ SiteDataTestUtils.addToCookies({ origin: "https://sub2.example.com" });
+ SiteDataTestUtils.addToCookies({ origin: "https://sub2.example.com" });
+
+ SiteDataTestUtils.addToCookies({ origin: "https://example.org" });
+
+ // Cleaning up.
+ await ForgetAboutSite.removeDataFromDomain("sub.example.com");
+
+ // All good.
+ ok(!checkCookie("example.com", {}), "No cookies for example.com");
+ ok(checkCookie("example.org", {}), "Has cookies for example.org");
+
+ // Clean up.
+ await SiteDataTestUtils.clear();
+});
diff --git a/toolkit/components/forgetaboutsite/test/unit/head_forgetaboutsite.js b/toolkit/components/forgetaboutsite/test/unit/head_forgetaboutsite.js
new file mode 100644
index 0000000000..0e0e178e96
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/test/unit/head_forgetaboutsite.js
@@ -0,0 +1,28 @@
+/* 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 profileDir = do_get_profile();
+
+/**
+ * Removes any files that could make our tests fail.
+ */
+async function cleanUp() {
+ let files = ["places.sqlite", "cookies.sqlite"];
+
+ for (let i = 0; i < files.length; i++) {
+ let file = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ file.append(files[i]);
+ if (file.exists()) {
+ file.remove(false);
+ }
+ }
+
+ await new Promise(resolve => {
+ Services.clearData.deleteData(
+ Ci.nsIClearDataService.CLEAR_PERMISSIONS,
+ value => resolve()
+ );
+ });
+}
+cleanUp();
diff --git a/toolkit/components/forgetaboutsite/test/unit/test_removeDataFromDomain.js b/toolkit/components/forgetaboutsite/test/unit/test_removeDataFromDomain.js
new file mode 100644
index 0000000000..84894d1ec5
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -0,0 +1,574 @@
+/* -*- 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
+
+const { ForgetAboutSite } = ChromeUtils.importESModule(
+ "resource://gre/modules/ForgetAboutSite.sys.mjs"
+);
+
+const { PlacesUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/PlacesUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+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
+
+/**
+ * Add a cookie to the cookie service.
+ *
+ * @param aDomain
+ */
+function add_cookie(aDomain) {
+ check_cookie_exists(aDomain, false);
+ Services.cookies.add(
+ aDomain,
+ COOKIE_PATH,
+ COOKIE_NAME,
+ "",
+ false,
+ false,
+ false,
+ COOKIE_EXPIRY,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+ 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) {
+ Assert.equal(
+ aExists,
+ Services.cookies.cookieExists(aDomain, COOKIE_PATH, COOKIE_NAME, {})
+ );
+}
+
+/**
+ * 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);
+ Services.logins.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) {
+ Assert.equal(!aIsDisabled, Services.logins.getLoginSavingEnabled(aHost));
+}
+
+/**
+ * Adds a login for the specified host to the login manager.
+ *
+ * @param aHost
+ * The host to add the login for.
+ */
+async function add_login(aHost) {
+ await 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
+ );
+ await Services.logins.addLoginAsync(login);
+ await 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.
+ */
+async function check_login_exists(aHost, aExists) {
+ let logins = await Services.logins.searchLoginsAsync({ origin: aHost });
+ Assert.equal(logins.length, 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 principal = Services.scriptSecurityManager.createContentPrincipal(
+ aURI,
+ {}
+ );
+
+ Services.perms.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 principal = Services.scriptSecurityManager.createContentPrincipal(
+ aURI,
+ {}
+ );
+
+ let perm = Services.perms.testExactPermissionFromPrincipal(
+ principal,
+ PERMISSION_TYPE
+ );
+ let checker = aExists ? "equal" : "notEqual";
+ Assert[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) {
+ return new Promise(resolve => {
+ let cp = Cc["@mozilla.org/content-pref/service;1"].getService(
+ Ci.nsIContentPrefService2
+ );
+ cp.set(aURI.spec, PREFERENCE_NAME, "foo", null, {
+ handleCompletion: () => resolve(),
+ });
+ });
+}
+
+/**
+ * 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) {
+ return new Promise(resolve => {
+ 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: () => resolve(exists),
+ });
+ });
+}
+
+// Test Functions
+
+// History
+async function test_history_cleared_with_direct_match() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org/foo");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+ await PlacesTestUtils.addVisits(TEST_URI);
+ Assert.ok(await PlacesUtils.history.hasVisits(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+}
+
+async function test_history_cleared_with_subdomain() {
+ const TEST_URI = Services.io.newURI("http://www.mozilla.org/foo");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+ await PlacesTestUtils.addVisits(TEST_URI);
+ Assert.ok(await PlacesUtils.history.hasVisits(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+}
+
+async function test_history_not_cleared_with_uri_contains_domain() {
+ const TEST_URI = Services.io.newURI("http://ilovemozilla.org/foo");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+ await PlacesTestUtils.addVisits(TEST_URI);
+ Assert.ok(await PlacesUtils.history.hasVisits(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.ok(await PlacesUtils.history.hasVisits(TEST_URI));
+
+ // Clear history since we left something there from this test.
+ await PlacesUtils.history.clear();
+}
+
+async function test_history_cleared_base_domain() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org/foo");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+ await PlacesTestUtils.addVisits(TEST_URI);
+ Assert.ok(await PlacesUtils.history.hasVisits(TEST_URI));
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ Assert.equal(false, await PlacesUtils.history.hasVisits(TEST_URI));
+}
+
+// Cookie Service
+async function test_cookie_cleared_with_direct_match() {
+ const TEST_DOMAIN = "mozilla.org";
+ add_cookie(TEST_DOMAIN);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, false);
+}
+
+async function test_cookie_cleared_with_subdomain() {
+ const TEST_DOMAIN = "www.mozilla.org";
+ add_cookie(TEST_DOMAIN);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, false);
+}
+
+async function test_cookie_not_cleared_with_uri_contains_domain() {
+ const TEST_DOMAIN = "ilovemozilla.org";
+ add_cookie(TEST_DOMAIN);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, true);
+}
+
+async function test_cookie_cleared_base_domain() {
+ const TEST_DOMAIN = "mozilla.org";
+ add_cookie(TEST_DOMAIN);
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ check_cookie_exists(TEST_DOMAIN, false);
+}
+
+// Login Manager
+async function test_login_manager_disabled_hosts_cleared_with_direct_match() {
+ const TEST_HOST = "http://mozilla.org";
+ add_disabled_host(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, false);
+}
+
+async function test_login_manager_disabled_hosts_cleared_with_subdomain() {
+ const TEST_HOST = "http://www.mozilla.org";
+ add_disabled_host(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, false);
+}
+
+async function test_login_manager_disabled_hosts_not_cleared_with_uri_contains_domain() {
+ const TEST_HOST = "http://ilovemozilla.org";
+ add_disabled_host(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, true);
+
+ // Reset state
+ Services.logins.setLoginSavingEnabled(TEST_HOST, true);
+ check_disabled_host(TEST_HOST, false);
+}
+
+async function test_login_manager_logins_cleared_with_direct_match() {
+ const TEST_HOST = "http://mozilla.org";
+ await add_login(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ await check_login_exists(TEST_HOST, true);
+}
+
+async function test_login_manager_logins_cleared_with_subdomain() {
+ const TEST_HOST = "http://www.mozilla.org";
+ await add_login(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ await check_login_exists(TEST_HOST, true);
+}
+
+async function test_login_manager_logins_not_cleared_with_uri_contains_domain() {
+ const TEST_HOST = "http://ilovemozilla.org";
+ await add_login(TEST_HOST);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ await check_login_exists(TEST_HOST, true);
+
+ Services.logins.removeAllUserFacingLogins();
+ await check_login_exists(TEST_HOST, false);
+}
+
+async function test_login_manager_disabled_hosts_cleared_base_domain() {
+ const TEST_HOST = "http://mozilla.org";
+ add_disabled_host(TEST_HOST);
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ check_disabled_host(TEST_HOST, false);
+}
+
+// Permission Manager
+async function test_permission_manager_cleared_with_direct_match() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org");
+ add_permission(TEST_URI);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, false);
+}
+
+async function test_permission_manager_cleared_with_subdomain() {
+ const TEST_URI = Services.io.newURI("http://www.mozilla.org");
+ add_permission(TEST_URI);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, false);
+}
+
+async function test_permission_manager_not_cleared_with_uri_contains_domain() {
+ const TEST_URI = Services.io.newURI("http://ilovemozilla.org");
+ add_permission(TEST_URI);
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ check_permission_exists(TEST_URI, true);
+
+ // Reset state
+ Services.perms.removeAll();
+ check_permission_exists(TEST_URI, false);
+}
+
+async function test_permission_manager_cleared_base_domain() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org");
+ add_permission(TEST_URI);
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ check_permission_exists(TEST_URI, false);
+}
+
+// Content Preferences
+async function test_content_preferences_cleared_with_direct_match() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+ await add_preference(TEST_URI);
+ Assert.ok(await preference_exists(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+}
+
+async function test_content_preferences_cleared_with_subdomain() {
+ const TEST_URI = Services.io.newURI("http://www.mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+ await add_preference(TEST_URI);
+ Assert.ok(await preference_exists(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+}
+
+async function test_content_preferences_not_cleared_with_uri_contains_domain() {
+ const TEST_URI = Services.io.newURI("http://ilovemozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+ await add_preference(TEST_URI);
+ Assert.ok(await preference_exists(TEST_URI));
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ Assert.ok(await preference_exists(TEST_URI));
+
+ // Reset state
+ await ForgetAboutSite.removeDataFromDomain("ilovemozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+}
+
+async function test_content_preferences_cleared_base_domain() {
+ const TEST_URI = Services.io.newURI("http://mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+ await add_preference(TEST_URI);
+ Assert.ok(await preference_exists(TEST_URI));
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ Assert.equal(false, await preference_exists(TEST_URI));
+}
+
+// Push
+async function test_push_cleared() {
+ return helper_push_cleared(false);
+}
+
+async function test_push_cleared_base_domain() {
+ return helper_push_cleared(true);
+}
+
+async function helper_push_cleared(aBaseDomainTest) {
+ let ps;
+ try {
+ ps = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
+ } catch (e) {
+ // No push service, skip test.
+ return;
+ }
+
+ // Enable the push service, but disable the connection, so that accessing
+ // `pushImpl.service` doesn't try to connect.
+ Services.prefs.setBoolPref("dom.push.connection.enabled", false);
+ Services.prefs.setBoolPref("dom.push.enabled", true);
+ let pushImpl = ps.wrappedJSObject;
+ if (typeof pushImpl != "object" || !pushImpl || !("service" in pushImpl)) {
+ // GeckoView, for example, uses a different implementation; bail if it's
+ // not something we can replace.
+ info("Can't test with this push service implementation");
+ return;
+ }
+ // Otherwise, tear down the old one and replace it with our mock backend,
+ // so that we don't have to initialize an entire mock WebSocket only to
+ // test clearing data.
+ await pushImpl.service.uninit?.();
+ let wasCleared = false;
+ pushImpl.service = {
+ async clear({ domain } = {}) {
+ Assert.equal(
+ domain,
+ "mozilla.org",
+ "Should pass domain to clear to push service"
+ );
+ wasCleared = true;
+ },
+ };
+ Services.prefs.setBoolPref("dom.push.enabled", true);
+
+ if (aBaseDomainTest) {
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ } else {
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ }
+
+ Assert.ok(wasCleared, "Should have cleared push data");
+}
+
+function test_storage_cleared() {
+ return helper_storage_cleared(false);
+}
+function test_storage_cleared_base_domain() {
+ return helper_storage_cleared(true);
+}
+
+async function helper_storage_cleared(aBaseDomainTest) {
+ function getStorageForURI(aURI) {
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ aURI,
+ {}
+ );
+
+ return Services.domStorageManager.createStorage(
+ null,
+ principal,
+ principal,
+ ""
+ );
+ }
+
+ Services.prefs.setBoolPref("dom.storage.client_validation", false);
+
+ let s = [
+ getStorageForURI(Services.io.newURI("http://mozilla.org")),
+ getStorageForURI(Services.io.newURI("http://my.mozilla.org")),
+ getStorageForURI(Services.io.newURI("http://ilovemozilla.org")),
+ ];
+
+ for (let i = 0; i < s.length; ++i) {
+ let storage = s[i];
+ storage.setItem("test", "value" + i);
+ Assert.equal(storage.length, 1);
+ Assert.equal(storage.key(0), "test");
+ Assert.equal(storage.getItem("test"), "value" + i);
+ }
+
+ if (aBaseDomainTest) {
+ await ForgetAboutSite.removeDataFromBaseDomain("mozilla.org");
+ } else {
+ await ForgetAboutSite.removeDataFromDomain("mozilla.org");
+ }
+
+ Assert.equal(s[0].getItem("test"), null);
+ Assert.equal(s[0].length, 0);
+ Assert.equal(s[1].getItem("test"), null);
+ Assert.equal(s[1].length, 0);
+ Assert.equal(s[2].getItem("test"), "value2");
+ Assert.equal(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,
+ test_history_cleared_base_domain,
+
+ // Cookie Service
+ test_cookie_cleared_with_direct_match,
+ test_cookie_cleared_with_subdomain,
+ test_cookie_not_cleared_with_uri_contains_domain,
+ test_cookie_cleared_base_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,
+ test_login_manager_disabled_hosts_cleared_base_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,
+ test_permission_manager_cleared_base_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,
+ test_content_preferences_cleared_base_domain,
+
+ // Push
+ test_push_cleared,
+ test_push_cleared_base_domain,
+
+ // Storage
+ test_storage_cleared,
+ test_storage_cleared_base_domain,
+];
+
+function run_test() {
+ for (let i = 0; i < tests.length; i++) {
+ add_task(tests[i]);
+ }
+
+ run_next_test();
+}
diff --git a/toolkit/components/forgetaboutsite/test/unit/xpcshell.toml b/toolkit/components/forgetaboutsite/test/unit/xpcshell.toml
new file mode 100644
index 0000000000..475e73cda1
--- /dev/null
+++ b/toolkit/components/forgetaboutsite/test/unit/xpcshell.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+head = "head_forgetaboutsite.js"
+skip-if = ["os == 'android'"]
+
+["test_removeDataFromDomain.js"]