Bug 1247201 - Run cleaners async to clear as much as possible, r?mak draft
authormilindl <i.milind.luthra@gmail.com>
Thu, 16 Mar 2017 10:54:18 +0530
changeset 499736 9ffcdaccf7bb53ed24ef61dc1144e191c9f8b9a5
parent 499512 ff04d410e74b69acfab17ef7e73e7397602d5a68
child 500422 d4c0c5e7bc9320e6804cc3a7f8282799bddb7410
child 500425 d312aa019a9063a7e4473981065cc94c95b37a00
child 500505 217f25f25b7a0c5f61d3768c616c408029ae090f
push id49502
push userbmo:i.milind.luthra@gmail.com
push dateThu, 16 Mar 2017 05:28:28 +0000
reviewersmak
bugs1247201
milestone55.0a1
Bug 1247201 - Run cleaners async to clear as much as possible, r?mak Amended to fix review changes (stylistic + other) Turns all cleaners into promises so they run asyc Turns removeDataFromDomain to Task.async MozReview-Commit-ID: 5EILXOenPyS
browser/components/places/content/controller.js
commit-message-9d61c
toolkit/forgetaboutsite/ForgetAboutSite.jsm
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -248,17 +248,18 @@ PlacesController.prototype = {
       break;
     case "placesCmd_deleteDataHost":
       var host;
       if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
         var queries = this._view.selectedNode.getQueries();
         host = queries[0].domain;
       } else
         host = NetUtil.newURI(this._view.selectedNode.uri).host;
-      ForgetAboutSite.removeDataFromDomain(host);
+      ForgetAboutSite.removeDataFromDomain(host)
+                     .catch(Components.utils.reportError);
       break;
     case "cmd_selectAll":
       this.selectAll();
       break;
     case "placesCmd_open":
       PlacesUIUtils.openNodeIn(this._view.selectedNode, "current", this._view);
       break;
     case "placesCmd_open:window":
new file mode 100644
--- /dev/null
+++ b/commit-message-9d61c
@@ -0,0 +1,9 @@
+Bug 1247201 - Run cleaners async to clear as much as possible, r=mak
+
+Amended to fix review changes
+(stylistic + other)
+Turns all cleaners into promises so they run asyc
+Turns removeDataFromDomain to Task.async
+Adds .catch in controller.js
+
+MozReview-Commit-ID: 6XCgLd9HGFA
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -39,188 +39,234 @@ function hasRootDomain(str, aDomain) {
          (prevChar == "." || prevChar == "/");
 }
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 this.ForgetAboutSite = {
-  removeDataFromDomain: function CRH_removeDataFromDomain(aDomain) {
+  removeDataFromDomain: Task.async(function* (aDomain) {
     PlacesUtils.history.removePagesFromHost(aDomain, true);
 
+    let promises = [];
     // Cache
-    let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
-             getService(Ci.nsICacheStorageService);
-    // NOTE: there is no way to clear just that domain, so we clear out
-    //       everything)
-    try {
-      cs.clear();
-    } catch (ex) {
-      Cu.reportError("Exception thrown while clearing the cache: " +
-        ex.toString());
-    }
+    promises.push(new Promise((resolve, reject) => {
+      let cs = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+               getService(Ci.nsICacheStorageService);
+      // NOTE: there is no way to clear just that domain, so we clear out
+      //       everything)
+      try {
+        cs.clear();
+        resolve();
+      } catch (ex) {
+        reject("Exception thrown while clearing the cache: " + ex);
+      }
+    }));
 
     // Image Cache
-    let imageCache = Cc["@mozilla.org/image/tools;1"].
-                     getService(Ci.imgITools).getImgCacheForDocument(null);
-    try {
-      imageCache.clearCache(false); // true=chrome, false=content
-    } catch (ex) {
-      Cu.reportError("Exception thrown while clearing the image cache: " +
-        ex.toString());
-    }
+    promises.push(new Promise((resolve, reject) => {
+      let imageCache = Cc["@mozilla.org/image/tools;1"].
+                       getService(Ci.imgITools).getImgCacheForDocument(null);
+      try {
+        imageCache.clearCache(false); // true=chrome, false=content
+        resolve();
+      } catch (ex) {
+        reject("Exception thrown while clearing the image cache: " + ex);
+      }
+    }));
 
     // Cookies
+    // Need to maximize the number of cookies cleaned here
     let cm = Cc["@mozilla.org/cookiemanager;1"].
              getService(Ci.nsICookieManager2);
     let enumerator = cm.getCookiesWithOriginAttributes(JSON.stringify({}), aDomain);
     while (enumerator.hasMoreElements()) {
       let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie);
-      cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+      promises.push(new Promise((resolve, reject) => {
+        try {
+          cm.remove(cookie.host, cookie.name, cookie.path, false, cookie.originAttributes);
+          resolve();
+        } catch (ex) {
+          reject("Exception thrown while clearning cookie " + cookie.name + ": " + ex);
+        }
+      }));
     }
 
     // EME
-    let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
-               getService(Ci.mozIGeckoMediaPluginChromeService);
-    mps.forgetThisSite(aDomain, JSON.stringify({}));
+    promises.push(new Promise((resolve, reject) => {
+      let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
+                getService(Ci.mozIGeckoMediaPluginChromeService);
+      try {
+        mps.forgetThisSite(aDomain, JSON.stringify({}));
+        resolve();
+      } catch (ex) {
+        reject("Exception thrown while clearing Encrypted Media Extensions: " + ex);
+      }
+    }));
 
     // Plugin data
     const phInterface = Ci.nsIPluginHost;
     const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
     let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
     let tags = ph.getPluginTags();
-    let promises = [];
     for (let i = 0; i < tags.length; i++) {
-      let promise = new Promise(resolve => {
+      promises.push(new Promise((resolve, reject) => {
         try {
           ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, function(rv) {
             resolve();
           });
-        } catch (e) {
-          // Ignore errors from the plugin, but resolve the promise
-          resolve();
+        } catch (ex) {
+          reject("Exception thrown while clearing plugin with tag " + tags[i] + ": " + ex);
         }
-      });
-      promises.push(promise);
+      }));
     }
 
     // Downloads
-    Task.spawn(function*() {
+    promises.push(Task.spawn(function*() {
       let list = yield Downloads.getList(Downloads.ALL);
       list.removeFinished(download => hasRootDomain(
-           NetUtil.newURI(download.source.url).host, aDomain));
-    }).then(null, Cu.reportError);
+        NetUtil.newURI(download.source.url).host, aDomain));
+    }));
 
     // Passwords
-    let lm = Cc["@mozilla.org/login-manager;1"].
-             getService(Ci.nsILoginManager);
-    // Clear all passwords for domain
-    try {
-      let logins = lm.getAllLogins();
-      for (let i = 0; i < logins.length; i++)
-        if (hasRootDomain(logins[i].hostname, aDomain))
-          lm.removeLogin(logins[i]);
-    } catch (ex) {
-      // XXXehsan: is there a better way to do this rather than this
-      // hacky comparison?
-      if (ex.message.indexOf("User canceled Master Password entry") == -1) {
-        throw ex;
+    promises.push(new Promise((resolve, reject) => {
+      let lm = Cc["@mozilla.org/login-manager;1"].
+               getService(Ci.nsILoginManager);
+      // Clear all passwords for domain
+      try {
+        let logins = lm.getAllLogins();
+        for (let i = 0; i < logins.length; i++)
+          if (hasRootDomain(logins[i].hostname, aDomain))
+            lm.removeLogin(logins[i]);
+        resolve();
+      } catch (ex) {
+        // XXXehsan: is there a better way to do this rather than this
+        // hacky comparison?
+        if (ex.message.indexOf("User canceled Master Password entry") == -1) {
+          reject("Exception occured in clearing passwords :" + ex);
+        } else {
+          resolve();
+        }
       }
-    }
+    }));
 
     // Permissions
     let pm = Cc["@mozilla.org/permissionmanager;1"].
              getService(Ci.nsIPermissionManager);
     // Enumerate all of the permissions, and if one matches, remove it
     enumerator = pm.enumerator;
     while (enumerator.hasMoreElements()) {
       let perm = enumerator.getNext().QueryInterface(Ci.nsIPermission);
-      try {
-        if (hasRootDomain(perm.principal.URI.host, aDomain)) {
-          pm.removePermission(perm);
+      promises.push(new Promise((resolve, reject) => {
+        try {
+          if (hasRootDomain(perm.principal.URI.host, aDomain)) {
+            pm.removePermission(perm);
+          }
+          resolve();
+        } catch (ex) {
+          reject("Exception occured while clearing a permission: " + ex);
         }
-      } catch (e) {
-        /* Ignore entry */
-      }
+      }));
     }
 
     // Offline Storages
-    let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
-              getService(Ci.nsIQuotaManagerService);
-    // delete data from both HTTP and HTTPS sites
-    let caUtils = {};
-    let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
-                       getService(Ci.mozIJSSubScriptLoader);
-    scriptLoader.loadSubScript("chrome://global/content/contentAreaUtils.js",
-                               caUtils);
-    let httpURI = caUtils.makeURI("http://" + aDomain);
-    let httpsURI = caUtils.makeURI("https://" + aDomain);
-    // Following code section has been reverted to the state before Bug 1238183,
-    // but added a new argument to clearStoragesForPrincipal() for indicating
-    // clear all storages under a given origin.
-    let httpPrincipal = Services.scriptSecurityManager
-                                .createCodebasePrincipal(httpURI, {});
-    let httpsPrincipal = Services.scriptSecurityManager
-                                 .createCodebasePrincipal(httpsURI, {});
-    qms.clearStoragesForPrincipal(httpPrincipal, null, true);
-    qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
-
-
-    function onContentPrefsRemovalFinished() {
-      // Everybody else (including extensions)
-      Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
-    }
+    promises.push(new Promise((resolve, reject) => {
+      try {
+        let qms = Cc["@mozilla.org/dom/quota-manager-service;1"].
+                  getService(Ci.nsIQuotaManagerService);
+        // delete data from both HTTP and HTTPS sites
+        let httpURI = NetUtil.newURI("http://" + aDomain);
+        let httpsURI = NetUtil.newURI("https://" + aDomain);
+        // Following code section has been reverted to the state before Bug 1238183,
+        // but added a new argument to clearStoragesForPrincipal() for indicating
+        // clear all storages under a given origin.
+        let httpPrincipal = Services.scriptSecurityManager
+                                    .createCodebasePrincipal(httpURI, {});
+        let httpsPrincipal = Services.scriptSecurityManager
+                                     .createCodebasePrincipal(httpsURI, {});
+        qms.clearStoragesForPrincipal(httpPrincipal, null, true);
+        qms.clearStoragesForPrincipal(httpsPrincipal, null, true);
+        resolve();
+      } catch (ex) {
+        reject("Exception occured while clearing offline storages: " + ex);
+      }
+    }));
 
     // Content Preferences
-    let cps2 = Cc["@mozilla.org/content-pref/service;1"].
-               getService(Ci.nsIContentPrefService2);
-    cps2.removeBySubdomain(aDomain, null, {
-      handleCompletion: () => onContentPrefsRemovalFinished(),
-      handleError() {}
-    });
+    promises.push(new Promise((resolve, reject) => {
+      let cps2 = Cc["@mozilla.org/content-pref/service;1"].
+                 getService(Ci.nsIContentPrefService2);
+      cps2.removeBySubdomain(aDomain, null, {
+        handleCompletion: (reason) => {
+          // Notify other consumers, including extensions
+          Services.obs.notifyObservers(null, "browser:purge-domain-data", aDomain);
+          if (reason === cps2.COMPLETE_ERROR)
+            reject("Exception occured while clearing content preferences");
+          else
+            resolve();
+        },
+        handleError() {}
+      });
+    }));
 
     // Predictive network data - like cache, no way to clear this per
     // domain, so just trash it all
-    let np = Cc["@mozilla.org/network/predictor;1"].
-             getService(Ci.nsINetworkPredictor);
-    np.reset();
+    promises.push(new Promise((resolve, reject) => {
+      try {
+        let np = Cc["@mozilla.org/network/predictor;1"].
+                 getService(Ci.nsINetworkPredictor);
+        np.reset();
+        resolve();
+      } catch (ex) {
+        reject("Exception occured while clearing predictive network data: " + ex);
+      }
+    }));
 
     // Push notifications.
     promises.push(new Promise((resolve, reject) => {
-      var push = Cc["@mozilla.org/push/Service;1"]
-                  .getService(Ci.nsIPushService);
+      var push = Cc["@mozilla.org/push/Service;1"].
+                 getService(Ci.nsIPushService);
       push.clearForDomain(aDomain, status => {
         (Components.isSuccessCode(status) ? resolve : reject)(status);
       });
-    }).catch(e => {
-      Cu.reportError("Exception thrown while clearing Push notifications: " +
-                     e.toString());
     }));
 
     // HSTS and HPKP
-    try {
-      let sss = Cc["@mozilla.org/ssservice;1"].
-                getService(Ci.nsISiteSecurityService);
-      for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
-                        Ci.nsISiteSecurityService.HEADER_HPKP]) {
-        // Also remove HSTS/HPKP information for subdomains by enumerating the
-        // information in the site security service.
-        let enumerator = sss.enumerate(type);
-        while (enumerator.hasMoreElements()) {
-          let entry = enumerator.getNext();
-          let hostname = entry.QueryInterface(Ci.nsISiteSecurityState).hostname;
-          // If the hostname is aDomain's subdomain, we remove its state.
-          if (hostname == aDomain || hostname.endsWith("." + aDomain)) {
-            // This uri is used as a key to remove the state.
-            let uri = caUtils.makeURI("https://" + hostname);
-            sss.removeState(type, uri, 0, entry.originAttributes);
+    promises.push(new Promise((resolve, reject) => {
+      try {
+        let sss = Cc["@mozilla.org/ssservice;1"].
+                  getService(Ci.nsISiteSecurityService);
+        for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
+                          Ci.nsISiteSecurityService.HEADER_HPKP]) {
+          // Also remove HSTS/HPKP information for subdomains by enumerating the
+          // information in the site security service.
+          let enumerator = sss.enumerate(type);
+          while (enumerator.hasMoreElements()) {
+            let entry = enumerator.getNext();
+            let hostname = entry.QueryInterface(Ci.nsISiteSecurityState).hostname;
+            // If the hostname is aDomain's subdomain, we remove its state.
+            if (hostname == aDomain || hostname.endsWith("." + aDomain)) {
+              // This uri is used as a key to remove the state.
+              let uri = NetUtil.newURI("https://" + hostname);
+              sss.removeState(type, uri, 0, entry.originAttributes);
+            }
           }
         }
+        resolve();
+      } catch (e) {
+        reject("Exception thrown while clearing HSTS/HPKP: " + e);
       }
-    } catch (e) {
-      Cu.reportError("Exception thrown while clearing HSTS/HPKP: " +
-                     e.toString());
+    }));
+
+    let ErrorCount = 0;
+    for (let promise in promises) {
+      try {
+        yield promise;
+      } catch (ex) {
+        Cu.reportError(ex);
+        ErrorCount++;
+      }
     }
-
-    return Promise.all(promises);
-  }
-};
+    if (ErrorCount !== 0)
+      throw new Error(`There were a total of ${ErrorCount} errors during removal`);
+  })
+}