Bug 1422365 - Introduce nsIClearDataService - part 3 - plugin data, r=johannh
authorAndrea Marchesini <amarchesini@mozilla.com>
Fri, 01 Jun 2018 14:29:59 +0200
changeset 420872 ef06c41bff1df36828cb9d31f5d0cd6e24a10ba2
parent 420871 945c1f6e5c2957c88976bf3041c837efda497173
child 420873 bb116c8d9d2cb9cdfdf8bf46b7b9cbcc931951bc
push id34083
push userapavel@mozilla.com
push dateSat, 02 Jun 2018 23:03:25 +0000
treeherdermozilla-central@1f62ecdf59b6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjohannh
bugs1422365
milestone62.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 1422365 - Introduce nsIClearDataService - part 3 - plugin data, r=johannh
browser/modules/Sanitizer.jsm
toolkit/components/cleardata/ClearDataService.js
toolkit/components/cleardata/nsIClearDataService.idl
toolkit/components/cleardata/tests/unit/test_basic.js
toolkit/forgetaboutsite/ForgetAboutSite.jsm
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -9,17 +9,16 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
   FormHistory: "resource://gre/modules/FormHistory.jsm",
   Downloads: "resource://gre/modules/Downloads.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
-  setTimeout: "resource://gre/modules/Timer.jsm",
   ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
   OfflineAppCacheHelper: "resource://gre/modules/offlineAppCache.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "sas",
                                    "@mozilla.org/storage/activity-service;1",
                                    "nsIStorageActivityService");
@@ -320,43 +319,28 @@ var Sanitizer = {
         TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
         await clearData(range, Ci.nsIClearDataService.CLEAR_ALL_CACHES);
         TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
       }
     },
 
     cookies: {
       async clear(range) {
-        let seenException;
         let refObj = {};
 
-        // Clear cookies.
+        // Clear cookies and plugin data.
         TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
-        await clearData(range, Ci.nsIClearDataService.CLEAR_COOKIES);
+        await clearData(range, Ci.nsIClearDataService.CLEAR_COOKIES |
+                               Ci.nsIClearDataService.CLEAR_PLUGIN_DATA);
         TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
 
         // Clear deviceIds. Done asynchronously (returns before complete).
-        try {
-          let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"]
-                           .getService(Ci.nsIMediaManagerService);
-          mediaMgr.sanitizeDeviceIds(range && range[0]);
-        } catch (ex) {
-          seenException = ex;
-        }
-
-        // Clear plugin data.
-        try {
-          await clearPluginData(range);
-        } catch (ex) {
-          seenException = ex;
-        }
-
-        if (seenException) {
-          throw seenException;
-        }
+        let mediaMgr = Cc["@mozilla.org/mediaManagerService;1"]
+                         .getService(Ci.nsIMediaManagerService);
+        mediaMgr.sanitizeDeviceIds(range && range[0]);
       },
     },
 
     offlineApps: {
       async clear(range) {
         // AppCache: this doesn't wait for the cleanup to be complete.
         OfflineAppCacheHelper.clear();
 
@@ -767,17 +751,17 @@ var Sanitizer = {
         }
         newWindow.focus();
         await promiseReady;
       }
     },
 
     pluginData: {
       async clear(range) {
-        await clearPluginData(range);
+        await clearData(range, Ci.nsIClearDataService.CLEAR_PLUGIN_DATA);
       },
     },
   },
 };
 
 async function sanitizeInternal(items, aItemsToClear, progress, options = {}) {
   let { ignoreTimespan = true, range } = options;
   let seenError = false;
@@ -857,80 +841,16 @@ async function sanitizeInternal(items, a
   if (!progress.isShutdown)
     removePendingSanitization(uid);
   progress = {};
   if (seenError) {
     throw new Error("Error sanitizing");
   }
 }
 
-async function clearPluginData(range) {
-  // Clear plugin data.
-  // As evidenced in bug 1253204, clearing plugin data can sometimes be
-  // very, very long, for mysterious reasons. Unfortunately, this is not
-  // something actionable by Mozilla, so crashing here serves no purpose.
-  //
-  // For this reason, instead of waiting for sanitization to always
-  // complete, we introduce a soft timeout. Once this timeout has
-  // elapsed, we proceed with the shutdown of Firefox.
-  let seenException;
-
-  let promiseClearPluginData = async function() {
-    const FLAG_CLEAR_ALL = Ci.nsIPluginHost.FLAG_CLEAR_ALL;
-    let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-
-    // Determine age range in seconds. (-1 means clear all.) We don't know
-    // that range[1] is actually now, so we compute age range based
-    // on the lower bound. If range results in a negative age, do nothing.
-    let age = range ? (Date.now() / 1000 - range[0] / 1000000) : -1;
-    if (!range || age >= 0) {
-      let tags = ph.getPluginTags();
-      for (let tag of tags) {
-        try {
-          let rv = await new Promise(resolve =>
-            ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, resolve)
-          );
-          // If the plugin doesn't support clearing by age, clear everything.
-          if (rv == Cr.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
-            await new Promise(resolve =>
-              ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
-            );
-          }
-        } catch (ex) {
-          // Ignore errors from plug-ins
-        }
-      }
-    }
-  };
-
-  try {
-    // We don't want to wait for this operation to complete...
-    promiseClearPluginData = promiseClearPluginData(range);
-
-    // ... at least, not for more than 10 seconds.
-    await Promise.race([
-      promiseClearPluginData,
-      new Promise(resolve => setTimeout(resolve, 10000 /* 10 seconds */))
-    ]);
-  } catch (ex) {
-    seenException = ex;
-  }
-
-  // Detach waiting for plugin data to be cleared.
-  promiseClearPluginData.catch(() => {
-    // If this exception is raised before the soft timeout, it
-    // will appear in `seenException`. Otherwise, it's too late
-    // to do anything about it.
-  });
-
-  if (seenException) {
-    throw seenException;
-  }
-}
-
 async function sanitizeOnShutdown(progress) {
   if (Sanitizer.shouldSanitizeOnShutdown) {
     // Need to sanitize upon shutdown
     let itemsToClear = getItemsToClearFromPrefBranch(Sanitizer.PREF_SHUTDOWN_BRANCH);
     await Sanitizer.sanitize(itemsToClear, { progress });
   }
 
   // Clear out QuotaManager storage for principals that have been marked as
--- a/toolkit/components/cleardata/ClearDataService.js
+++ b/toolkit/components/cleardata/ClearDataService.js
@@ -84,26 +84,111 @@ const ImageCacheCleaner = {
                          .getService(Ci.imgITools)
                          .getImgCacheForDocument(null);
       imageCache.clearCache(false); // true=chrome, false=content
       aResolve();
     });
   },
 };
 
+const PluginDataCleaner = {
+  deleteByHost(aHost, aOriginAttributes) {
+    return this._deleteInternal((aPh, aTag) => {
+      return new Promise(aResolve => {
+        try {
+          aPh.clearSiteData(aTag, aHost,
+                            Ci.nsIPluginHost.FLAG_CLEAR_ALL,
+                            -1, aResolve);
+        } catch (e) {
+          // Ignore errors from the plugin, but resolve the promise
+          // We cannot check if something is a bailout or an error
+          aResolve();
+        }
+      });
+    });
+  },
+
+  deleteByRange(aFrom, aTo) {
+    let age = Date.now() / 1000 - aFrom / 1000000;
+
+    return this._deleteInternal((aPh, aTag) => {
+      return new Promise(aResolve => {
+        try {
+          aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL,
+                            age, aResolve);
+        } catch (e) {
+          aResolve(Cr.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED);
+        }
+      }).then(aRv => {
+        // If the plugin doesn't support clearing by age, clear everything.
+        if (aRv == Cr.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+          return new Promise(aResolve => {
+            try {
+              aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL,
+                                -1, aResolve);
+            } catch (e) {
+              aResolve();
+            }
+          });
+        }
+
+        return true;
+      });
+    });
+  },
+
+  deleteAll() {
+    return this._deleteInternal((aPh, aTag) => {
+      return new Promise(aResolve => {
+        try {
+          aPh.clearSiteData(aTag, null, Ci.nsIPluginHost.FLAG_CLEAR_ALL, -1,
+                            aResolve);
+        } catch (e) {
+          aResolve();
+        }
+      });
+    });
+  },
+
+  _deleteInternal(aCb) {
+    let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let promises = [];
+    let tags = ph.getPluginTags();
+    for (let tag of tags) {
+      promises.push(aCb(ph, tag));
+    }
+
+    // As evidenced in bug 1253204, clearing plugin data can sometimes be
+    // very, very long, for mysterious reasons. Unfortunately, this is not
+    // something actionable by Mozilla, so crashing here serves no purpose.
+    //
+    // For this reason, instead of waiting for sanitization to always
+    // complete, we introduce a soft timeout. Once this timeout has
+    // elapsed, we proceed with the shutdown of Firefox.
+    return Promise.race([
+      Promise.all(promises),
+      new Promise(aResolve => setTimeout(aResolve, 10000 /* 10 seconds */))
+    ]);
+  },
+};
+
 // Here the map of Flags-Cleaner.
 const FLAGS_MAP = [
  { flag: Ci.nsIClearDataService.CLEAR_COOKIES,
    cleaner: CookieCleaner },
 
  { flag: Ci.nsIClearDataService.CLEAR_NETWORK_CACHE,
    cleaner: NetworkCacheCleaner },
 
  { flag: Ci.nsIClearDataService.CLEAR_IMAGE_CACHE,
    cleaner: ImageCacheCleaner, },
+
+ { flag: Ci.nsIClearDataService.CLEAR_PLUGIN_DATA,
+   cleaner: PluginDataCleaner, },
 ];
 
 this.ClearDataService = function() {};
 
 ClearDataService.prototype = Object.freeze({
   classID: Components.ID("{0c06583d-7dd8-4293-b1a5-912205f779aa}"),
   QueryInterface: ChromeUtils.generateQI([Ci.nsIClearDataService]),
   _xpcom_factory: XPCOMUtils.generateSingletonFactory(ClearDataService),
--- a/toolkit/components/cleardata/nsIClearDataService.idl
+++ b/toolkit/components/cleardata/nsIClearDataService.idl
@@ -93,19 +93,23 @@ interface nsIClearDataService : nsISuppo
    */
   const uint32_t CLEAR_NETWORK_CACHE = 1 << 1;
 
   /**
    * Image cache.
    */
   const uint32_t CLEAR_IMAGE_CACHE = 1 << 2;
 
+  /**
+   * Data stored by external plugins.
+   */
+  const uint32_t CLEAR_PLUGIN_DATA = 1 << 3;
+
   /* TODO
-  const uint32_t CLEAR_EME = 1 << 3;
-  const uint32_t CLEAR_PLUGIN_DATA = 1 << 4;
+  const uint32_t CLEAR_EME = 1 << 4;
   const uint32_t CLEAR_DOWNLOADS = 1 << 5;
   const uint32_t CLEAR_PASSWORDS = 1 << 6;
   const uint32_t CLEAR_PERMISSIONS = 1 << 7;
   const uint32_t CLEAR_DOM_QUOTA = 1 << 8;
   const uint32_t CLEAR_CONTENT_PREFERENCES = 1 << 9;
   const uint32_t CLEAR_PREDICTOR_CACHE = 1 << 10;
   const uint32_t CLEAR_DOM_PUSH_NOTIFICATIONS = 1 << 11;
   const uint32_t CLEAR_HSTS = 1 << 12;
--- a/toolkit/components/cleardata/tests/unit/test_basic.js
+++ b/toolkit/components/cleardata/tests/unit/test_basic.js
@@ -8,15 +8,14 @@
 "use strict";
 
 add_task(async function test_basic() {
   const service = Cc["@mozilla.org/clear-data-service;1"]
                   .getService(Ci.nsIClearDataService);
   Assert.ok(!!service);
 
   await new Promise(aResolve => {
-    service.deleteData(Ci.nsIClearDataService.CLEAR_IMAGE_CACHE |
-                       Ci.nsIClearDataService.CLEAR_COOKIES, value => {
+    service.deleteData(Ci.nsIClearDataService.CLEAR_ALL, value => {
       Assert.equal(value, 0);
       aResolve();
     });
   });
 });
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -59,33 +59,16 @@ var ForgetAboutSite = {
       let mps = Cc["@mozilla.org/gecko-media-plugin-service;1"].
                 getService(Ci.mozIGeckoMediaPluginChromeService);
       mps.forgetThisSite(aDomain, JSON.stringify({}));
     })().catch(ex => {
       throw new Error("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();
-    for (let i = 0; i < tags.length; i++) {
-      promises.push(new Promise(resolve => {
-        try {
-          ph.clearSiteData(tags[i], aDomain, FLAG_CLEAR_ALL, -1, resolve);
-        } catch (e) {
-          // Ignore errors from the plugin, but resolve the promise
-          // We cannot check if something is a bailout or an error
-          resolve();
-        }
-      }));
-    }
-
     // Downloads
     promises.push((async function() {
       let list = await Downloads.getList(Downloads.ALL);
       list.removeFinished(download => hasRootDomain(
         NetUtil.newURI(download.source.url).host, aDomain));
     })().catch(ex => {
       throw new Error("Exception in clearing Downloads: " + ex);
     }));