diff options
Diffstat (limited to 'toolkit/forgetaboutsite')
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] |