From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- browser/base/content/test/sanitize/browser.toml | 39 + .../test/sanitize/browser_cookiePermission.js | 1 + .../sanitize/browser_cookiePermission_aboutURL.js | 111 ++ .../browser_cookiePermission_containers.js | 1 + .../browser_cookiePermission_subDomains.js | 290 ++++ .../browser_cookiePermission_subDomains_v2.js | 288 ++++ .../sanitize/browser_purgehistory_clears_sh.js | 86 ++ .../sanitize/browser_sanitize-cookie-exceptions.js | 274 ++++ .../test/sanitize/browser_sanitize-formhistory.js | 32 + .../test/sanitize/browser_sanitize-history.js | 136 ++ .../test/sanitize/browser_sanitize-offlineData.js | 249 ++++ .../browser_sanitize-passwordDisabledHosts.js | 28 + .../sanitize/browser_sanitize-sitepermissions.js | 37 + .../test/sanitize/browser_sanitize-timespans.js | 1194 ++++++++++++++++ .../test/sanitize/browser_sanitize-timespans_v2.js | 1190 ++++++++++++++++ .../test/sanitize/browser_sanitizeDialog.js | 837 ++++++++++++ .../test/sanitize/browser_sanitizeDialog_v2.js | 1429 ++++++++++++++++++++ .../browser_sanitizeOnShutdown_migration.js | 312 +++++ browser/base/content/test/sanitize/dummy.js | 0 browser/base/content/test/sanitize/dummy_page.html | 9 + browser/base/content/test/sanitize/head.js | 371 +++++ .../base/content/test/sanitize/site_data_test.html | 29 + 22 files changed, 6943 insertions(+) create mode 100644 browser/base/content/test/sanitize/browser.toml create mode 100644 browser/base/content/test/sanitize/browser_cookiePermission.js create mode 100644 browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js create mode 100644 browser/base/content/test/sanitize/browser_cookiePermission_containers.js create mode 100644 browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js create mode 100644 browser/base/content/test/sanitize/browser_cookiePermission_subDomains_v2.js create mode 100644 browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-formhistory.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-history.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-offlineData.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-passwordDisabledHosts.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-sitepermissions.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-timespans.js create mode 100644 browser/base/content/test/sanitize/browser_sanitize-timespans_v2.js create mode 100644 browser/base/content/test/sanitize/browser_sanitizeDialog.js create mode 100644 browser/base/content/test/sanitize/browser_sanitizeDialog_v2.js create mode 100644 browser/base/content/test/sanitize/browser_sanitizeOnShutdown_migration.js create mode 100644 browser/base/content/test/sanitize/dummy.js create mode 100644 browser/base/content/test/sanitize/dummy_page.html create mode 100644 browser/base/content/test/sanitize/head.js create mode 100644 browser/base/content/test/sanitize/site_data_test.html (limited to 'browser/base/content/test/sanitize') diff --git a/browser/base/content/test/sanitize/browser.toml b/browser/base/content/test/sanitize/browser.toml new file mode 100644 index 0000000000..814dc54c3d --- /dev/null +++ b/browser/base/content/test/sanitize/browser.toml @@ -0,0 +1,39 @@ +[DEFAULT] +support-files = [ + "head.js", + "dummy.js", + "dummy_page.html", + "site_data_test.html", +] + +["browser_cookiePermission.js"] + +["browser_cookiePermission_aboutURL.js"] + +["browser_cookiePermission_containers.js"] + +["browser_cookiePermission_subDomains.js"] + +["browser_cookiePermission_subDomains_v2.js"] + +["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_sanitizeDialog_v2.js"] + +["browser_sanitizeOnShutdown_migration.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..ada8286437 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js @@ -0,0 +1,111 @@ +const { SiteDataTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SiteDataTestUtils.sys.mjs" +); + +// We will be removing the ["cookies","offlineApps"] option once we remove the +// old clear history dialog in Bug 1856418 - Remove all old clear data dialog boxes +let prefs = [["cookiesAndStorage"], ["cookies", "offlineApps"]]; + +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); + }; + }); +} + +for (let itemsToClear of prefs) { + 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(itemsToClear); + + 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(itemsToClear); + + 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..b4b62be110 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js @@ -0,0 +1,290 @@ +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_cookiePermission_subDomains_v2.js b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains_v2.js new file mode 100644 index 0000000000..d1887db91b --- /dev/null +++ b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains_v2.js @@ -0,0 +1,288 @@ +const { SiteDataTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SiteDataTestUtils.sys.mjs" +); + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.sanitize.sanitizeOnShutdown", true], + ["privacy.clearOnShutdown.cookiesAndStorage", true], + ["privacy.clearOnShutdown.cache", false], + ["privacy.clearOnShutdown.historyAndFormData", false], + ["privacy.clearOnShutdown.downloads", false], + ["privacy.clearOnShutdown.siteSettings", false], + ["browser.sanitizer.loglevel", "All"], + ["privacy.sanitize.useOldClearHistoryDialog", false], + ], + }); +}); +// 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..5ad7b78d69 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js @@ -0,0 +1,86 @@ +/* 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"; + +// We will be removing the ["history"] option once we remove the +// old clear history dialog in Bug 1856418 - Remove all old clear data dialog boxes +let prefs = [["history"], ["historyFormDataAndDownloads"]]; + +for (let itemsToClear of prefs) { + 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(itemsToClear); + + 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..a3ab8aa0f3 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js @@ -0,0 +1,274 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const { SiteDataTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SiteDataTestUtils.sys.mjs" +); +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +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 + ); +}); + +add_task(async function sanitizeWithSessionExceptionsOnShutdown() { + info( + "Test that cookies that are marked as allowed on session is cleared when sanitizeOnShutdown is false" + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.sanitizer.loglevel", "All"], + ["privacy.sanitize.sanitizeOnShutdown", false], + ], + }); + + // Clean up before start + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + + let originAllowSession = "https://mozilla.org"; + PermissionTestUtils.add( + originAllowSession, + "cookie", + Ci.nsICookiePermission.ACCESS_SESSION + ); + + SiteDataTestUtils.addToCookies({ origin: originAllowSession }); + ok( + SiteDataTestUtils.hasCookies(originAllowSession), + "We have cookies for " + originAllowSession + ); + + await Sanitizer.runSanitizeOnShutdown(); + + ok( + !SiteDataTestUtils.hasCookies(originAllowSession), + "We should not have cookies for " + originAllowSession + ); +}); + +add_task(async function sanitizeWithManySessionExceptionsOnShutdown() { + info( + "Test that lots of allowed on session exceptions are cleared when sanitizeOnShutdown is false" + ); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.sanitize.sanitizeOnShutdown", false], + ["dom.quotaManager.backgroundTask.enabled", true], + ], + }); + + // Clean up before start + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + + info("Setting cookies"); + + const origins = new Array(300) + .fill(0) + .map((v, i) => `https://mozilla${i}.org`); + + for (const origin of origins) { + PermissionTestUtils.add( + origin, + "cookie", + Ci.nsICookiePermission.ACCESS_SESSION + ); + SiteDataTestUtils.addToCookies({ origin }); + } + + ok( + origins.every(origin => SiteDataTestUtils.hasCookies(origin)), + "All origins have cookies" + ); + + info("Running sanitization"); + + await Sanitizer.runSanitizeOnShutdown(); + + ok( + origins.every(origin => !SiteDataTestUtils.hasCookies(origin)), + "All origins lost cookies" + ); +}); 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..ae043dbd62 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-formhistory.js @@ -0,0 +1,32 @@ +/* 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() { + let prefs = ["history", "historyFormDataAndDownloads"]; + + for (let pref of prefs) { + // 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([pref]); + 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..e003762f35 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-history.js @@ -0,0 +1,136 @@ +/* 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() { + let categories = ["history", "historyFormDataAndDownloads"]; + + for (let pref of categories) { + 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([pref], { + // 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([pref]); + + 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([pref, "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..2dfc62c01f --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js @@ -0,0 +1,249 @@ +/* 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 { 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; + +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); + }); +} + +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; +} + +// We will be removing the ["cookies","offlineApps"] option once we remove the +// old clear history dialog in Bug 1856418 - Remove all old clear data dialog boxes +let prefs = [["cookiesAndStorage"], ["cookies", "offlineApps"]]; + +for (let itemsToClear of prefs) { + 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 createDummyDataForHost("example.org"); + await createDummyDataForHost("example.com"); + + endDate = Date.now() * 1000; + principals = sas.getActiveOrigins(endDate - oneHour, endDate); + ok(!!principals, "We have an active origin."); + Assert.greaterOrEqual(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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.clear(); + }); + + add_task(async function testExceptionsOnShutdown() { + await createDummyDataForHost("example.org"); + await createDummyDataForHost("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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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 SiteDataTestUtils.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" + ); + + // Clean up + await SiteDataTestUtils.clear(); + Services.perms.removeAll(); + }); +} 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..305fe37e7e --- /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..30ccb90666 --- /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_sanitize-timespans_v2.js b/browser/base/content/test/sanitize/browser_sanitize-timespans_v2.js new file mode 100644 index 0000000000..c732262a1a --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-timespans_v2.js @@ -0,0 +1,1190 @@ +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 + + await SpecialPowers.pushPrefEnv({ + set: [["privacy.sanitize.useOldClearHistoryDialog", false]], + }); + + let itemsToClear = ["historyAndFormData", "downloads"]; + + 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { 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(itemsToClear, { + ignoreTimespan: false, + }); + Assert.deepEqual(progress, { + historyAndFormData: "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(itemsToClear, { 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..2df7d83c6e --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitizeDialog.js @@ -0,0 +1,837 @@ +/* -*- 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(); + }); + + await SpecialPowers.pushPrefEnv({ + set: [["privacy.sanitize.useOldClearHistoryDialog", true]], + }); +}); + +/** + * 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]"); + Assert.greater(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/browser_sanitizeDialog_v2.js b/browser/base/content/test/sanitize/browser_sanitizeDialog_v2.js new file mode 100644 index 0000000000..29f760f57f --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitizeDialog_v2.js @@ -0,0 +1,1429 @@ +/* -*- 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", + PermissionTestUtils: "resource://testing-common/PermissionTestUtils.sys.mjs", + FileTestUtils: "resource://testing-common/FileTestUtils.sys.mjs", + Downloads: "resource://gre/modules/Downloads.sys.mjs", +}); + +const kMsecPerMin = 60 * 1000; +const kUsecPerMin = 60 * 1000000; +let today = Date.now() - new Date().setHours(0, 0, 0, 0); +let nowMSec = Date.now(); +let nowUSec = nowMSec * 1000; +let fileURL; + +const TEST_TARGET_FILE_NAME = "test-download.txt"; +const TEST_QUOTA_USAGE_HOST = "example.com"; +const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST; +const TEST_QUOTA_USAGE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + TEST_QUOTA_USAGE_ORIGIN + ) + "site_data_test.html"; + +const siteOrigins = [ + "https://www.example.com", + "https://example.org", + "http://localhost:8000", + "http://localhost:3000", +]; + +/** + * 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` + ); + } +} + +/** + * Ensures that the given pref is the expected value. + * + * @param {String} aPrefName + * The pref's sub-branch under the privacy branch + * @param {Boolean} aExpectedVal + * The pref's expected value + * @param {String} 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" + ); + } +} + +/** + * Checks if a form entry exists. + */ +async function formNameExists(name) { + return !!(await FormHistory.count({ fieldname: 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 = nowUSec - aMinutesAgo * kUsecPerMin; + + return FormHistory.update({ + op: "add", + fieldname: name, + value: "dummy", + firstUsed: timestamp, + }); +} + +/** + * 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(nowMSec - 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 multiple downloads to the PUBLIC download list + */ +async function addToDownloadList() { + const url = createFileURL(); + const downloadsList = await Downloads.getList(Downloads.PUBLIC); + let timeOptions = [1, 2, 4, 24, 128, 128]; + let buffer = 100000; + + for (let i = 0; i < timeOptions.length; i++) { + let timeDownloaded = 60 * kMsecPerMin * timeOptions[i]; + if (timeOptions[i] === 24) { + timeDownloaded = today; + } + + let download = await Downloads.createDownload({ + source: { url: url.spec, isPrivate: false }, + target: { path: FileTestUtils.getTempFile(TEST_TARGET_FILE_NAME).path }, + startTime: { + getTime: _ => { + return nowMSec - timeDownloaded + buffer; + }, + }, + }); + + Assert.ok(!!download); + downloadsList.add(download); + } + let items = await downloadsList.getAll(); + Assert.equal(items.length, 6, "Items were added to the list"); +} + +async function addToSiteUsage() { + // Fill indexedDB with test data. + // Don't wait for the page to load, to register the content event handler as quickly as possible. + // If this test goes intermittent, we might have to tell the page to wait longer before + // firing the event. + BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false); + await BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "test-indexedDB-done", + false, + null, + true + ); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + let siteLastAccessed = [1, 2, 4, 24]; + + let staticUsage = 4096 * 6; + // Add a time buffer so the site access falls within the time range + const buffer = 10000; + + // Change lastAccessed of sites + for (let index = 0; index < siteLastAccessed.length; index++) { + let lastAccessedTime = 60 * kMsecPerMin * siteLastAccessed[index]; + if (siteLastAccessed[index] === 24) { + lastAccessedTime = today; + } + + let site = SiteDataManager._testInsertSite(siteOrigins[index], { + quotaUsage: staticUsage, + lastAccessed: (nowMSec - lastAccessedTime + buffer) * 1000, + }); + Assert.ok(site, "Site added successfully"); + } +} + +/** + * Helper function to create file URL to open + * + * @returns {Object} a file URL + */ +function createFileURL() { + if (!fileURL) { + let file = Services.dirsvc.get("TmpD", Ci.nsIFile); + file.append("foo.txt"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600); + + fileURL = Services.io.newFileURI(file); + } + return fileURL; +} + +add_setup(async function () { + requestLongerTimeout(3); + await blankSlate(); + registerCleanupFunction(async function () { + await blankSlate(); + await PlacesTestUtils.promiseAsyncUpdates(); + }); + await SpecialPowers.pushPrefEnv({ + set: [["privacy.sanitize.useOldClearHistoryDialog", false]], + }); + + // open preferences to trigger an updateSites() + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +/** + * 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(); +} + +/** + * 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. + * @param mode (optional) + * One of + * clear on shutdown settings context ("clearOnShutdown"), + * clear site data settings context ("clearSiteData"), + * clear history context ("clearHistory"), + * browser context ("browser") + * "browser" by default + */ +function DialogHelper(openContext = "browser") { + this._browserWin = window; + this.win = null; + this._mode = openContext; + 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 cb = this.win.document.querySelectorAll( + "checkbox[id='" + aPrefName + "']" + ); + is(cb.length, 1, "found checkbox for " + aPrefName + " id"); + if (cb[0].checked != aCheckState) { + cb[0].click(); + } + }, + + /** + * @param {String} aCheckboxId + * The checkbox id name + * @param {Boolean} aCheckState + * True if the checkbox should be checked, false otherwise + */ + validateCheckbox(aCheckboxId, aCheckState) { + let cb = this.win.document.querySelectorAll( + "checkbox[id='" + aCheckboxId + "']" + ); + is(cb.length, 1, `found checkbox for id=${aCheckboxId}`); + is( + cb[0].checked, + aCheckState, + `checkbox for ${aCheckboxId} is ${aCheckState}` + ); + }, + + /** + * Makes sure all the checkboxes are checked. + */ + _checkAllCheckboxesCustom(check) { + var cb = this.win.document.querySelectorAll(".clearingItemCheckbox"); + ok(cb.length, "found checkboxes for ids"); + for (var i = 0; i < cb.length; ++i) { + if (cb[i].checked != 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_v2.xhtml", + { + isSubDialog: true, + } + ); + + // We want to simulate opening the dialog inside preferences for clear history + // and clear site data + if (this._mode != "browser") { + await openPreferencesViaOpenPreferencesAPI("privacy", { + leaveOpen: true, + }); + let tabWindow = gBrowser.selectedBrowser.contentWindow; + let clearDialogOpenButtonId = this._mode + "Button"; + // the id for clear on shutdown is of a different format + if (this._mode == "clearOnShutdown") { + // set always clear to true to enable the clear on shutdown dialog + let enableSettingsCheckbox = + tabWindow.document.getElementById("alwaysClear"); + if (!enableSettingsCheckbox.checked) { + enableSettingsCheckbox.click(); + } + clearDialogOpenButtonId = "clearDataSettings"; + } + // open dialog + tabWindow.document.getElementById(clearDialogOpenButtonId).click(); + } + // We open the dialog in the chrome context in other cases + else { + executeSoon(() => { + Sanitizer.showUI(this._browserWin, this._mode); + }); + } + + this.win = await dialogPromise; + this.win.addEventListener( + "load", + () => { + // Run onload on next tick so that gSanitizePromptDialog.init can run first. + executeSoon(async () => { + await this.win.gSanitizePromptDialog.dataSizesFinishedUpdatingPromise; + 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(); + } + if (this._mode != "browser") { + BrowserTestUtils.removeTab(gBrowser.selectedTab); + } + 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" + ); + } + }, +}; + +/** + * 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 nowUSec - aMinutesAgo * kUsecPerMin; +} + +function promiseSanitizationComplete() { + return TestUtils.topicObserved("sanitizer-sanitization-complete"); +} + +/** + * Helper function to validate the data sizes shown for each time selection + * + * @param {DialogHelper} dh - dialog object to access window and timespan + */ +async function validateDataSizes(dialogHelper) { + let timespans = [ + "TIMESPAN_HOUR", + "TIMESPAN_2HOURS", + "TIMESPAN_4HOURS", + "TIMESPAN_TODAY", + "TIMESPAN_EVERYTHING", + ]; + + // get current data sizes from siteDataManager + let cacheUsage = await SiteDataManager.getCacheSize(); + let quotaUsage = await SiteDataManager.getQuotaUsageForTimeRanges(timespans); + + for (let i = 0; i < timespans.length; i++) { + // select timespan to check + dialogHelper.selectDuration(Sanitizer[timespans[i]]); + + // get the elements + let clearCookiesAndSiteDataCheckbox = + dialogHelper.win.document.getElementById("cookiesAndStorage"); + let clearCacheCheckbox = dialogHelper.win.document.getElementById("cache"); + + let [convertedQuotaUsage] = DownloadUtils.convertByteUnits( + quotaUsage[timespans[i]] + ); + let [, convertedCacheUnit] = DownloadUtils.convertByteUnits(cacheUsage); + + // Ensure l10n is finished before inspecting the category labels. + await dialogHelper.win.document.l10n.translateElements([ + clearCookiesAndSiteDataCheckbox, + clearCacheCheckbox, + ]); + ok( + clearCacheCheckbox.label.includes(convertedCacheUnit), + "Should show the cache usage" + ); + ok( + clearCookiesAndSiteDataCheckbox.label.includes(convertedQuotaUsage), + `Should show the quota usage as ${convertedQuotaUsage}` + ); + } +} + +/** + * + * Opens dialog in the provided context and selects the checkboxes + * as sent in the parameters + * + * @param {Object} context the dialog is opened in, timespan to select, + * if historyFormDataAndDownloads, cookiesAndStorage, cache or siteSettings + * are checked + */ +async function performActionsOnDialog({ + context = "browser", + timespan = Sanitizer.TIMESPAN_HOUR, + historyFormDataAndDownloads = true, + cookiesAndStorage = true, + cache = false, + siteSettings = false, +}) { + let dh = new DialogHelper(context); + dh.onload = function () { + this.selectDuration(timespan); + this.checkPrefCheckbox( + "historyFormDataAndDownloads", + historyFormDataAndDownloads + ); + this.checkPrefCheckbox("cookiesAndStorage", cookiesAndStorage); + this.checkPrefCheckbox("cache", cache); + this.checkPrefCheckbox("siteSettings", siteSettings); + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; +} + +/** + * 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("historyFormDataAndDownloads", 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 "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("historyFormDataAndDownloads", 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("historyFormDataAndDownloads", 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; +}); + +/** + * Tests that the clearing button gets disabled if no checkboxes are checked + * and enabled when at least one checkbox is checked + */ +add_task(async function testAcceptButtonDisabled() { + let dh = new DialogHelper(); + dh.onload = async function () { + let clearButton = this.win.document + .querySelector("dialog") + .getButton("accept"); + this.uncheckAllCheckboxes(); + await new Promise(resolve => SimpleTest.executeSoon(resolve)); + is(clearButton.disabled, true, "Clear button should be disabled"); + // await BrowserTestUtils.waitForMutationCondition( + // clearButton, + // { attributes: true }, + // () => clearButton.disabled, + // "Clear button should be disabled" + // ); + + this.checkPrefCheckbox("cache", true); + await new Promise(resolve => SimpleTest.executeSoon(resolve)); + is(clearButton.disabled, false, "Clear button should not be disabled"); + + this.cancelDialog(); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Tests to see if the warning box is hidden when opened in the clear on shutdown context + */ +add_task(async function testWarningBoxInClearOnShutdown() { + let dh = new DialogHelper("clearSiteData"); + dh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + is( + BrowserTestUtils.isVisible(this.getWarningPanel()), + true, + `warning panel should be visible` + ); + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; + + dh = new DialogHelper("clearOnShutdown"); + dh.onload = function () { + is( + BrowserTestUtils.isVisible(this.getWarningPanel()), + false, + `warning panel should not be visible` + ); + + this.cancelDialog(); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Checks if clearing history and downloads for the simple timespan + * behaves as expected + */ +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("historyFormDataAndDownloads", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + intPrefIs( + "sanitize.timeSpan", + Sanitizer.TIMESPAN_HOUR, + "timeSpan pref should be hour after accepting dialog with " + + "hour selected" + ); + + 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; +}); + +/** + * 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 () { + var cb = this.win.document.querySelectorAll( + "checkbox[id='historyFormDataAndDownloads']" + ); + 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 () { + var cb = this.win.document.querySelectorAll( + "checkbox[id='historyFormDataAndDownloads']" + ); + 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 () { + var cb = this.win.document.querySelectorAll( + "checkbox[id='historyFormDataAndDownloads']" + ); + is(cb.length, 1, "There is only one checkbox for history and 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; +}); + +add_task(async function test_cookie_sizes() { + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: false, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_HOUR, + }); + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: false, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_4HOURS, + }); + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: false, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_EVERYTHING, + }); +}); + +add_task(async function test_cache_sizes() { + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: true, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_HOUR, + }); + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: true, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_4HOURS, + }); + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: true, + clearDownloads: false, + timespan: Sanitizer.TIMESPAN_EVERYTHING, + }); +}); + +add_task(async function test_downloads_sizes() { + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: false, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_HOUR, + }); + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: false, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_4HOURS, + }); + await clearAndValidateDataSizes({ + clearCookies: false, + clearCache: false, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_EVERYTHING, + }); +}); + +add_task(async function test_all_data_sizes() { + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: true, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_HOUR, + }); + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: true, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_4HOURS, + }); + await clearAndValidateDataSizes({ + clearCookies: true, + clearCache: true, + clearDownloads: true, + timespan: Sanitizer.TIMESPAN_EVERYTHING, + }); +}); + +// test the case when we open the dialog through the clear on shutdown settings +add_task(async function test_clear_on_shutdown() { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.sanitize.sanitizeOnShutdown", true]], + }); + + let dh = new DialogHelper("clearOnShutdown"); + dh.onload = async function () { + this.uncheckAllCheckboxes(); + this.checkPrefCheckbox("historyFormDataAndDownloads", false); + this.checkPrefCheckbox("cookiesAndStorage", true); + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; + + // 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); + } + + boolPrefIs( + "clearOnShutdown_v2.historyFormDataAndDownloads", + false, + "clearOnShutdown_v2 history should be false" + ); + + boolPrefIs( + "clearOnShutdown_v2.cookiesAndStorage", + true, + "clearOnShutdown_v2 cookies should be true" + ); + + boolPrefIs( + "clearOnShutdown_v2.cache", + false, + "clearOnShutdown_v2 cache should be false" + ); + + await createDummyDataForHost("example.org"); + await createDummyDataForHost("example.com"); + + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.org"), + "We have indexedDB data for example.org" + ); + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.com"), + "We have indexedDB data for example.com" + ); + + // Cleaning up + await Sanitizer.runSanitizeOnShutdown(); + + // Data for example.org should be cleared + ok( + !(await SiteDataTestUtils.hasIndexedDB("https://example.org")), + "We don't have indexedDB data for example.org" + ); + // Data for example.com should be cleared + ok( + !(await SiteDataTestUtils.hasIndexedDB("https://example.com")), + "We don't have indexedDB data for example.com" + ); + + // Downloads shouldn't have cleared + await ensureDownloadsClearedState(downloadIDs, false); + await ensureDownloadsClearedState(olderDownloadIDs, false); + + dh = new DialogHelper("clearOnShutdown"); + dh.onload = async function () { + this.uncheckAllCheckboxes(); + this.checkPrefCheckbox("historyFormDataAndDownloads", true); + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; + + boolPrefIs( + "clearOnShutdown_v2.historyFormDataAndDownloads", + true, + "clearOnShutdown_v2 history should be true" + ); + + boolPrefIs( + "clearOnShutdown_v2.cookiesAndStorage", + false, + "clearOnShutdown_v2 cookies should be false" + ); + + boolPrefIs( + "clearOnShutdown_v2.cache", + false, + "clearOnShutdown_v2 cache should be false" + ); + + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.org"), + "We have indexedDB data for example.org" + ); + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.com"), + "We have indexedDB data for example.com" + ); + + // Cleaning up + await Sanitizer.runSanitizeOnShutdown(); + + // Data for example.org should not be cleared + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.org"), + "We have indexedDB data for example.org" + ); + // Data for example.com should not be cleared + ok( + await SiteDataTestUtils.hasIndexedDB("https://example.com"), + "We have indexedDB data for example.com" + ); + + // downloads should have cleared + await ensureDownloadsClearedState(downloadIDs, true); + await ensureDownloadsClearedState(olderDownloadIDs, true); + + // Clean up + await SiteDataTestUtils.clear(); +}); + +// test default prefs for entry points +add_task(async function test_defaults_prefs() { + let dh = new DialogHelper("clearSiteData"); + dh.onload = function () { + this.validateCheckbox("historyFormDataAndDownloads", false); + this.validateCheckbox("cache", true); + this.validateCheckbox("cookiesAndStorage", true); + this.validateCheckbox("siteSettings", false); + + this.cancelDialog(); + }; + dh.open(); + await dh.promiseClosed; + + // We don't need to specify the mode again, + // as the default mode is taken (browser, clear history) + + dh = new DialogHelper(); + dh.onload = function () { + // Default checked for browser and clear history mode + this.validateCheckbox("historyFormDataAndDownloads", true); + this.validateCheckbox("cache", true); + this.validateCheckbox("cookiesAndStorage", true); + this.validateCheckbox("siteSettings", false); + + this.cancelDialog(); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Helper function to simulate switching timespan selections and + * validate data sizes before and after clearing + * + * @param {Object} + * clearCookies - boolean + * clearDownloads - boolean + * clearCaches - boolean + * timespan - one of Sanitizer.TIMESPAN_* + */ +async function clearAndValidateDataSizes({ + clearCache, + clearDownloads, + clearCookies, + timespan, +}) { + await blankSlate(); + + await addToDownloadList(); + await addToSiteUsage(); + let promiseSanitized = promiseSanitizationComplete(); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + + let dh = new DialogHelper(); + dh.onload = async function () { + await validateDataSizes(this); + this.checkPrefCheckbox("cache", clearCache); + this.checkPrefCheckbox("cookiesAndStorage", clearCookies); + this.checkPrefCheckbox("historyFormDataAndDownloads", clearDownloads); + this.selectDuration(timespan); + this.acceptDialog(); + }; + dh.onunload = async function () { + await promiseSanitized; + }; + dh.open(); + await dh.promiseClosed; + + let dh2 = new DialogHelper(); + // Check if the newly cleared values are reflected + dh2.onload = async function () { + await validateDataSizes(this); + this.acceptDialog(); + }; + dh2.open(); + await dh2.promiseClosed; + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +} + +add_task(async function testEntryPointTelemetry() { + Services.fog.testResetFOG(); + + // Telemetry count we expect for each context + const EXPECTED_CONTEXT_COUNTS = { + browser: 3, + clearHistory: 2, + clearSiteData: 1, + }; + + for (let key in EXPECTED_CONTEXT_COUNTS) { + let count = 0; + + for (let i = 0; i < EXPECTED_CONTEXT_COUNTS[key]; i++) { + await performActionsOnDialog({ context: key }); + } + + let contextTelemetry = Glean.privacySanitize.dialogOpen.testGetValue(); + for (let object of contextTelemetry) { + if (object.extra.context == key) { + count += 1; + } + } + + is( + count, + EXPECTED_CONTEXT_COUNTS[key], + `There should be ${EXPECTED_CONTEXT_COUNTS[key]} opens from ${key} context` + ); + } +}); + +add_task(async function testTimespanTelemetry() { + Services.fog.testResetFOG(); + + // Expected timespan selections from telemetry + const EXPECTED_TIMESPANS = [ + Sanitizer.TIMESPAN_HOUR, + Sanitizer.TIMESPAN_2HOURS, + Sanitizer.TIMESPAN_4HOURS, + Sanitizer.TIMESPAN_EVERYTHING, + ]; + + for (let timespan of EXPECTED_TIMESPANS) { + await performActionsOnDialog({ timespan }); + } + + for (let index in EXPECTED_TIMESPANS) { + is( + Glean.privacySanitize.clearingTimeSpanSelected.testGetValue()[index].extra + .time_span, + EXPECTED_TIMESPANS[index].toString(), + `Selected timespan should be ${EXPECTED_TIMESPANS[index]}` + ); + } +}); + +add_task(async function testLoadtimeTelemetry() { + Services.fog.testResetFOG(); + + // loadtime metric is collected everytime that the dialog is opened + // expected number of times dialog will be opened for the test for each context + let EXPECTED_CONTEXT_COUNTS = { + browser: 2, + clearHistory: 3, + clearSiteData: 2, + }; + + // open dialog based on expected_context_counts + for (let context in EXPECTED_CONTEXT_COUNTS) { + for (let i = 0; i < EXPECTED_CONTEXT_COUNTS[context]; i++) { + await performActionsOnDialog({ context }); + } + } + + let loadTimeDistribution = Glean.privacySanitize.loadTime.testGetValue(); + + let expectedNumberOfCounts = Object.entries(EXPECTED_CONTEXT_COUNTS).reduce( + (acc, [key, value]) => acc + value, + 0 + ); + // No guarantees from timers means no guarantees on buckets. + // But we can guarantee it's only two samples. + is( + Object.entries(loadTimeDistribution.values).reduce( + (acc, [bucket, count]) => acc + count, + 0 + ), + expectedNumberOfCounts, + `Only ${expectedNumberOfCounts} buckets with samples` + ); +}); + +add_task(async function testClearingOptionsTelemetry() { + Services.fog.testResetFOG(); + + let expectedObject = { + context: "clearSiteData", + history_form_data_downloads: "true", + cookies_and_storage: "false", + cache: "true", + site_settings: "true", + }; + + await performActionsOnDialog({ + context: "clearSiteData", + historyFormDataAndDownloads: true, + cookiesAndStorage: false, + cache: true, + siteSettings: true, + }); + + let telemetryObject = Glean.privacySanitize.clear.testGetValue(); + Assert.equal( + telemetryObject.length, + 1, + "There should be only 1 telemetry object recorded" + ); + + Assert.deepEqual( + expectedObject, + telemetryObject[0].extra, + `Expected ${telemetryObject} to be the same as ${expectedObject}` + ); +}); + +add_task(async function testCheckboxStatesAfterMigration() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.history", false], + ["privacy.clearOnShutdown.formdata", true], + ["privacy.clearOnShutdown.cookies", true], + ["privacy.clearOnShutdown.offlineApps", false], + ["privacy.clearOnShutdown.sessions", false], + ["privacy.clearOnShutdown.siteSettings", false], + ["privacy.clearOnShutdown.cache", true], + ["privacy.clearOnShutdown_v2.cookiesAndStorage", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + let dh = new DialogHelper("clearOnShutdown"); + dh.onload = function () { + this.validateCheckbox("cookiesAndStorage", true); + this.validateCheckbox("historyFormDataAndDownloads", false); + this.validateCheckbox("cache", true); + this.validateCheckbox("siteSettings", false); + this.cancelDialog(); + }; + dh.open(); + await dh.promiseClosed; +}); diff --git a/browser/base/content/test/sanitize/browser_sanitizeOnShutdown_migration.js b/browser/base/content/test/sanitize/browser_sanitizeOnShutdown_migration.js new file mode 100644 index 0000000000..3c2af1d513 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitizeOnShutdown_migration.js @@ -0,0 +1,312 @@ +/* -*- 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/. */ + +add_setup(async function () { + await SpecialPowers.pushPrefEnv({ + set: [["privacy.sanitize.useOldClearHistoryDialog", false]], + }); +}); + +add_task(async function testMigrationOfCacheAndSiteSettings() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.cache", true], + ["privacy.clearOnShutdown.siteSettings", true], + ["privacy.clearOnShutdown_v2.cache", false], + ["privacy.clearOnShutdown_v2.siteSettings", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.cache"), + true, + "Cache should be set to true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.siteSettings"), + true, + "siteSettings should be set to true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cache"), + true, + "old cache should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.siteSettings"), + true, + "old siteSettings should remain true" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function testHistoryAndFormData_historyTrue() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.history", true], + ["privacy.clearOnShutdown.formdata", false], + ["privacy.clearOnShutdown_v2.historyFormDataAndDownloads", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.clearOnShutdown_v2.historyFormDataAndDownloads" + ), + true, + "historyFormDataAndDownloads should be set to true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.history"), + true, + "old history pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata"), + false, + "old formdata pref should remain false" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function testHistoryAndFormData_historyFalse() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.history", false], + ["privacy.clearOnShutdown.formdata", true], + ["privacy.clearOnShutdown_v2.historyFormDataAndDownloads", true], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.clearOnShutdown_v2.historyFormDataAndDownloads" + ), + false, + "historyFormDataAndDownloads should be set to true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.history"), + false, + "old history pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata"), + true, + "old formdata pref should remain true" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function testCookiesAndStorage_cookiesFalse() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.cookies", false], + ["privacy.clearOnShutdown.offlineApps", true], + ["privacy.clearOnShutdown.sessions", true], + ["privacy.clearOnShutdown_v2.cookiesAndStorage", true], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + // Simulate clearing on shutdown. + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.cookiesAndStorage"), + false, + "cookiesAndStorage should be set to false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"), + false, + "old cookies pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"), + true, + "old offlineApps pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"), + true, + "old sessions pref should remain true" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function testCookiesAndStorage_cookiesTrue() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.cookies", true], + ["privacy.clearOnShutdown.offlineApps", false], + ["privacy.clearOnShutdown.sessions", false], + ["privacy.clearOnShutdown_v2.cookiesAndStorage", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.cookiesAndStorage"), + true, + "cookiesAndStorage should be set to true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"), + true, + "old cookies pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"), + false, + "old offlineApps pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"), + false, + "old sessions pref should remain false" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function testMigrationDoesNotRepeat() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.cookies", true], + ["privacy.clearOnShutdown.offlineApps", false], + ["privacy.clearOnShutdown.sessions", false], + ["privacy.clearOnShutdown_v2.cookiesAndStorage", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", true], + ], + }); + + // Simulate clearing on shutdown. + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.cookiesAndStorage"), + false, + "cookiesAndStorage should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"), + true, + "old cookies pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"), + false, + "old offlineApps pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"), + false, + "old sessions pref should remain false" + ); + + Assert.equal( + Services.prefs.getBoolPref( + "privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs" + ), + true, + "migration pref has been flipped" + ); +}); + +add_task(async function ensureNoOldPrefsAreEffectedByMigration() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["privacy.clearOnShutdown.history", true], + ["privacy.clearOnShutdown.formdata", true], + ["privacy.clearOnShutdown.cookies", true], + ["privacy.clearOnShutdown.offlineApps", false], + ["privacy.clearOnShutdown.sessions", false], + ["privacy.clearOnShutdown.siteSettings", true], + ["privacy.clearOnShutdown.cache", true], + ["privacy.clearOnShutdown_v2.cookiesAndStorage", false], + ["privacy.sanitize.sanitizeOnShutdown.hasMigratedToNewPrefs", false], + ], + }); + + Sanitizer.runSanitizeOnShutdown(); + + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown_v2.cookiesAndStorage"), + true, + "cookiesAndStorage should become true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.cookies"), + true, + "old cookies pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.offlineApps"), + false, + "old offlineApps pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.sessions"), + false, + "old sessions pref should remain false" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.history"), + true, + "old history pref should remain true" + ); + Assert.equal( + Services.prefs.getBoolPref("privacy.clearOnShutdown.formdata"), + true, + "old formdata pref should remain true" + ); +}); 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 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 @@ + + +Dummy test page + + + +

Dummy test page

+ + diff --git a/browser/base/content/test/sanitize/head.js b/browser/base/content/test/sanitize/head.js new file mode 100644 index 0000000000..f5a6031b84 --- /dev/null +++ b/browser/base/content/test/sanitize/head.js @@ -0,0 +1,371 @@ +ChromeUtils.defineESModuleGetters(this, { + Downloads: "resource://gre/modules/Downloads.sys.mjs", + FormHistory: "resource://gre/modules/FormHistory.sys.mjs", + PermissionTestUtils: "resource://testing-common/PermissionTestUtils.sys.mjs", + PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs", + Sanitizer: "resource:///modules/Sanitizer.sys.mjs", + SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs", +}); + +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, + }); + }); + }); +} + +function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) { + return new Promise(resolve => { + let finalPrefPaneLoaded = TestUtils.topicObserved( + "sync-pane-loaded", + () => true + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + openPreferences(aPane); + let newTabBrowser = gBrowser.selectedBrowser; + + newTabBrowser.addEventListener( + "Initialized", + function () { + newTabBrowser.contentWindow.addEventListener( + "load", + async function () { + let win = gBrowser.contentWindow; + let selectedPane = win.history.state; + await finalPrefPaneLoaded; + if (!aOptions || !aOptions.leaveOpen) { + gBrowser.removeCurrentTab(); + } + resolve({ selectedPane }); + }, + { once: true } + ); + }, + { capture: true, once: true } + ); + }); +} + +async function createDummyDataForHost(host) { + let origin = "https://" + host; + let dummySWURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "dummy.js"; + + await SiteDataTestUtils.addToIndexedDB(origin); + await SiteDataTestUtils.addServiceWorker(dummySWURL); +} diff --git a/browser/base/content/test/sanitize/site_data_test.html b/browser/base/content/test/sanitize/site_data_test.html new file mode 100644 index 0000000000..7e5ede2646 --- /dev/null +++ b/browser/base/content/test/sanitize/site_data_test.html @@ -0,0 +1,29 @@ + + + + + + + + Site Data Test + + + + +

Site Data Test

+ + + -- cgit v1.2.3