summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/sanitize
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/sanitize')
-rw-r--r--browser/base/content/test/sanitize/browser.ini21
-rw-r--r--browser/base/content/test/sanitize/browser_cookiePermission.js1
-rw-r--r--browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js102
-rw-r--r--browser/base/content/test/sanitize/browser_cookiePermission_containers.js1
-rw-r--r--browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js293
-rw-r--r--browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js72
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js192
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-formhistory.js28
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-history.js132
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-offlineData.js272
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-passwordDisabledHosts.js28
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-sitepermissions.js37
-rw-r--r--browser/base/content/test/sanitize/browser_sanitize-timespans.js1194
-rw-r--r--browser/base/content/test/sanitize/browser_sanitizeDialog.js833
-rw-r--r--browser/base/content/test/sanitize/dummy.js0
-rw-r--r--browser/base/content/test/sanitize/dummy_page.html9
-rw-r--r--browser/base/content/test/sanitize/head.js336
17 files changed, 3551 insertions, 0 deletions
diff --git a/browser/base/content/test/sanitize/browser.ini b/browser/base/content/test/sanitize/browser.ini
new file mode 100644
index 0000000000..ec6e4bc982
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+support-files=
+ head.js
+ dummy.js
+ dummy_page.html
+prefs=
+ browser.cache.offline.enable=true
+
+[browser_purgehistory_clears_sh.js]
+[browser_sanitize-cookie-exceptions.js]
+[browser_sanitize-formhistory.js]
+[browser_sanitize-history.js]
+[browser_sanitize-offlineData.js]
+[browser_sanitize-passwordDisabledHosts.js]
+[browser_sanitize-sitepermissions.js]
+[browser_sanitize-timespans.js]
+[browser_sanitizeDialog.js]
+[browser_cookiePermission.js]
+[browser_cookiePermission_aboutURL.js]
+[browser_cookiePermission_containers.js]
+[browser_cookiePermission_subDomains.js]
diff --git a/browser/base/content/test/sanitize/browser_cookiePermission.js b/browser/base/content/test/sanitize/browser_cookiePermission.js
new file mode 100644
index 0000000000..9fadfa91db
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_cookiePermission.js
@@ -0,0 +1 @@
+runAllCookiePermissionTests({ name: "default", oa: {} });
diff --git a/browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js b/browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js
new file mode 100644
index 0000000000..29f4ae3fe8
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js
@@ -0,0 +1,102 @@
+const { Sanitizer } = ChromeUtils.importESModule(
+ "resource:///modules/Sanitizer.sys.mjs"
+);
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+function checkDataForAboutURL() {
+ return new Promise(resolve => {
+ let data = true;
+ let uri = Services.io.newURI("about:newtab");
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+ request.onupgradeneeded = function(e) {
+ data = false;
+ };
+ request.onsuccess = function(e) {
+ resolve(data);
+ };
+ });
+}
+
+add_task(async function deleteStorageInAboutURL() {
+ info("Test about:newtab");
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sanitizer.loglevel", "All"]],
+ });
+
+ // Let's create a tab with some data.
+ await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {});
+
+ ok(await checkDataForAboutURL(), "We have data for about:newtab");
+
+ // Cleaning up.
+ await Sanitizer.runSanitizeOnShutdown();
+
+ ok(await checkDataForAboutURL(), "about:newtab data is not deleted.");
+
+ // Clean up.
+ await Sanitizer.sanitize(["cookies", "offlineApps"]);
+
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "about:newtab"
+ );
+ await new Promise(aResolve => {
+ let req = Services.qms.clearStoragesForPrincipal(principal);
+ req.callback = () => {
+ aResolve();
+ };
+ });
+});
+
+add_task(async function deleteStorageOnlyCustomPermissionInAboutURL() {
+ info("Test about:newtab + permissions");
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.sanitizer.loglevel", "All"]],
+ });
+
+ // Custom permission without considering OriginAttributes
+ let uri = Services.io.newURI("about:newtab");
+ PermissionTestUtils.add(uri, "cookie", Ci.nsICookiePermission.ACCESS_SESSION);
+
+ // Let's create a tab with some data.
+ await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {});
+
+ ok(await checkDataForAboutURL(), "We have data for about:newtab");
+
+ // Cleaning up.
+ await Sanitizer.runSanitizeOnShutdown();
+
+ ok(await checkDataForAboutURL(), "about:newtab data is not deleted.");
+
+ // Clean up.
+ await Sanitizer.sanitize(["cookies", "offlineApps"]);
+
+ let principal = Services.scriptSecurityManager.createContentPrincipalFromOrigin(
+ "about:newtab"
+ );
+ await new Promise(aResolve => {
+ let req = Services.qms.clearStoragesForPrincipal(principal);
+ req.callback = () => {
+ aResolve();
+ };
+ });
+
+ PermissionTestUtils.remove(uri, "cookie");
+});
diff --git a/browser/base/content/test/sanitize/browser_cookiePermission_containers.js b/browser/base/content/test/sanitize/browser_cookiePermission_containers.js
new file mode 100644
index 0000000000..236c0913e8
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_cookiePermission_containers.js
@@ -0,0 +1 @@
+runAllCookiePermissionTests({ name: "container", oa: { userContextId: 1 } });
diff --git a/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js
new file mode 100644
index 0000000000..d3e34ed493
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js
@@ -0,0 +1,293 @@
+const { Sanitizer } = ChromeUtils.importESModule(
+ "resource:///modules/Sanitizer.sys.mjs"
+);
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+
+add_setup(async function() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ["privacy.clearOnShutdown.cookies", true],
+ ["privacy.clearOnShutdown.offlineApps", true],
+ ["privacy.clearOnShutdown.cache", false],
+ ["privacy.clearOnShutdown.sessions", false],
+ ["privacy.clearOnShutdown.history", false],
+ ["privacy.clearOnShutdown.formdata", false],
+ ["privacy.clearOnShutdown.downloads", false],
+ ["privacy.clearOnShutdown.siteSettings", false],
+ ["browser.sanitizer.loglevel", "All"],
+ ],
+ });
+});
+// 2 domains: www.mozilla.org (session-only) mozilla.org (allowed) - after the
+// cleanp, mozilla.org must have data.
+add_task(async function subDomains1() {
+ info("Test subdomains and custom setting");
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ // Domains and data
+ let originA = "https://www.mozilla.org";
+ PermissionTestUtils.add(
+ originA,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_SESSION
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originA });
+ await SiteDataTestUtils.addToIndexedDB(originA);
+
+ let originB = "https://mozilla.org";
+ PermissionTestUtils.add(
+ originB,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originB });
+ await SiteDataTestUtils.addToIndexedDB(originB);
+
+ // Check
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+
+ // Cleaning up
+ await Sanitizer.runSanitizeOnShutdown();
+
+ // Check again
+ ok(
+ !SiteDataTestUtils.hasCookies(originA),
+ "We should not have cookies for " + originA
+ );
+ ok(
+ !(await SiteDataTestUtils.hasIndexedDB(originA)),
+ "We should not have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+
+ // Cleaning up permissions
+ PermissionTestUtils.remove(originA, "cookie");
+ PermissionTestUtils.remove(originB, "cookie");
+});
+
+// session only cookie life-time, 2 domains (sub.mozilla.org, www.mozilla.org),
+// only the former has a cookie permission.
+add_task(async function subDomains2() {
+ info("Test subdomains and custom setting with cookieBehavior == 2");
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ // Domains and data
+ let originA = "https://sub.mozilla.org";
+ PermissionTestUtils.add(
+ originA,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originA });
+ await SiteDataTestUtils.addToIndexedDB(originA);
+
+ let originB = "https://www.mozilla.org";
+
+ SiteDataTestUtils.addToCookies({ origin: originB });
+ await SiteDataTestUtils.addToIndexedDB(originB);
+
+ // Check
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+
+ // Cleaning up
+ await Sanitizer.runSanitizeOnShutdown();
+
+ // Check again
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(originB),
+ "We should not have cookies for " + originB
+ );
+ ok(
+ !(await SiteDataTestUtils.hasIndexedDB(originB)),
+ "We should not have IDB for " + originB
+ );
+
+ // Cleaning up permissions
+ PermissionTestUtils.remove(originA, "cookie");
+});
+
+// session only cookie life-time, 3 domains (sub.mozilla.org, www.mozilla.org, mozilla.org),
+// only the former has a cookie permission. Both sub.mozilla.org and mozilla.org should
+// be sustained.
+add_task(async function subDomains3() {
+ info(
+ "Test base domain and subdomains and custom setting with cookieBehavior == 2"
+ );
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ // Domains and data
+ let originA = "https://sub.mozilla.org";
+ PermissionTestUtils.add(
+ originA,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+ SiteDataTestUtils.addToCookies({ origin: originA });
+ await SiteDataTestUtils.addToIndexedDB(originA);
+
+ let originB = "https://mozilla.org";
+ SiteDataTestUtils.addToCookies({ origin: originB });
+ await SiteDataTestUtils.addToIndexedDB(originB);
+
+ let originC = "https://www.mozilla.org";
+ SiteDataTestUtils.addToCookies({ origin: originC });
+ await SiteDataTestUtils.addToIndexedDB(originC);
+
+ // Check
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+ ok(SiteDataTestUtils.hasCookies(originC), "We have cookies for " + originC);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originC),
+ "We have IDB for " + originC
+ );
+
+ // Cleaning up
+ await Sanitizer.runSanitizeOnShutdown();
+
+ // Check again
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+ ok(
+ !SiteDataTestUtils.hasCookies(originC),
+ "We should not have cookies for " + originC
+ );
+ ok(
+ !(await SiteDataTestUtils.hasIndexedDB(originC)),
+ "We should not have IDB for " + originC
+ );
+
+ // Cleaning up permissions
+ PermissionTestUtils.remove(originA, "cookie");
+});
+
+// clear on shutdown, 3 domains (sub.sub.mozilla.org, sub.mozilla.org, mozilla.org),
+// only the former has a cookie permission. Both sub.mozilla.org and mozilla.org should
+// be sustained due to Permission of sub.sub.mozilla.org
+add_task(async function subDomains4() {
+ info("Test subdomain cookie permission inheritance with two subdomains");
+
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ // Domains and data
+ let originA = "https://sub.sub.mozilla.org";
+ PermissionTestUtils.add(
+ originA,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+ SiteDataTestUtils.addToCookies({ origin: originA });
+ await SiteDataTestUtils.addToIndexedDB(originA);
+
+ let originB = "https://sub.mozilla.org";
+ SiteDataTestUtils.addToCookies({ origin: originB });
+ await SiteDataTestUtils.addToIndexedDB(originB);
+
+ let originC = "https://mozilla.org";
+ SiteDataTestUtils.addToCookies({ origin: originC });
+ await SiteDataTestUtils.addToIndexedDB(originC);
+
+ // Check
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+ ok(SiteDataTestUtils.hasCookies(originC), "We have cookies for " + originC);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originC),
+ "We have IDB for " + originC
+ );
+
+ // Cleaning up
+ await Sanitizer.runSanitizeOnShutdown();
+
+ // Check again
+ ok(SiteDataTestUtils.hasCookies(originA), "We have cookies for " + originA);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originA),
+ "We have IDB for " + originA
+ );
+ ok(SiteDataTestUtils.hasCookies(originB), "We have cookies for " + originB);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originB),
+ "We have IDB for " + originB
+ );
+ ok(SiteDataTestUtils.hasCookies(originC), "We have cookies for " + originC);
+ ok(
+ await SiteDataTestUtils.hasIndexedDB(originC),
+ "We have IDB for " + originC
+ );
+
+ // Cleaning up permissions
+ PermissionTestUtils.remove(originA, "cookie");
+});
diff --git a/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js
new file mode 100644
index 0000000000..1c3c801e3d
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const url =
+ "https://example.org/browser/browser/base/content/test/sanitize/dummy_page.html";
+
+add_task(async function purgeHistoryTest() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url,
+ },
+ async function purgeHistoryTestInner(browser) {
+ let backButton = browser.ownerDocument.getElementById("Browser:Back");
+ let forwardButton = browser.ownerDocument.getElementById(
+ "Browser:Forward"
+ );
+
+ ok(
+ !browser.webNavigation.canGoBack,
+ "Initial value for webNavigation.canGoBack"
+ );
+ ok(
+ !browser.webNavigation.canGoForward,
+ "Initial value for webNavigation.canGoBack"
+ );
+ ok(backButton.hasAttribute("disabled"), "Back button is disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled");
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ let startHistory = content.history.length;
+ content.history.pushState({}, "");
+ content.history.pushState({}, "");
+ content.history.back();
+ await new Promise(function(r) {
+ content.onpopstate = r;
+ });
+ let newHistory = content.history.length;
+ Assert.equal(startHistory, 1, "Initial SHistory size");
+ Assert.equal(newHistory, 3, "New SHistory size");
+ });
+
+ ok(
+ browser.webNavigation.canGoBack,
+ "New value for webNavigation.canGoBack"
+ );
+ ok(
+ browser.webNavigation.canGoForward,
+ "New value for webNavigation.canGoForward"
+ );
+ ok(!backButton.hasAttribute("disabled"), "Back button was enabled");
+ ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled");
+
+ await Sanitizer.sanitize(["history"]);
+
+ await SpecialPowers.spawn(browser, [], async function() {
+ Assert.equal(content.history.length, 1, "SHistory correctly cleared");
+ });
+
+ ok(
+ !browser.webNavigation.canGoBack,
+ "webNavigation.canGoBack correctly cleared"
+ );
+ ok(
+ !browser.webNavigation.canGoForward,
+ "webNavigation.canGoForward correctly cleared"
+ );
+ ok(backButton.hasAttribute("disabled"), "Back button was disabled");
+ ok(forwardButton.hasAttribute("disabled"), "Forward button was disabled");
+ }
+ );
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js b/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js
new file mode 100644
index 0000000000..8c95a08818
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js
@@ -0,0 +1,192 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { Sanitizer } = ChromeUtils.importESModule(
+ "resource:///modules/Sanitizer.sys.mjs"
+);
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+const { PermissionTestUtils } = ChromeUtils.import(
+ "resource://testing-common/PermissionTestUtils.jsm"
+);
+
+const oneHour = 3600000000;
+
+add_task(async function sanitizeWithExceptionsOnShutdown() {
+ info(
+ "Test that cookies that are marked as allowed from the user do not get \
+ cleared when cleaning on shutdown is done"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sanitizer.loglevel", "All"],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+
+ // Clean up before start
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ let originALLOW = "https://mozilla.org";
+ PermissionTestUtils.add(
+ originALLOW,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ let originDENY = "https://example123.com";
+ PermissionTestUtils.add(
+ originDENY,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DENY
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originALLOW });
+ ok(
+ SiteDataTestUtils.hasCookies(originALLOW),
+ "We have cookies for " + originALLOW
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originDENY });
+ ok(
+ SiteDataTestUtils.hasCookies(originDENY),
+ "We have cookies for " + originDENY
+ );
+
+ await Sanitizer.runSanitizeOnShutdown();
+
+ ok(
+ SiteDataTestUtils.hasCookies(originALLOW),
+ "We should have cookies for " + originALLOW
+ );
+
+ ok(
+ !SiteDataTestUtils.hasCookies(originDENY),
+ "We should not have cookies for " + originDENY
+ );
+});
+
+add_task(async function sanitizeNoExceptionsInTimeRange() {
+ info(
+ "Test that no exceptions are made when not clearing on shutdown, e.g. clearing within a range"
+ );
+
+ // Clean up before start
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ let originALLOW = "https://mozilla.org";
+ PermissionTestUtils.add(
+ originALLOW,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ let originDENY = "https://bar123.com";
+ PermissionTestUtils.add(
+ originDENY,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DENY
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originALLOW });
+ ok(
+ SiteDataTestUtils.hasCookies(originALLOW),
+ "We have cookies for " + originALLOW
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originDENY });
+ ok(
+ SiteDataTestUtils.hasCookies(originDENY),
+ "We have cookies for " + originDENY
+ );
+
+ let to = Date.now() * 1000;
+ let from = to - oneHour;
+ await Sanitizer.sanitize(["cookies"], { range: [from, to] });
+
+ ok(
+ !SiteDataTestUtils.hasCookies(originALLOW),
+ "We should not have cookies for " + originALLOW
+ );
+
+ ok(
+ !SiteDataTestUtils.hasCookies(originDENY),
+ "We should not have cookies for " + originDENY
+ );
+});
+
+add_task(async function sanitizeWithExceptionsOnStartup() {
+ info(
+ "Test that cookies that are marked as allowed from the user do not get \
+ cleared when cleaning on startup is done, for example after a crash"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sanitizer.loglevel", "All"],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+
+ // Clean up before start
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ let originALLOW = "https://mozilla.org";
+ PermissionTestUtils.add(
+ originALLOW,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ let originDENY = "https://example123.com";
+ PermissionTestUtils.add(
+ originDENY,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_DENY
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originALLOW });
+ ok(
+ SiteDataTestUtils.hasCookies(originALLOW),
+ "We have cookies for " + originALLOW
+ );
+
+ SiteDataTestUtils.addToCookies({ origin: originDENY });
+ ok(
+ SiteDataTestUtils.hasCookies(originDENY),
+ "We have cookies for " + originDENY
+ );
+
+ let pendingSanitizations = [
+ {
+ id: "shutdown",
+ itemsToClear: ["cookies"],
+ options: {},
+ },
+ ];
+ Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, true);
+ Services.prefs.setStringPref(
+ Sanitizer.PREF_PENDING_SANITIZATIONS,
+ JSON.stringify(pendingSanitizations)
+ );
+
+ await Sanitizer.onStartup();
+
+ ok(
+ SiteDataTestUtils.hasCookies(originALLOW),
+ "We should have cookies for " + originALLOW
+ );
+
+ ok(
+ !SiteDataTestUtils.hasCookies(originDENY),
+ "We should not have cookies for " + originDENY
+ );
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-formhistory.js b/browser/base/content/test/sanitize/browser_sanitize-formhistory.js
new file mode 100644
index 0000000000..5547a88d64
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-formhistory.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/. */
+
+add_task(async function test() {
+ // This test relies on the form history being empty to start with delete
+ // all the items first.
+ // The TabContextMenu initializes its strings only on a focus or mouseover event.
+ // Calls focus event on the TabContextMenu early in the test.
+ gBrowser.selectedTab.focus();
+ await FormHistory.update({ op: "remove" });
+
+ // Sanitize now so we can test the baseline point.
+ await Sanitizer.sanitize(["formdata"]);
+ await gFindBarPromise;
+ ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer");
+
+ gFindBar.getElement("findbar-textbox").value = "m";
+ ok(gFindBar.hasTransactions, "formdata can be cleared after input");
+
+ await Sanitizer.sanitize(["formdata"]);
+ is(
+ gFindBar.getElement("findbar-textbox").value,
+ "",
+ "findBar textbox should be empty after sanitize"
+ );
+ ok(!gFindBar.hasTransactions, "No transactions after sanitize");
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-history.js b/browser/base/content/test/sanitize/browser_sanitize-history.js
new file mode 100644
index 0000000000..5ca2843174
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-history.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that sanitizing history will clear storage access permissions
+// for sites without cookies or site data.
+add_task(async function sanitizeStorageAccessPermissions() {
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ await SiteDataTestUtils.addToIndexedDB("https://sub.example.org");
+ await SiteDataTestUtils.addToCookies({ origin: "https://example.com" });
+
+ PermissionTestUtils.add(
+ "https://example.org",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ "https://example.com",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+ PermissionTestUtils.add(
+ "http://mochi.test",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Add some time in between taking the snapshot of the timestamp
+ // to avoid flakyness.
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(c => setTimeout(c, 100));
+ let timestamp = Date.now();
+ // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+ await new Promise(c => setTimeout(c, 100));
+
+ PermissionTestUtils.add(
+ "http://example.net",
+ "storageAccessAPI",
+ Services.perms.ALLOW_ACTION
+ );
+
+ await Sanitizer.sanitize(["history"], {
+ // Sanitizer and ClearDataService work with time range in PRTime (microseconds)
+ range: [timestamp * 1000, Date.now() * 1000],
+ });
+
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "http://example.net",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "http://mochi.test",
+ "storageAccessAPI"
+ ),
+ Services.perms.ALLOW_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.com",
+ "storageAccessAPI"
+ ),
+ Services.perms.ALLOW_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.org",
+ "storageAccessAPI"
+ ),
+ Services.perms.ALLOW_ACTION
+ );
+
+ await Sanitizer.sanitize(["history"]);
+
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "http://mochi.test",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "http://example.net",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.com",
+ "storageAccessAPI"
+ ),
+ Services.perms.ALLOW_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.org",
+ "storageAccessAPI"
+ ),
+ Services.perms.ALLOW_ACTION
+ );
+
+ await Sanitizer.sanitize(["history", "siteSettings"]);
+
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "http://mochi.test",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.com",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+ Assert.equal(
+ PermissionTestUtils.testExactPermission(
+ "https://example.org",
+ "storageAccessAPI"
+ ),
+ Services.perms.UNKNOWN_ACTION
+ );
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-offlineData.js b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js
new file mode 100644
index 0000000000..a4ed18f37e
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js
@@ -0,0 +1,272 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+const { Sanitizer } = ChromeUtils.importESModule(
+ "resource:///modules/Sanitizer.sys.mjs"
+);
+const { SiteDataTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/SiteDataTestUtils.sys.mjs"
+);
+const { PromiseTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromiseTestUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "sas",
+ "@mozilla.org/storage/activity-service;1",
+ "nsIStorageActivityService"
+);
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "swm",
+ "@mozilla.org/serviceworkers/manager;1",
+ "nsIServiceWorkerManager"
+);
+
+const oneHour = 3600000000;
+const fiveHours = oneHour * 5;
+
+const itemsToClear = ["cookies", "offlineApps"];
+
+function hasIndexedDB(origin) {
+ return new Promise(resolve => {
+ let hasData = true;
+ let uri = Services.io.newURI(origin);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ {}
+ );
+ let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+ request.onupgradeneeded = function(e) {
+ hasData = false;
+ };
+ request.onsuccess = function(e) {
+ resolve(hasData);
+ };
+ });
+}
+
+function waitForUnregister(host) {
+ return new Promise(resolve => {
+ let listener = {
+ onUnregister: registration => {
+ if (registration.principal.host != host) {
+ return;
+ }
+ swm.removeListener(listener);
+ resolve(registration);
+ },
+ };
+ swm.addListener(listener);
+ });
+}
+
+async function createData(host) {
+ let origin = "https://" + host;
+ let dummySWURL =
+ getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) +
+ "dummy.js";
+
+ await SiteDataTestUtils.addToIndexedDB(origin);
+ await SiteDataTestUtils.addServiceWorker(dummySWURL);
+}
+
+function moveOriginInTime(principals, endDate, host) {
+ for (let i = 0; i < principals.length; ++i) {
+ let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
+ if (principal.host == host) {
+ sas.moveOriginInTime(principal, endDate - fiveHours);
+ return true;
+ }
+ }
+ return false;
+}
+
+add_task(async function testWithRange() {
+ // We have intermittent occurrences of NS_ERROR_ABORT being
+ // thrown at closing database instances when using Santizer.sanitize().
+ // This does not seem to impact cleanup, since our tests run fine anyway.
+ PromiseTestUtils.allowMatchingRejectionsGlobally(/NS_ERROR_ABORT/);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.serviceWorkers.enabled", true],
+ ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+ ["dom.serviceWorkers.testing.enabled", true],
+ ],
+ });
+
+ // The service may have picked up activity from prior tests in this run.
+ // Clear it.
+ sas.testOnlyReset();
+
+ let endDate = Date.now() * 1000;
+ let principals = sas.getActiveOrigins(endDate - oneHour, endDate);
+ is(principals.length, 0, "starting from clear activity state");
+
+ info("sanitize: " + itemsToClear.join(", "));
+ await Sanitizer.sanitize(itemsToClear, { ignoreTimespan: false });
+
+ await createData("example.org");
+ await createData("example.com");
+
+ endDate = Date.now() * 1000;
+ principals = sas.getActiveOrigins(endDate - oneHour, endDate);
+ ok(!!principals, "We have an active origin.");
+ ok(principals.length >= 2, "We have an active origin.");
+
+ let found = 0;
+ for (let i = 0; i < principals.length; ++i) {
+ let principal = principals.queryElementAt(i, Ci.nsIPrincipal);
+ if (principal.host == "example.org" || principal.host == "example.com") {
+ found++;
+ }
+ }
+
+ is(found, 2, "Our origins are active.");
+
+ ok(
+ await hasIndexedDB("https://example.org"),
+ "We have indexedDB data for example.org"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+ "We have serviceWorker data for example.org"
+ );
+
+ ok(
+ await hasIndexedDB("https://example.com"),
+ "We have indexedDB data for example.com"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+ "We have serviceWorker data for example.com"
+ );
+
+ // Let's move example.com in the past.
+ ok(
+ moveOriginInTime(principals, endDate, "example.com"),
+ "Operation completed!"
+ );
+
+ let p = waitForUnregister("example.org");
+
+ // Clear it
+ info("sanitize: " + itemsToClear.join(", "));
+ await Sanitizer.sanitize(itemsToClear, { ignoreTimespan: false });
+ await p;
+
+ ok(
+ !(await hasIndexedDB("https://example.org")),
+ "We don't have indexedDB data for example.org"
+ );
+ ok(
+ !SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+ "We don't have serviceWorker data for example.org"
+ );
+
+ ok(
+ await hasIndexedDB("https://example.com"),
+ "We still have indexedDB data for example.com"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+ "We still have serviceWorker data for example.com"
+ );
+
+ // We have to move example.com in the past because how we check IDB triggers
+ // a storage activity.
+ ok(
+ moveOriginInTime(principals, endDate, "example.com"),
+ "Operation completed!"
+ );
+
+ // Let's call the clean up again.
+ info("sanitize again to ensure clearing doesn't expand the activity scope");
+ await Sanitizer.sanitize(itemsToClear, { ignoreTimespan: false });
+
+ ok(
+ await hasIndexedDB("https://example.com"),
+ "We still have indexedDB data for example.com"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+ "We still have serviceWorker data for example.com"
+ );
+
+ ok(
+ !(await hasIndexedDB("https://example.org")),
+ "We don't have indexedDB data for example.org"
+ );
+ ok(
+ !SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+ "We don't have serviceWorker data for example.org"
+ );
+
+ sas.testOnlyReset();
+
+ // Clean up.
+ await Sanitizer.sanitize(itemsToClear);
+});
+
+add_task(async function testExceptionsOnShutdown() {
+ await createData("example.org");
+ await createData("example.com");
+
+ // Set exception for example.org to not get cleaned
+ let originALLOW = "https://example.org";
+ PermissionTestUtils.add(
+ originALLOW,
+ "cookie",
+ Ci.nsICookiePermission.ACCESS_ALLOW
+ );
+
+ ok(
+ await hasIndexedDB("https://example.org"),
+ "We have indexedDB data for example.org"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+ "We have serviceWorker data for example.org"
+ );
+
+ ok(
+ await hasIndexedDB("https://example.com"),
+ "We have indexedDB data for example.com"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+ "We have serviceWorker data for example.com"
+ );
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.sanitizer.loglevel", "All"],
+ ["privacy.clearOnShutdown.offlineApps", true],
+ ["privacy.sanitize.sanitizeOnShutdown", true],
+ ],
+ });
+ // Clear it
+ await Sanitizer.runSanitizeOnShutdown();
+ // Data for example.org should not have been cleared
+ ok(
+ await hasIndexedDB("https://example.org"),
+ "We still have indexedDB data for example.org"
+ );
+ ok(
+ SiteDataTestUtils.hasServiceWorkers("https://example.org"),
+ "We still have serviceWorker data for example.org"
+ );
+ // Data for example.com should be cleared
+ ok(
+ !(await hasIndexedDB("https://example.com")),
+ "We don't have indexedDB data for example.com"
+ );
+ ok(
+ !SiteDataTestUtils.hasServiceWorkers("https://example.com"),
+ "We don't have serviceWorker data for example.com"
+ );
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-passwordDisabledHosts.js b/browser/base/content/test/sanitize/browser_sanitize-passwordDisabledHosts.js
new file mode 100644
index 0000000000..e7c99f28c0
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-passwordDisabledHosts.js
@@ -0,0 +1,28 @@
+// Bug 474792 - Clear "Never remember passwords for this site" when
+// clearing site-specific settings in Clear Recent History dialog
+
+add_task(async function() {
+ // getLoginSavingEnabled always returns false if password capture is disabled.
+ await SpecialPowers.pushPrefEnv({ set: [["signon.rememberSignons", true]] });
+
+ // Add a disabled host
+ Services.logins.setLoginSavingEnabled("https://example.com", false);
+ // Sanity check
+ is(
+ Services.logins.getLoginSavingEnabled("https://example.com"),
+ false,
+ "example.com should be disabled for password saving since we haven't cleared that yet."
+ );
+
+ // Clear it
+ await Sanitizer.sanitize(["siteSettings"], { ignoreTimespan: false });
+
+ // Make sure it's gone
+ is(
+ Services.logins.getLoginSavingEnabled("https://example.com"),
+ true,
+ "example.com should be enabled for password saving again now that we've cleared."
+ );
+
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-sitepermissions.js b/browser/base/content/test/sanitize/browser_sanitize-sitepermissions.js
new file mode 100644
index 0000000000..034727852a
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-sitepermissions.js
@@ -0,0 +1,37 @@
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+function countPermissions() {
+ return Services.perms.all.length;
+}
+
+add_task(async function test() {
+ // sanitize before we start so we have a good baseline.
+ await Sanitizer.sanitize(["siteSettings"], { ignoreTimespan: false });
+
+ // Count how many permissions we start with - some are defaults that
+ // will not be sanitized.
+ let numAtStart = countPermissions();
+
+ // Add a permission entry
+ PermissionTestUtils.add(
+ "https://example.com",
+ "testing",
+ Services.perms.ALLOW_ACTION
+ );
+
+ // Sanity check
+ ok(
+ !!Services.perms.all.length,
+ "Permission manager should have elements, since we just added one"
+ );
+
+ // Clear it
+ await Sanitizer.sanitize(["siteSettings"], { ignoreTimespan: false });
+
+ // Make sure it's gone
+ is(
+ numAtStart,
+ countPermissions(),
+ "Permission manager should have the same count it started with"
+ );
+});
diff --git a/browser/base/content/test/sanitize/browser_sanitize-timespans.js b/browser/base/content/test/sanitize/browser_sanitize-timespans.js
new file mode 100644
index 0000000000..828eeceacb
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitize-timespans.js
@@ -0,0 +1,1194 @@
+requestLongerTimeout(2);
+
+const { PlacesTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PlacesTestUtils.sys.mjs"
+);
+
+// Bug 453440 - Test the timespan-based logic of the sanitizer code
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+function promiseFormHistoryRemoved() {
+ return new Promise(resolve => {
+ Services.obs.addObserver(function onfh() {
+ Services.obs.removeObserver(onfh, "satchel-storage-changed");
+ resolve();
+ }, "satchel-storage-changed");
+ });
+}
+
+function promiseDownloadRemoved(list) {
+ return new Promise(resolve => {
+ let view = {
+ onDownloadRemoved(download) {
+ list.removeView(view);
+ resolve();
+ },
+ };
+
+ list.addView(view);
+ });
+}
+
+add_task(async function test() {
+ await setupDownloads();
+ await setupFormHistory();
+ await setupHistory();
+ await onHistoryReady();
+});
+
+async function countEntries(name, message, check) {
+ var obj = {};
+ if (name !== null) {
+ obj.fieldname = name;
+ }
+ let count = await FormHistory.count(obj);
+ check(count, message);
+}
+
+async function onHistoryReady() {
+ var hoursSinceMidnight = new Date().getHours();
+ var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
+
+ // Should test cookies here, but nsICookieManager/nsICookieService
+ // doesn't let us fake creation times. bug 463127
+
+ var itemPrefs = Services.prefs.getBranch("privacy.cpd.");
+ itemPrefs.setBoolPref("history", true);
+ itemPrefs.setBoolPref("downloads", true);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", true);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloadPromise = promiseDownloadRemoved(publicList);
+ let formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 10 minutes ago
+ let range = [now_uSec - 10 * 60 * 1000000, now_uSec];
+ await Sanitizer.sanitize(null, { range, ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://10minutes.com")),
+ "Pretend visit to 10minutes.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://1hour.com"),
+ "Pretend visit to 1hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://1hour10minutes.com"),
+ "Pretend visit to 1hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour.com"),
+ "Pretend visit to 2hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour10minutes.com"),
+ "Pretend visit to 2hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour.com"),
+ "Pretend visit to 4hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (minutesSinceMidnight > 10) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ let checkZero = function(num, message) {
+ is(num, 0, message);
+ };
+ let checkOne = function(num, message) {
+ is(num, 1, message);
+ };
+
+ await countEntries(
+ "10minutes",
+ "10minutes form entry should be deleted",
+ checkZero
+ );
+ await countEntries("1hour", "1hour form entry should still exist", checkOne);
+ await countEntries(
+ "1hour10minutes",
+ "1hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("2hour", "2hour form entry should still exist", checkOne);
+ await countEntries(
+ "2hour10minutes",
+ "2hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("4hour", "4hour form entry should still exist", checkOne);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (minutesSinceMidnight > 10) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-10-minutes")),
+ "10 minute download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-1-hour"),
+ "<1 hour download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-1-hour-10-minutes"),
+ "1 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour"),
+ "<2 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour-10-minutes"),
+ "2 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "<4 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+
+ if (minutesSinceMidnight > 10) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour
+ Services.prefs.setIntPref(Sanitizer.PREF_TIMESPAN, 1);
+ await Sanitizer.sanitize(null, { ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://1hour.com")),
+ "Pretend visit to 1hour.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://1hour10minutes.com"),
+ "Pretend visit to 1hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour.com"),
+ "Pretend visit to 2hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour10minutes.com"),
+ "Pretend visit to 2hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour.com"),
+ "Pretend visit to 4hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (hoursSinceMidnight > 1) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries("1hour", "1hour form entry should be deleted", checkZero);
+ await countEntries(
+ "1hour10minutes",
+ "1hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("2hour", "2hour form entry should still exist", checkOne);
+ await countEntries(
+ "2hour10minutes",
+ "2hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("4hour", "4hour form entry should still exist", checkOne);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (hoursSinceMidnight > 1) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-1-hour")),
+ "<1 hour download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-1-hour-10-minutes"),
+ "1 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour"),
+ "<2 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour-10-minutes"),
+ "2 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "<4 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+
+ if (hoursSinceMidnight > 1) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 1 hour 10 minutes
+ range = [now_uSec - 70 * 60 * 1000000, now_uSec];
+ await Sanitizer.sanitize(null, { range, ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://1hour10minutes.com")),
+ "Pretend visit to 1hour10minutes.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour.com"),
+ "Pretend visit to 2hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour10minutes.com"),
+ "Pretend visit to 2hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour.com"),
+ "Pretend visit to 4hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (minutesSinceMidnight > 70) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries(
+ "1hour10minutes",
+ "1hour10minutes form entry should be deleted",
+ checkZero
+ );
+ await countEntries("2hour", "2hour form entry should still exist", checkOne);
+ await countEntries(
+ "2hour10minutes",
+ "2hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("4hour", "4hour form entry should still exist", checkOne);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (minutesSinceMidnight > 70) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-1-hour-10-minutes")),
+ "1 hour 10 minute old download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour"),
+ "<2 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour-10-minutes"),
+ "2 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "<4 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+ if (minutesSinceMidnight > 70) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours
+ Services.prefs.setIntPref(Sanitizer.PREF_TIMESPAN, 2);
+ await Sanitizer.sanitize(null, { ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://2hour.com")),
+ "Pretend visit to 2hour.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://2hour10minutes.com"),
+ "Pretend visit to 2hour10minutes.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour.com"),
+ "Pretend visit to 4hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (hoursSinceMidnight > 2) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries("2hour", "2hour form entry should be deleted", checkZero);
+ await countEntries(
+ "2hour10minutes",
+ "2hour10minutes form entry should still exist",
+ checkOne
+ );
+ await countEntries("4hour", "4hour form entry should still exist", checkOne);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (hoursSinceMidnight > 2) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-2-hour")),
+ "<2 hour old download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour-10-minutes"),
+ "2 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "<4 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+ if (hoursSinceMidnight > 2) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 2 hours 10 minutes
+ range = [now_uSec - 130 * 60 * 1000000, now_uSec];
+ await Sanitizer.sanitize(null, { range, ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://2hour10minutes.com")),
+ "Pretend visit to 2hour10minutes.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour.com"),
+ "Pretend visit to 4hour.com should should still exist"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (minutesSinceMidnight > 130) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries(
+ "2hour10minutes",
+ "2hour10minutes form entry should be deleted",
+ checkZero
+ );
+ await countEntries("4hour", "4hour form entry should still exist", checkOne);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (minutesSinceMidnight > 130) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-2-hour-10-minutes")),
+ "2 hour 10 minute old download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "<4 hour old download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ if (minutesSinceMidnight > 130) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours
+ Services.prefs.setIntPref(Sanitizer.PREF_TIMESPAN, 3);
+ await Sanitizer.sanitize(null, { ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://4hour.com")),
+ "Pretend visit to 4hour.com should now be deleted"
+ );
+ ok(
+ await PlacesUtils.history.hasVisits("https://4hour10minutes.com"),
+ "Pretend visit to 4hour10minutes.com should should still exist"
+ );
+ if (hoursSinceMidnight > 4) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries("4hour", "4hour form entry should be deleted", checkZero);
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should still exist",
+ checkOne
+ );
+ if (hoursSinceMidnight > 4) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-4-hour")),
+ "<4 hour old download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "4 hour 10 minute download should still be present"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ if (hoursSinceMidnight > 4) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Clear 4 hours 10 minutes
+ range = [now_uSec - 250 * 60 * 1000000, now_uSec];
+ await Sanitizer.sanitize(null, { range, ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://4hour10minutes.com")),
+ "Pretend visit to 4hour10minutes.com should now be deleted"
+ );
+ if (minutesSinceMidnight > 250) {
+ ok(
+ await PlacesUtils.history.hasVisits("https://today.com"),
+ "Pretend visit to today.com should still exist"
+ );
+ }
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+
+ await countEntries(
+ "4hour10minutes",
+ "4hour10minutes form entry should be deleted",
+ checkZero
+ );
+ if (minutesSinceMidnight > 250) {
+ await countEntries(
+ "today",
+ "today form entry should still exist",
+ checkOne
+ );
+ }
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-4-hour-10-minutes")),
+ "4 hour 10 minute download should now be deleted"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+ if (minutesSinceMidnight > 250) {
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "'Today' download should still be present"
+ );
+ }
+
+ // The 'Today' download might have been already deleted, in which case we
+ // should not wait for a download removal notification.
+ if (minutesSinceMidnight > 250) {
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+ } else {
+ downloadPromise = formHistoryPromise = Promise.resolve();
+ }
+
+ // Clear Today
+ Services.prefs.setIntPref(Sanitizer.PREF_TIMESPAN, 4);
+ let progress = await Sanitizer.sanitize(null, { ignoreTimespan: false });
+ Assert.deepEqual(progress, {
+ history: "cleared",
+ formdata: "cleared",
+ downloads: "cleared",
+ });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ // Be careful. If we add our objectss just before midnight, and sanitize
+ // runs immediately after, they won't be expired. This is expected, but
+ // we should not test in that case. We cannot just test for opposite
+ // condition because we could cross midnight just one moment after we
+ // cache our time, then we would have an even worse random failure.
+ var today = isToday(new Date(now_mSec));
+ if (today) {
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://today.com")),
+ "Pretend visit to today.com should now be deleted"
+ );
+
+ await countEntries(
+ "today",
+ "today form entry should be deleted",
+ checkZero
+ );
+ ok(
+ !(await downloadExists(publicList, "fakefile-today")),
+ "'Today' download should now be deleted"
+ );
+ }
+
+ ok(
+ await PlacesUtils.history.hasVisits("https://before-today.com"),
+ "Pretend visit to before-today.com should still exist"
+ );
+ await countEntries(
+ "b4today",
+ "b4today form entry should still exist",
+ checkOne
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Year old download should still be present"
+ );
+
+ downloadPromise = promiseDownloadRemoved(publicList);
+ formHistoryPromise = promiseFormHistoryRemoved();
+
+ // Choose everything
+ Services.prefs.setIntPref(Sanitizer.PREF_TIMESPAN, 0);
+ await Sanitizer.sanitize(null, { ignoreTimespan: false });
+
+ await formHistoryPromise;
+ await downloadPromise;
+
+ ok(
+ !(await PlacesUtils.history.hasVisits("https://before-today.com")),
+ "Pretend visit to before-today.com should now be deleted"
+ );
+
+ await countEntries(
+ "b4today",
+ "b4today form entry should be deleted",
+ checkZero
+ );
+
+ ok(
+ !(await downloadExists(publicList, "fakefile-old")),
+ "Year old download should now be deleted"
+ );
+}
+
+async function setupHistory() {
+ let places = [];
+
+ function addPlace(aURI, aTitle, aVisitDate) {
+ places.push({
+ uri: aURI,
+ title: aTitle,
+ visitDate: aVisitDate,
+ transition: Ci.nsINavHistoryService.TRANSITION_LINK,
+ });
+ }
+
+ addPlace(
+ "https://10minutes.com/",
+ "10 minutes ago",
+ now_uSec - 10 * kUsecPerMin
+ );
+ addPlace(
+ "https://1hour.com/",
+ "Less than 1 hour ago",
+ now_uSec - 45 * kUsecPerMin
+ );
+ addPlace(
+ "https://1hour10minutes.com/",
+ "1 hour 10 minutes ago",
+ now_uSec - 70 * kUsecPerMin
+ );
+ addPlace(
+ "https://2hour.com/",
+ "Less than 2 hours ago",
+ now_uSec - 90 * kUsecPerMin
+ );
+ addPlace(
+ "https://2hour10minutes.com/",
+ "2 hours 10 minutes ago",
+ now_uSec - 130 * kUsecPerMin
+ );
+ addPlace(
+ "https://4hour.com/",
+ "Less than 4 hours ago",
+ now_uSec - 180 * kUsecPerMin
+ );
+ addPlace(
+ "https://4hour10minutes.com/",
+ "4 hours 10 minutesago",
+ now_uSec - 250 * kUsecPerMin
+ );
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(0);
+ today.setMilliseconds(1);
+ addPlace("https://today.com/", "Today", today.getTime() * 1000);
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ addPlace(
+ "https://before-today.com/",
+ "Before Today",
+ lastYear.getTime() * 1000
+ );
+ await PlacesTestUtils.addVisits(places);
+}
+
+async function setupFormHistory() {
+ function searchEntries(terms, params) {
+ return FormHistory.search(terms, params);
+ }
+
+ // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
+ await FormHistory.update([
+ {
+ op: "remove",
+ },
+ {
+ op: "add",
+ fieldname: "10minutes",
+ value: "10m",
+ },
+ {
+ op: "add",
+ fieldname: "1hour",
+ value: "1h",
+ },
+ {
+ op: "add",
+ fieldname: "1hour10minutes",
+ value: "1h10m",
+ },
+ {
+ op: "add",
+ fieldname: "2hour",
+ value: "2h",
+ },
+ {
+ op: "add",
+ fieldname: "2hour10minutes",
+ value: "2h10m",
+ },
+ {
+ op: "add",
+ fieldname: "4hour",
+ value: "4h",
+ },
+ {
+ op: "add",
+ fieldname: "4hour10minutes",
+ value: "4h10m",
+ },
+ {
+ op: "add",
+ fieldname: "today",
+ value: "1d",
+ },
+ {
+ op: "add",
+ fieldname: "b4today",
+ value: "1y",
+ },
+ ]);
+
+ // Artifically age the entries to the proper vintage.
+ let timestamp = now_uSec - 10 * kUsecPerMin;
+ let results = await searchEntries(["guid"], { fieldname: "10minutes" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 45 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "1hour" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 70 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "1hour10minutes" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 90 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "2hour" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 130 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "2hour10minutes" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 180 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "4hour" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ timestamp = now_uSec - 250 * kUsecPerMin;
+ results = await searchEntries(["guid"], { fieldname: "4hour10minutes" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(0);
+ today.setMilliseconds(1);
+ timestamp = today.getTime() * 1000;
+ results = await searchEntries(["guid"], { fieldname: "today" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ timestamp = lastYear.getTime() * 1000;
+ results = await searchEntries(["guid"], { fieldname: "b4today" });
+ await FormHistory.update({
+ op: "update",
+ firstUsed: timestamp,
+ guid: results[0].guid,
+ });
+
+ var checks = 0;
+ let checkOne = function(num, message) {
+ is(num, 1, message);
+ checks++;
+ };
+
+ // Sanity check.
+ await countEntries(
+ "10minutes",
+ "Checking for 10minutes form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "1hour",
+ "Checking for 1hour form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "1hour10minutes",
+ "Checking for 1hour10minutes form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "2hour",
+ "Checking for 2hour form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "2hour10minutes",
+ "Checking for 2hour10minutes form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "4hour",
+ "Checking for 4hour form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "4hour10minutes",
+ "Checking for 4hour10minutes form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "today",
+ "Checking for today form history entry creation",
+ checkOne
+ );
+ await countEntries(
+ "b4today",
+ "Checking for b4today form history entry creation",
+ checkOne
+ );
+ is(checks, 9, "9 checks made");
+}
+
+async function setupDownloads() {
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+
+ let download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-10-minutes",
+ });
+ download.startTime = new Date(now_mSec - 10 * kMsecPerMin); // 10 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-1-hour",
+ });
+ download.startTime = new Date(now_mSec - 45 * kMsecPerMin); // 45 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-1-hour-10-minutes",
+ });
+ download.startTime = new Date(now_mSec - 70 * kMsecPerMin); // 70 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-2-hour",
+ });
+ download.startTime = new Date(now_mSec - 90 * kMsecPerMin); // 90 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-2-hour-10-minutes",
+ });
+ download.startTime = new Date(now_mSec - 130 * kMsecPerMin); // 130 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-4-hour",
+ });
+ download.startTime = new Date(now_mSec - 180 * kMsecPerMin); // 180 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-4-hour-10-minutes",
+ });
+ download.startTime = new Date(now_mSec - 250 * kMsecPerMin); // 250 minutes ago
+ download.canceled = true;
+ await publicList.add(download);
+
+ // Add "today" download
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(0);
+ today.setMilliseconds(1);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-today",
+ });
+ download.startTime = today; // 12:00:01 AM this morning
+ download.canceled = true;
+ await publicList.add(download);
+
+ // Add "before today" download
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+
+ download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-old",
+ });
+ download.startTime = lastYear;
+ download.canceled = true;
+ await publicList.add(download);
+
+ // Confirm everything worked
+ let downloads = await publicList.getAll();
+ is(downloads.length, 9, "9 Pretend downloads added");
+
+ ok(
+ await downloadExists(publicList, "fakefile-old"),
+ "Pretend download for everything case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-10-minutes"),
+ "Pretend download for 10-minutes case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-1-hour"),
+ "Pretend download for 1-hour case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-1-hour-10-minutes"),
+ "Pretend download for 1-hour-10-minutes case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour"),
+ "Pretend download for 2-hour case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-2-hour-10-minutes"),
+ "Pretend download for 2-hour-10-minutes case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour"),
+ "Pretend download for 4-hour case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-4-hour-10-minutes"),
+ "Pretend download for 4-hour-10-minutes case should exist"
+ );
+ ok(
+ await downloadExists(publicList, "fakefile-today"),
+ "Pretend download for Today case should exist"
+ );
+}
+
+/**
+ * Checks to see if the downloads with the specified id exists.
+ *
+ * @param aID
+ * The ids of the downloads to check.
+ */
+let downloadExists = async function(list, path) {
+ let listArray = await list.getAll();
+ return listArray.some(i => i.target.path == path);
+};
+
+function isToday(aDate) {
+ return aDate.getDate() == new Date().getDate();
+}
diff --git a/browser/base/content/test/sanitize/browser_sanitizeDialog.js b/browser/base/content/test/sanitize/browser_sanitizeDialog.js
new file mode 100644
index 0000000000..aa94d50764
--- /dev/null
+++ b/browser/base/content/test/sanitize/browser_sanitizeDialog.js
@@ -0,0 +1,833 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/**
+ * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
+ * See bug 480169.
+ *
+ * The purpose of this test is not to fully flex the sanitize timespan code;
+ * browser/base/content/test/sanitize/browser_sanitize-timespans.js does that. This
+ * test checks the UI of the dialog and makes sure it's correctly connected to
+ * the sanitize timespan code.
+ *
+ * Some of this code, especially the history creation parts, was taken from
+ * browser/base/content/test/sanitize/browser_sanitize-timespans.js.
+ */
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ Timer: "resource://gre/modules/Timer.sys.mjs",
+});
+
+const kMsecPerMin = 60 * 1000;
+const kUsecPerMin = 60 * 1000000;
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ * Array of page URIs
+ * @param aShouldBeCleared
+ * True if each visit to the URI should be cleared, false otherwise
+ */
+async function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+ for (let uri of aURIs) {
+ let visited = await PlacesUtils.history.hasVisits(uri);
+ Assert.equal(
+ visited,
+ !aShouldBeCleared,
+ `history visit ${uri.spec} should ${
+ aShouldBeCleared ? "no longer" : "still"
+ } exist`
+ );
+ }
+}
+
+add_setup(async function() {
+ requestLongerTimeout(3);
+ await blankSlate();
+ registerCleanupFunction(async function() {
+ await blankSlate();
+ await PlacesTestUtils.promiseAsyncUpdates();
+ });
+});
+
+/**
+ * Initializes the dialog to its default state.
+ */
+add_task(async function default_state() {
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ // Select "Last Hour"
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.acceptDialog();
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * Cancels the dialog, makes sure history not cleared.
+ */
+add_task(async function test_cancel() {
+ // Add history (within the past hour)
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("https://" + i + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
+ uris.push(pURI);
+ }
+ await PlacesTestUtils.addVisits(places);
+
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", false);
+ this.cancelDialog();
+ };
+ dh.onunload = async function() {
+ await promiseHistoryClearedState(uris, false);
+ await blankSlate();
+ await promiseHistoryClearedState(uris, true);
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox clears both history
+ * visits and downloads when checked; the dialog respects simple timespan.
+ */
+add_task(async function test_history_downloads_checked() {
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ await addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+ // Add downloads (over an hour ago).
+ let olderDownloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ await addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
+ }
+
+ // Add history (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("https://" + i + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
+ uris.push(pURI);
+ }
+ // Add history (over an hour ago).
+ let olderURIs = [];
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("https://" + (61 + i) + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i) });
+ olderURIs.push(pURI);
+ }
+ let promiseSanitized = promiseSanitizationComplete();
+
+ await PlacesTestUtils.addVisits(places);
+
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ intPrefIs(
+ "sanitize.timeSpan",
+ Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected"
+ );
+ boolPrefIs(
+ "cpd.history",
+ true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked"
+ );
+ boolPrefIs(
+ "cpd.downloads",
+ true,
+ "downloads pref should be true after accepting dialog with " +
+ "history checkbox checked"
+ );
+
+ await promiseSanitized;
+
+ // History visits and downloads within one hour should be cleared.
+ await promiseHistoryClearedState(uris, true);
+ await ensureDownloadsClearedState(downloadIDs, true);
+
+ // Visits and downloads > 1 hour should still exist.
+ await promiseHistoryClearedState(olderURIs, false);
+ await ensureDownloadsClearedState(olderDownloadIDs, false);
+
+ // OK, done, cleanup after ourselves.
+ await blankSlate();
+ await promiseHistoryClearedState(olderURIs, true);
+ await ensureDownloadsClearedState(olderDownloadIDs, true);
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * Ensures that the combined history-downloads checkbox removes neither
+ * history visits nor downloads when not checked.
+ */
+add_task(async function test_history_downloads_unchecked() {
+ // Add form entries
+ let formEntries = [];
+
+ for (let i = 0; i < 5; i++) {
+ formEntries.push(await promiseAddFormEntryWithMinutesAgo(i));
+ }
+
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ await addDownloadWithMinutesAgo(downloadIDs, i);
+ }
+
+ // Add history, downloads, form entries (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("https://" + i + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) });
+ uris.push(pURI);
+ }
+
+ await PlacesTestUtils.addVisits(places);
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ is(
+ this.isWarningPanelVisible(),
+ false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan"
+ );
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+
+ // Remove only form entries, leave history (including downloads).
+ this.checkPrefCheckbox("history", false);
+ this.checkPrefCheckbox("formdata", true);
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ intPrefIs(
+ "sanitize.timeSpan",
+ Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected"
+ );
+ boolPrefIs(
+ "cpd.history",
+ false,
+ "history pref should be false after accepting dialog with " +
+ "history checkbox unchecked"
+ );
+ boolPrefIs(
+ "cpd.downloads",
+ false,
+ "downloads pref should be false after accepting dialog with " +
+ "history checkbox unchecked"
+ );
+
+ // Of the three only form entries should be cleared.
+ await promiseHistoryClearedState(uris, false);
+ await ensureDownloadsClearedState(downloadIDs, false);
+
+ for (let entry of formEntries) {
+ let exists = await formNameExists(entry);
+ ok(!exists, "form entry " + entry + " should no longer exist");
+ }
+
+ // OK, done, cleanup after ourselves.
+ await blankSlate();
+ await promiseHistoryClearedState(uris, true);
+ await ensureDownloadsClearedState(downloadIDs, true);
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" duration option works.
+ */
+add_task(async function test_everything() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("https://" + aValue + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) });
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ await PlacesTestUtils.addVisits(places);
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ is(
+ this.isWarningPanelVisible(),
+ false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan"
+ );
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ await promiseSanitized;
+ intPrefIs(
+ "sanitize.timeSpan",
+ Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected"
+ );
+
+ await promiseHistoryClearedState(uris, true);
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * Ensures that the "Everything" warning is visible on dialog open after
+ * the previous test.
+ */
+add_task(async function test_everything_warning() {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("https://" + aValue + "-minutes-ago.com/");
+ places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) });
+ uris.push(pURI);
+ });
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ await PlacesTestUtils.addVisits(places);
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ is(
+ this.isWarningPanelVisible(),
+ true,
+ "Warning panel should be visible after previously accepting dialog " +
+ "with clearing everything"
+ );
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ intPrefIs(
+ "sanitize.timeSpan",
+ Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected"
+ );
+
+ await promiseSanitized;
+
+ await promiseHistoryClearedState(uris, true);
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+/**
+ * The next three tests checks that when a certain history item cannot be
+ * cleared then the checkbox should be both disabled and unchecked.
+ * In addition, we ensure that this behavior does not modify the preferences.
+ */
+add_task(async function test_cannot_clear_history() {
+ // Add form entries
+ let formEntries = [await promiseAddFormEntryWithMinutesAgo(10)];
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Add history.
+ let pURI = makeURI("https://" + 10 + "-minutes-ago.com/");
+ await PlacesTestUtils.addVisits({
+ uri: pURI,
+ visitDate: visitTimeForMinutesAgo(10),
+ });
+ let uris = [pURI];
+
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ // Check that the relevant checkboxes are enabled
+ var cb = this.win.document.querySelectorAll(
+ "checkbox[preference='privacy.cpd.formdata']"
+ );
+ ok(
+ cb.length == 1 && !cb[0].disabled,
+ "There is formdata, checkbox to clear formdata should be enabled."
+ );
+
+ cb = this.win.document.querySelectorAll(
+ "checkbox[preference='privacy.cpd.history']"
+ );
+ ok(
+ cb.length == 1 && !cb[0].disabled,
+ "There is history, checkbox to clear history should be enabled."
+ );
+
+ this.checkAllCheckboxes();
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ await promiseSanitized;
+
+ await promiseHistoryClearedState(uris, true);
+
+ let exists = await formNameExists(formEntries[0]);
+ ok(!exists, "form entry " + formEntries[0] + " should no longer exist");
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+add_task(async function test_no_formdata_history_to_clear() {
+ let promiseSanitized = promiseSanitizationComplete();
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ boolPrefIs(
+ "cpd.history",
+ true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked"
+ );
+ boolPrefIs(
+ "cpd.formdata",
+ true,
+ "formdata pref should be true after accepting dialog with " +
+ "formdata checkbox checked"
+ );
+
+ var cb = this.win.document.querySelectorAll(
+ "checkbox[preference='privacy.cpd.history']"
+ );
+ ok(
+ cb.length == 1 && !cb[0].disabled && cb[0].checked,
+ "There is no history, but history checkbox should always be enabled " +
+ "and will be checked from previous preference."
+ );
+
+ this.acceptDialog();
+ };
+ dh.open();
+ await dh.promiseClosed;
+ await promiseSanitized;
+});
+
+add_task(async function test_form_entries() {
+ let formEntry = await promiseAddFormEntryWithMinutesAgo(10);
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ boolPrefIs(
+ "cpd.formdata",
+ true,
+ "formdata pref should persist previous value after accepting " +
+ "dialog where you could not clear formdata."
+ );
+
+ var cb = this.win.document.querySelectorAll(
+ "checkbox[preference='privacy.cpd.formdata']"
+ );
+
+ info(
+ "There exists formEntries so the checkbox should be in sync with the pref."
+ );
+ is(cb.length, 1, "There is only one checkbox for form data");
+ ok(!cb[0].disabled, "The checkbox is enabled");
+ ok(cb[0].checked, "The checkbox is checked");
+
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ await promiseSanitized;
+ let exists = await formNameExists(formEntry);
+ ok(!exists, "form entry " + formEntry + " should no longer exist");
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+// Test for offline apps permission deletion
+add_task(async function test_offline_apps_permissions() {
+ // Prepare stuff, we will work with www.example.com
+ var URL = "https://www.example.com";
+ var URI = makeURI(URL);
+ var principal = Services.scriptSecurityManager.createContentPrincipal(
+ URI,
+ {}
+ );
+
+ let promiseSanitized = promiseSanitizationComplete();
+
+ // Open the dialog
+ let dh = new DialogHelper();
+ dh.onload = function() {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", true);
+ this.acceptDialog();
+ };
+ dh.onunload = async function() {
+ await promiseSanitized;
+
+ // Check all has been deleted (privileges, data, cache)
+ is(
+ Services.perms.testPermissionFromPrincipal(principal, "offline-app"),
+ 0,
+ "offline-app permissions removed"
+ );
+ };
+ dh.open();
+ await dh.promiseClosed;
+});
+
+var now_mSec = Date.now();
+var now_uSec = now_mSec * 1000;
+
+/**
+ * This wraps the dialog and provides some convenience methods for interacting
+ * with it.
+ *
+ * @param browserWin (optional)
+ * The browser window that the dialog is expected to open in. If not
+ * supplied, the initial browser window of the test run is used.
+ */
+function DialogHelper(browserWin = window) {
+ this._browserWin = browserWin;
+ this.win = null;
+ this.promiseClosed = new Promise(resolve => {
+ this._resolveClosed = resolve;
+ });
+}
+
+DialogHelper.prototype = {
+ /**
+ * "Presses" the dialog's OK button.
+ */
+ acceptDialog() {
+ let dialogEl = this.win.document.querySelector("dialog");
+ is(
+ dialogEl.getButton("accept").disabled,
+ false,
+ "Dialog's OK button should not be disabled"
+ );
+ dialogEl.acceptDialog();
+ },
+
+ /**
+ * "Presses" the dialog's Cancel button.
+ */
+ cancelDialog() {
+ this.win.document.querySelector("dialog").cancelDialog();
+ },
+
+ /**
+ * (Un)checks a history scope checkbox (browser & download history,
+ * form history, etc.).
+ *
+ * @param aPrefName
+ * The final portion of the checkbox's privacy.cpd.* preference name
+ * @param aCheckState
+ * True if the checkbox should be checked, false otherwise
+ */
+ checkPrefCheckbox(aPrefName, aCheckState) {
+ var pref = "privacy.cpd." + aPrefName;
+ var cb = this.win.document.querySelectorAll(
+ "checkbox[preference='" + pref + "']"
+ );
+ is(cb.length, 1, "found checkbox for " + pref + " preference");
+ if (cb[0].checked != aCheckState) {
+ cb[0].click();
+ }
+ },
+
+ /**
+ * Makes sure all the checkboxes are checked.
+ */
+ _checkAllCheckboxesCustom(check) {
+ var cb = this.win.document.querySelectorAll("checkbox[preference]");
+ ok(cb.length > 1, "found checkboxes for preferences");
+ for (var i = 0; i < cb.length; ++i) {
+ var pref = this.win.Preferences.get(cb[i].getAttribute("preference"));
+ if (!!pref.value ^ check) {
+ cb[i].click();
+ }
+ }
+ },
+
+ checkAllCheckboxes() {
+ this._checkAllCheckboxesCustom(true);
+ },
+
+ uncheckAllCheckboxes() {
+ this._checkAllCheckboxesCustom(false);
+ },
+
+ /**
+ * @return The dialog's duration dropdown
+ */
+ getDurationDropdown() {
+ return this.win.document.getElementById("sanitizeDurationChoice");
+ },
+
+ /**
+ * @return The clear-everything warning box
+ */
+ getWarningPanel() {
+ return this.win.document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ /**
+ * @return True if the "Everything" warning panel is visible (as opposed to
+ * the tree)
+ */
+ isWarningPanelVisible() {
+ return !this.getWarningPanel().hidden;
+ },
+
+ /**
+ * Opens the clear recent history dialog. Before calling this, set
+ * this.onload to a function to execute onload. It should close the dialog
+ * when done so that the tests may continue. Set this.onunload to a function
+ * to execute onunload. this.onunload is optional. If it returns true, the
+ * caller is expected to call promiseAsyncUpdates at some point; if false is
+ * returned, promiseAsyncUpdates is called automatically.
+ */
+ async open() {
+ let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(
+ null,
+ "chrome://browser/content/sanitize.xhtml",
+ {
+ isSubDialog: true,
+ }
+ );
+
+ executeSoon(() => {
+ Sanitizer.showUI(this._browserWin);
+ });
+
+ this.win = await dialogPromise;
+ this.win.addEventListener(
+ "load",
+ () => {
+ // Run onload on next tick so that gSanitizePromptDialog.init can run first.
+ executeSoon(() => this.onload());
+ },
+ { once: true }
+ );
+
+ this.win.addEventListener(
+ "unload",
+ () => {
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ (async () => {
+ if (this.onunload) {
+ await this.onunload();
+ }
+ await PlacesTestUtils.promiseAsyncUpdates();
+ this._resolveClosed();
+ this.win = null;
+ })();
+ },
+ { once: true }
+ );
+ },
+
+ /**
+ * Selects a duration in the duration dropdown.
+ *
+ * @param aDurVal
+ * One of the Sanitizer.TIMESPAN_* values
+ */
+ selectDuration(aDurVal) {
+ this.getDurationDropdown().value = aDurVal;
+ if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ is(
+ this.isWarningPanelVisible(),
+ true,
+ "Warning panel should be visible for TIMESPAN_EVERYTHING"
+ );
+ } else {
+ is(
+ this.isWarningPanelVisible(),
+ false,
+ "Warning panel should not be visible for non-TIMESPAN_EVERYTHING"
+ );
+ }
+ },
+};
+
+function promiseSanitizationComplete() {
+ return TestUtils.topicObserved("sanitizer-sanitization-complete");
+}
+
+/**
+ * Adds a download to history.
+ *
+ * @param aMinutesAgo
+ * The download will be downloaded this many minutes ago
+ */
+async function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+
+ let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
+ let download = await Downloads.createDownload({
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: name,
+ });
+ download.startTime = new Date(now_mSec - aMinutesAgo * kMsecPerMin);
+ download.canceled = true;
+ publicList.add(download);
+
+ ok(
+ await downloadExists(name),
+ "Sanity check: download " + name + " should exist after creating it"
+ );
+
+ aExpectedPathList.push(name);
+}
+
+/**
+ * Adds a form entry to history.
+ *
+ * @param aMinutesAgo
+ * The entry will be added this many minutes ago
+ */
+function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) {
+ let name = aMinutesAgo + "-minutes-ago";
+
+ // Artifically age the entry to the proper vintage.
+ let timestamp = now_uSec - aMinutesAgo * kUsecPerMin;
+
+ return FormHistory.update({
+ op: "add",
+ fieldname: name,
+ value: "dummy",
+ firstUsed: timestamp,
+ });
+}
+
+/**
+ * Checks if a form entry exists.
+ */
+async function formNameExists(name) {
+ return !!(await FormHistory.count({ fieldname: name }));
+}
+
+/**
+ * Removes all history visits, downloads, and form entries.
+ */
+async function blankSlate() {
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let downloads = await publicList.getAll();
+ for (let download of downloads) {
+ await publicList.remove(download);
+ await download.finalize(true);
+ }
+
+ await FormHistory.update({ op: "remove" });
+ await PlacesUtils.history.clear();
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(Services.prefs.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Checks to see if the download with the specified path exists.
+ *
+ * @param aPath
+ * The path of the download to check
+ * @return True if the download exists, false otherwise
+ */
+async function downloadExists(aPath) {
+ let publicList = await Downloads.getList(Downloads.PUBLIC);
+ let listArray = await publicList.getAll();
+ return listArray.some(i => i.target.path == aPath);
+}
+
+/**
+ * Ensures that the specified downloads are either cleared or not.
+ *
+ * @param aDownloadIDs
+ * Array of download database IDs
+ * @param aShouldBeCleared
+ * True if each download should be cleared, false otherwise
+ */
+async function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ for (let id of aDownloadIDs) {
+ is(
+ await downloadExists(id),
+ !aShouldBeCleared,
+ "download " + id + " should " + niceStr + " exist"
+ );
+ }
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function intPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(Services.prefs.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ * The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+ return now_uSec - aMinutesAgo * kUsecPerMin;
+}
diff --git a/browser/base/content/test/sanitize/dummy.js b/browser/base/content/test/sanitize/dummy.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/browser/base/content/test/sanitize/dummy.js
diff --git a/browser/base/content/test/sanitize/dummy_page.html b/browser/base/content/test/sanitize/dummy_page.html
new file mode 100644
index 0000000000..1a87e28408
--- /dev/null
+++ b/browser/base/content/test/sanitize/dummy_page.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Dummy test page</title>
+<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/sanitize/head.js b/browser/base/content/test/sanitize/head.js
new file mode 100644
index 0000000000..3cfcf87422
--- /dev/null
+++ b/browser/base/content/test/sanitize/head.js
@@ -0,0 +1,336 @@
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ Downloads: "resource://gre/modules/Downloads.sys.mjs",
+ FormHistory: "resource://gre/modules/FormHistory.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ Sanitizer: "resource:///modules/Sanitizer.sys.mjs",
+ SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ PermissionTestUtils: "resource://testing-common/PermissionTestUtils.jsm",
+});
+
+function createIndexedDB(host, originAttributes) {
+ let uri = Services.io.newURI("https://" + host);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ originAttributes
+ );
+ return SiteDataTestUtils.addToIndexedDB(principal.origin);
+}
+
+function checkIndexedDB(host, originAttributes) {
+ return new Promise(resolve => {
+ let data = true;
+ let uri = Services.io.newURI("https://" + host);
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ uri,
+ originAttributes
+ );
+ let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1);
+ request.onupgradeneeded = function(e) {
+ data = false;
+ };
+ request.onsuccess = function(e) {
+ resolve(data);
+ };
+ });
+}
+
+function createHostCookie(host, originAttributes) {
+ Services.cookies.add(
+ host,
+ "/test",
+ "foo",
+ "bar",
+ false,
+ false,
+ false,
+ Date.now() + 24000 * 60 * 60,
+ originAttributes,
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+}
+
+function createDomainCookie(host, originAttributes) {
+ Services.cookies.add(
+ "." + host,
+ "/test",
+ "foo",
+ "bar",
+ false,
+ false,
+ false,
+ Date.now() + 24000 * 60 * 60,
+ originAttributes,
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_HTTPS
+ );
+}
+
+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;
+}
+
+async function deleteOnShutdown(opt) {
+ // Let's clean up all the data.
+ await new Promise(resolve => {
+ Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve);
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["privacy.sanitize.sanitizeOnShutdown", opt.sanitize],
+ ["privacy.clearOnShutdown.cookies", opt.sanitize],
+ ["privacy.clearOnShutdown.offlineApps", opt.sanitize],
+ ["browser.sanitizer.loglevel", "All"],
+ ],
+ });
+
+ // Custom permission without considering OriginAttributes
+ if (opt.cookiePermission !== undefined) {
+ let uri = Services.io.newURI("https://www.example.com");
+ PermissionTestUtils.add(uri, "cookie", opt.cookiePermission);
+ }
+
+ // Let's create a tab with some data.
+ await opt.createData(
+ (opt.fullHost ? "www." : "") + "example.org",
+ opt.originAttributes
+ );
+ ok(
+ await opt.checkData(
+ (opt.fullHost ? "www." : "") + "example.org",
+ opt.originAttributes
+ ),
+ "We have data for www.example.org"
+ );
+ await opt.createData(
+ (opt.fullHost ? "www." : "") + "example.com",
+ opt.originAttributes
+ );
+ ok(
+ await opt.checkData(
+ (opt.fullHost ? "www." : "") + "example.com",
+ opt.originAttributes
+ ),
+ "We have data for www.example.com"
+ );
+
+ // Cleaning up.
+ await Sanitizer.runSanitizeOnShutdown();
+
+ // All gone!
+ is(
+ !!(await opt.checkData(
+ (opt.fullHost ? "www." : "") + "example.org",
+ opt.originAttributes
+ )),
+ opt.expectedForOrg,
+ "Do we have data for www.example.org?"
+ );
+ is(
+ !!(await opt.checkData(
+ (opt.fullHost ? "www." : "") + "example.com",
+ opt.originAttributes
+ )),
+ opt.expectedForCom,
+ "Do we have data for www.example.com?"
+ );
+
+ // Clean up.
+ await Sanitizer.sanitize(["cookies", "offlineApps"]);
+
+ if (opt.cookiePermission !== undefined) {
+ let uri = Services.io.newURI("https://www.example.com");
+ PermissionTestUtils.remove(uri, "cookie");
+ }
+}
+
+function runAllCookiePermissionTests(originAttributes) {
+ let tests = [
+ { name: "IDB", createData: createIndexedDB, checkData: checkIndexedDB },
+ {
+ name: "Host Cookie",
+ createData: createHostCookie,
+ checkData: checkCookie,
+ },
+ {
+ name: "Domain Cookie",
+ createData: createDomainCookie,
+ checkData: checkCookie,
+ },
+ ];
+
+ // Delete all, no custom permission, data in example.com, cookie permission set
+ // for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageOnShutdown() {
+ info(
+ methods.name +
+ ": Delete all, no custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: true,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: undefined,
+ expectedForOrg: false,
+ expectedForCom: false,
+ fullHost: false,
+ });
+ });
+ });
+
+ // Delete all, no custom permission, data in www.example.com, cookie permission
+ // set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageOnShutdown() {
+ info(
+ methods.name +
+ ": Delete all, no custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: true,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: undefined,
+ expectedForOrg: false,
+ expectedForCom: false,
+ fullHost: true,
+ });
+ });
+ });
+
+ // All is session, but with ALLOW custom permission, data in example.com,
+ // cookie permission set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageWithCustomPermission() {
+ info(
+ methods.name +
+ ": All is session, but with ALLOW custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: true,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW,
+ expectedForOrg: false,
+ expectedForCom: true,
+ fullHost: false,
+ });
+ });
+ });
+
+ // All is session, but with ALLOW custom permission, data in www.example.com,
+ // cookie permission set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageWithCustomPermission() {
+ info(
+ methods.name +
+ ": All is session, but with ALLOW custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: true,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: Ci.nsICookiePermission.ACCESS_ALLOW,
+ expectedForOrg: false,
+ expectedForCom: true,
+ fullHost: true,
+ });
+ });
+ });
+
+ // All is default, but with SESSION custom permission, data in example.com,
+ // cookie permission set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageOnlyCustomPermission() {
+ info(
+ methods.name +
+ ": All is default, but with SESSION custom permission, data in example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: false,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION,
+ expectedForOrg: true,
+ // expected data just for example.com when using indexedDB because
+ // QuotaManager deletes for principal.
+ expectedForCom: false,
+ fullHost: false,
+ });
+ });
+ });
+
+ // All is default, but with SESSION custom permission, data in www.example.com,
+ // cookie permission set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageOnlyCustomPermission() {
+ info(
+ methods.name +
+ ": All is default, but with SESSION custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: false,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: Ci.nsICookiePermission.ACCESS_SESSION,
+ expectedForOrg: true,
+ expectedForCom: false,
+ fullHost: true,
+ });
+ });
+ });
+
+ // Session mode, but with unsupported custom permission, data in
+ // www.example.com, cookie permission set for www.example.com
+ tests.forEach(methods => {
+ add_task(async function deleteStorageOnlyCustomPermission() {
+ info(
+ methods.name +
+ ": All is session only, but with unsupported custom custom permission, data in www.example.com, cookie permission set for www.example.com - OA: " +
+ originAttributes.name
+ );
+ await deleteOnShutdown({
+ sanitize: true,
+ createData: methods.createData,
+ checkData: methods.checkData,
+ originAttributes: originAttributes.oa,
+ cookiePermission: 123, // invalid cookie permission
+ expectedForOrg: false,
+ expectedForCom: false,
+ fullHost: true,
+ });
+ });
+ });
+}