Bug 1323391 - Sort sites listed in Settings of Site Data, r=Gijs draft
authorFischer.json <fischer.json@gmail.com>
Mon, 19 Dec 2016 14:26:21 +0800
changeset 457226 c52b2300e2aa533a3dc50d70ff72ebf0a6c29370
parent 456716 a14094edbad78fc1d16e8d4c57902537cf286fd1
child 541429 5d116c4ea4cb52cfe06310c5bb54a16e282e60c2
push id40718
push userbmo:fliu@mozilla.com
push dateSat, 07 Jan 2017 07:17:28 +0000
reviewersGijs
bugs1323391
milestone53.0a1
Bug 1323391 - Sort sites listed in Settings of Site Data, r=Gijs MozReview-Commit-ID: ExHvd6OJNF7
browser/components/preferences/in-content/tests/browser_advanced_siteData.js
browser/components/preferences/siteDataSettings.js
browser/components/preferences/siteDataSettings.xul
--- a/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
+++ b/browser/components/preferences/in-content/tests/browser_advanced_siteData.js
@@ -28,16 +28,68 @@ const mockOfflineAppCacheHelper = {
     OfflineAppCacheHelper.clear = this.clear;
   },
 
   unregister() {
     OfflineAppCacheHelper.clear = this.originalClear;
   }
 };
 
+const mockSiteDataManager = {
+  sites: new Map([
+    [
+      "https://shopping.xyz.com/",
+      {
+        usage: 102400,
+        host: "shopping.xyz.com",
+        status: Ci.nsIPermissionManager.ALLOW_ACTION
+      }
+    ],
+    [
+      "https://music.bar.com/",
+      {
+        usage: 10240,
+        host: "music.bar.com",
+        status: Ci.nsIPermissionManager.ALLOW_ACTION
+      }
+    ],
+    [
+      "https://news.foo.com/",
+      {
+        usage: 1024,
+        host: "news.foo.com",
+        status: Ci.nsIPermissionManager.DENY_ACTION
+      }
+    ]
+  ]),
+
+  _originalGetSites: null,
+
+  getSites() {
+    let list = [];
+    this.sites.forEach((data, origin) => {
+      list.push({
+        usage: data.usage,
+        status: data.status,
+        uri: NetUtil.newURI(origin)
+      });
+    });
+    return Promise.resolve(list);
+  },
+
+  register() {
+    this._originalGetSites = SiteDataManager.getSites;
+    SiteDataManager.getSites = this.getSites.bind(this);
+  },
+
+  unregister() {
+    SiteDataManager.getSites = this._originalGetSites;
+  }
+};
+
 function addPersistentStoragePerm(origin) {
   let uri = NetUtil.newURI(origin);
   let principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
   Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
 }
 
 function getPersistentStoragePermStatus(origin) {
   let uri = NetUtil.newURI(origin);
@@ -86,17 +138,17 @@ registerCleanupFunction(function() {
 });
 
 add_task(function* () {
   yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   addPersistentStoragePerm(TEST_ORIGIN);
 
   yield BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_BASE_URL + "site_data_test.html");
   yield waitForEvent(gBrowser.selectedBrowser.contentWindow, "test-indexedDB-done");
-  gBrowser.removeCurrentTab();
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 
   yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
 
   // Test the initial states
   let cacheUsage = yield getCacheUsage();
   let quotaUsage = yield getQuotaUsage(TEST_ORIGIN);
   let totalUsage = yield SiteDataManager.getTotalUsage();
   Assert.greater(cacheUsage, 0, "The cache usage should not be 0");
@@ -146,10 +198,109 @@ add_task(function* () {
   cacheUsage = yield getCacheUsage();
   quotaUsage = yield getQuotaUsage(TEST_ORIGIN);
   totalUsage = yield SiteDataManager.getTotalUsage();
   is(cacheUsage, 0, "The cahce usage should be removed");
   is(quotaUsage, 0, "The quota usage should be removed");
   is(totalUsage, 0, "The total usage should be removed");
   // Test accepting "Clear All Data" ends
 
-  gBrowser.removeCurrentTab();
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
+
+add_task(function* () {
+  yield SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
+
+  mockSiteDataManager.register();
+  let updatePromise = promiseSitesUpdated();
+  yield openPreferencesViaOpenPreferencesAPI("advanced", "networkTab", { leaveOpen: true });
+  yield updatePromise;
+
+  // Open the siteDataSettings subdialog
+  let doc = gBrowser.selectedBrowser.contentDocument;
+  let settingsBtn = doc.getElementById("siteDataSettings");
+  let dialogOverlay = doc.getElementById("dialogOverlay");
+  let dialogPromise = promiseLoadSubDialog("chrome://browser/content/preferences/siteDataSettings.xul");
+  settingsBtn.doCommand();
+  yield dialogPromise;
+  is(dialogOverlay.style.visibility, "visible", "The dialog should be visible");
+
+  let dialogFrame = doc.getElementById("dialogFrame");
+  let frameDoc = dialogFrame.contentDocument;
+  let hostCol = frameDoc.getElementById("hostCol");
+  let usageCol = frameDoc.getElementById("usageCol");
+  let statusCol = frameDoc.getElementById("statusCol");
+  let sitesList = frameDoc.getElementById("sitesList");
+  let mockSites = mockSiteDataManager.sites;
+
+  // Test default sorting
+  assertSortByHost("ascending");
+
+  // Test sorting on the host column
+  hostCol.click();
+  assertSortByHost("descending");
+  hostCol.click();
+  assertSortByHost("ascending");
+
+  // Test sorting on the permission status column
+  statusCol.click();
+  assertSortByStatus("ascending");
+  statusCol.click();
+  assertSortByStatus("descending");
+
+  // Test sorting on the usage column
+  usageCol.click();
+  assertSortByUsage("ascending");
+  usageCol.click();
+  assertSortByUsage("descending");
+
+  mockSiteDataManager.unregister();
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  function assertSortByHost(order) {
+    let siteItems = sitesList.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aOrigin = siteItems[i].getAttribute("data-origin");
+      let bOrigin = siteItems[i + 1].getAttribute("data-origin");
+      let a = mockSites.get(aOrigin);
+      let b = mockSites.get(bOrigin);
+      let result = a.host.localeCompare(b.host);
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by host");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by host");
+      }
+    }
+  }
+
+  function assertSortByStatus(order) {
+    let siteItems = sitesList.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aOrigin = siteItems[i].getAttribute("data-origin");
+      let bOrigin = siteItems[i + 1].getAttribute("data-origin");
+      let a = mockSites.get(aOrigin);
+      let b = mockSites.get(bOrigin);
+      let result = a.status - b.status;
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by permission status");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by permission status");
+      }
+    }
+  }
+
+  function assertSortByUsage(order) {
+    let siteItems = sitesList.getElementsByTagName("richlistitem");
+    for (let i = 0; i < siteItems.length - 1; ++i) {
+      let aOrigin = siteItems[i].getAttribute("data-origin");
+      let bOrigin = siteItems[i + 1].getAttribute("data-origin");
+      let a = mockSites.get(aOrigin);
+      let b = mockSites.get(bOrigin);
+      let result = a.usage - b.usage;
+      if (order == "ascending") {
+        Assert.lessOrEqual(result, 0, "Should sort sites in the ascending order by usage");
+      } else {
+        Assert.greaterOrEqual(result, 0, "Should sort sites in the descending order by usage");
+      }
+    }
+  }
+});
+
--- a/browser/components/preferences/siteDataSettings.js
+++ b/browser/components/preferences/siteDataSettings.js
@@ -20,37 +20,78 @@ let gSiteDataSettings = {
   // - uri: uri of site; instance of nsIURI
   // - status: persistent-storage permission status
   // - usage: disk usage which site uses
   _sites: null,
 
   _list: null,
 
   init() {
+    function setEventListener(id, eventType, callback) {
+      document.getElementById(id)
+              .addEventListener(eventType, callback.bind(gSiteDataSettings));
+    }
+
     this._list = document.getElementById("sitesList");
     SiteDataManager.getSites().then(sites => {
       this._sites = sites;
-      this._sortSites(this._sites, "decending");
+      let sortCol = document.getElementById("hostCol");
+      this._sortSites(this._sites, sortCol);
       this._buildSitesList(this._sites);
     });
+
+    setEventListener("hostCol", "click", this.onClickTreeCol);
+    setEventListener("usageCol", "click", this.onClickTreeCol);
+    setEventListener("statusCol", "click", this.onClickTreeCol);
   },
 
   /**
-   * Sort sites by usages
-   *
    * @param sites {Array}
-   * @param order {String} indicate to sort in the "decending" or "ascending" order
+   * @param col {XULElement} the <treecol> being sorted on
    */
-  _sortSites(sites, order) {
-    sites.sort((a, b) => {
-      if (order === "ascending") {
-        return a.usage - b.usage;
-      }
-      return b.usage - a.usage;
+  _sortSites(sites, col) {
+    let isCurrentSortCol = col.getAttribute("data-isCurrentSortCol")
+    let sortDirection = col.getAttribute("data-last-sortDirection") || "ascending";
+    if (isCurrentSortCol) {
+      // Sort on the current column, flip the sorting direction
+      sortDirection = sortDirection === "ascending" ? "descending" : "ascending";
+    }
+
+    let sortFunc = null;
+    switch (col.id) {
+      case "hostCol":
+        sortFunc = (a, b) => {
+          let aHost = a.uri.host.toLowerCase();
+          let bHost = b.uri.host.toLowerCase();
+          return aHost.localeCompare(bHost);
+        }
+        break;
+
+      case "statusCol":
+        sortFunc = (a, b) => a.status - b.status;
+        break;
+
+      case "usageCol":
+        sortFunc = (a, b) => a.usage - b.usage;
+        break;
+    }
+    if (sortDirection === "descending") {
+      sites.sort((a, b) => sortFunc(b, a));
+    } else {
+      sites.sort(sortFunc);
+    }
+
+    let cols = this._list.querySelectorAll("treecol");
+    cols.forEach(c => {
+      c.removeAttribute("sortDirection");
+      c.removeAttribute("data-isCurrentSortCol");
     });
+    col.setAttribute("data-isCurrentSortCol", true);
+    col.setAttribute("sortDirection", sortDirection);
+    col.setAttribute("data-last-sortDirection", sortDirection);
   },
 
   _buildSitesList(sites) {
     // Clear old entries.
     while (this._list.childNodes.length > 1) {
       this._list.removeChild(this._list.lastChild);
     }
 
@@ -60,10 +101,15 @@ let gSiteDataSettings = {
       let size = DownloadUtils.convertByteUnits(data.usage);
       let item = document.createElement("richlistitem");
       item.setAttribute("data-origin", data.uri.spec);
       item.setAttribute("host", data.uri.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);
   }
 };
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -23,16 +23,16 @@
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <vbox flex="1">
     <description>&settings.description;</description>
     <separator class="thin"/>
 
     <richlistbox id="sitesList" orient="vertical" flex="1">
       <listheader>
-        <treecol flex="4" width="50" label="&hostCol.label;"/>
-        <treecol flex="2" width="50" label="&statusCol.label;"/>
-        <treecol flex="1" width="50" label="&usageCol.label;"/>
+        <treecol flex="4" width="50" label="&hostCol.label;" id="hostCol"/>
+        <treecol flex="2" width="50" label="&statusCol.label;" id="statusCol"/>
+        <treecol flex="1" width="50" label="&usageCol.label;" id="usageCol"/>
       </listheader>
     </richlistbox>
   </vbox>
 
 </window>