Bug 1312380 - Should be able to remove data of all sites visible on the list in Settings of Site Data draft
authorFischer.json <fischer.json@gmail.com>
Tue, 31 Jan 2017 21:34:08 +0800
changeset 468429 833711d2e29f22674d79d0f51b7025de2b1e5e10
parent 468403 adab5d5d0372d1a26685d6fbc59cdfc977ad76c6
child 543955 53378b54a00c4f04f73a27214ced3d32efb3e05c
push id43471
push userbmo:fliu@mozilla.com
push dateTue, 31 Jan 2017 14:49:04 +0000
bugs1312380
milestone54.0a1
Bug 1312380 - Should be able to remove data of all sites visible on the list in Settings of Site Data MozReview-Commit-ID: 5hkmYLGGkue
browser/components/preferences/in-content/tests/browser_advanced_siteData.js
browser/components/preferences/siteDataSettings.js
browser/components/preferences/siteDataSettings.xul
browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
modules/libpref/init/all.js
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -7,16 +7,17 @@ const { classes: Cc, interfaces: Ci, uti
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 /* import-globals-from ../../../../../testing/modules/sinon-1.16.1.js */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-1.16.1.js");
 
 const TEST_HOST = "example.com";
 const TEST_ORIGIN = "http://" + TEST_HOST;
 const TEST_BASE_URL = TEST_ORIGIN + "/browser/browser/components/preferences/in-content/tests/";
+const REMOVE_DIALOG_URL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
 
 const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const { SiteDataManager } = Cu.import("resource:///modules/SiteDataManager.jsm", {});
 const { OfflineAppCacheHelper } = Cu.import("resource:///modules/offlineAppCache.jsm", {});
 
 const mockOfflineAppCacheHelper = {
   clear: null,
 
@@ -182,16 +183,31 @@ function promiseSitesUpdated() {
 }
 
 function promiseCookiesCleared() {
   return TestUtils.topicObserved("cookie-changed", (subj, data) => {
     return data === "cleared";
   });
 }
 
+function assertSitesListed(doc, origins) {
+  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let removeBtn = frameDoc.getElementById("removeSelected");
+  let removeAllBtn = frameDoc.getElementById("removeAll");
+  let sitesList = frameDoc.getElementById("sitesList");
+  let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
+  is(totalSitesNumber, origins.length, "Should list the right sites number");
+  origins.forEach(origin => {
+    let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
+    ok(site instanceof XULElement, `Should list the site of ${origin}`);
+  });
+  is(removeBtn.disabled, false, "Should enable the removeSelected button");
+  is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
+}
+
 registerCleanupFunction(function() {
   delete window.sinon;
   delete window.setImmediate;
   delete window.clearImmediate;
   mockOfflineAppCacheHelper.unregister();
 });
 
 add_task(function* () {
@@ -365,38 +381,28 @@ add_task(function* () {
 
   let doc = gBrowser.selectedBrowser.contentDocument;
   let frameDoc = doc.getElementById("dialogFrame").contentDocument;
   let searchBox = frameDoc.getElementById("searchBox");
   let mockOrigins = Array.from(mockSiteDataManager.sites.keys());
 
   searchBox.value = "xyz";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins.filter(o => o.includes("xyz")));
+  assertSitesListed(doc, mockOrigins.filter(o => o.includes("xyz")));
 
   searchBox.value = "bar";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins.filter(o => o.includes("bar")));
+  assertSitesListed(doc, mockOrigins.filter(o => o.includes("bar")));
 
   searchBox.value = "";
   searchBox.doCommand();
-  assertSitesListed(mockOrigins);
+  assertSitesListed(doc, mockOrigins);
 
   mockSiteDataManager.unregister();
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
-
-  function assertSitesListed(origins) {
-    let sitesList = frameDoc.getElementById("sitesList");
-    let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
-    is(totalSitesNumber, origins.length, "Should list the right sites number");
-    origins.forEach(origin => {
-      let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
-      ok(site instanceof XULElement, `Should list the site of ${origin}`);
-    });
-  }
 });
 
 // Test selecting and removing all sites one by one
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   let fakeOrigins = [
     "https://news.foo.com/",
     "https://mails.bar.com/",
@@ -473,29 +479,33 @@ add_task(function* () {
       sites[i].click();
       removeBtn.doCommand();
     }
   }
 
   function assertAllSitesListed() {
     frameDoc = doc.getElementById("dialogFrame").contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
+    let removeAllBtn = frameDoc.getElementById("removeAll");
     let sitesList = frameDoc.getElementById("sitesList");
     let sites = sitesList.getElementsByTagName("richlistitem");
     is(sites.length, fakeOrigins.length, "Should list all sites");
     is(removeBtn.disabled, false, "Should enable the removeSelected button");
+    is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
   }
 
   function assertAllSitesNotListed() {
     frameDoc = doc.getElementById("dialogFrame").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 partial sites
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   let fakeOrigins = [
     "https://news.foo.com/",
@@ -507,63 +517,62 @@ add_task(function* () {
   ];
   fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
 
   let updatePromise = promiseSitesUpdated();
   yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
   yield updatePromise;
   yield openSettingsDialog();
 
-  const removeDialogURL = "chrome://browser/content/preferences/siteDataRemoveSelected.xul";
   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(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Cancel" button
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   cancelBtn = frameDoc.getElementById("cancel");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   cancelBtn.doCommand();
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Save Changes" button but canceling save
-  removeDialogOpenPromise = promiseWindowDialogOpen("cancel", removeDialogURL);
+  removeDialogOpenPromise = promiseWindowDialogOpen("cancel", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   saveBtn.doCommand();
   yield removeDialogOpenPromise;
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins);
+  assertSitesListed(doc, fakeOrigins);
 
   // Test the "Save Changes" button and accepting save
-  removeDialogOpenPromise = promiseWindowDialogOpen("accept", removeDialogURL);
+  removeDialogOpenPromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
   settingsDialogClosePromise = promiseSettingsDialogClose();
   frameDoc = doc.getElementById("dialogFrame").contentDocument;
   saveBtn = frameDoc.getElementById("save");
   removeSelectedSite(fakeOrigins.slice(0, 4));
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
   saveBtn.doCommand();
   yield removeDialogOpenPromise;
   yield settingsDialogClosePromise;
   yield openSettingsDialog();
-  assertSitesListed(fakeOrigins.slice(4));
+  assertSitesListed(doc, fakeOrigins.slice(4));
 
   // Always clean up the fake origins
   fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   function removeSelectedSite(origins) {
     frameDoc = doc.getElementById("dialogFrame").contentDocument;
     let removeBtn = frameDoc.getElementById("removeSelected");
@@ -573,22 +582,51 @@ add_task(function* () {
       if (site) {
         site.click();
         removeBtn.doCommand();
       } else {
         ok(false, `Should not select and remove inexisted site of ${origin}`);
       }
     });
   }
+});
 
-  function assertSitesListed(origins) {
-    frameDoc = doc.getElementById("dialogFrame").contentDocument;
-    let removeBtn = frameDoc.getElementById("removeSelected");
-    let sitesList = frameDoc.getElementById("sitesList");
-    let totalSitesNumber = sitesList.getElementsByTagName("richlistitem").length;
-    is(totalSitesNumber, origins.length, "Should list the right sites number");
-    origins.forEach(origin => {
-      let site = sitesList.querySelector(`richlistitem[data-origin="${origin}"]`);
-      ok(!!site, `Should list the site of ${origin}`);
-    });
-    is(removeBtn.disabled, false, "Should enable the removeSelected button");
-  }
+add_task(function* () {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+  let fakeOrigins = [
+    "https://news.foo.com/",
+    "https://books.foo.com/",
+    "https://mails.bar.com/",
+    "https://account.bar.com/",
+    "https://videos.xyz.com/",
+    "https://shopping.xyz.com/"
+  ];
+  fakeOrigins.forEach(origin => addPersistentStoragePerm(origin));
+
+  let updatePromise = promiseSitesUpdated();
+  yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+  yield updatePromise;
+  yield openSettingsDialog();
+
+  // Search "foo" to only list foo.com sites
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let frameDoc = doc.getElementById("dialogFrame").contentDocument;
+  let searchBox = frameDoc.getElementById("searchBox");
+  searchBox.value = "foo";
+  searchBox.doCommand();
+  assertSitesListed(doc, fakeOrigins.slice(0, 2));
+
+  // Test only removing all visible sites listed
+  let acceptRemovePromise = promiseWindowDialogOpen("accept", REMOVE_DIALOG_URL);
+  let settingsDialogClosePromise = promiseSettingsDialogClose();
+  let removeAllBtn = frameDoc.getElementById("removeAll");
+  let saveBtn = frameDoc.getElementById("save");
+  removeAllBtn.doCommand();
+  saveBtn.doCommand();
+  yield acceptRemovePromise;
+  yield settingsDialogClosePromise;
+  yield openSettingsDialog();
+  assertSitesListed(doc, fakeOrigins.slice(2));
+
+  // Always clean up the fake origins
+  fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
+  gBrowser.removeCurrentTab();
 });
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -35,33 +35,35 @@ let gSiteDataSettings = {
 
     this._list = document.getElementById("sitesList");
     this._searchBox = document.getElementById("searchBox");
     SiteDataManager.getSites().then(sites => {
       this._sites = sites;
       let sortCol = document.getElementById("hostCol");
       this._sortSites(this._sites, sortCol);
       this._buildSitesList(this._sites);
-      this._updateButtonsState();
       Services.obs.notifyObservers(null, "sitedata-settings-init", null);
     });
 
     setEventListener("hostCol", "click", this.onClickTreeCol);
     setEventListener("usageCol", "click", this.onClickTreeCol);
     setEventListener("statusCol", "click", this.onClickTreeCol);
-    setEventListener("searchBox", "command", this.onCommandSearch);
     setEventListener("cancel", "command", this.close);
     setEventListener("save", "command", this.saveChanges);
-    setEventListener("removeSelected", "command", this.removeSelected);
+    setEventListener("searchBox", "command", this.onCommandSearch);
+    setEventListener("removeAll", "command", this.onClickRemoveAll);
+    setEventListener("removeSelected", "command", this.onClickRemoveSelected);
   },
 
   _updateButtonsState() {
     let items = this._list.getElementsByTagName("richlistitem");
-    let removeBtn = document.getElementById("removeSelected");
-    removeBtn.disabled = !(items.length > 0);
+    let removeSelectedBtn = document.getElementById("removeSelected");
+    let removeAllBtn = document.getElementById("removeAll");
+    removeSelectedBtn.disabled = !(items.length > 0);
+    removeAllBtn.disabled = removeSelectedBtn.disabled;
   },
 
   /**
    * @param sites {Array}
    * @param col {XULElement} the <treecol> being sorted on
    */
   _sortSites(sites, col) {
     let isCurrentSortCol = col.getAttribute("data-isCurrentSortCol")
@@ -131,40 +133,32 @@ let gSiteDataSettings = {
       let size = DownloadUtils.convertByteUnits(data.usage);
       let item = document.createElement("richlistitem");
       item.setAttribute("data-origin", data.uri.spec);
       item.setAttribute("host", host);
       item.setAttribute("status", prefStrBundle.getString(statusStrId));
       item.setAttribute("usage", prefStrBundle.getFormattedString("siteUsage", size));
       this._list.appendChild(item);
     }
-  },
-
-  onClickTreeCol(e) {
-    this._sortSites(this._sites, e.target);
-    this._buildSitesList(this._sites);
+    this._updateButtonsState();
   },
 
-  onCommandSearch() {
-    this._buildSitesList(this._sites);
-  },
-
-  removeSelected() {
-    let selected = this._list.selectedItem;
-    if (selected) {
-      let origin = selected.getAttribute("data-origin");
+  _removeSiteItems(items) {
+    for (let i = items.length - 1; i >= 0; --i) {
+      let item = items[i];
+      let origin = item.getAttribute("data-origin");
       for (let site of this._sites) {
         if (site.uri.spec === origin) {
           site.userAction = "remove";
           break;
         }
       }
-      this._list.removeChild(selected);
-      this._updateButtonsState();
+      item.remove();
     }
+    this._updateButtonsState();
   },
 
   saveChanges() {
     let allowed = true;
 
     // Confirm user really wants to remove site data starts
     let removals = [];
     this._sites = this._sites.filter(site => {
@@ -230,10 +224,33 @@ let gSiteDataSettings = {
     }
     // Confirm user really wants to remove site data ends
 
     this.close();
   },
 
   close() {
     window.close();
+  },
+
+  onClickTreeCol(e) {
+    this._sortSites(this._sites, e.target);
+    this._buildSitesList(this._sites);
+  },
+
+  onCommandSearch() {
+    this._buildSitesList(this._sites);
+  },
+
+  onClickRemoveSelected() {
+    let selected = this._list.selectedItem;
+    if (selected) {
+      this._removeSiteItems([selected]);
+    }
+  },
+
+  onClickRemoveAll() {
+    let siteItems = this._list.getElementsByTagName("richlistitem");
+    if (siteItems.length > 0) {
+      this._removeSiteItems(siteItems);
+    }
   }
 };
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -39,16 +39,17 @@
         <treecol flex="2" width="50" label="&statusCol.label;" id="statusCol"/>
         <treecol flex="1" width="50" label="&usageCol.label;" id="usageCol"/>
       </listheader>
     </richlistbox>
   </vbox>
 
   <hbox align="start">
     <button id="removeSelected" label="&removeSelected.label;" accesskey="&removeSelected.accesskey;"/>
+    <button id="removeAll" label="&removeAll.label;" accesskey="&removeAll.accesskey;"/>
   </hbox>
 
   <vbox align="end">
     <hbox>
         <button id="cancel" label="&cancel.label;" accesskey="&cancel.accesskey;"/>
         <button id="save" label="&save.label;" accesskey="&save.accesskey;"/>
     </hbox>
   </vbox>
--- a/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/siteDataSettings.dtd
@@ -6,15 +6,17 @@
 <!ENTITY     settings.description          "The following websites asked to store site data in your disk. You can specify which websites are allowed to store site data. Default site data is temporary and could be deleted automatically.">
 <!ENTITY     hostCol.label                 "Site">
 <!ENTITY     statusCol.label               "Status">
 <!ENTITY     usageCol.label                "Storage">
 <!ENTITY     search.label                  "Search:">
 <!ENTITY     search.accesskey              "S">
 <!ENTITY     removeSelected.label          "Remove Selected">
 <!ENTITY     removeSelected.accesskey      "r">
+<!ENTITY     removeAll.label               "Remove All">
+<!ENTITY     removeAll.accesskey           "e">
 <!ENTITY     save.label                    "Save Changes">
 <!ENTITY     save.accesskey                "a">
 <!ENTITY     cancel.label                  "Cancel">
 <!ENTITY     cancel.accesskey              "C">
 <!ENTITY     removingDialog.title          "Removing Site Data">
 <!ENTITY     removingDialog.description    "Removing site data will also remove cookies. This may log you out of websites and remove offline web content. Are you sure you want to make the changes?">
 <!ENTITY     siteTree.label                "The following website cookies will be removed:">
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5574,13 +5574,14 @@ pref("dom.storageManager.enabled", false
 // a single web page in a row, all following authentication dialogs will
 // be blocked (automatically canceled) for that page. The counter resets
 // when the page is reloaded. To turn this feature off, just set the limit to 0.
 pref("prompts.authentication_dialog_abuse_limit", 3);
 
 // Enable the Storage management in about:preferences and persistent-storage permission request
 // To enable the DOM implementation, turn on "dom.storageManager.enabled"
 pref("browser.storageManager.enabled", false);
+
 pref("dom.IntersectionObserver.enabled", false);
 
 #ifdef FUZZING
 pref("fuzzing.enabled", false);
 #endif