Bug 1313003 - Add Site Data section into Network of Advanced of about:preferences, r=jaws
authorFischer.json <fischer.json@gmail.com>
Thu, 03 Nov 2016 17:34:39 +0800
changeset 442487 e2e6505f3fc07f7c56d56027b3fad0c08c3c8794
parent 442486 099d20dd67459abc9d3140900e6d2bda59dfdf01
child 442488 1f8649133d7c5ec32721004583858dfff667caea
push id36705
push useraschen@mozilla.com
push dateTue, 22 Nov 2016 15:05:57 +0000
reviewersjaws
bugs1313003
milestone53.0a1
Bug 1313003 - Add Site Data section into Network of Advanced of about:preferences, r=jaws MozReview-Commit-ID: KVUs8FTftxY
browser/components/preferences/SiteDataManager.jsm
browser/components/preferences/in-content/advanced.js
browser/components/preferences/in-content/advanced.xul
browser/components/preferences/moz.build
browser/locales/en-US/chrome/browser/preferences/advanced.dtd
browser/locales/en-US/chrome/browser/preferences/preferences.properties
modules/libpref/init/all.js
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/SiteDataManager.jsm
@@ -0,0 +1,154 @@
+"use strict";
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = [
+  "SiteDataManager"
+];
+
+this.SiteDataManager = {
+
+  _qms: Services.qms,
+
+  _diskCache: Services.cache2.diskCacheStorage(Services.loadContextInfo.default, false),
+
+  _appCache: Cc["@mozilla.org/network/application-cache-service;1"].getService(Ci.nsIApplicationCacheService),
+
+  // A Map of sites using the persistent-storage API (have requested persistent-storage permission)
+  // Key is site's origin.
+  // Value is one object holding:
+  //   - perm: persistent-storage permision; instance of nsIPermission
+  //   - status: the permission granted/rejected status
+  //   - quotaUsage: the usage of indexedDB and localStorage.
+  //   - appCacheList: an array of app cache; instances of nsIApplicationCache
+  //   - diskCacheList: an array. Each element is object holding metadata of http cache:
+  //       - dataSize: that http cache size
+  //       - idEnhance: the id extension of that http cache
+  _sites: new Map(),
+
+  _updateQuotaPromise: null,
+
+  _updateDiskCachePromise: null,
+
+  _quotaUsageRequests: null,
+
+  updateSites() {
+    // Clear old data and requests first
+    this._sites.clear();
+    this._cancelQuotaUpdate();
+
+    // Collect sites granted/rejected with the persistent-storage permission
+    let perm = null;
+    let status = null;
+    let e = Services.perms.enumerator;
+    while (e.hasMoreElements()) {
+      perm = e.getNext();
+      status = Services.perms.testExactPermissionFromPrincipal(perm.principal, "persistent-storage");
+      if (status === Ci.nsIPermissionManager.ALLOW_ACTION ||
+          status === Ci.nsIPermissionManager.DENY_ACTION) {
+        this._sites.set(perm.principal.origin, {
+          perm: perm,
+          status: status,
+          quotaUsage: 0,
+          appCacheList: [],
+          diskCacheList: []
+        });
+      }
+    }
+
+    this._updateQuota();
+    this._updateAppCache();
+    this._updateDiskCache();
+  },
+
+  _updateQuota() {
+    this._quotaUsageRequests = [];
+    let promises = [];
+    for (let [key, site] of this._sites) { // eslint-disable-line no-unused-vars
+      promises.push(new Promise(resolve => {
+        let callback = {
+          onUsageResult: function(request) {
+            site.quotaUsage = request.usage;
+            resolve();
+          }
+        };
+        // XXX: The work of integrating localStorage into Quota Manager is in progress.
+        //      After the bug 742822 and 1286798 landed, localStorage usage will be included.
+        //      So currently only get indexedDB usage.
+        this._quotaUsageRequests.push(
+          this._qms.getUsageForPrincipal(site.perm.principal, callback));
+      }));
+    }
+    this._updateQuotaPromise = Promise.all(promises);
+  },
+
+  _cancelQuotaUpdate() {
+    if (this._quotaUsageRequests) {
+      for (let request of this._quotaUsageRequests) {
+        request.cancel();
+      }
+      this._quotaUsageRequests = null;
+    }
+  },
+
+  _updateAppCache() {
+    let groups = this._appCache.getGroups();
+    for (let [key, site] of this._sites) { // eslint-disable-line no-unused-vars
+      for (let group of groups) {
+        let uri = Services.io.newURI(group, null, null);
+        if (site.perm.matchesURI(uri, true)) {
+          let cache = this._appCache.getActiveCache(group);
+          site.appCacheList.push(cache);
+        }
+      }
+    }
+  },
+
+  _updateDiskCache() {
+    this._updateDiskCachePromise = new Promise(resolve => {
+      if (this._sites.size) {
+        let sites = this._sites;
+        let visitor = {
+          onCacheEntryInfo: function(uri, idEnhance, dataSize) {
+            for (let [key, site] of sites) { // eslint-disable-line no-unused-vars
+              if (site.perm.matchesURI(uri, true)) {
+                site.diskCacheList.push({
+                  dataSize,
+                  idEnhance
+                });
+                break;
+              }
+            }
+          },
+          onCacheEntryVisitCompleted: function() {
+            resolve();
+          }
+        };
+        this._diskCache.asyncVisitStorage(visitor, true);
+      } else {
+        resolve();
+      }
+    });
+  },
+
+  getTotalUsage() {
+    return Promise.all([this._updateQuotaPromise, this._updateDiskCachePromise])
+                  .then(() => {
+                    let usage = 0;
+                    for (let [key, site] of this._sites) { // eslint-disable-line no-unused-vars
+                      let cache = null;
+                      for (cache of site.appCacheList) {
+                        usage += cache.usage;
+                      }
+                      for (cache of site.diskCacheList) {
+                        usage += cache.dataSize;
+                      }
+                      usage += site.quotaUsage;
+                    }
+                    return usage;
+                  });
+  },
+};
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -2,16 +2,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Load DownloadUtils module for convertByteUnits
 Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
 Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "SiteDataManager",
+                                  "resource:///modules/SiteDataManager.jsm");
+
 const PREF_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
 
 var gAdvancedPane = {
   _inited: false,
 
   /**
    * Brings the appropriate tab to the front and initializes various bits of UI.
    */
@@ -47,16 +50,21 @@ var gAdvancedPane = {
     if (AppConstants.MOZ_TELEMETRY_REPORTING) {
       this.initSubmitHealthReport();
     }
     this.updateOnScreenKeyboardVisibility();
     this.updateCacheSizeInputField();
     this.updateActualCacheSize();
     this.updateActualAppCacheSize();
 
+    if (Services.prefs.getBoolPref("browser.storageManager.enabled")) {
+      SiteDataManager.updateSites();
+      this.updateTotalSiteDataSize();
+    }
+
     setEventListener("layers.acceleration.disabled", "change",
                      gAdvancedPane.updateHardwareAcceleration);
     setEventListener("advancedPrefs", "select",
                      gAdvancedPane.tabSelectionChanged);
     if (AppConstants.MOZ_TELEMETRY_REPORTING) {
       setEventListener("submitHealthReportBox", "command",
                        gAdvancedPane.updateSubmitHealthReport);
     }
@@ -324,16 +332,28 @@ var gAdvancedPane = {
   /**
    * Displays a dialog in which proxy settings may be changed.
    */
   showConnections: function()
   {
     gSubDialog.open("chrome://browser/content/preferences/connection.xul");
   },
 
+  updateTotalSiteDataSize: function() {
+    SiteDataManager.getTotalUsage()
+      .then(usage => {
+        let size = DownloadUtils.convertByteUnits(usage);
+        let prefStrBundle = document.getElementById("bundlePreferences");
+        let totalSiteDataSizeLabel = document.getElementById("totalSiteDataSize");
+        totalSiteDataSizeLabel.textContent = prefStrBundle.getFormattedString("totalSiteDataSize", size);
+        let siteDataGroup = document.getElementById("siteDataGroup");
+        siteDataGroup.hidden = false;
+      });
+  },
+
   // Retrieves the amount of space currently used by disk cache
   updateActualCacheSize: function()
   {
     var actualSizeLabel = document.getElementById("actualDiskCacheSize");
     var prefStrBundle = document.getElementById("bundlePreferences");
 
     // Needs to root the observer since cache service keeps only a weak reference.
     this.observer = {
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -323,16 +323,25 @@
           <vbox pack="end">
             <button id="offlineAppsListRemove"
                     disabled="true"
                     label="&offlineAppsListRemove.label;"
                     accesskey="&offlineAppsListRemove.accesskey;"/>
           </vbox>
         </hbox>
       </groupbox>
+
+      <!-- Site Data -->
+      <groupbox id="siteDataGroup" hidden="true">
+        <caption><label>&siteData.label;</label></caption>
+
+        <hbox align="center">
+          <label id="totalSiteDataSize" flex="1"></label>
+        </hbox>
+      </groupbox>
     </tabpanel>
 
     <!-- Update -->
     <tabpanel id="updatePanel" orient="vertical">
 #ifdef MOZ_UPDATER
       <groupbox id="updateApp" align="start">
         <caption><label>&updateApp.label;</label></caption>
         <radiogroup id="updateRadioGroup" align="start">
--- a/browser/components/preferences/moz.build
+++ b/browser/components/preferences/moz.build
@@ -13,10 +13,14 @@ BROWSER_CHROME_MANIFESTS += [
 for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
     DEFINES[var] = CONFIG[var]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
     DEFINES['HAVE_SHELL_SERVICE'] = 1
 
 JAR_MANIFESTS += ['jar.mn']
 
+EXTRA_JS_MODULES += [
+    'SiteDataManager.jsm',
+]
+
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Preferences')
--- a/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -52,16 +52,19 @@
 <!ENTITY connectionDesc.label            "Configure how &brandShortName; connects to the Internet">
 <!ENTITY connectionSettings.label        "Settings…">
 <!ENTITY connectionSettings.accesskey    "e">
 
 <!ENTITY httpCache.label                 "Cached Web Content">
 
 <!ENTITY offlineStorage2.label           "Offline Web Content and User Data">
 
+<!--  Site Data section manages sites using Storage API and is under Network -->
+<!ENTITY siteData.label           "Site Data">
+
 <!-- LOCALIZATION NOTE:
   The entities limitCacheSizeBefore.label and limitCacheSizeAfter.label appear on a single
   line in preferences as follows:
 
   &limitCacheSizeBefore.label [textbox for cache size in MB] &limitCacheSizeAfter.label;
 -->
 <!ENTITY limitCacheSizeBefore.label      "Limit cache to">
 <!ENTITY limitCacheSizeBefore.accesskey  "L">
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -159,16 +159,23 @@ actualDiskCacheSizeCalculated=Calculating web content cache size…
 
 ####Preferences::Advanced::Network
 #LOCALIZATION NOTE: The next string is for the disk usage of the application cache.
 #   e.g., "Your application cache is currently using 200 MB"
 #   %1$S = size
 #   %2$S = unit (MB, KB, etc.)
 actualAppCacheSize=Your application cache is currently using %1$S %2$S of disk space
 
+####Preferences::Advanced::Network
+#LOCALIZATION NOTE: The next string is for the total usage of site data.
+#   e.g., "The total usage is currently using 200 MB"
+#   %1$S = size
+#   %2$S = unit (MB, KB, etc.)
+totalSiteDataSize=Your stored site data is currently using %1$S %2$S of disk space
+
 syncUnlink.title=Do you want to unlink your device?
 syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
 syncUnlinkConfirm.label=Unlink
 
 # LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
 featureEnableRequiresRestart=%S must restart to enable this feature.
 featureDisableRequiresRestart=%S must restart to disable this feature.
 shouldRestartTitle=Restart %S
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5527,8 +5527,13 @@ pref ("security.mixed_content.hsts_primi
 pref ("security.mixed_content.hsts_priming_request_timeout", 3000);
 
 // Disable Storage api in release builds.
 #ifdef NIGHTLY_BUILD
 pref("dom.storageManager.enabled", true);
 #else
 pref("dom.storageManager.enabled", false);
 #endif
+
+// 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);
+