summaryrefslogtreecommitdiffstats
path: root/browser/components/preferences/tests/siteData
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/preferences/tests/siteData')
-rw-r--r--browser/components/preferences/tests/siteData/browser.ini22
-rw-r--r--browser/components/preferences/tests/siteData/browser_clearSiteData.js219
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData.js400
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData2.js475
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData3.js327
-rw-r--r--browser/components/preferences/tests/siteData/browser_siteData_multi_select.js119
-rw-r--r--browser/components/preferences/tests/siteData/head.js280
-rw-r--r--browser/components/preferences/tests/siteData/offline/manifest.appcache3
-rw-r--r--browser/components/preferences/tests/siteData/offline/offline.html13
-rw-r--r--browser/components/preferences/tests/siteData/service_worker_test.html19
-rw-r--r--browser/components/preferences/tests/siteData/service_worker_test.js1
-rw-r--r--browser/components/preferences/tests/siteData/site_data_test.html29
12 files changed, 1907 insertions, 0 deletions
diff --git a/browser/components/preferences/tests/siteData/browser.ini b/browser/components/preferences/tests/siteData/browser.ini
new file mode 100644
index 0000000000..deb8e82f7d
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser.ini
@@ -0,0 +1,22 @@
+[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]
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_siteData2.js]
+skip-if =
+ win10_2004 && (!debug && !asan) # Bug 1669937
+ win11_2009 && (!debug && !asan) # Bug 1797751
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+ 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..217656707e
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_clearSiteData.js
@@ -0,0 +1,219 @@
+/* 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"
+);
+
+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 dialogOpened = promiseLoadSubDialog(
+ "chrome://browser/content/preferences/dialogs/clearSiteData.xhtml"
+ );
+ 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 clearSiteDataCheckbox =
+ dialogWin.document.getElementById("clearSiteData");
+ let clearCacheCheckbox = dialogWin.document.getElementById("clearCache");
+ // 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;
+
+ // Some additional promises/assertions to wait for
+ // when deleting site data.
+ let acceptPromise;
+ let updatePromise;
+ let cookiesClearedPromise;
+ if (clearSiteData) {
+ 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) {
+ 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..d3a73d6f4b
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData.js
@@ -0,0 +1,400 @@
+/* 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.clearStoragesForPrincipal(
+ principal,
+ null,
+ null,
+ true
+ );
+ 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..58aa5bf1b9
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/browser_siteData3.js
@@ -0,0 +1,327 @@
+"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.
+ ok(
+ 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.is_visible(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..01f56d879d
--- /dev/null
+++ b/browser/components/preferences/tests/siteData/head.js
@@ -0,0 +1,280 @@
+/* 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.is_hidden(aElement), aMsg);
+}
+
+function is_element_hidden(aElement, aMsg) {
+ isnot(aElement, null, "Element should not be null, when checking visibility");
+ ok(BrowserTestUtils.is_hidden(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, data) => {
+ return data === "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>