Bug 1312380 - Should be able to remove data of all sites visible on the list in Settings of Site Data, r=jaws draft
authorFischer.json <fischer.json@gmail.com>
Tue, 31 Jan 2017 21:34:08 +0800
changeset 480291 be057a9fa096ccfa217738dbef4fd432a48f73a8
parent 480238 f4f374622111022d41dd8d5eb9220624135c534a
child 544916 65c8b84fdd9788b1d807b929103ea5824adc3c6a
push id44505
push userbmo:fliu@mozilla.com
push dateWed, 08 Feb 2017 03:23:41 +0000
reviewersjaws
bugs1312380
milestone54.0a1
Bug 1312380 - Should be able to remove data of all sites visible on the list in Settings of Site Data, r=jaws 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
--- 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,32 @@ 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}"]`);
+    let host = site.getAttribute("host");
+    ok(origin.includes(host), `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 +382,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 +480,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 +518,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 +583,53 @@ 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
+  updatePromise = promiseSitesUpdated();
+  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 updatePromise;
+  yield openSettingsDialog();
+  assertSitesListed(doc, fakeOrigins.slice(2));
+
+  // Always clean up the fake origins
+  fakeOrigins.forEach(origin => removePersistentStoragePerm(origin));
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- 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:">