Bug 1410416 - Part 1: Have SiteDateManager.jsm call `propagateUnregister` to remove service workers, r?baku draft
authorFischer.json <fischer.json@gmail.com>
Fri, 20 Oct 2017 21:35:58 +0800
changeset 693283 a7f1520e1dc96d1175f50ef9077012c23bd9c036
parent 693279 e7fee7042d971d73c0e5caafba3b8d15da1bc8ca
child 693284 545f710bc4d420594c1c0a6131fd02d3a7827136
push id87743
push userbmo:fliu@mozilla.com
push dateSun, 05 Nov 2017 05:32:00 +0000
reviewersbaku
bugs1410416
milestone58.0a1
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) {