Bug 1410416 - Part 1: Have SiteDateManager.jsm call `propagateUnregister` to remove service workers, r=baku
authorFischer.json <fischer.json@gmail.com>
Fri, 20 Oct 2017 21:35:58 +0800
changeset 443529 8ef662f3fa226a9661e3744600bef42bef98258e
parent 443528 c9ed5bfc1e34ed57be8cae7b0ccc3e6cac55b7ab
child 443530 f89f5660e3d74421398997e947938245069e97a5
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbaku
bugs1410416
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1410416 - Part 1: Have SiteDateManager.jsm call `propagateUnregister` to remove service workers, r=baku MozReview-Commit-ID: BNUhm6a2x1b
browser/components/preferences/SiteDataManager.jsm
--- a/browser/components/preferences/SiteDataManager.jsm
+++ b/browser/components/preferences/SiteDataManager.jsm
@@ -1,11 +1,11 @@
 "use strict";
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "OfflineAppCacheHelper",
                                   "resource:///modules/offlineAppCache.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
@@ -44,47 +44,49 @@ this.SiteDataManager = {
   },
 
   _getQuotaUsage() {
     // Clear old data and requests first
     this._sites.clear();
     this._cancelGetQuotaUsage();
     this._getQuotaUsagePromise = new Promise(resolve => {
       let onUsageResult = request => {
-        let items = request.result;
-        for (let item of items) {
-          if (!item.persisted && item.usage <= 0) {
-            // An non-persistent-storage site with 0 byte quota usage is redundant for us so skip it.
-            continue;
-          }
-          let principal =
-            Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
-          let uri = principal.URI;
-          if (uri.scheme == "http" || uri.scheme == "https") {
-            let site = this._sites.get(uri.host);
-            if (!site) {
-              site = {
-                persisted: false,
-                quotaUsage: 0,
-                principals: [],
-                appCacheList: [],
-              };
+        if (request.resultCode == Cr.NS_OK) {
+          let items = request.result;
+          for (let item of items) {
+            if (!item.persisted && item.usage <= 0) {
+              // An non-persistent-storage site with 0 byte quota usage is redundant for us so skip it.
+              continue;
             }
-            // Assume 3 sites:
-            //   - Site A (not persisted): https://www.foo.com
-            //   - Site B (not persisted): https://www.foo.com^userContextId=2
-            //   - Site C (persisted):     https://www.foo.com:1234
-            // Although only C is persisted, grouping by host, as a result,
-            // we still mark as persisted here under this host group.
-            if (item.persisted) {
-              site.persisted = true;
+            let principal =
+              Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(item.origin);
+            let uri = principal.URI;
+            if (uri.scheme == "http" || uri.scheme == "https") {
+              let site = this._sites.get(uri.host);
+              if (!site) {
+                site = {
+                  persisted: false,
+                  quotaUsage: 0,
+                  principals: [],
+                  appCacheList: [],
+                };
+              }
+              // Assume 3 sites:
+              //   - Site A (not persisted): https://www.foo.com
+              //   - Site B (not persisted): https://www.foo.com^userContextId=2
+              //   - Site C (persisted):     https://www.foo.com:1234
+              // Although only C is persisted, grouping by host, as a result,
+              // we still mark as persisted here under this host group.
+              if (item.persisted) {
+                site.persisted = true;
+              }
+              site.principals.push(principal);
+              site.quotaUsage += item.usage;
+              this._sites.set(uri.host, site);
             }
-            site.principals.push(principal);
-            site.quotaUsage += item.usage;
-            this._sites.set(uri.host, site);
           }
         }
         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._quotaUsageRequest = this._qms.getUsage(onUsageResult);
@@ -217,78 +219,96 @@ this.SiteDataManager = {
             cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
         }
       }
 
       Services.obs.notifyObservers(null, "browser:purge-domain-data", principal.URI.host);
     }
   },
 
-  _removeServiceWorkers(site) {
+  _unregisterServiceWorker(serviceWorker) {
+    return new Promise(resolve => {
+      let unregisterCallback = {
+        unregisterSucceeded: resolve,
+        unregisterFailed: resolve, // We don't care about failures.
+        QueryInterface: XPCOMUtils.generateQI([Ci.nsIServiceWorkerUnregisterCallback])
+      };
+      serviceWorkerManager.propagateUnregister(serviceWorker.principal, unregisterCallback, serviceWorker.scope);
+    });
+  },
+
+  _removeServiceWorkersForSites(sites) {
+    let promises = [];
+    let targetHosts = sites.map(s => s.principals[0].URI.host);
     let serviceWorkers = serviceWorkerManager.getAllRegistrations();
     for (let i = 0; i < serviceWorkers.length; i++) {
       let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-      for (let principal of site.principals) {
-        if (sw.principal.equals(principal)) {
-          serviceWorkerManager.removeAndPropagate(sw.principal.URI.host);
-          break;
-        }
+      // Sites are grouped and removed by host so we unregister service workers by the same host as well
+      if (targetHosts.includes(sw.principal.URI.host)) {
+        promises.push(this._unregisterServiceWorker(sw));
       }
     }
+    return Promise.all(promises);
   },
 
   remove(hosts) {
-    let promises = [];
     let unknownHost = "";
+    let targetSites = [];
     for (let host of hosts) {
       let site = this._sites.get(host);
       if (site) {
         this._removePermission(site);
         this._removeAppCache(site);
         this._removeCookie(site);
-        this._removeServiceWorkers(site);
-        promises.push(this._removeQuotaUsage(site));
+        targetSites.push(site);
       } else {
         unknownHost = host;
         break;
       }
     }
-    if (promises.length > 0) {
-      Promise.all(promises).then(() => this.updateSites());
+
+    if (targetSites.length > 0) {
+      this._removeServiceWorkersForSites(targetSites)
+          .then(() => {
+            let promises = targetSites.map(s => this._removeQuotaUsage(s));
+            return Promise.all(promises);
+          })
+          .then(() => this.updateSites());
     }
     if (unknownHost) {
       throw `SiteDataManager: removing unknown site of ${unknownHost}`;
     }
   },
 
   async removeAll() {
     Services.cache2.clear();
     Services.cookies.removeAll();
     OfflineAppCacheHelper.clear();
 
     // Iterate through the service workers and remove them.
+    let promises = [];
     let serviceWorkers = serviceWorkerManager.getAllRegistrations();
     for (let i = 0; i < serviceWorkers.length; i++) {
       let sw = serviceWorkers.queryElementAt(i, Ci.nsIServiceWorkerRegistrationInfo);
-      let host = sw.principal.URI.host;
-      serviceWorkerManager.removeAndPropagate(host);
+      promises.push(this._unregisterServiceWorker(sw));
     }
+    await Promise.all(promises);
 
     // Refresh sites using quota usage again.
     // This is for the case:
     //   1. User goes to the about:preferences Site Data section.
     //   2. With the about:preferences opened, user visits another website.
     //   3. The website saves to quota usage, like indexedDB.
     //   4. User goes back to the Site Data section and commands to clear all site data.
     // For this case, we should refresh the site list so not to miss the website in the step 3.
     // We don't do "Clear All" on the quota manager like the cookie, appcache, http cache above
     // because that would clear browser data as well too,
     // see https://bugzilla.mozilla.org/show_bug.cgi?id=1312361#c9
     await this._getQuotaUsage();
-    let promises = [];
+    promises = [];
     for (let site of this._sites.values()) {
       this._removePermission(site);
       promises.push(this._removeQuotaUsage(site));
     }
     return Promise.all(promises).then(() => this.updateSites());
   },
 
   isPrivateCookie(cookie) {