Bug 1324760 - Expose a function that clears plugin data in sanitize.js, r?mak draft
authorBob Silverberg <bsilverberg@mozilla.com>
Tue, 20 Dec 2016 09:24:06 -0500
changeset 451563 20580b65ea237541a257ece8d8e808353838aebd
parent 451264 567894f026558e6dada617a3998f29aed06ac7d8
child 540072 6171f4079e47570759060577209d3db68c7ef910
push id39229
push userbmo:bob.silverberg@gmail.com
push dateTue, 20 Dec 2016 17:48:38 +0000
reviewersmak
bugs1324760
milestone53.0a1
Bug 1324760 - Expose a function that clears plugin data in sanitize.js, r?mak MozReview-Commit-ID: A8AxhyzyEwk
browser/base/content/sanitize.js
browser/base/content/test/plugins/browser_clearplugindata.js
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -273,89 +273,26 @@ Sanitizer.prototype = {
           let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
                                    .getService(Ci.nsIMediaManagerService);
           mediaMgr.sanitizeDeviceIds(range && range[0]);
         } catch (ex) {
           seenException = ex;
         }
 
         // 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 promiseClearPluginCookies;
         try {
-          // We don't want to wait for this operation to complete...
-          promiseClearPluginCookies = this.promiseClearPluginCookies(range);
-
-          // ... at least, not for more than 10 seconds.
-          yield Promise.race([
-            promiseClearPluginCookies,
-            new Promise(resolve => setTimeout(resolve, 10000 /* 10 seconds */))
-          ]);
+          yield Sanitizer.clearPluginData(range);
         } catch (ex) {
           seenException = ex;
         }
 
-        // Detach waiting for plugin cookies to be cleared.
-        promiseClearPluginCookies.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;
         }
       }),
-
-      promiseClearPluginCookies: Task.async(function* (range) {
-        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) {
-            let refObj = {};
-            let probe = "";
-            if (/\bFlash\b/.test(tag.name)) {
-              probe = tag.loaded ? "FX_SANITIZE_LOADED_FLASH"
-                                 : "FX_SANITIZE_UNLOADED_FLASH";
-              TelemetryStopwatch.start(probe, refObj);
-            }
-            try {
-              let rv = yield 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 == Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
-                yield new Promise(resolve =>
-                  ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
-                );
-              }
-              if (probe) {
-                TelemetryStopwatch.finish(probe, refObj);
-              }
-            } catch (ex) {
-              // Ignore errors from plug-ins
-              if (probe) {
-                TelemetryStopwatch.cancel(probe, refObj);
-              }
-            }
-          }
-        }
-      })
     },
 
     offlineApps: {
       clear: Task.async(function* (range) {
         Components.utils.import("resource:///modules/offlineAppCache.jsm");
         // This doesn't wait for the cleanup to be complete.
         OfflineAppCacheHelper.clear();
       })
@@ -700,16 +637,22 @@ Sanitizer.prototype = {
         // Start the process of closing windows
         while (windowList.length) {
           windowList.pop().close();
         }
         newWindow.focus();
         yield promiseReady;
       })
     },
+
+    pluginData: {
+      clear: Task.async(function* (range) {
+        yield Sanitizer.clearPluginData(range);
+      }),
+    },
   }
 };
 
 // The preferences branch for the sanitizer.
 Sanitizer.PREF_DOMAIN = "privacy.sanitize.";
 // Whether we should sanitize on shutdown.
 Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
 // During a sanitization this is set to a json containing the array of items
@@ -769,16 +712,93 @@ Sanitizer.getClearRange = function(ts) {
       startDate = endDate - 86400000000; // 24*60*60*1000000
       break;
     default:
       throw "Invalid time span for clear private data: " + ts;
   }
   return [startDate, endDate];
 };
 
+Sanitizer.clearPluginData = Task.async(function* (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 = Task.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) {
+        let refObj = {};
+        let probe = "";
+        if (/\bFlash\b/.test(tag.name)) {
+          probe = tag.loaded ? "FX_SANITIZE_LOADED_FLASH"
+                             : "FX_SANITIZE_UNLOADED_FLASH";
+          TelemetryStopwatch.start(probe, refObj);
+        }
+        try {
+          let rv = yield 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 == Components.results.NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+            yield new Promise(resolve =>
+              ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, resolve)
+            );
+          }
+          if (probe) {
+            TelemetryStopwatch.finish(probe, refObj);
+          }
+        } catch (ex) {
+          // Ignore errors from plug-ins
+          if (probe) {
+            TelemetryStopwatch.cancel(probe, refObj);
+          }
+        }
+      }
+    }
+  });
+
+  try {
+    // We don't want to wait for this operation to complete...
+    promiseClearPluginData = promiseClearPluginData(range);
+
+    // ... at least, not for more than 10 seconds.
+    yield 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;
+  }
+});
+
 Sanitizer._prefs = null;
 Sanitizer.__defineGetter__("prefs", function()
 {
   return Sanitizer._prefs ? Sanitizer._prefs
     : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefService)
                          .getBranch(Sanitizer.PREF_DOMAIN);
 });
--- a/browser/base/content/test/plugins/browser_clearplugindata.js
+++ b/browser/base/content/test/plugins/browser_clearplugindata.js
@@ -41,87 +41,79 @@ add_task(function* () {
     setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
     setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
     if (gTestBrowser) {
       gBrowser.removeCurrentTab();
     }
     window.focus();
     gTestBrowser = null;
   });
-});
 
-add_task(function* () {
   Services.prefs.setBoolPref("plugins.click_to_play", true);
 
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Test Plug-in");
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_ENABLED, "Second Test Plug-in");
+});
 
+function* setPrefs(cookies, pluginData) {
   sanitizer = new Sanitizer();
   sanitizer.ignoreTimespan = false;
   sanitizer.prefDomain = "privacy.cpd.";
   let itemPrefs = gPrefService.getBranch(sanitizer.prefDomain);
   itemPrefs.setBoolPref("history", false);
   itemPrefs.setBoolPref("downloads", false);
   itemPrefs.setBoolPref("cache", false);
-  itemPrefs.setBoolPref("cookies", true); // plugin data
+  itemPrefs.setBoolPref("cookies", cookies);
   itemPrefs.setBoolPref("formdata", false);
   itemPrefs.setBoolPref("offlineApps", false);
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", false);
-});
+  itemPrefs.setBoolPref("pluginData", pluginData);
+}
 
-add_task(function* () {
+function* testClearingData(url) {
   // Load page to set data for the plugin.
   gBrowser.selectedTab = gBrowser.addTab();
   gTestBrowser = gBrowser.selectedBrowser;
 
-  yield promiseTabLoadEvent(gBrowser.selectedTab, testURL1);
+  yield promiseTabLoadEvent(gBrowser.selectedTab, url);
 
   yield promiseUpdatePluginBindings(gTestBrowser);
 
   ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
     "Data stored for sites");
 
-  // Clear 20 seconds ago
-  let now_uSec = Date.now() * 1000;
-  sanitizer.range = [now_uSec - 20 * 1000000, now_uSec];
-  yield sanitizer.sanitize();
-
-  ok(stored(["bar.com", "qux.com"]), "Data stored for sites");
-  ok(!stored(["foo.com"]), "Data cleared for foo.com");
-  ok(!stored(["baz.com"]), "Data cleared for baz.com");
-
-  // Clear everything
-  sanitizer.range = null;
-  yield sanitizer.sanitize();
-
-  ok(!stored(null), "All data cleared");
-
-  gBrowser.removeCurrentTab();
-  gTestBrowser = null;
-});
-
-add_task(function* () {
-  // Load page to set data for the plugin.
-  gBrowser.selectedTab = gBrowser.addTab();
-  gTestBrowser = gBrowser.selectedBrowser;
-
-  yield promiseTabLoadEvent(gBrowser.selectedTab, testURL2);
-
-  yield promiseUpdatePluginBindings(gTestBrowser);
-
-  ok(stored(["foo.com", "bar.com", "baz.com", "qux.com"]),
-    "Data stored for sites");
-
-  // Attempt to clear 20 seconds ago. The plugin will throw
+  // Clear 20 seconds ago.
+  // In the case of testURL2 the plugin will throw
   // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
   // clearing all data regardless of age.
   let now_uSec = Date.now() * 1000;
   sanitizer.range = [now_uSec - 20 * 1000000, now_uSec];
   yield sanitizer.sanitize();
 
+  if (url == testURL1) {
+    ok(stored(["bar.com", "qux.com"]), "Data stored for sites");
+    ok(!stored(["foo.com"]), "Data cleared for foo.com");
+    ok(!stored(["baz.com"]), "Data cleared for baz.com");
+
+    // Clear everything.
+    sanitizer.range = null;
+    yield sanitizer.sanitize();
+  }
+
   ok(!stored(null), "All data cleared");
 
   gBrowser.removeCurrentTab();
   gTestBrowser = null;
+}
+
+add_task(function* () {
+  // Test when santizing cookies.
+  yield setPrefs(true, false);
+  yield testClearingData(testURL1);
+  yield testClearingData(testURL2);
+
+  // Test when santizing pluginData.
+  yield setPrefs(false, true);
+  yield testClearingData(testURL1);
+  yield testClearingData(testURL2);
 });
-