Bug 1167238 - Part 2 - Convert sanitize.js to a module. r=mak draft
authorJohann Hofmann <jhofmann@mozilla.com>
Thu, 18 Jan 2018 15:58:03 +0100
changeset 751169 63b0f274f8e84e4003edb61f2dace78981884048
parent 751168 f2db143467752b3ff0c6eaf05a1991c064470a20
child 751170 e6696a5246db3f6ef9dd25aeab5d239d7fc7f8e3
push id97884
push userjhofmann@mozilla.com
push dateMon, 05 Feb 2018 17:07:12 +0000
reviewersmak
bugs1167238
milestone60.0a1
Bug 1167238 - Part 2 - Convert sanitize.js to a module. r=mak MozReview-Commit-ID: IWu7Dg61Vv4
browser/base/content/sanitize.js
browser/base/jar.mn
browser/modules/Sanitizer.jsm
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -111,17 +111,16 @@ browser.jar:
         content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
         content/browser/robot.ico                     (content/robot.ico)
         content/browser/static-robot.png              (content/static-robot.png)
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
-        content/browser/sanitize.js                   (content/sanitize.js)
         content/browser/sanitize.xul                  (content/sanitize.xul)
         content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
         content/browser/contentSearchUI.js            (content/contentSearchUI.js)
         content/browser/contentSearchUI.css           (content/contentSearchUI.css)
         content/browser/tabbrowser.css                (content/tabbrowser.css)
         content/browser/tabbrowser.xml                (content/tabbrowser.xml)
 *       content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
rename from browser/base/content/sanitize.js
rename to browser/modules/Sanitizer.jsm
--- a/browser/base/content/sanitize.js
+++ b/browser/modules/Sanitizer.jsm
@@ -1,75 +1,224 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* import-globals-from sanitizeDialog.js */
+var EXPORTED_SYMBOLS = ["Sanitizer"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 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",
   DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
   TelemetryStopwatch: "resource://gre/modules/TelemetryStopwatch.jsm",
   console: "resource://gre/modules/Console.jsm",
   setTimeout: "resource://gre/modules/Timer.jsm",
 });
 
-
 XPCOMUtils.defineLazyServiceGetter(this, "serviceWorkerManager",
                                    "@mozilla.org/serviceworkers/manager;1",
                                    "nsIServiceWorkerManager");
 XPCOMUtils.defineLazyServiceGetter(this, "quotaManagerService",
                                    "@mozilla.org/dom/quota-manager-service;1",
                                    "nsIQuotaManagerService");
 
-var {classes: Cc, interfaces: Ci, results: Cr} = Components;
-
 /**
  * A number of iterations after which to yield time back
  * to the system.
  */
 const YIELD_PERIOD = 10;
 
-function Sanitizer() {
-}
-Sanitizer.prototype = {
-  // warning to the caller: this one may raise an exception (e.g. bug #265028)
-  clearItem(aItemName) {
-    this.items[aItemName].clear();
+var Sanitizer = {
+  /**
+   * Whether we should sanitize on shutdown.
+   */
+  PREF_SANITIZE_ON_SHUTDOWN: "privacy.sanitize.sanitizeOnShutdown",
+
+  /**
+   * During a sanitization this is set to a json containing the array of items
+   * being sanitized, then cleared once the sanitization is complete.
+   * This allows to retry a sanitization on startup in case it was interrupted
+   * by a crash.
+   */
+  PREF_SANITIZE_IN_PROGRESS: "privacy.sanitize.sanitizeInProgress",
+
+  /**
+   * Whether the previous shutdown sanitization completed successfully.
+   * This is used to detect cases where we were supposed to sanitize on shutdown
+   * but due to a crash we were unable to.  In such cases there may not be any
+   * sanitization in progress, cause we didn't have a chance to start it yet.
+   */
+  PREF_SANITIZE_DID_SHUTDOWN: "privacy.sanitize.didShutdownSanitize",
+
+  /**
+   * The fallback timestamp used when no argument is given to
+   * Sanitizer.getClearRange.
+   */
+  PREF_TIMESPAN: "privacy.sanitize.timeSpan",
+
+  /**
+   * Time span constants corresponding to values of the privacy.sanitize.timeSpan
+   * pref.  Used to determine how much history to clear, for various items
+   */
+  TIMESPAN_EVERYTHING: 0,
+  TIMESPAN_HOUR:       1,
+  TIMESPAN_2HOURS:     2,
+  TIMESPAN_4HOURS:     3,
+  TIMESPAN_TODAY:      4,
+  TIMESPAN_5MIN:       5,
+  TIMESPAN_24HOURS:    6,
+
+  /**
+   * Shows a sanitization dialog to the user.
+   *
+   * @param [optional] parentWindow the window to use as
+   *                   parent for the created dialog.
+   */
+  showUI(parentWindow) {
+    let win = AppConstants.platform == "macosx" ?
+      null : // make this an app-modal window on Mac
+      parentWindow;
+    Services.ww.openWindow(win,
+                           "chrome://browser/content/sanitize.xul",
+                           "Sanitize",
+                           "chrome,titlebar,dialog,centerscreen,modal",
+                           null);
   },
 
-  prefDomain: "",
+  /**
+   * Performs startup tasks:
+   *  - Checks if sanitization was interrupted during last shutdown.
+   *  - Registers sanitize-on-shutdown.
+   */
+  async onStartup() {
+    // Check if we were interrupted during the last shutdown sanitization.
+    let shutdownSanitizationWasInterrupted =
+      Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
+      Services.prefs.getPrefType(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN) == Ci.nsIPrefBranch.PREF_INVALID;
+
+    if (Services.prefs.prefHasUserValue(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
+      // Reset the pref, so that if we crash before having a chance to
+      // sanitize on shutdown, we will do at the next startup.
+      // Flushing prefs has a cost, so do this only if necessary.
+      Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
+      Services.prefs.savePrefFile(null);
+    }
+
+    // Make sure that we are triggered during shutdown.
+    let shutdownClient = PlacesUtils.history.shutdownClient.jsclient;
+    // We need to pass to sanitize() (through sanitizeOnShutdown) a state object
+    // that tracks the status of the shutdown blocker. This `progress` object
+    // will be updated during sanitization and reported with the crash in case of
+    // a shutdown timeout.
+    // We use the `options` argument to pass the `progress` object to sanitize().
+    let progress = { isShutdown: true };
+    shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
+      () => sanitizeOnShutdown({ progress }),
+      {
+        fetchState: () => ({ progress })
+      }
+    );
+
+    // Check if Firefox crashed during a sanitization.
+    let lastInterruptedSanitization = Services.prefs.getStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
+    if (lastInterruptedSanitization) {
+      // If the json is invalid this will just throw and reject the Task.
+      let {itemsToClear, options} = JSON.parse(lastInterruptedSanitization);
+      await this.sanitize(itemsToClear, options);
+    } else if (shutdownSanitizationWasInterrupted) {
+      // Otherwise, could be we were supposed to sanitize on shutdown but we
+      // didn't have a chance, due to an earlier crash.
+      // In such a case, just redo a shutdown sanitize now, during startup.
+      await sanitizeOnShutdown();
+    }
+  },
 
-  getNameFromPreference(aPreferenceName) {
-    return aPreferenceName.substr(this.prefDomain.length);
+  /**
+   * Returns a 2 element array representing the start and end times,
+   * in the uSec-since-epoch format that PRTime likes. If we should
+   * clear everything, this function returns null.
+   *
+   * @param ts [optional] a timespan to convert to start and end time.
+   *                      Falls back to the privacy.sanitize.timeSpan preference
+   *                      if this argument is omitted.
+   *                      If this argument is provided, it has to be one of the
+   *                      Sanitizer.TIMESPAN_* constants. This function will
+   *                      throw an error otherwise.
+   *
+   * @return {Array} a 2-element Array containing the start and end times.
+   */
+  getClearRange(ts) {
+    if (ts === undefined)
+      ts = Services.prefs.getIntPref(Sanitizer.PREF_TIMESPAN);
+    if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+      return null;
+
+    // PRTime is microseconds while JS time is milliseconds
+    var endDate = Date.now() * 1000;
+    switch (ts) {
+      case Sanitizer.TIMESPAN_5MIN :
+        var startDate = endDate - 300000000; // 5*60*1000000
+        break;
+      case Sanitizer.TIMESPAN_HOUR :
+        startDate = endDate - 3600000000; // 1*60*60*1000000
+        break;
+      case Sanitizer.TIMESPAN_2HOURS :
+        startDate = endDate - 7200000000; // 2*60*60*1000000
+        break;
+      case Sanitizer.TIMESPAN_4HOURS :
+        startDate = endDate - 14400000000; // 4*60*60*1000000
+        break;
+      case Sanitizer.TIMESPAN_TODAY :
+        var d = new Date(); // Start with today
+        d.setHours(0); // zero us back to midnight...
+        d.setMinutes(0);
+        d.setSeconds(0);
+        startDate = d.valueOf() * 1000; // convert to epoch usec
+        break;
+      case Sanitizer.TIMESPAN_24HOURS :
+        startDate = endDate - 86400000000; // 24*60*60*1000000
+        break;
+      default:
+        throw "Invalid time span for clear private data: " + ts;
+    }
+    return [startDate, endDate];
   },
 
   /**
    * Deletes privacy sensitive data in a batch, according to user preferences.
    * Returns a promise which is resolved if no errors occurred.  If an error
    * occurs, a message is reported to the console and all other items are still
    * cleared before the promise is finally rejected.
    *
    * @param [optional] itemsToClear
    *        Array of items to be cleared. if specified only those
    *        items get cleared, irrespectively of the preference settings.
    * @param [optional] options
-   *        Object whose properties are options for this sanitization.
-   *        TODO (bug 1167238) document options here.
+   *        Object whose properties are options for this sanitization:
+   *         - ignoreTimespan (default: true): Time span only makes sense in
+   *           certain cases.  Consumers who want to only clear some private
+   *           data can opt in by setting this to false, and can optionally
+   *           specify a specific range.
+   *           If timespan is not ignored, and range is not set, sanitize() will
+   *           use the value of the timespan pref to determine a range.
+   *         - range (default: null)
+   *         - prefDomain (default: "privacy.cpd."): indicates the preferences
+   *           branch to collect the list of items to sanitize from.
+   *         - privateStateForNewWindow (default: "non-private"): when clearing
+   *           open windows, defines the private state for the newly opened window.
    */
   async sanitize(itemsToClear = null, options = {}) {
     let progress = options.progress || {};
-    let promise = this._sanitize(itemsToClear, progress);
+    let promise = sanitizeInternal(this.items, itemsToClear, progress, options);
 
     // Depending on preferences, the sanitizer may perform asynchronous
     // work before it starts cleaning up the Places database (e.g. closing
     // windows). We need to make sure that the connection to that database
     // hasn't been closed by the time we use it.
     // Though, if this is a sanitize on shutdown, we already have a blocker.
     if (!progress.isShutdown) {
       let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
@@ -86,118 +235,16 @@ Sanitizer.prototype = {
 
     try {
       await promise;
     } finally {
       Services.obs.notifyObservers(null, "sanitizer-sanitization-complete");
     }
   },
 
-  async _sanitize(aItemsToClear, progress = {}) {
-    let seenError = false;
-    let itemsToClear;
-    if (Array.isArray(aItemsToClear)) {
-      // Shallow copy the array, as we are going to modify
-      // it in place later.
-      itemsToClear = [...aItemsToClear];
-    } else {
-      let branch = Services.prefs.getBranch(this.prefDomain);
-      itemsToClear = Object.keys(this.items).filter(itemName => {
-        try {
-          return branch.getBoolPref(itemName);
-        } catch (ex) {
-          return false;
-        }
-      });
-    }
-
-    // Store the list of items to clear, in case we are killed before we
-    // get a chance to complete.
-    Services.prefs.setStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS,
-                                 JSON.stringify(itemsToClear));
-
-    // Store the list of items to clear, for debugging/forensics purposes
-    for (let k of itemsToClear) {
-      progress[k] = "ready";
-    }
-
-    // Ensure open windows get cleared first, if they're in our list, so that they don't stick
-    // around in the recently closed windows list, and so we can cancel the whole thing
-    // if the user selects to keep a window open from a beforeunload prompt.
-    let openWindowsIndex = itemsToClear.indexOf("openWindows");
-    if (openWindowsIndex != -1) {
-      itemsToClear.splice(openWindowsIndex, 1);
-      await this.items.openWindows.clear();
-      progress.openWindows = "cleared";
-    }
-
-    // Cache the range of times to clear
-    let range = null;
-    // If we ignore timespan, clear everything,
-    // otherwise, pick a range.
-    if (!this.ignoreTimespan) {
-      range = this.range || Sanitizer.getClearRange();
-    }
-
-    // For performance reasons we start all the clear tasks at once, then wait
-    // for their promises later.
-    // Some of the clear() calls may raise exceptions (for example bug 265028),
-    // we catch and store them, but continue to sanitize as much as possible.
-    // Callers should check returned errors and give user feedback
-    // about items that could not be sanitized
-    let refObj = {};
-    TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
-
-    let annotateError = (name, ex) => {
-      progress[name] = "failed";
-      seenError = true;
-      console.error("Error sanitizing " + name, ex);
-    };
-
-    // Array of objects in form { name, promise }.
-    // `name` is the item's name and `promise` may be a promise, if the
-    // sanitization is asynchronous, or the function return value, otherwise.
-    let handles = [];
-    for (let name of itemsToClear) {
-      let item = this.items[name];
-      try {
-        // Catch errors here, so later we can just loop through these.
-        handles.push({ name,
-                       promise: item.clear(range)
-                                    .then(() => progress[name] = "cleared",
-                                          ex => annotateError(name, ex))
-                     });
-      } catch (ex) {
-        annotateError(name, ex);
-      }
-    }
-    for (let handle of handles) {
-      progress[handle.name] = "blocking";
-      await handle.promise;
-    }
-
-    // Sanitization is complete.
-    TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
-    // Reset the inProgress preference since we were not killed during
-    // sanitization.
-    Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
-    progress = {};
-    if (seenError) {
-      throw new Error("Error sanitizing");
-    }
-  },
-
-  // Time span only makes sense in certain cases.  Consumers who want
-  // to only clear some private data can opt in by setting this to false,
-  // and can optionally specify a specific range.  If timespan is not ignored,
-  // and range is not set, sanitize() will use the value of the timespan
-  // pref to determine a range
-  ignoreTimespan: true,
-  range: null,
-
   items: {
     cache: {
       async clear(range) {
         let seenException;
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
 
         try {
@@ -266,17 +313,17 @@ Sanitizer.prototype = {
                                    .getService(Ci.nsIMediaManagerService);
           mediaMgr.sanitizeDeviceIds(range && range[0]);
         } catch (ex) {
           seenException = ex;
         }
 
         // Clear plugin data.
         try {
-          await Sanitizer.clearPluginData(range);
+          await clearPluginData(range);
         } catch (ex) {
           seenException = ex;
         }
 
         if (seenException) {
           throw seenException;
         }
       },
@@ -556,33 +603,32 @@ Sanitizer.prototype = {
         TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
         if (seenException) {
           throw seenException;
         }
       }
     },
 
     openWindows: {
-      privateStateForNewWindow: "non-private",
-      _canCloseWindow(aWindow) {
-        if (aWindow.CanCloseWindow()) {
+      _canCloseWindow(win) {
+        if (win.CanCloseWindow()) {
           // We already showed PermitUnload for the window, so let's
           // make sure we don't do it again when we actually close the
           // window.
-          aWindow.skipNextCanClose = true;
+          win.skipNextCanClose = true;
           return true;
         }
         return false;
       },
-      _resetAllWindowClosures(aWindowList) {
-        for (let win of aWindowList) {
+      _resetAllWindowClosures(windowList) {
+        for (let win of windowList) {
           win.skipNextCanClose = false;
         }
       },
-      async clear() {
+      async clear(range, privateStateForNewWindow = "non-private") {
         // NB: this closes all *browser* windows, not other windows like the library, about window,
         // browser console, etc.
 
         // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
         // dialogs
         let existingWindow = Services.appShell.hiddenDOMWindow;
         let startDate = existingWindow.performance.now();
 
@@ -612,17 +658,17 @@ Sanitizer.prototype = {
 
         let refObj = {};
         TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
 
         // First create a new window. We do this first so that on non-mac, we don't
         // accidentally close the app by closing all the windows.
         let handler = Cc["@mozilla.org/browser/clh;1"].getService(Ci.nsIBrowserHandler);
         let defaultArgs = handler.defaultArgs;
-        let features = "chrome,all,dialog=no," + this.privateStateForNewWindow;
+        let features = "chrome,all,dialog=no," + privateStateForNewWindow;
         let newWindow = existingWindow.openDialog("chrome://browser/content/", "_blank",
                                                   features, defaultArgs);
 
         let onFullScreen = null;
         if (AppConstants.platform == "macosx") {
           onFullScreen = function(e) {
             newWindow.removeEventListener("fullscreen", onFullScreen);
             let docEl = newWindow.document.documentElement;
@@ -684,89 +730,117 @@ Sanitizer.prototype = {
         }
         newWindow.focus();
         await promiseReady;
       }
     },
 
     pluginData: {
       async clear(range) {
-        await Sanitizer.clearPluginData(range);
+        await 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
-// being sanitized, then cleared once the sanitization is complete.
-// This allows to retry a sanitization on startup in case it was interrupted
-// by a crash.
-Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
-// Whether the previous shutdown sanitization completed successfully.
-// This is used to detect cases where we were supposed to sanitize on shutdown
-// but due to a crash we were unable to.  In such cases there may not be any
-// sanitization in progress, cause we didn't have a chance to start it yet.
-Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
+async function sanitizeInternal(items, aItemsToClear, progress, options = {}) {
+  let { prefDomain = "privacy.cpd.", ignoreTimespan = true, range } = options;
+  let seenError = false;
+  let itemsToClear;
+  if (Array.isArray(aItemsToClear)) {
+    // Shallow copy the array, as we are going to modify
+    // it in place later.
+    itemsToClear = [...aItemsToClear];
+  } else {
+    let branch = Services.prefs.getBranch(prefDomain);
+    itemsToClear = Object.keys(items).filter(itemName => {
+      try {
+        return branch.getBoolPref(itemName);
+      } catch (ex) {
+        return false;
+      }
+    });
+  }
 
-// Time span constants corresponding to values of the privacy.sanitize.timeSpan
-// pref.  Used to determine how much history to clear, for various items
-Sanitizer.TIMESPAN_EVERYTHING = 0;
-Sanitizer.TIMESPAN_HOUR       = 1;
-Sanitizer.TIMESPAN_2HOURS     = 2;
-Sanitizer.TIMESPAN_4HOURS     = 3;
-Sanitizer.TIMESPAN_TODAY      = 4;
-Sanitizer.TIMESPAN_5MIN       = 5;
-Sanitizer.TIMESPAN_24HOURS    = 6;
+  // Store the list of items to clear, in case we are killed before we
+  // get a chance to complete.
+  Services.prefs.setStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS,
+                               JSON.stringify({itemsToClear, options}));
+
+  // Store the list of items to clear, for debugging/forensics purposes
+  for (let k of itemsToClear) {
+    progress[k] = "ready";
+  }
+
+  // Ensure open windows get cleared first, if they're in our list, so that
+  // they don't stick around in the recently closed windows list, and so we
+  // can cancel the whole thing if the user selects to keep a window open
+  // from a beforeunload prompt.
+  let openWindowsIndex = itemsToClear.indexOf("openWindows");
+  if (openWindowsIndex != -1) {
+    itemsToClear.splice(openWindowsIndex, 1);
+    await items.openWindows.clear(null, options);
+    progress.openWindows = "cleared";
+  }
+
+  // If we ignore timespan, clear everything,
+  // otherwise, pick a range.
+  if (!ignoreTimespan && !range) {
+    range = Sanitizer.getClearRange();
+  }
 
-// Return a 2 element array representing the start and end times,
-// in the uSec-since-epoch format that PRTime likes.  If we should
-// clear everything, return null.  Use ts if it is defined; otherwise
-// use the timeSpan pref.
-Sanitizer.getClearRange = function(ts) {
-  if (ts === undefined)
-    ts = Sanitizer.prefs.getIntPref("timeSpan");
-  if (ts === Sanitizer.TIMESPAN_EVERYTHING)
-    return null;
+  // For performance reasons we start all the clear tasks at once, then wait
+  // for their promises later.
+  // Some of the clear() calls may raise exceptions (for example bug 265028),
+  // we catch and store them, but continue to sanitize as much as possible.
+  // Callers should check returned errors and give user feedback
+  // about items that could not be sanitized
+  let refObj = {};
+  TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
+
+  let annotateError = (name, ex) => {
+    progress[name] = "failed";
+    seenError = true;
+    console.error("Error sanitizing " + name, ex);
+  };
 
-  // PRTime is microseconds while JS time is milliseconds
-  var endDate = Date.now() * 1000;
-  switch (ts) {
-    case Sanitizer.TIMESPAN_5MIN :
-      var startDate = endDate - 300000000; // 5*60*1000000
-      break;
-    case Sanitizer.TIMESPAN_HOUR :
-      startDate = endDate - 3600000000; // 1*60*60*1000000
-      break;
-    case Sanitizer.TIMESPAN_2HOURS :
-      startDate = endDate - 7200000000; // 2*60*60*1000000
-      break;
-    case Sanitizer.TIMESPAN_4HOURS :
-      startDate = endDate - 14400000000; // 4*60*60*1000000
-      break;
-    case Sanitizer.TIMESPAN_TODAY :
-      var d = new Date(); // Start with today
-      d.setHours(0); // zero us back to midnight...
-      d.setMinutes(0);
-      d.setSeconds(0);
-      startDate = d.valueOf() * 1000; // convert to epoch usec
-      break;
-    case Sanitizer.TIMESPAN_24HOURS :
-      startDate = endDate - 86400000000; // 24*60*60*1000000
-      break;
-    default:
-      throw "Invalid time span for clear private data: " + ts;
+  // Array of objects in form { name, promise }.
+  // `name` is the item's name and `promise` may be a promise, if the
+  // sanitization is asynchronous, or the function return value, otherwise.
+  let handles = [];
+  for (let name of itemsToClear) {
+    let item = items[name];
+    try {
+      // Catch errors here, so later we can just loop through these.
+      handles.push({ name,
+                     promise: item.clear(range, options)
+                                  .then(() => progress[name] = "cleared",
+                                        ex => annotateError(name, ex))
+                   });
+    } catch (ex) {
+      annotateError(name, ex);
+    }
   }
-  return [startDate, endDate];
-};
+  for (let handle of handles) {
+    progress[handle.name] = "blocking";
+    await handle.promise;
+  }
 
-Sanitizer.clearPluginData = async function(range) {
+  // Sanitization is complete.
+  TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
+  // Reset the inProgress preference since we were not killed during
+  // sanitization.
+  Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
+  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.
@@ -818,96 +892,22 @@ Sanitizer.clearPluginData = async functi
     // 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 = Services.prefs.getBranch(Sanitizer.PREF_DOMAIN);
-});
-
-// Shows sanitization UI
-Sanitizer.showUI = function(aParentWindow) {
-  let win = AppConstants.platform == "macosx" ?
-    null : // make this an app-modal window on Mac
-    aParentWindow;
-  Services.ww.openWindow(win,
-                         "chrome://browser/content/sanitize.xul",
-                         "Sanitize",
-                         "chrome,titlebar,dialog,centerscreen,modal",
-                         null);
-};
-
-/**
- * Deletes privacy sensitive data in a batch, optionally showing the
- * sanitize UI, according to user preferences
- */
-Sanitizer.sanitize = function(aParentWindow) {
-  Sanitizer.showUI(aParentWindow);
-};
-
-Sanitizer.onStartup = async function() {
-  // Check if we were interrupted during the last shutdown sanitization.
-  let shutownSanitizationWasInterrupted =
-    Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
-    Services.prefs.getPrefType(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN) == Ci.nsIPrefBranch.PREF_INVALID;
+}
 
-  if (Services.prefs.prefHasUserValue(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
-    // Reset the pref, so that if we crash before having a chance to
-    // sanitize on shutdown, we will do at the next startup.
-    // Flushing prefs has a cost, so do this only if necessary.
-    Services.prefs.clearUserPref(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
-    Services.prefs.savePrefFile(null);
-  }
-
-  // Make sure that we are triggered during shutdown.
-  let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
-                         .getService(Ci.nsPIPlacesDatabase)
-                         .shutdownClient
-                         .jsclient;
-  // We need to pass to sanitize() (through sanitizeOnShutdown) a state object
-  // that tracks the status of the shutdown blocker. This `progress` object
-  // will be updated during sanitization and reported with the crash in case of
-  // a shutdown timeout.
-  // We use the `options` argument to pass the `progress` object to sanitize().
-  let progress = { isShutdown: true };
-  shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
-    () => sanitizeOnShutdown({ progress }),
-    {
-      fetchState: () => ({ progress })
-    }
-  );
-
-  // Check if Firefox crashed during a sanitization.
-  let lastInterruptedSanitization = Services.prefs.getStringPref(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
-  if (lastInterruptedSanitization) {
-    let s = new Sanitizer();
-    // If the json is invalid this will just throw and reject the Task.
-    let itemsToClear = JSON.parse(lastInterruptedSanitization);
-    await s.sanitize(itemsToClear);
-  } else if (shutownSanitizationWasInterrupted) {
-    // Otherwise, could be we were supposed to sanitize on shutdown but we
-    // didn't have a chance, due to an earlier crash.
-    // In such a case, just redo a shutdown sanitize now, during startup.
-    await sanitizeOnShutdown();
-  }
-};
-
-var sanitizeOnShutdown = async function(options = {}) {
+async function sanitizeOnShutdown(options = {}) {
   if (!Services.prefs.getBoolPref(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
     return;
   }
   // Need to sanitize upon shutdown
-  let s = new Sanitizer();
-  s.prefDomain = "privacy.clearOnShutdown.";
-  await s.sanitize(null, options);
+  options.prefDomain = "privacy.clearOnShutdown.";
+  await Sanitizer.sanitize(null, options);
   // We didn't crash during shutdown sanitization, so annotate it to avoid
   // sanitizing again on startup.
   Services.prefs.setBoolPref(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
   Services.prefs.savePrefFile(null);
-};
+}