diff options
Diffstat (limited to 'browser/base/content/test/sanitize')
17 files changed, 3602 insertions, 0 deletions
diff --git a/browser/base/content/test/sanitize/browser.ini b/browser/base/content/test/sanitize/browser.ini new file mode 100644 index 0000000000..2a0a77a288 --- /dev/null +++ b/browser/base/content/test/sanitize/browser.ini @@ -0,0 +1,19 @@ +[DEFAULT] +support-files= + head.js + dummy.js + dummy_page.html + +[browser_cookiePermission.js] +[browser_cookiePermission_aboutURL.js] +[browser_cookiePermission_containers.js] +[browser_cookiePermission_subDomains.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] 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..7ae8ec158e --- /dev/null +++ b/browser/base/content/test/sanitize/browser_cookiePermission_aboutURL.js @@ -0,0 +1,101 @@ +const { SiteDataTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/SiteDataTestUtils.sys.mjs" +); + +function checkDataForAboutURL() { + return new Promise(resolve => { + let data = true; + let uri = Services.io.newURI("about:newtab"); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + let request = indexedDB.openForPrincipal(principal, "TestDatabase", 1); + request.onupgradeneeded = function (e) { + data = false; + }; + request.onsuccess = function (e) { + resolve(data); + }; + }); +} + +add_task(async function deleteStorageInAboutURL() { + info("Test about:newtab"); + + // Let's clean up all the data. + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.sanitizer.loglevel", "All"]], + }); + + // Let's create a tab with some data. + await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {}); + + ok(await checkDataForAboutURL(), "We have data for about:newtab"); + + // Cleaning up. + await Sanitizer.runSanitizeOnShutdown(); + + ok(await checkDataForAboutURL(), "about:newtab data is not deleted."); + + // Clean up. + await Sanitizer.sanitize(["cookies", "offlineApps"]); + + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "about:newtab" + ); + await new Promise(aResolve => { + let req = Services.qms.clearStoragesForPrincipal(principal); + req.callback = () => { + aResolve(); + }; + }); +}); + +add_task(async function deleteStorageOnlyCustomPermissionInAboutURL() { + info("Test about:newtab + permissions"); + + // Let's clean up all the data. + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.sanitizer.loglevel", "All"]], + }); + + // Custom permission without considering OriginAttributes + let uri = Services.io.newURI("about:newtab"); + PermissionTestUtils.add(uri, "cookie", Ci.nsICookiePermission.ACCESS_SESSION); + + // Let's create a tab with some data. + await SiteDataTestUtils.addToIndexedDB("about:newtab", "foo", "bar", {}); + + ok(await checkDataForAboutURL(), "We have data for about:newtab"); + + // Cleaning up. + await Sanitizer.runSanitizeOnShutdown(); + + ok(await checkDataForAboutURL(), "about:newtab data is not deleted."); + + // Clean up. + await Sanitizer.sanitize(["cookies", "offlineApps"]); + + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + "about:newtab" + ); + await new Promise(aResolve => { + let req = Services.qms.clearStoragesForPrincipal(principal); + req.callback = () => { + aResolve(); + }; + }); + + PermissionTestUtils.remove(uri, "cookie"); +}); diff --git a/browser/base/content/test/sanitize/browser_cookiePermission_containers.js b/browser/base/content/test/sanitize/browser_cookiePermission_containers.js new file mode 100644 index 0000000000..236c0913e8 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_cookiePermission_containers.js @@ -0,0 +1 @@ +runAllCookiePermissionTests({ name: "container", oa: { userContextId: 1 } }); diff --git a/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js b/browser/base/content/test/sanitize/browser_cookiePermission_subDomains.js new file mode 100644 index 0000000000..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_purgehistory_clears_sh.js b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js new file mode 100644 index 0000000000..abf11017dd --- /dev/null +++ b/browser/base/content/test/sanitize/browser_purgehistory_clears_sh.js @@ -0,0 +1,71 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const url = + "https://example.org/browser/browser/base/content/test/sanitize/dummy_page.html"; + +add_task(async function purgeHistoryTest() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url, + }, + async function purgeHistoryTestInner(browser) { + let backButton = browser.ownerDocument.getElementById("Browser:Back"); + let forwardButton = + browser.ownerDocument.getElementById("Browser:Forward"); + + ok( + !browser.webNavigation.canGoBack, + "Initial value for webNavigation.canGoBack" + ); + ok( + !browser.webNavigation.canGoForward, + "Initial value for webNavigation.canGoBack" + ); + ok(backButton.hasAttribute("disabled"), "Back button is disabled"); + ok(forwardButton.hasAttribute("disabled"), "Forward button is disabled"); + + await SpecialPowers.spawn(browser, [], async function () { + let startHistory = content.history.length; + content.history.pushState({}, ""); + content.history.pushState({}, ""); + content.history.back(); + await new Promise(function (r) { + content.onpopstate = r; + }); + let newHistory = content.history.length; + Assert.equal(startHistory, 1, "Initial SHistory size"); + Assert.equal(newHistory, 3, "New SHistory size"); + }); + + ok( + browser.webNavigation.canGoBack, + "New value for webNavigation.canGoBack" + ); + ok( + browser.webNavigation.canGoForward, + "New value for webNavigation.canGoForward" + ); + ok(!backButton.hasAttribute("disabled"), "Back button was enabled"); + ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled"); + + await Sanitizer.sanitize(["history"]); + + await SpecialPowers.spawn(browser, [], async function () { + Assert.equal(content.history.length, 1, "SHistory correctly cleared"); + }); + + ok( + !browser.webNavigation.canGoBack, + "webNavigation.canGoBack correctly cleared" + ); + ok( + !browser.webNavigation.canGoForward, + "webNavigation.canGoForward correctly cleared" + ); + ok(backButton.hasAttribute("disabled"), "Back button was disabled"); + ok(forwardButton.hasAttribute("disabled"), "Forward button was disabled"); + } + ); +}); diff --git a/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js b/browser/base/content/test/sanitize/browser_sanitize-cookie-exceptions.js new file mode 100644 index 0000000000..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..5547a88d64 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-formhistory.js @@ -0,0 +1,28 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +add_task(async function test() { + // This test relies on the form history being empty to start with delete + // all the items first. + // The TabContextMenu initializes its strings only on a focus or mouseover event. + // Calls focus event on the TabContextMenu early in the test. + gBrowser.selectedTab.focus(); + await FormHistory.update({ op: "remove" }); + + // Sanitize now so we can test the baseline point. + await Sanitizer.sanitize(["formdata"]); + await gFindBarPromise; + ok(!gFindBar.hasTransactions, "pre-test baseline for sanitizer"); + + gFindBar.getElement("findbar-textbox").value = "m"; + ok(gFindBar.hasTransactions, "formdata can be cleared after input"); + + await Sanitizer.sanitize(["formdata"]); + is( + gFindBar.getElement("findbar-textbox").value, + "", + "findBar textbox should be empty after sanitize" + ); + ok(!gFindBar.hasTransactions, "No transactions after sanitize"); +}); diff --git a/browser/base/content/test/sanitize/browser_sanitize-history.js b/browser/base/content/test/sanitize/browser_sanitize-history.js new file mode 100644 index 0000000000..5ca2843174 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-history.js @@ -0,0 +1,132 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Tests that sanitizing history will clear storage access permissions +// for sites without cookies or site data. +add_task(async function sanitizeStorageAccessPermissions() { + await new Promise(resolve => { + Services.clearData.deleteData(Ci.nsIClearDataService.CLEAR_ALL, resolve); + }); + + await SiteDataTestUtils.addToIndexedDB("https://sub.example.org"); + await SiteDataTestUtils.addToCookies({ origin: "https://example.com" }); + + PermissionTestUtils.add( + "https://example.org", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "https://example.com", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + PermissionTestUtils.add( + "http://mochi.test", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + // Add some time in between taking the snapshot of the timestamp + // to avoid flakyness. + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(c => setTimeout(c, 100)); + let timestamp = Date.now(); + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(c => setTimeout(c, 100)); + + PermissionTestUtils.add( + "http://example.net", + "storageAccessAPI", + Services.perms.ALLOW_ACTION + ); + + await Sanitizer.sanitize(["history"], { + // Sanitizer and ClearDataService work with time range in PRTime (microseconds) + range: [timestamp * 1000, Date.now() * 1000], + }); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "http://example.net", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "http://mochi.test", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.com", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.org", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + + await Sanitizer.sanitize(["history"]); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "http://mochi.test", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "http://example.net", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.com", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.org", + "storageAccessAPI" + ), + Services.perms.ALLOW_ACTION + ); + + await Sanitizer.sanitize(["history", "siteSettings"]); + + Assert.equal( + PermissionTestUtils.testExactPermission( + "http://mochi.test", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.com", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); + Assert.equal( + PermissionTestUtils.testExactPermission( + "https://example.org", + "storageAccessAPI" + ), + Services.perms.UNKNOWN_ACTION + ); +}); diff --git a/browser/base/content/test/sanitize/browser_sanitize-offlineData.js b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js new file mode 100644 index 0000000000..64604684b1 --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitize-offlineData.js @@ -0,0 +1,255 @@ +/* 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; + +const itemsToClear = ["cookies", "offlineApps"]; + +function waitForUnregister(host) { + return new Promise(resolve => { + let listener = { + onUnregister: registration => { + if (registration.principal.host != host) { + return; + } + swm.removeListener(listener); + resolve(registration); + }, + }; + swm.addListener(listener); + }); +} + +async function createData(host) { + let origin = "https://" + host; + let dummySWURL = + getRootDirectory(gTestPath).replace("chrome://mochitests/content", origin) + + "dummy.js"; + + await SiteDataTestUtils.addToIndexedDB(origin); + await SiteDataTestUtils.addServiceWorker(dummySWURL); +} + +function moveOriginInTime(principals, endDate, host) { + for (let i = 0; i < principals.length; ++i) { + let principal = principals.queryElementAt(i, Ci.nsIPrincipal); + if (principal.host == host) { + sas.moveOriginInTime(principal, endDate - fiveHours); + return true; + } + } + return false; +} + +add_task(async function testWithRange() { + // We have intermittent occurrences of NS_ERROR_ABORT being + // thrown at closing database instances when using Santizer.sanitize(). + // This does not seem to impact cleanup, since our tests run fine anyway. + PromiseTestUtils.allowMatchingRejectionsGlobally(/NS_ERROR_ABORT/); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.exemptFromPerDomainMax", true], + ["dom.serviceWorkers.testing.enabled", true], + ], + }); + + // The service may have picked up activity from prior tests in this run. + // Clear it. + sas.testOnlyReset(); + + let endDate = Date.now() * 1000; + let principals = sas.getActiveOrigins(endDate - oneHour, endDate); + is(principals.length, 0, "starting from clear activity state"); + + info("sanitize: " + itemsToClear.join(", ")); + await Sanitizer.sanitize(itemsToClear, { ignoreTimespan: false }); + + await createData("example.org"); + await createData("example.com"); + + endDate = Date.now() * 1000; + principals = sas.getActiveOrigins(endDate - oneHour, endDate); + ok(!!principals, "We have an active origin."); + ok(principals.length >= 2, "We have an active origin."); + + let found = 0; + for (let i = 0; i < principals.length; ++i) { + let principal = principals.queryElementAt(i, Ci.nsIPrincipal); + if (principal.host == "example.org" || principal.host == "example.com") { + found++; + } + } + + is(found, 2, "Our origins are active."); + + ok( + await 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 createData("example.org"); + await createData("example.com"); + + // Set exception for example.org to not get cleaned + let originALLOW = "https://example.org"; + PermissionTestUtils.add( + originALLOW, + "cookie", + Ci.nsICookiePermission.ACCESS_ALLOW + ); + + ok( + await 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_sanitizeDialog.js b/browser/base/content/test/sanitize/browser_sanitizeDialog.js new file mode 100644 index 0000000000..aece90f16e --- /dev/null +++ b/browser/base/content/test/sanitize/browser_sanitizeDialog.js @@ -0,0 +1,833 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/** + * Tests the sanitize dialog (a.k.a. the clear recent history dialog). + * See bug 480169. + * + * The purpose of this test is not to fully flex the sanitize timespan code; + * browser/base/content/test/sanitize/browser_sanitize-timespans.js does that. This + * test checks the UI of the dialog and makes sure it's correctly connected to + * the sanitize timespan code. + * + * Some of this code, especially the history creation parts, was taken from + * browser/base/content/test/sanitize/browser_sanitize-timespans.js. + */ + +ChromeUtils.defineESModuleGetters(this, { + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs", + Timer: "resource://gre/modules/Timer.sys.mjs", +}); + +const kMsecPerMin = 60 * 1000; +const kUsecPerMin = 60 * 1000000; + +/** + * Ensures that the specified URIs are either cleared or not. + * + * @param aURIs + * Array of page URIs + * @param aShouldBeCleared + * True if each visit to the URI should be cleared, false otherwise + */ +async function promiseHistoryClearedState(aURIs, aShouldBeCleared) { + for (let uri of aURIs) { + let visited = await PlacesUtils.history.hasVisits(uri); + Assert.equal( + visited, + !aShouldBeCleared, + `history visit ${uri.spec} should ${ + aShouldBeCleared ? "no longer" : "still" + } exist` + ); + } +} + +add_setup(async function () { + requestLongerTimeout(3); + await blankSlate(); + registerCleanupFunction(async function () { + await blankSlate(); + await PlacesTestUtils.promiseAsyncUpdates(); + }); +}); + +/** + * Initializes the dialog to its default state. + */ +add_task(async function default_state() { + let dh = new DialogHelper(); + dh.onload = function () { + // Select "Last Hour" + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Cancels the dialog, makes sure history not cleared. + */ +add_task(async function test_cancel() { + // Add history (within the past hour) + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 30; i++) { + pURI = makeURI("https://" + i + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) }); + uris.push(pURI); + } + await PlacesTestUtils.addVisits(places); + + let dh = new DialogHelper(); + dh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + this.checkPrefCheckbox("history", false); + this.cancelDialog(); + }; + dh.onunload = async function () { + await promiseHistoryClearedState(uris, false); + await blankSlate(); + await promiseHistoryClearedState(uris, true); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Ensures that the combined history-downloads checkbox clears both history + * visits and downloads when checked; the dialog respects simple timespan. + */ +add_task(async function test_history_downloads_checked() { + // Add downloads (within the past hour). + let downloadIDs = []; + for (let i = 0; i < 5; i++) { + await addDownloadWithMinutesAgo(downloadIDs, i); + } + // Add downloads (over an hour ago). + let olderDownloadIDs = []; + for (let i = 0; i < 5; i++) { + await addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i); + } + + // Add history (within the past hour). + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 30; i++) { + pURI = makeURI("https://" + i + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) }); + uris.push(pURI); + } + // Add history (over an hour ago). + let olderURIs = []; + for (let i = 0; i < 5; i++) { + pURI = makeURI("https://" + (61 + i) + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i) }); + olderURIs.push(pURI); + } + let promiseSanitized = promiseSanitizationComplete(); + + await PlacesTestUtils.addVisits(places); + + let dh = new DialogHelper(); + dh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + this.checkPrefCheckbox("history", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + intPrefIs( + "sanitize.timeSpan", + Sanitizer.TIMESPAN_HOUR, + "timeSpan pref should be hour after accepting dialog with " + + "hour selected" + ); + boolPrefIs( + "cpd.history", + true, + "history pref should be true after accepting dialog with " + + "history checkbox checked" + ); + boolPrefIs( + "cpd.downloads", + true, + "downloads pref should be true after accepting dialog with " + + "history checkbox checked" + ); + + await promiseSanitized; + + // History visits and downloads within one hour should be cleared. + await promiseHistoryClearedState(uris, true); + await ensureDownloadsClearedState(downloadIDs, true); + + // Visits and downloads > 1 hour should still exist. + await promiseHistoryClearedState(olderURIs, false); + await ensureDownloadsClearedState(olderDownloadIDs, false); + + // OK, done, cleanup after ourselves. + await blankSlate(); + await promiseHistoryClearedState(olderURIs, true); + await ensureDownloadsClearedState(olderDownloadIDs, true); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Ensures that the combined history-downloads checkbox removes neither + * history visits nor downloads when not checked. + */ +add_task(async function test_history_downloads_unchecked() { + // Add form entries + let formEntries = []; + + for (let i = 0; i < 5; i++) { + formEntries.push(await promiseAddFormEntryWithMinutesAgo(i)); + } + + // Add downloads (within the past hour). + let downloadIDs = []; + for (let i = 0; i < 5; i++) { + await addDownloadWithMinutesAgo(downloadIDs, i); + } + + // Add history, downloads, form entries (within the past hour). + let uris = []; + let places = []; + let pURI; + for (let i = 0; i < 5; i++) { + pURI = makeURI("https://" + i + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(i) }); + uris.push(pURI); + } + + await PlacesTestUtils.addVisits(places); + let dh = new DialogHelper(); + dh.onload = function () { + is( + this.isWarningPanelVisible(), + false, + "Warning panel should be hidden after previously accepting dialog " + + "with a predefined timespan" + ); + this.selectDuration(Sanitizer.TIMESPAN_HOUR); + + // Remove only form entries, leave history (including downloads). + this.checkPrefCheckbox("history", false); + this.checkPrefCheckbox("formdata", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + intPrefIs( + "sanitize.timeSpan", + Sanitizer.TIMESPAN_HOUR, + "timeSpan pref should be hour after accepting dialog with " + + "hour selected" + ); + boolPrefIs( + "cpd.history", + false, + "history pref should be false after accepting dialog with " + + "history checkbox unchecked" + ); + boolPrefIs( + "cpd.downloads", + false, + "downloads pref should be false after accepting dialog with " + + "history checkbox unchecked" + ); + + // Of the three only form entries should be cleared. + await promiseHistoryClearedState(uris, false); + await ensureDownloadsClearedState(downloadIDs, false); + + for (let entry of formEntries) { + let exists = await formNameExists(entry); + ok(!exists, "form entry " + entry + " should no longer exist"); + } + + // OK, done, cleanup after ourselves. + await blankSlate(); + await promiseHistoryClearedState(uris, true); + await ensureDownloadsClearedState(downloadIDs, true); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Ensures that the "Everything" duration option works. + */ +add_task(async function test_everything() { + // Add history. + let uris = []; + let places = []; + let pURI; + // within past hour, within past two hours, within past four hours and + // outside past four hours + [10, 70, 130, 250].forEach(function (aValue) { + pURI = makeURI("https://" + aValue + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) }); + uris.push(pURI); + }); + + let promiseSanitized = promiseSanitizationComplete(); + + await PlacesTestUtils.addVisits(places); + let dh = new DialogHelper(); + dh.onload = function () { + is( + this.isWarningPanelVisible(), + false, + "Warning panel should be hidden after previously accepting dialog " + + "with a predefined timespan" + ); + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + this.checkPrefCheckbox("history", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + await promiseSanitized; + intPrefIs( + "sanitize.timeSpan", + Sanitizer.TIMESPAN_EVERYTHING, + "timeSpan pref should be everything after accepting dialog " + + "with everything selected" + ); + + await promiseHistoryClearedState(uris, true); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * Ensures that the "Everything" warning is visible on dialog open after + * the previous test. + */ +add_task(async function test_everything_warning() { + // Add history. + let uris = []; + let places = []; + let pURI; + // within past hour, within past two hours, within past four hours and + // outside past four hours + [10, 70, 130, 250].forEach(function (aValue) { + pURI = makeURI("https://" + aValue + "-minutes-ago.com/"); + places.push({ uri: pURI, visitDate: visitTimeForMinutesAgo(aValue) }); + uris.push(pURI); + }); + + let promiseSanitized = promiseSanitizationComplete(); + + await PlacesTestUtils.addVisits(places); + let dh = new DialogHelper(); + dh.onload = function () { + is( + this.isWarningPanelVisible(), + true, + "Warning panel should be visible after previously accepting dialog " + + "with clearing everything" + ); + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + this.checkPrefCheckbox("history", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + intPrefIs( + "sanitize.timeSpan", + Sanitizer.TIMESPAN_EVERYTHING, + "timeSpan pref should be everything after accepting dialog " + + "with everything selected" + ); + + await promiseSanitized; + + await promiseHistoryClearedState(uris, true); + }; + dh.open(); + await dh.promiseClosed; +}); + +/** + * The next three tests checks that when a certain history item cannot be + * cleared then the checkbox should be both disabled and unchecked. + * In addition, we ensure that this behavior does not modify the preferences. + */ +add_task(async function test_cannot_clear_history() { + // Add form entries + let formEntries = [await promiseAddFormEntryWithMinutesAgo(10)]; + + let promiseSanitized = promiseSanitizationComplete(); + + // Add history. + let pURI = makeURI("https://" + 10 + "-minutes-ago.com/"); + await PlacesTestUtils.addVisits({ + uri: pURI, + visitDate: visitTimeForMinutesAgo(10), + }); + let uris = [pURI]; + + let dh = new DialogHelper(); + dh.onload = function () { + // Check that the relevant checkboxes are enabled + var cb = this.win.document.querySelectorAll( + "checkbox[preference='privacy.cpd.formdata']" + ); + ok( + cb.length == 1 && !cb[0].disabled, + "There is formdata, checkbox to clear formdata should be enabled." + ); + + cb = this.win.document.querySelectorAll( + "checkbox[preference='privacy.cpd.history']" + ); + ok( + cb.length == 1 && !cb[0].disabled, + "There is history, checkbox to clear history should be enabled." + ); + + this.checkAllCheckboxes(); + this.acceptDialog(); + }; + dh.onunload = async function () { + await promiseSanitized; + + await promiseHistoryClearedState(uris, true); + + let exists = await formNameExists(formEntries[0]); + ok(!exists, "form entry " + formEntries[0] + " should no longer exist"); + }; + dh.open(); + await dh.promiseClosed; +}); + +add_task(async function test_no_formdata_history_to_clear() { + let promiseSanitized = promiseSanitizationComplete(); + let dh = new DialogHelper(); + dh.onload = function () { + boolPrefIs( + "cpd.history", + true, + "history pref should be true after accepting dialog with " + + "history checkbox checked" + ); + boolPrefIs( + "cpd.formdata", + true, + "formdata pref should be true after accepting dialog with " + + "formdata checkbox checked" + ); + + var cb = this.win.document.querySelectorAll( + "checkbox[preference='privacy.cpd.history']" + ); + ok( + cb.length == 1 && !cb[0].disabled && cb[0].checked, + "There is no history, but history checkbox should always be enabled " + + "and will be checked from previous preference." + ); + + this.acceptDialog(); + }; + dh.open(); + await dh.promiseClosed; + await promiseSanitized; +}); + +add_task(async function test_form_entries() { + let formEntry = await promiseAddFormEntryWithMinutesAgo(10); + + let promiseSanitized = promiseSanitizationComplete(); + + let dh = new DialogHelper(); + dh.onload = function () { + boolPrefIs( + "cpd.formdata", + true, + "formdata pref should persist previous value after accepting " + + "dialog where you could not clear formdata." + ); + + var cb = this.win.document.querySelectorAll( + "checkbox[preference='privacy.cpd.formdata']" + ); + + info( + "There exists formEntries so the checkbox should be in sync with the pref." + ); + is(cb.length, 1, "There is only one checkbox for form data"); + ok(!cb[0].disabled, "The checkbox is enabled"); + ok(cb[0].checked, "The checkbox is checked"); + + this.acceptDialog(); + }; + dh.onunload = async function () { + await promiseSanitized; + let exists = await formNameExists(formEntry); + ok(!exists, "form entry " + formEntry + " should no longer exist"); + }; + dh.open(); + await dh.promiseClosed; +}); + +// Test for offline apps permission deletion +add_task(async function test_offline_apps_permissions() { + // Prepare stuff, we will work with www.example.com + var URL = "https://www.example.com"; + var URI = makeURI(URL); + var principal = Services.scriptSecurityManager.createContentPrincipal( + URI, + {} + ); + + let promiseSanitized = promiseSanitizationComplete(); + + // Open the dialog + let dh = new DialogHelper(); + dh.onload = function () { + this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING); + // Clear only offlineApps + this.uncheckAllCheckboxes(); + this.checkPrefCheckbox("siteSettings", true); + this.acceptDialog(); + }; + dh.onunload = async function () { + await promiseSanitized; + + // Check all has been deleted (privileges, data, cache) + is( + Services.perms.testPermissionFromPrincipal(principal, "offline-app"), + 0, + "offline-app permissions removed" + ); + }; + dh.open(); + await dh.promiseClosed; +}); + +var now_mSec = Date.now(); +var now_uSec = now_mSec * 1000; + +/** + * This wraps the dialog and provides some convenience methods for interacting + * with it. + * + * @param browserWin (optional) + * The browser window that the dialog is expected to open in. If not + * supplied, the initial browser window of the test run is used. + */ +function DialogHelper(browserWin = window) { + this._browserWin = browserWin; + this.win = null; + this.promiseClosed = new Promise(resolve => { + this._resolveClosed = resolve; + }); +} + +DialogHelper.prototype = { + /** + * "Presses" the dialog's OK button. + */ + acceptDialog() { + let dialogEl = this.win.document.querySelector("dialog"); + is( + dialogEl.getButton("accept").disabled, + false, + "Dialog's OK button should not be disabled" + ); + dialogEl.acceptDialog(); + }, + + /** + * "Presses" the dialog's Cancel button. + */ + cancelDialog() { + this.win.document.querySelector("dialog").cancelDialog(); + }, + + /** + * (Un)checks a history scope checkbox (browser & download history, + * form history, etc.). + * + * @param aPrefName + * The final portion of the checkbox's privacy.cpd.* preference name + * @param aCheckState + * True if the checkbox should be checked, false otherwise + */ + checkPrefCheckbox(aPrefName, aCheckState) { + var pref = "privacy.cpd." + aPrefName; + var cb = this.win.document.querySelectorAll( + "checkbox[preference='" + pref + "']" + ); + is(cb.length, 1, "found checkbox for " + pref + " preference"); + if (cb[0].checked != aCheckState) { + cb[0].click(); + } + }, + + /** + * Makes sure all the checkboxes are checked. + */ + _checkAllCheckboxesCustom(check) { + var cb = this.win.document.querySelectorAll("checkbox[preference]"); + ok(cb.length > 1, "found checkboxes for preferences"); + for (var i = 0; i < cb.length; ++i) { + var pref = this.win.Preferences.get(cb[i].getAttribute("preference")); + if (!!pref.value ^ check) { + cb[i].click(); + } + } + }, + + checkAllCheckboxes() { + this._checkAllCheckboxesCustom(true); + }, + + uncheckAllCheckboxes() { + this._checkAllCheckboxesCustom(false); + }, + + /** + * @return The dialog's duration dropdown + */ + getDurationDropdown() { + return this.win.document.getElementById("sanitizeDurationChoice"); + }, + + /** + * @return The clear-everything warning box + */ + getWarningPanel() { + return this.win.document.getElementById("sanitizeEverythingWarningBox"); + }, + + /** + * @return True if the "Everything" warning panel is visible (as opposed to + * the tree) + */ + isWarningPanelVisible() { + return !this.getWarningPanel().hidden; + }, + + /** + * Opens the clear recent history dialog. Before calling this, set + * this.onload to a function to execute onload. It should close the dialog + * when done so that the tests may continue. Set this.onunload to a function + * to execute onunload. this.onunload is optional. If it returns true, the + * caller is expected to call promiseAsyncUpdates at some point; if false is + * returned, promiseAsyncUpdates is called automatically. + */ + async open() { + let dialogPromise = BrowserTestUtils.promiseAlertDialogOpen( + null, + "chrome://browser/content/sanitize.xhtml", + { + isSubDialog: true, + } + ); + + executeSoon(() => { + Sanitizer.showUI(this._browserWin); + }); + + this.win = await dialogPromise; + this.win.addEventListener( + "load", + () => { + // Run onload on next tick so that gSanitizePromptDialog.init can run first. + executeSoon(() => this.onload()); + }, + { once: true } + ); + + this.win.addEventListener( + "unload", + () => { + // Some exceptions that reach here don't reach the test harness, but + // ok()/is() do... + (async () => { + if (this.onunload) { + await this.onunload(); + } + await PlacesTestUtils.promiseAsyncUpdates(); + this._resolveClosed(); + this.win = null; + })(); + }, + { once: true } + ); + }, + + /** + * Selects a duration in the duration dropdown. + * + * @param aDurVal + * One of the Sanitizer.TIMESPAN_* values + */ + selectDuration(aDurVal) { + this.getDurationDropdown().value = aDurVal; + if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) { + is( + this.isWarningPanelVisible(), + true, + "Warning panel should be visible for TIMESPAN_EVERYTHING" + ); + } else { + is( + this.isWarningPanelVisible(), + false, + "Warning panel should not be visible for non-TIMESPAN_EVERYTHING" + ); + } + }, +}; + +function promiseSanitizationComplete() { + return TestUtils.topicObserved("sanitizer-sanitization-complete"); +} + +/** + * Adds a download to history. + * + * @param aMinutesAgo + * The download will be downloaded this many minutes ago + */ +async function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) { + let publicList = await Downloads.getList(Downloads.PUBLIC); + + let name = "fakefile-" + aMinutesAgo + "-minutes-ago"; + let download = await Downloads.createDownload({ + source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169", + target: name, + }); + download.startTime = new Date(now_mSec - aMinutesAgo * kMsecPerMin); + download.canceled = true; + publicList.add(download); + + ok( + await downloadExists(name), + "Sanity check: download " + name + " should exist after creating it" + ); + + aExpectedPathList.push(name); +} + +/** + * Adds a form entry to history. + * + * @param aMinutesAgo + * The entry will be added this many minutes ago + */ +function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) { + let name = aMinutesAgo + "-minutes-ago"; + + // Artifically age the entry to the proper vintage. + let timestamp = now_uSec - aMinutesAgo * kUsecPerMin; + + return FormHistory.update({ + op: "add", + fieldname: name, + value: "dummy", + firstUsed: timestamp, + }); +} + +/** + * Checks if a form entry exists. + */ +async function formNameExists(name) { + return !!(await FormHistory.count({ fieldname: name })); +} + +/** + * Removes all history visits, downloads, and form entries. + */ +async function blankSlate() { + let publicList = await Downloads.getList(Downloads.PUBLIC); + let downloads = await publicList.getAll(); + for (let download of downloads) { + await publicList.remove(download); + await download.finalize(true); + } + + await FormHistory.update({ op: "remove" }); + await PlacesUtils.history.clear(); +} + +/** + * Ensures that the given pref is the expected value. + * + * @param aPrefName + * The pref's sub-branch under the privacy branch + * @param aExpectedVal + * The pref's expected value + * @param aMsg + * Passed to is() + */ +function boolPrefIs(aPrefName, aExpectedVal, aMsg) { + is(Services.prefs.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg); +} + +/** + * Checks to see if the download with the specified path exists. + * + * @param aPath + * The path of the download to check + * @return True if the download exists, false otherwise + */ +async function downloadExists(aPath) { + let publicList = await Downloads.getList(Downloads.PUBLIC); + let listArray = await publicList.getAll(); + return listArray.some(i => i.target.path == aPath); +} + +/** + * Ensures that the specified downloads are either cleared or not. + * + * @param aDownloadIDs + * Array of download database IDs + * @param aShouldBeCleared + * True if each download should be cleared, false otherwise + */ +async function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) { + let niceStr = aShouldBeCleared ? "no longer" : "still"; + for (let id of aDownloadIDs) { + is( + await downloadExists(id), + !aShouldBeCleared, + "download " + id + " should " + niceStr + " exist" + ); + } +} + +/** + * Ensures that the given pref is the expected value. + * + * @param aPrefName + * The pref's sub-branch under the privacy branch + * @param aExpectedVal + * The pref's expected value + * @param aMsg + * Passed to is() + */ +function intPrefIs(aPrefName, aExpectedVal, aMsg) { + is(Services.prefs.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg); +} + +/** + * Creates a visit time. + * + * @param aMinutesAgo + * The visit will be visited this many minutes ago + */ +function visitTimeForMinutesAgo(aMinutesAgo) { + return now_uSec - aMinutesAgo * kUsecPerMin; +} diff --git a/browser/base/content/test/sanitize/dummy.js b/browser/base/content/test/sanitize/dummy.js new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/browser/base/content/test/sanitize/dummy.js diff --git a/browser/base/content/test/sanitize/dummy_page.html b/browser/base/content/test/sanitize/dummy_page.html new file mode 100644 index 0000000000..1a87e28408 --- /dev/null +++ b/browser/base/content/test/sanitize/dummy_page.html @@ -0,0 +1,9 @@ +<html> +<head> +<title>Dummy test page</title> +<meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta> +</head> +<body> +<p>Dummy test page</p> +</body> +</html> diff --git a/browser/base/content/test/sanitize/head.js b/browser/base/content/test/sanitize/head.js new file mode 100644 index 0000000000..161ccdc9fc --- /dev/null +++ b/browser/base/content/test/sanitize/head.js @@ -0,0 +1,329 @@ +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, + }); + }); + }); +} |