diff options
Diffstat (limited to '')
12 files changed, 1932 insertions, 0 deletions
diff --git a/browser/components/preferences/tests/siteData/browser.toml b/browser/components/preferences/tests/siteData/browser.toml new file mode 100644 index 0000000000..9f4f8306e1 --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser.toml @@ -0,0 +1,24 @@ +[DEFAULT] +support-files = [ + "head.js", + "site_data_test.html", + "service_worker_test.html", + "service_worker_test.js", + "offline/offline.html", + "offline/manifest.appcache", +] + +["browser_clearSiteData.js"] + +["browser_siteData.js"] + +["browser_siteData2.js"] +skip-if = [ + "win11_2009 && (!debug && !asan)", # Bug 1797751 + "apple_catalina && debug", # Bug 1775910 +] + +["browser_siteData3.js"] + +["browser_siteData_multi_select.js"] +skip-if = ["tsan"] # Bug 1683730 diff --git a/browser/components/preferences/tests/siteData/browser_clearSiteData.js b/browser/components/preferences/tests/siteData/browser_clearSiteData.js new file mode 100644 index 0000000000..ad1d27bfe2 --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser_clearSiteData.js @@ -0,0 +1,243 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const { PermissionTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PermissionTestUtils.sys.mjs" +); + +let useOldClearHistoryDialog = Services.prefs.getBoolPref( + "privacy.sanitize.useOldClearHistoryDialog" +); + +async function testClearData(clearSiteData, clearCache) { + PermissionTestUtils.add( + TEST_QUOTA_USAGE_ORIGIN, + "persistent-storage", + Services.perms.ALLOW_ACTION + ); + + // Open a test site which saves into appcache. + await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_OFFLINE_URL); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // Fill indexedDB with test data. + // Don't wait for the page to load, to register the content event handler as quickly as possible. + // If this test goes intermittent, we might have to tell the page to wait longer before + // firing the event. + BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL, false); + await BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "test-indexedDB-done", + false, + null, + true + ); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // Register some service workers. + await loadServiceWorkerTestPage(TEST_SERVICE_WORKER_URL); + await promiseServiceWorkerRegisteredFor(TEST_SERVICE_WORKER_URL); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + + // Test the initial states. + let cacheUsage = await SiteDataManager.getCacheSize(); + let quotaUsage = await SiteDataTestUtils.getQuotaUsage( + TEST_QUOTA_USAGE_ORIGIN + ); + let totalUsage = await SiteDataManager.getTotalUsage(); + Assert.greater(cacheUsage, 0, "The cache usage should not be 0"); + Assert.greater(quotaUsage, 0, "The quota usage should not be 0"); + Assert.greater(totalUsage, 0, "The total usage should not be 0"); + + let initialSizeLabelValue = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async function () { + let sizeLabel = content.document.getElementById("totalSiteDataSize"); + return sizeLabel.textContent; + } + ); + + let doc = gBrowser.selectedBrowser.contentDocument; + let clearSiteDataButton = doc.getElementById("clearSiteDataButton"); + + let url = useOldClearHistoryDialog + ? "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml" + : "chrome://browser/content/sanitize_v2.xhtml"; + let dialogOpened = promiseLoadSubDialog(url); + clearSiteDataButton.doCommand(); + let dialogWin = await dialogOpened; + + // Convert the usage numbers in the same way the UI does it to assert + // that they're displayed in the dialog. + let [convertedTotalUsage] = DownloadUtils.convertByteUnits(totalUsage); + // For cache we just assert that the right unit (KB, probably) is displayed, + // since we've had cache intermittently changing under our feet. + let [, convertedCacheUnit] = DownloadUtils.convertByteUnits(cacheUsage); + + let cookiesCheckboxId = useOldClearHistoryDialog + ? "clearSiteData" + : "cookiesAndStorage"; + let cacheCheckboxId = useOldClearHistoryDialog ? "clearCache" : "cache"; + let clearSiteDataCheckbox = + dialogWin.document.getElementById(cookiesCheckboxId); + let clearCacheCheckbox = dialogWin.document.getElementById(cacheCheckboxId); + // The usage details are filled asynchronously, so we assert that they're present by + // waiting for them to be filled in. + await Promise.all([ + TestUtils.waitForCondition( + () => + clearSiteDataCheckbox.label && + clearSiteDataCheckbox.label.includes(convertedTotalUsage), + "Should show the quota usage" + ), + TestUtils.waitForCondition( + () => + clearCacheCheckbox.label && + clearCacheCheckbox.label.includes(convertedCacheUnit), + "Should show the cache usage" + ), + ]); + + // Check the boxes according to our test input. + clearSiteDataCheckbox.checked = clearSiteData; + clearCacheCheckbox.checked = clearCache; + + if (!useOldClearHistoryDialog) { + // The new clear history dialog has a seperate checkbox for site settings + let siteSettingsCheckbox = + dialogWin.document.getElementById("siteSettings"); + siteSettingsCheckbox.checked = clearSiteData; + // select clear everything to match the old dialog boxes behaviour for this test + let timespanSelection = dialogWin.document.getElementById( + "sanitizeDurationChoice" + ); + timespanSelection.value = 0; + } + // Some additional promises/assertions to wait for + // when deleting site data. + let acceptPromise; + let updatePromise; + let cookiesClearedPromise; + if (clearSiteData) { + // the new clear history dialog does not have a extra prompt + // to clear site data after clicking clear + if (useOldClearHistoryDialog) { + acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept"); + } + updatePromise = promiseSiteDataManagerSitesUpdated(); + cookiesClearedPromise = promiseCookiesCleared(); + } + + let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload"); + + let clearButton = dialogWin.document + .querySelector("dialog") + .getButton("accept"); + if (!clearSiteData && !clearCache) { + // Simulate user input on one of the checkboxes to trigger the event listener for + // disabling the clearButton. + clearCacheCheckbox.doCommand(); + // Check that the clearButton gets disabled by unchecking both options. + await TestUtils.waitForCondition( + () => clearButton.disabled, + "Clear button should be disabled" + ); + let cancelButton = dialogWin.document + .querySelector("dialog") + .getButton("cancel"); + // Cancel, since we can't delete anything. + cancelButton.click(); + } else { + // Delete stuff! + clearButton.click(); + } + + // For site data we display an extra warning dialog, make sure + // to accept it. + if (clearSiteData && useOldClearHistoryDialog) { + await acceptPromise; + } + + await dialogClosed; + + if (clearCache) { + TestUtils.waitForCondition(async function () { + let usage = await SiteDataManager.getCacheSize(); + return usage == 0; + }, "The cache usage should be removed"); + } else { + Assert.greater( + await SiteDataManager.getCacheSize(), + 0, + "The cache usage should not be 0" + ); + } + + if (clearSiteData) { + await updatePromise; + await cookiesClearedPromise; + await promiseServiceWorkersCleared(); + + TestUtils.waitForCondition(async function () { + let usage = await SiteDataManager.getTotalUsage(); + return usage == 0; + }, "The total usage should be removed"); + } else { + quotaUsage = await SiteDataTestUtils.getQuotaUsage(TEST_QUOTA_USAGE_ORIGIN); + totalUsage = await SiteDataManager.getTotalUsage(); + Assert.greater(quotaUsage, 0, "The quota usage should not be 0"); + Assert.greater(totalUsage, 0, "The total usage should not be 0"); + } + + if (clearCache || clearSiteData) { + // Check that the size label in about:preferences updates after we cleared data. + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ initialSizeLabelValue }], + async function (opts) { + let sizeLabel = content.document.getElementById("totalSiteDataSize"); + await ContentTaskUtils.waitForCondition( + () => sizeLabel.textContent != opts.initialSizeLabelValue, + "Site data size label should have updated." + ); + } + ); + } + + let permission = PermissionTestUtils.getPermissionObject( + TEST_QUOTA_USAGE_ORIGIN, + "persistent-storage" + ); + is( + clearSiteData ? permission : permission.capability, + clearSiteData ? null : Services.perms.ALLOW_ACTION, + "Should have the correct permission state." + ); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); + await SiteDataManager.removeAll(); +} + +// Test opening the "Clear All Data" dialog and cancelling. +add_task(async function () { + await testClearData(false, false); +}); + +// Test opening the "Clear All Data" dialog and removing all site data. +add_task(async function () { + await testClearData(true, false); +}); + +// Test opening the "Clear All Data" dialog and removing all cache. +add_task(async function () { + await testClearData(false, true); +}); + +// Test opening the "Clear All Data" dialog and removing everything. +add_task(async function () { + await testClearData(true, true); +}); diff --git a/browser/components/preferences/tests/siteData/browser_siteData.js b/browser/components/preferences/tests/siteData/browser_siteData.js new file mode 100644 index 0000000000..9c421ed295 --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser_siteData.js @@ -0,0 +1,395 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +function getPersistentStoragePermStatus(origin) { + let uri = Services.io.newURI(origin); + let principal = Services.scriptSecurityManager.createContentPrincipal( + uri, + {} + ); + return Services.perms.testExactPermissionFromPrincipal( + principal, + "persistent-storage" + ); +} + +// Test listing site using quota usage or site using appcache +// This is currently disabled because of bug 1414751. +add_task(async function () { + // Open a test site which would save into appcache + await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_OFFLINE_URL); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + // Open a test site which would save into quota manager + BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_QUOTA_USAGE_URL); + await BrowserTestUtils.waitForContentEvent( + gBrowser.selectedBrowser, + "test-indexedDB-done", + false, + null, + true + ); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + let updatedPromise = promiseSiteDataManagerSitesUpdated(); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatedPromise; + await openSiteDataSettingsDialog(); + let dialog = content.gSubDialog._topDialog; + let dialogFrame = dialog._frame; + let frameDoc = dialogFrame.contentDocument; + + let siteItems = frameDoc.getElementsByTagName("richlistitem"); + is(siteItems.length, 2, "Should list sites using quota usage or appcache"); + + let appcacheSite = frameDoc.querySelector( + `richlistitem[host="${TEST_OFFLINE_HOST}"]` + ); + ok(appcacheSite, "Should list site using appcache"); + + let qoutaUsageSite = frameDoc.querySelector( + `richlistitem[host="${TEST_QUOTA_USAGE_HOST}"]` + ); + ok(qoutaUsageSite, "Should list site using quota usage"); + + // Always remember to clean up + await new Promise(resolve => { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + TEST_QUOTA_USAGE_ORIGIN + ); + let request = Services.qms.clearStoragesForOriginPrefix(principal); + request.callback = resolve; + }); + + await SiteDataManager.removeAll(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}).skip(); // Bug 1414751 + +// Test buttons are disabled and loading message shown while updating sites +add_task(async function () { + let updatedPromise = promiseSiteDataManagerSitesUpdated(); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatedPromise; + let cacheSize = await SiteDataManager.getCacheSize(); + + let doc = gBrowser.selectedBrowser.contentDocument; + let clearBtn = doc.getElementById("clearSiteDataButton"); + let settingsButton = doc.getElementById("siteDataSettings"); + let totalSiteDataSizeLabel = doc.getElementById("totalSiteDataSize"); + is( + clearBtn.disabled, + false, + "Should enable clear button after sites updated" + ); + is( + settingsButton.disabled, + false, + "Should enable settings button after sites updated" + ); + await SiteDataManager.getTotalUsage().then(usage => { + let [value, unit] = DownloadUtils.convertByteUnits(usage + cacheSize); + Assert.deepEqual( + doc.l10n.getAttributes(totalSiteDataSizeLabel), + { + id: "sitedata-total-size", + args: { value, unit }, + }, + "Should show the right total site data size" + ); + }); + + Services.obs.notifyObservers(null, "sitedatamanager:updating-sites"); + is( + clearBtn.disabled, + true, + "Should disable clear button while updating sites" + ); + is( + settingsButton.disabled, + true, + "Should disable settings button while updating sites" + ); + Assert.deepEqual( + doc.l10n.getAttributes(totalSiteDataSizeLabel), + { + id: "sitedata-total-size-calculating", + args: null, + }, + "Should show the loading message while updating" + ); + + Services.obs.notifyObservers(null, "sitedatamanager:sites-updated"); + is( + clearBtn.disabled, + false, + "Should enable clear button after sites updated" + ); + is( + settingsButton.disabled, + false, + "Should enable settings button after sites updated" + ); + cacheSize = await SiteDataManager.getCacheSize(); + await SiteDataManager.getTotalUsage().then(usage => { + let [value, unit] = DownloadUtils.convertByteUnits(usage + cacheSize); + Assert.deepEqual( + doc.l10n.getAttributes(totalSiteDataSizeLabel), + { + id: "sitedata-total-size", + args: { value, unit }, + }, + "Should show the right total site data size" + ); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test clearing service worker through the settings panel +add_task(async function () { + // Register a test service worker + await loadServiceWorkerTestPage(TEST_SERVICE_WORKER_URL); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + // Test the initial states + await promiseServiceWorkerRegisteredFor(TEST_SERVICE_WORKER_URL); + // Open the Site Data Settings panel and remove the site + await openSiteDataSettingsDialog(); + let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept"); + let updatePromise = promiseSiteDataManagerSitesUpdated(); + SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ TEST_OFFLINE_HOST }], + args => { + let host = args.TEST_OFFLINE_HOST; + let frameDoc = content.gSubDialog._topDialog._frame.contentDocument; + let sitesList = frameDoc.getElementById("sitesList"); + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + if (site) { + let removeBtn = frameDoc.getElementById("removeSelected"); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + site.click(); + removeBtn.doCommand(); + saveBtn.doCommand(); + } else { + ok(false, `Should have one site of ${host}`); + } + } + ); + await acceptRemovePromise; + await updatePromise; + await promiseServiceWorkersCleared(); + await SiteDataManager.removeAll(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test showing and removing sites with cookies. +add_task(async function () { + // Add some test cookies. + let uri = Services.io.newURI("https://example.com"); + let uri2 = Services.io.newURI("https://example.org"); + Services.cookies.add( + uri.host, + uri.pathQueryRef, + "test1", + "1", + false, + false, + false, + Date.now() + 1000 * 60 * 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + Services.cookies.add( + uri.host, + uri.pathQueryRef, + "test2", + "2", + false, + false, + false, + Date.now() + 1000 * 60 * 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + Services.cookies.add( + uri2.host, + uri2.pathQueryRef, + "test1", + "1", + false, + false, + false, + Date.now() + 1000 * 60 * 60, + {}, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + + // Ensure that private browsing cookies are ignored. + Services.cookies.add( + uri.host, + uri.pathQueryRef, + "test3", + "3", + false, + false, + false, + Date.now() + 1000 * 60 * 60, + { privateBrowsingId: 1 }, + Ci.nsICookie.SAMESITE_NONE, + Ci.nsICookie.SCHEME_HTTPS + ); + + // Get the exact creation date from the cookies (to avoid intermittents + // from minimal time differences, since we round up to minutes). + let cookies1 = Services.cookies.getCookiesFromHost(uri.host, {}); + let cookies2 = Services.cookies.getCookiesFromHost(uri2.host, {}); + // We made two valid cookies for example.com. + let cookie1 = cookies1[1]; + let cookie2 = cookies2[0]; + + let fullFormatter = new Services.intl.DateTimeFormat(undefined, { + dateStyle: "short", + timeStyle: "short", + }); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + + // Open the site data manager and remove one site. + await openSiteDataSettingsDialog(); + let creationDate1 = new Date(cookie1.lastAccessed / 1000); + let creationDate1Formatted = fullFormatter.format(creationDate1); + let creationDate2 = new Date(cookie2.lastAccessed / 1000); + let creationDate2Formatted = fullFormatter.format(creationDate2); + let removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen( + "accept", + REMOVE_DIALOG_URL + ); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [ + { + creationDate1Formatted, + creationDate2Formatted, + }, + ], + function (args) { + let frameDoc = content.gSubDialog._topDialog._frame.contentDocument; + + let siteItems = frameDoc.getElementsByTagName("richlistitem"); + is(siteItems.length, 2, "Should list two sites with cookies"); + let sitesList = frameDoc.getElementById("sitesList"); + let site1 = sitesList.querySelector(`richlistitem[host="example.com"]`); + let site2 = sitesList.querySelector(`richlistitem[host="example.org"]`); + + let columns = site1.querySelectorAll(".item-box > label"); + let boxes = site1.querySelectorAll(".item-box"); + is(columns[0].value, "example.com", "Should show the correct host."); + is(columns[1].value, "2", "Should show the correct number of cookies."); + is(columns[2].value, "", "Should show no site data."); + is( + /(now|second)/.test(columns[3].value), + true, + "Should show the relative date." + ); + is( + boxes[3].getAttribute("tooltiptext"), + args.creationDate1Formatted, + "Should show the correct date." + ); + + columns = site2.querySelectorAll(".item-box > label"); + boxes = site2.querySelectorAll(".item-box"); + is(columns[0].value, "example.org", "Should show the correct host."); + is(columns[1].value, "1", "Should show the correct number of cookies."); + is(columns[2].value, "", "Should show no site data."); + is( + /(now|second)/.test(columns[3].value), + true, + "Should show the relative date." + ); + is( + boxes[3].getAttribute("tooltiptext"), + args.creationDate2Formatted, + "Should show the correct date." + ); + + let removeBtn = frameDoc.getElementById("removeSelected"); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + site2.click(); + removeBtn.doCommand(); + saveBtn.doCommand(); + } + ); + await removeDialogOpenPromise; + + await TestUtils.waitForCondition( + () => Services.cookies.countCookiesFromHost(uri2.host) == 0, + "Cookies from the first host should be cleared" + ); + is( + Services.cookies.countCookiesFromHost(uri.host), + 2, + "Cookies from the second host should not be cleared" + ); + + // Open the site data manager and remove another site. + await openSiteDataSettingsDialog(); + let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept"); + await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [{ creationDate1Formatted }], + function (args) { + let frameDoc = content.gSubDialog._topDialog._frame.contentDocument; + + let siteItems = frameDoc.getElementsByTagName("richlistitem"); + is(siteItems.length, 1, "Should list one site with cookies"); + let sitesList = frameDoc.getElementById("sitesList"); + let site1 = sitesList.querySelector(`richlistitem[host="example.com"]`); + + let columns = site1.querySelectorAll(".item-box > label"); + let boxes = site1.querySelectorAll(".item-box"); + is(columns[0].value, "example.com", "Should show the correct host."); + is(columns[1].value, "2", "Should show the correct number of cookies."); + is(columns[2].value, "", "Should show no site data."); + is( + /(now|second)/.test(columns[3].value), + true, + "Should show the relative date." + ); + is( + boxes[3].getAttribute("tooltiptext"), + args.creationDate1Formatted, + "Should show the correct date." + ); + + let removeBtn = frameDoc.getElementById("removeSelected"); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + site1.click(); + removeBtn.doCommand(); + saveBtn.doCommand(); + } + ); + await acceptRemovePromise; + + await TestUtils.waitForCondition( + () => Services.cookies.countCookiesFromHost(uri.host) == 0, + "Cookies from the second host should be cleared" + ); + + await openSiteDataSettingsDialog(); + + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], function () { + let frameDoc = content.gSubDialog._topDialog._frame.contentDocument; + + let siteItems = frameDoc.getElementsByTagName("richlistitem"); + is(siteItems.length, 0, "Should list no sites with cookies"); + }); + + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/tests/siteData/browser_siteData2.js b/browser/components/preferences/tests/siteData/browser_siteData2.js new file mode 100644 index 0000000000..863a8dcefe --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser_siteData2.js @@ -0,0 +1,475 @@ +"use strict"; + +function assertAllSitesNotListed(win) { + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let removeBtn = frameDoc.getElementById("removeSelected"); + let removeAllBtn = frameDoc.getElementById("removeAll"); + let sitesList = frameDoc.getElementById("sitesList"); + let sites = sitesList.getElementsByTagName("richlistitem"); + is(sites.length, 0, "Should not list all sites"); + is(removeBtn.disabled, true, "Should disable the removeSelected button"); + is(removeAllBtn.disabled, true, "Should disable the removeAllBtn button"); +} + +// Test selecting and removing all sites one by one +add_task(async function test_selectRemove() { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + let win = gBrowser.selectedBrowser.contentWindow; + let doc = gBrowser.selectedBrowser.contentDocument; + let frameDoc = null; + let saveBtn = null; + let cancelBtn = null; + let settingsDialogClosePromise = null; + + // Test the initial state + assertSitesListed(doc, hosts); + + // Test the "Cancel" button + settingsDialogClosePromise = promiseSettingsDialogClose(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + cancelBtn = frameDoc.querySelector("dialog").getButton("cancel"); + removeAllSitesOneByOne(); + assertAllSitesNotListed(win); + cancelBtn.doCommand(); + await settingsDialogClosePromise; + await openSiteDataSettingsDialog(); + assertSitesListed(doc, hosts); + + // Test the "Save Changes" button but cancelling save + let cancelPromise = BrowserTestUtils.promiseAlertDialogOpen("cancel"); + settingsDialogClosePromise = promiseSettingsDialogClose(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + cancelBtn = frameDoc.querySelector("dialog").getButton("cancel"); + removeAllSitesOneByOne(); + assertAllSitesNotListed(win); + saveBtn.doCommand(); + await cancelPromise; + cancelBtn.doCommand(); + await settingsDialogClosePromise; + await openSiteDataSettingsDialog(); + assertSitesListed(doc, hosts); + + // Test the "Save Changes" button and accepting save + let acceptPromise = BrowserTestUtils.promiseAlertDialogOpen("accept"); + settingsDialogClosePromise = promiseSettingsDialogClose(); + updatePromise = promiseSiteDataManagerSitesUpdated(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + removeAllSitesOneByOne(); + assertAllSitesNotListed(win); + saveBtn.doCommand(); + await acceptPromise; + await settingsDialogClosePromise; + await updatePromise; + await openSiteDataSettingsDialog(); + assertAllSitesNotListed(win); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + function removeAllSitesOneByOne() { + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let removeBtn = frameDoc.getElementById("removeSelected"); + let sitesList = frameDoc.getElementById("sitesList"); + let sites = sitesList.getElementsByTagName("richlistitem"); + for (let i = sites.length - 1; i >= 0; --i) { + sites[i].click(); + removeBtn.doCommand(); + } + } +}); + +// Test selecting and removing partial sites +add_task(async function test_removePartialSites() { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + { + usage: 1024, + origin: "https://s3-us-west-2.amazonaws.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://127.0.0.1", + persisted: false, + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + let win = gBrowser.selectedBrowser.contentWindow; + let doc = gBrowser.selectedBrowser.contentDocument; + let frameDoc = null; + let saveBtn = null; + let cancelBtn = null; + let removeDialogOpenPromise = null; + let settingsDialogClosePromise = null; + + // Test the initial state + assertSitesListed(doc, hosts); + + // Test the "Cancel" button + settingsDialogClosePromise = promiseSettingsDialogClose(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + cancelBtn = frameDoc.querySelector("dialog").getButton("cancel"); + await removeSelectedSite(hosts.slice(0, 2)); + assertSitesListed(doc, hosts.slice(2)); + cancelBtn.doCommand(); + await settingsDialogClosePromise; + await openSiteDataSettingsDialog(); + assertSitesListed(doc, hosts); + + // Test the "Save Changes" button but canceling save + removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen( + "cancel", + REMOVE_DIALOG_URL + ); + settingsDialogClosePromise = promiseSettingsDialogClose(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + cancelBtn = frameDoc.querySelector("dialog").getButton("cancel"); + await removeSelectedSite(hosts.slice(0, 2)); + assertSitesListed(doc, hosts.slice(2)); + saveBtn.doCommand(); + await removeDialogOpenPromise; + cancelBtn.doCommand(); + await settingsDialogClosePromise; + await openSiteDataSettingsDialog(); + assertSitesListed(doc, hosts); + + // Test the "Save Changes" button and accepting save + removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen( + "accept", + REMOVE_DIALOG_URL + ); + settingsDialogClosePromise = promiseSettingsDialogClose(); + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + await removeSelectedSite(hosts.slice(0, 2)); + assertSitesListed(doc, hosts.slice(2)); + saveBtn.doCommand(); + await removeDialogOpenPromise; + await settingsDialogClosePromise; + await openSiteDataSettingsDialog(); + assertSitesListed(doc, hosts.slice(2)); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); + + function removeSelectedSite(removeHosts) { + frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let removeBtn = frameDoc.getElementById("removeSelected"); + is( + removeBtn.disabled, + true, + "Should start with disabled removeSelected button" + ); + let sitesList = frameDoc.getElementById("sitesList"); + removeHosts.forEach(host => { + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + if (site) { + site.click(); + let currentSelectedIndex = sitesList.selectedIndex; + is( + removeBtn.disabled, + false, + "Should enable the removeSelected button" + ); + removeBtn.doCommand(); + let newSelectedIndex = sitesList.selectedIndex; + if (currentSelectedIndex >= sitesList.itemCount) { + is(newSelectedIndex, currentSelectedIndex - 1); + } else { + is(newSelectedIndex, currentSelectedIndex); + } + } else { + ok(false, `Should not select and remove inexistent site of ${host}`); + } + }); + } +}); + +// Test searching and then removing only visible sites +add_task(async function () { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + // Search "foo" to only list foo.com sites + let win = gBrowser.selectedBrowser.contentWindow; + let doc = gBrowser.selectedBrowser.contentDocument; + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let searchBox = frameDoc.getElementById("searchBox"); + searchBox.value = "xyz"; + searchBox.doCommand(); + assertSitesListed( + doc, + hosts.filter(host => host.includes("xyz")) + ); + + // Test only removing all visible sites listed + updatePromise = promiseSiteDataManagerSitesUpdated(); + let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen( + "accept", + REMOVE_DIALOG_URL + ); + let settingsDialogClosePromise = promiseSettingsDialogClose(); + let removeAllBtn = frameDoc.getElementById("removeAll"); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + removeAllBtn.doCommand(); + saveBtn.doCommand(); + await acceptRemovePromise; + await settingsDialogClosePromise; + await updatePromise; + await openSiteDataSettingsDialog(); + assertSitesListed( + doc, + hosts.filter(host => !host.includes("xyz")) + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test dynamically clearing all site data +add_task(async function () { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + persisted: false, + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + + // Test the initial state + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + let doc = gBrowser.selectedBrowser.contentDocument; + assertSitesListed(doc, hosts); + + await addTestData([ + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + ]); + + // Test clearing all site data dynamically + let win = gBrowser.selectedBrowser.contentWindow; + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + updatePromise = promiseSiteDataManagerSitesUpdated(); + let acceptRemovePromise = BrowserTestUtils.promiseAlertDialogOpen("accept"); + let settingsDialogClosePromise = promiseSettingsDialogClose(); + let removeAllBtn = frameDoc.getElementById("removeAll"); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + removeAllBtn.doCommand(); + saveBtn.doCommand(); + await acceptRemovePromise; + await settingsDialogClosePromise; + await updatePromise; + await openSiteDataSettingsDialog(); + assertAllSitesNotListed(win); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Tests clearing search box content via backspace does not delete site data +add_task(async function () { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + ]); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await openSiteDataSettingsDialog(); + + let win = gBrowser.selectedBrowser.contentWindow; + let doc = gBrowser.selectedBrowser.contentDocument; + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let searchBox = frameDoc.getElementById("searchBox"); + searchBox.value = "xyz"; + searchBox.doCommand(); + assertSitesListed( + doc, + hosts.filter(host => host.includes("xyz")) + ); + + // Make sure the focus is on the search box + searchBox.focus(); + if (AppConstants.platform == "macosx") { + EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win); + } else { + EventUtils.synthesizeKey("VK_DELETE", {}, win); + } + assertSitesListed( + doc, + hosts.filter(host => host.includes("xyz")) + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Tests remove site data via backspace +add_task(async function () { + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + ]); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await openSiteDataSettingsDialog(); + + let win = gBrowser.selectedBrowser.contentWindow; + let doc = gBrowser.selectedBrowser.contentDocument; + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + // Test initial state + assertSitesListed(doc, hosts); + + let sitesList = frameDoc.getElementById("sitesList"); + let site = sitesList.querySelector(`richlistitem[host="xyz.com"]`); + if (site) { + // Move the focus from the search box to the list and select an item + sitesList.focus(); + site.click(); + if (AppConstants.platform == "macosx") { + EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win); + } else { + EventUtils.synthesizeKey("VK_DELETE", {}, win); + } + } + + assertSitesListed( + doc, + hosts.filter(host => !host.includes("xyz")) + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/tests/siteData/browser_siteData3.js b/browser/components/preferences/tests/siteData/browser_siteData3.js new file mode 100644 index 0000000000..9a4aa4cd0a --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser_siteData3.js @@ -0,0 +1,328 @@ +"use strict"; + +// Test not displaying sites which store 0 byte and don't have persistent storage. +add_task(async function test_exclusions() { + let hosts = await addTestData([ + { + usage: 0, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 0, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024, + origin: "http://email.bar.com", + persisted: false, + }, + { + usage: 0, + origin: "http://cookies.bar.com", + cookies: 5, + persisted: false, + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + let doc = gBrowser.selectedBrowser.contentDocument; + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + assertSitesListed( + doc, + hosts.filter(host => host != "shopping.xyz.com") + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test grouping and listing sites across scheme, port and origin attributes by base domain. +add_task(async function test_grouping() { + let quotaUsage = 7000000; + let testData = [ + { + usage: quotaUsage, + origin: "https://account.xyz.com^userContextId=1", + cookies: 2, + persisted: true, + }, + { + usage: quotaUsage, + origin: "https://account.xyz.com", + cookies: 1, + persisted: false, + }, + { + usage: quotaUsage, + origin: "https://account.xyz.com:123", + cookies: 1, + persisted: false, + }, + { + usage: quotaUsage, + origin: "http://account.xyz.com", + cookies: 1, + persisted: false, + }, + { + usage: quotaUsage, + origin: "http://search.xyz.com", + cookies: 3, + persisted: false, + }, + { + usage: quotaUsage, + origin: "http://advanced.search.xyz.com", + cookies: 3, + persisted: true, + }, + { + usage: quotaUsage, + origin: "http://xyz.com", + cookies: 1, + persisted: false, + }, + ]; + await addTestData(testData); + + let updatedPromise = promiseSiteDataManagerSitesUpdated(); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatedPromise; + await openSiteDataSettingsDialog(); + let win = gBrowser.selectedBrowser.contentWindow; + let dialogFrame = win.gSubDialog._topDialog._frame; + let frameDoc = dialogFrame.contentDocument; + + let siteItems = frameDoc.getElementsByTagName("richlistitem"); + is( + siteItems.length, + 1, + "Should group sites across scheme, port and origin attributes" + ); + + let columns = siteItems[0].querySelectorAll(".item-box > label"); + + let expected = "xyz.com"; + is(columns[0].value, expected, "Should group and list sites by host"); + + let cookieCount = testData.reduce((count, { cookies }) => count + cookies, 0); + is( + columns[1].value, + cookieCount.toString(), + "Should group cookies across scheme, port and origin attributes" + ); + + let [value, unit] = DownloadUtils.convertByteUnits(quotaUsage * 4); + let l10nAttributes = frameDoc.l10n.getAttributes(columns[2]); + is( + l10nAttributes.id, + "site-storage-persistent", + "Should show the site as persistent if one origin is persistent." + ); + // The shown quota can be slightly larger than the raw data we put in (though it should + // never be smaller), but that doesn't really matter to us since we only want to test that + // the site data dialog accumulates this into a single column. + Assert.greaterOrEqual( + parseFloat(l10nAttributes.args.value), + parseFloat(value), + "Should show the correct accumulated quota size." + ); + is( + l10nAttributes.args.unit, + unit, + "Should show the correct quota size unit." + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test sorting +add_task(async function test_sorting() { + let testData = [ + { + usage: 1024, + origin: "https://account.xyz.com", + cookies: 6, + persisted: true, + }, + { + usage: 1024 * 2, + origin: "https://books.foo.com", + cookies: 0, + persisted: false, + }, + { + usage: 1024 * 3, + origin: "http://cinema.bar.com", + cookies: 3, + persisted: true, + }, + { + usage: 1024 * 3, + origin: "http://vod.bar.com", + cookies: 2, + persisted: false, + }, + ]; + + await addTestData(testData); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + let dialog = content.gSubDialog._topDialog; + let dialogFrame = dialog._frame; + let frameDoc = dialogFrame.contentDocument; + let hostCol = frameDoc.getElementById("hostCol"); + let usageCol = frameDoc.getElementById("usageCol"); + let cookiesCol = frameDoc.getElementById("cookiesCol"); + let sitesList = frameDoc.getElementById("sitesList"); + + function getHostOrder() { + let siteItems = sitesList.getElementsByTagName("richlistitem"); + return Array.from(siteItems).map(item => item.getAttribute("host")); + } + + // Test default sorting by usage, descending. + Assert.deepEqual( + getHostOrder(), + ["bar.com", "foo.com", "xyz.com"], + "Has sorted descending by usage" + ); + + // Test sorting on the usage column + usageCol.click(); + Assert.deepEqual( + getHostOrder(), + ["xyz.com", "foo.com", "bar.com"], + "Has sorted ascending by usage" + ); + usageCol.click(); + Assert.deepEqual( + getHostOrder(), + ["bar.com", "foo.com", "xyz.com"], + "Has sorted descending by usage" + ); + + // Test sorting on the host column + hostCol.click(); + Assert.deepEqual( + getHostOrder(), + ["bar.com", "foo.com", "xyz.com"], + "Has sorted ascending by base domain" + ); + hostCol.click(); + Assert.deepEqual( + getHostOrder(), + ["xyz.com", "foo.com", "bar.com"], + "Has sorted descending by base domain" + ); + + // Test sorting on the cookies column + cookiesCol.click(); + Assert.deepEqual( + getHostOrder(), + ["foo.com", "bar.com", "xyz.com"], + "Has sorted ascending by cookies" + ); + cookiesCol.click(); + Assert.deepEqual( + getHostOrder(), + ["xyz.com", "bar.com", "foo.com"], + "Has sorted descending by cookies" + ); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); + +// Test single entry removal +add_task(async function test_single_entry_removal() { + let testData = await addTestData([ + { + usage: 1024, + origin: "https://xyz.com", + cookies: 6, + persisted: true, + }, + { + usage: 1024 * 3, + origin: "http://bar.com", + cookies: 2, + persisted: false, + }, + ]); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + let dialog = content.gSubDialog._topDialog; + let dialogFrame = dialog._frame; + let frameDoc = dialogFrame.contentDocument; + + let sitesList = frameDoc.getElementById("sitesList"); + let host = testData[0]; + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + sitesList.addItemToSelection(site); + frameDoc.getElementById("removeSelected").doCommand(); + let saveChangesButton = frameDoc.querySelector("dialog").getButton("accept"); + let dialogOpened = BrowserTestUtils.promiseAlertDialogOpen( + null, + REMOVE_DIALOG_URL + ); + setTimeout(() => saveChangesButton.doCommand(), 0); + let dialogWin = await dialogOpened; + let rootElement = dialogWin.document.getElementById( + "SiteDataRemoveSelectedDialog" + ); + is(rootElement.classList.length, 1, "There should only be one class set"); + is( + rootElement.classList[0], + "single-entry", + "The only class set should be single-entry (to hide the list)" + ); + let description = dialogWin.document.getElementById("removing-description"); + is( + description.getAttribute("data-l10n-id"), + "site-data-removing-single-desc", + "The description for single site should be selected" + ); + + let removalList = dialogWin.document.getElementById("removalList"); + is( + BrowserTestUtils.isVisible(removalList), + false, + "The removal list should be invisible" + ); + let removeButton = dialogWin.document + .querySelector("dialog") + .getButton("accept"); + let dialogClosed = BrowserTestUtils.waitForEvent(dialogWin, "unload"); + updatePromise = promiseSiteDataManagerSitesUpdated(); + removeButton.doCommand(); + await dialogClosed; + await updatePromise; + await openSiteDataSettingsDialog(); + + dialog = content.gSubDialog._topDialog; + dialogFrame = dialog._frame; + frameDoc = dialogFrame.contentDocument; + assertSitesListed(frameDoc, testData.slice(1)); + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js b/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js new file mode 100644 index 0000000000..5ce9d7e1e1 --- /dev/null +++ b/browser/components/preferences/tests/siteData/browser_siteData_multi_select.js @@ -0,0 +1,119 @@ +"use strict"; + +// Test selecting and removing partial sites +add_task(async function () { + await SiteDataTestUtils.clear(); + + let hosts = await addTestData([ + { + usage: 1024, + origin: "https://127.0.0.1", + persisted: false, + }, + { + usage: 1024 * 4, + origin: "http://cinema.bar.com", + persisted: true, + }, + { + usage: 1024 * 3, + origin: "http://email.bar.com", + persisted: false, + }, + { + usage: 1024 * 2, + origin: "https://s3-us-west-2.amazonaws.com", + persisted: true, + }, + { + usage: 1024 * 6, + origin: "https://account.xyz.com", + persisted: true, + }, + { + usage: 1024 * 5, + origin: "https://shopping.xyz.com", + persisted: false, + }, + { + usage: 1024 * 5, + origin: "https://example.com", + persisted: false, + }, + { + usage: 1024 * 5, + origin: "https://example.net", + persisted: false, + }, + ]); + + // Align the order of test hosts with the order of the site data table. + hosts.sort(); + + let updatePromise = promiseSiteDataManagerSitesUpdated(); + await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); + await updatePromise; + await openSiteDataSettingsDialog(); + + let doc = gBrowser.selectedBrowser.contentDocument; + + // Test the initial state + assertSitesListed(doc, hosts); + let win = gBrowser.selectedBrowser.contentWindow; + let frameDoc = win.gSubDialog._topDialog._frame.contentDocument; + let removeBtn = frameDoc.getElementById("removeSelected"); + is( + removeBtn.disabled, + true, + "Should start with disabled removeSelected button" + ); + + let hostCol = frameDoc.getElementById("hostCol"); + hostCol.click(); + + let removeDialogOpenPromise = BrowserTestUtils.promiseAlertDialogOpen( + "accept", + REMOVE_DIALOG_URL + ); + let settingsDialogClosePromise = promiseSettingsDialogClose(); + + // Select some sites to remove. + let sitesList = frameDoc.getElementById("sitesList"); + hosts.slice(0, 2).forEach(host => { + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + sitesList.addItemToSelection(site); + }); + + is(removeBtn.disabled, false, "Should enable the removeSelected button"); + removeBtn.doCommand(); + is(sitesList.selectedIndex, 0, "Should select next item"); + assertSitesListed(doc, hosts.slice(2)); + + // Select some other sites to remove with Delete. + hosts.slice(2, 4).forEach(host => { + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + sitesList.addItemToSelection(site); + }); + + is(removeBtn.disabled, false, "Should enable the removeSelected button"); + // Move the focus from the search box to the list + sitesList.focus(); + EventUtils.synthesizeKey("VK_DELETE"); + is(sitesList.selectedIndex, 0, "Should select next item"); + assertSitesListed(doc, hosts.slice(4)); + + updatePromise = promiseSiteDataManagerSitesUpdated(); + let saveBtn = frameDoc.querySelector("dialog").getButton("accept"); + saveBtn.doCommand(); + + await removeDialogOpenPromise; + await settingsDialogClosePromise; + + await updatePromise; + await openSiteDataSettingsDialog(); + + assertSitesListed(doc, hosts.slice(4)); + + await SiteDataTestUtils.clear(); + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/browser/components/preferences/tests/siteData/head.js b/browser/components/preferences/tests/siteData/head.js new file mode 100644 index 0000000000..2b8a9984a6 --- /dev/null +++ b/browser/components/preferences/tests/siteData/head.js @@ -0,0 +1,283 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_QUOTA_USAGE_HOST = "example.com"; +const TEST_QUOTA_USAGE_ORIGIN = "https://" + TEST_QUOTA_USAGE_HOST; +const TEST_QUOTA_USAGE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + TEST_QUOTA_USAGE_ORIGIN + ) + "/site_data_test.html"; +const TEST_OFFLINE_HOST = "example.org"; +const TEST_OFFLINE_ORIGIN = "https://" + TEST_OFFLINE_HOST; +const TEST_OFFLINE_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + TEST_OFFLINE_ORIGIN + ) + "/offline/offline.html"; +const TEST_SERVICE_WORKER_URL = + getRootDirectory(gTestPath).replace( + "chrome://mochitests/content", + TEST_OFFLINE_ORIGIN + ) + "/service_worker_test.html"; + +const REMOVE_DIALOG_URL = + "chrome://browser/content/preferences/dialogs/siteDataRemoveSelected.xhtml"; + +ChromeUtils.defineESModuleGetters(this, { + SiteDataTestUtils: "resource://testing-common/SiteDataTestUtils.sys.mjs", +}); + +XPCOMUtils.defineLazyServiceGetter( + this, + "serviceWorkerManager", + "@mozilla.org/serviceworkers/manager;1", + "nsIServiceWorkerManager" +); + +function promiseSiteDataManagerSitesUpdated() { + return TestUtils.topicObserved("sitedatamanager:sites-updated", () => true); +} + +function is_element_visible(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(!BrowserTestUtils.isHidden(aElement), aMsg); +} + +function is_element_hidden(aElement, aMsg) { + isnot(aElement, null, "Element should not be null, when checking visibility"); + ok(BrowserTestUtils.isHidden(aElement), aMsg); +} + +function promiseLoadSubDialog(aURL) { + return new Promise((resolve, reject) => { + content.gSubDialog._dialogStack.addEventListener( + "dialogopen", + function dialogopen(aEvent) { + if ( + aEvent.detail.dialog._frame.contentWindow.location == "about:blank" + ) { + return; + } + content.gSubDialog._dialogStack.removeEventListener( + "dialogopen", + dialogopen + ); + + is( + aEvent.detail.dialog._frame.contentWindow.location.toString(), + aURL, + "Check the proper URL is loaded" + ); + + // Check visibility + is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible"); + + // Check that stylesheets were injected + let expectedStyleSheetURLs = + aEvent.detail.dialog._injectedStyleSheets.slice(0); + for (let styleSheet of aEvent.detail.dialog._frame.contentDocument + .styleSheets) { + let i = expectedStyleSheetURLs.indexOf(styleSheet.href); + if (i >= 0) { + info("found " + styleSheet.href); + expectedStyleSheetURLs.splice(i, 1); + } + } + is( + expectedStyleSheetURLs.length, + 0, + "All expectedStyleSheetURLs should have been found" + ); + + // Wait for the next event tick to make sure the remaining part of the + // testcase runs after the dialog gets ready for input. + executeSoon(() => resolve(aEvent.detail.dialog._frame.contentWindow)); + } + ); + }); +} + +function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) { + return new Promise(resolve => { + let finalPrefPaneLoaded = TestUtils.topicObserved( + "sync-pane-loaded", + () => true + ); + gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank"); + openPreferences(aPane); + let newTabBrowser = gBrowser.selectedBrowser; + + newTabBrowser.addEventListener( + "Initialized", + function () { + newTabBrowser.contentWindow.addEventListener( + "load", + async function () { + let win = gBrowser.contentWindow; + let selectedPane = win.history.state; + await finalPrefPaneLoaded; + if (!aOptions || !aOptions.leaveOpen) { + gBrowser.removeCurrentTab(); + } + resolve({ selectedPane }); + }, + { once: true } + ); + }, + { capture: true, once: true } + ); + }); +} + +function openSiteDataSettingsDialog() { + let doc = gBrowser.selectedBrowser.contentDocument; + let settingsBtn = doc.getElementById("siteDataSettings"); + let dialogOverlay = content.gSubDialog._preloadDialog._overlay; + let dialogLoadPromise = promiseLoadSubDialog( + "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml" + ); + let dialogInitPromise = TestUtils.topicObserved( + "sitedata-settings-init", + () => true + ); + let fullyLoadPromise = Promise.all([ + dialogLoadPromise, + dialogInitPromise, + ]).then(() => { + is_element_visible(dialogOverlay, "The Settings dialog should be visible"); + }); + settingsBtn.doCommand(); + return fullyLoadPromise; +} + +function promiseSettingsDialogClose() { + return new Promise(resolve => { + let win = gBrowser.selectedBrowser.contentWindow; + let dialogOverlay = win.gSubDialog._topDialog._overlay; + let dialogWin = win.gSubDialog._topDialog._frame.contentWindow; + dialogWin.addEventListener( + "unload", + function unload() { + if ( + dialogWin.document.documentURI === + "chrome://browser/content/preferences/dialogs/siteDataSettings.xhtml" + ) { + is_element_hidden( + dialogOverlay, + "The Settings dialog should be hidden" + ); + resolve(); + } + }, + { once: true } + ); + }); +} + +function assertSitesListed(doc, hosts) { + let frameDoc = content.gSubDialog._topDialog._frame.contentDocument; + let removeAllBtn = frameDoc.getElementById("removeAll"); + let sitesList = frameDoc.getElementById("sitesList"); + let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length; + is(totalSitesNumber, hosts.length, "Should list the right sites number"); + hosts.forEach(host => { + let site = sitesList.querySelector(`richlistitem[host="${host}"]`); + ok(site, `Should list the site of ${host}`); + }); + is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button"); +} + +// Counter used by addTestData to generate unique cookie names across function +// calls. +let cookieID = 0; + +async function addTestData(data) { + let hosts = new Set(); + + for (let site of data) { + is( + typeof site.origin, + "string", + "Passed an origin string into addTestData." + ); + if (site.persisted) { + await SiteDataTestUtils.persist(site.origin); + } + + if (site.usage) { + await SiteDataTestUtils.addToIndexedDB(site.origin, site.usage); + } + + for (let i = 0; i < (site.cookies || 0); i++) { + SiteDataTestUtils.addToCookies({ + origin: site.origin, + name: `cookie${cookieID++}`, + }); + } + + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin( + site.origin + ); + + hosts.add(principal.baseDomain || principal.host); + } + + return Array.from(hosts); +} + +function promiseCookiesCleared() { + return TestUtils.topicObserved("cookie-changed", subj => { + return ( + subj.QueryInterface(Ci.nsICookieNotification).action == + Ci.nsICookieNotification.ALL_COOKIES_CLEARED + ); + }); +} + +async function loadServiceWorkerTestPage(url) { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); + await TestUtils.waitForCondition(() => { + return SpecialPowers.spawn( + tab.linkedBrowser, + [], + () => + content.document.body.getAttribute( + "data-test-service-worker-registered" + ) === "true" + ); + }, `Fail to load service worker test ${url}`); + BrowserTestUtils.removeTab(tab); +} + +function promiseServiceWorkersCleared() { + return TestUtils.waitForCondition(() => { + let serviceWorkers = serviceWorkerManager.getAllRegistrations(); + if (!serviceWorkers.length) { + ok(true, "Cleared all service workers"); + return true; + } + return false; + }, "Should clear all service workers"); +} + +function promiseServiceWorkerRegisteredFor(url) { + return TestUtils.waitForCondition(() => { + try { + let principal = + Services.scriptSecurityManager.createContentPrincipalFromOrigin(url); + let sw = serviceWorkerManager.getRegistrationByPrincipal( + principal, + principal.spec + ); + if (sw) { + ok(true, `Found the service worker registered for ${url}`); + return true; + } + } catch (e) {} + return false; + }, `Should register service worker for ${url}`); +} diff --git a/browser/components/preferences/tests/siteData/offline/manifest.appcache b/browser/components/preferences/tests/siteData/offline/manifest.appcache new file mode 100644 index 0000000000..a9287c64e6 --- /dev/null +++ b/browser/components/preferences/tests/siteData/offline/manifest.appcache @@ -0,0 +1,3 @@ +CACHE MANIFEST +# V1 +offline.html diff --git a/browser/components/preferences/tests/siteData/offline/offline.html b/browser/components/preferences/tests/siteData/offline/offline.html new file mode 100644 index 0000000000..f76b8a2bce --- /dev/null +++ b/browser/components/preferences/tests/siteData/offline/offline.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html manifest="manifest.appcache">> + <head> + <meta charset="utf-8"> + <meta http-equiv="Cache-Control" content="public" /> + <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1"> + + </head> + + <body> + <h1>Set up offline appcache Test</h1> + </body> +</html> diff --git a/browser/components/preferences/tests/siteData/service_worker_test.html b/browser/components/preferences/tests/siteData/service_worker_test.html new file mode 100644 index 0000000000..56f5173481 --- /dev/null +++ b/browser/components/preferences/tests/siteData/service_worker_test.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta http-equiv="Cache-Control" content="public" /> + <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1"> + + <title>Service Worker Test</title> + + </head> + + <body> + <h1>Service Worker Test</h1> + <script type="text/javascript"> + navigator.serviceWorker.register("service_worker_test.js") + .then(regis => document.body.setAttribute("data-test-service-worker-registered", "true")); + </script> + </body> +</html> diff --git a/browser/components/preferences/tests/siteData/service_worker_test.js b/browser/components/preferences/tests/siteData/service_worker_test.js new file mode 100644 index 0000000000..2aba167d18 --- /dev/null +++ b/browser/components/preferences/tests/siteData/service_worker_test.js @@ -0,0 +1 @@ +// empty worker, always succeed! diff --git a/browser/components/preferences/tests/siteData/site_data_test.html b/browser/components/preferences/tests/siteData/site_data_test.html new file mode 100644 index 0000000000..758106b0a5 --- /dev/null +++ b/browser/components/preferences/tests/siteData/site_data_test.html @@ -0,0 +1,29 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="utf-8"> + <meta http-equiv="Cache-Control" content="public" /> + <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1"> + + <title>Site Data Test</title> + + </head> + + <body> + <h1>Site Data Test</h1> + <script type="text/javascript"> + let request = indexedDB.open("TestDatabase", 1); + request.onupgradeneeded = function(e) { + let db = e.target.result; + db.createObjectStore("TestStore", { keyPath: "id" }); + }; + request.onsuccess = function(e) { + let db = e.target.result; + let tx = db.transaction("TestStore", "readwrite"); + let store = tx.objectStore("TestStore"); + tx.oncomplete = () => document.dispatchEvent(new CustomEvent("test-indexedDB-done", {bubbles: true, cancelable: false})); + store.put({ id: "test_id", description: "Site Data Test"}); + }; + </script> + </body> +</html> |