Bug 1167238 - Moved sanitize.js to Sanitizer.jsm module. r?mak draft
authorAlex Johnson <me@alex-johnson.net>
Tue, 29 Dec 2015 11:48:12 -0500
changeset 323332 445e8d2a30f771813c06a4b1bf2d871bda68b1f7
parent 322694 b67316254602a63bf4e568198a5c7d3288a9db27
child 513195 3d421e93433af033cef9ca57155249eab0621723
push id9702
push userme@alex-johnson.net
push dateWed, 20 Jan 2016 02:07:13 +0000
reviewersmak
bugs1167238
milestone46.0a1
Bug 1167238 - Moved sanitize.js to Sanitizer.jsm module. r?mak
browser/base/content/sanitize.js
browser/base/content/sanitize.xul
browser/base/content/test/general/browser_bug409624.js
browser/base/content/test/general/browser_purgehistory_clears_sh.js
browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
browser/base/content/test/general/browser_sanitize-sitepermissions.js
browser/base/content/test/general/browser_sanitize-timespans.js
browser/base/content/test/general/browser_sanitizeDialog.js
browser/base/content/test/newtab/browser_newtab_bug722273.js
browser/base/content/test/newtab/head.js
browser/base/content/test/plugins/browser_clearplugindata.js
browser/base/jar.mn
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/nsBrowserGlue.js
browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
browser/modules/Sanitizer.jsm
testing/puppeteer/firefox/firefox_puppeteer/api/utils.py
toolkit/components/thumbnails/test/browser_thumbnails_storage.js
deleted file mode 100644
--- a/browser/base/content/sanitize.js
+++ /dev/null
@@ -1,733 +0,0 @@
-// -*- 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/. */
-
-Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-Components.utils.import("resource://gre/modules/Services.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
-                                  "resource://gre/modules/AppConstants.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
-                                  "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
-                                  "resource://gre/modules/FormHistory.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
-                                  "resource://gre/modules/Downloads.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-                                  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-                                  "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
-                                  "resource:///modules/DownloadsCommon.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
-                                  "resource://gre/modules/TelemetryStopwatch.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                  "resource://gre/modules/Console.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
-                                  "resource://gre/modules/Preferences.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
-                                  "resource://gre/modules/Timer.jsm");
-
-/**
- * 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: function (aItemName)
-  {
-    this.items[aItemName].clear();
-  },
-
-  prefDomain: "",
-
-  getNameFromPreference: function (aPreferenceName)
-  {
-    return aPreferenceName.substr(this.prefDomain.length);
-  },
-
-  /**
-   * 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.
-   *
-   * If the consumer specifies the (optional) array parameter, only those
-   * items get cleared (irrespective of the preference settings)
-   */
-  sanitize: Task.async(function*(aItemsToClear = null) {
-    let progress = {};
-    let promise = this._sanitize(aItemsToClear, progress);
-
-    //
-    // 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.
-    //
-    let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
-       .getService(Ci.nsPIPlacesDatabase)
-       .shutdownClient
-       .jsclient;
-
-    shutdownClient.addBlocker("sanitize.js: Sanitize",
-      promise,
-      {
-        fetchState: () => {
-          return { progress };
-        }
-      }
-    );
-    try {
-      yield promise;
-    } finally {
-      Services.obs.notifyObservers(null, "sanitizer-sanitization-complete", "");
-    }
-  }),
-
-  _sanitize: Task.async(function*(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.
-    Preferences.set(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);
-      yield 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 (let itemName of itemsToClear) {
-      let item = this.items[itemName];
-      if (!("clear" in item)) {
-        progress[itemName] = "`clear` not in item";
-        continue;
-      }
-      item.range = range;
-      // Some of these clear() may raise exceptions (see bug #265028)
-      // to sanitize as much as possible, we catch and store them,
-      // rather than fail fast.
-      // Callers should check returned errors and give user feedback
-      // about items that could not be sanitized
-      let refObj = {};
-      try {
-        TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
-        yield item.clear();
-        progress[itemName] = "cleared";
-      } catch(er) {
-        progress[itemName] = "failed";
-        seenError = true;
-        console.error("Error sanitizing " + itemName, er);
-      } finally {
-        TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
-      }
-    }
-
-    // Sanitization is complete.
-    Preferences.reset(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: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
-
-        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
-                    getService(Ci.nsICacheStorageService);
-        try {
-          // Cache doesn't consult timespan, nor does it have the
-          // facility for timespan-based eviction.  Wipe it.
-          cache.clear();
-        } catch(er) {}
-
-        var imageCache = Cc["@mozilla.org/image/tools;1"].
-                         getService(Ci.imgITools).getImgCacheForDocument(null);
-        try {
-          imageCache.clearCache(false); // true=chrome, false=content
-        } catch(er) {}
-
-        TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
-      }
-    },
-
-    cookies: {
-      clear: Task.async(function* ()
-      {
-        let yieldCounter = 0;
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_COOKIES", refObj);
-        TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
-
-        var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
-                                  .getService(Ci.nsICookieManager);
-        if (this.range) {
-          // Iterate through the cookies and delete any created after our cutoff.
-          var cookiesEnum = cookieMgr.enumerator;
-          while (cookiesEnum.hasMoreElements()) {
-            var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
-
-            if (cookie.creationTime > this.range[0]) {
-              // This cookie was created after our cutoff, clear it
-              cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
-
-              if (++yieldCounter % YIELD_PERIOD == 0) {
-                yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
-              }
-            }
-          }
-        }
-        else {
-          // Remove everything
-          cookieMgr.removeAll();
-          yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
-        }
-        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
-
-        // Clear deviceIds. Done asynchronously (returns before complete).
-        let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
-                                 .getService(Ci.nsIMediaManagerService);
-        mediaMgr.sanitizeDeviceIds(this.range && this.range[0]);
-
-        // Clear plugin data.
-        TelemetryStopwatch.start("FX_SANITIZE_PLUGINS", refObj);
-        yield this.promiseClearPluginCookies();
-        TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
-        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
-      }),
-
-      promiseClearPluginCookies: Task.async(function*() {
-        const phInterface = Ci.nsIPluginHost;
-        const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
-        let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
-
-        // Determine age range in seconds. (-1 means clear all.) We don't know
-        // that this.range[1] is actually now, so we compute age range based
-        // on the lower bound. If this.range results in a negative age, do
-        // nothing.
-        let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000) : -1;
-        if (!this.range || age >= 0) {
-          let tags = ph.getPluginTags();
-          for (let tag of tags) {
-            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)
-                );
-              }
-            } catch (ex) {
-              // Ignore errors from plug-ins
-            }
-          }
-        }
-      })
-    },
-
-    offlineApps: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS", refObj);
-        Components.utils.import("resource:///modules/offlineAppCache.jsm");
-        OfflineAppCacheHelper.clear();
-        TelemetryStopwatch.finish("FX_SANITIZE_OFFLINEAPPS", refObj);
-      }
-    },
-
-    history: {
-      clear: Task.async(function* ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
-        try {
-          if (this.range) {
-            yield PlacesUtils.history.removeVisitsByFilter({
-              beginDate: new Date(this.range[0] / 1000),
-              endDate: new Date(this.range[1] / 1000)
-            });
-          } else {
-            // Remove everything.
-            yield PlacesUtils.history.clear();
-          }
-
-          try {
-            let clearStartingTime = this.range ? String(this.range[0]) : "";
-            Services.obs.notifyObservers(null, "browser:purge-session-history", clearStartingTime);
-          } catch (e) { }
-
-          try {
-            let predictor = Components.classes["@mozilla.org/network/predictor;1"]
-                                      .getService(Components.interfaces.nsINetworkPredictor);
-            predictor.reset();
-          } catch (e) {
-            console.error("Error while resetting the predictor", e);
-          }
-        } finally {
-          TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
-        }
-      })
-    },
-
-    formdata: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
-
-        // Clear undo history of all searchBars
-        var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
-                                      .getService(Components.interfaces.nsIWindowMediator);
-        var windows = windowManager.getEnumerator("navigator:browser");
-        while (windows.hasMoreElements()) {
-          let currentWindow = windows.getNext();
-          let currentDocument = currentWindow.document;
-          let searchBar = currentDocument.getElementById("searchbar");
-          if (searchBar)
-            searchBar.textbox.reset();
-          let tabBrowser = currentWindow.gBrowser;
-          for (let tab of tabBrowser.tabs) {
-            if (tabBrowser.isFindBarInitialized(tab))
-              tabBrowser.getFindBar(tab).clear();
-          }
-          // Clear any saved find value
-          tabBrowser._lastFindValue = "";
-        }
-
-        let change = { op: "remove" };
-        if (this.range) {
-          [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
-        }
-        FormHistory.update(change);
-
-        TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
-      }
-    },
-
-    downloads: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
-        Task.spawn(function*() {
-          let filterByTime = null;
-          if (this.range) {
-            // Convert microseconds back to milliseconds for date comparisons.
-            let rangeBeginMs = this.range[0] / 1000;
-            let rangeEndMs = this.range[1] / 1000;
-            filterByTime = download => download.startTime >= rangeBeginMs &&
-                                       download.startTime <= rangeEndMs;
-          }
-
-          // Clear all completed/cancelled downloads
-          let list = yield Downloads.getList(Downloads.ALL);
-          list.removeFinished(filterByTime);
-          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
-        }.bind(this)).then(null, error => {
-          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
-          Components.utils.reportError(error);
-        });
-      }
-    },
-
-    sessions: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
-
-        // clear all auth tokens
-        var sdr = Components.classes["@mozilla.org/security/sdr;1"]
-                            .getService(Components.interfaces.nsISecretDecoderRing);
-        sdr.logoutAndTeardown();
-
-        // clear FTP and plain HTTP auth sessions
-        var os = Components.classes["@mozilla.org/observer-service;1"]
-                           .getService(Components.interfaces.nsIObserverService);
-        os.notifyObservers(null, "net:clear-active-logins", null);
-
-        TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
-      }
-    },
-
-    siteSettings: {
-      clear: function ()
-      {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
-
-        // Clear site-specific permissions like "Allow this site to open popups"
-        // we ignore the "end" range and hope it is now() - none of the
-        // interfaces used here support a true range anyway.
-        let startDateMS = this.range == null ? null : this.range[0] / 1000;
-        var pm = Components.classes["@mozilla.org/permissionmanager;1"]
-                           .getService(Components.interfaces.nsIPermissionManager);
-        if (startDateMS == null) {
-          pm.removeAll();
-        } else {
-          pm.removeAllSince(startDateMS);
-        }
-
-        // Clear site-specific settings like page-zoom level
-        var cps = Components.classes["@mozilla.org/content-pref/service;1"]
-                            .getService(Components.interfaces.nsIContentPrefService2);
-        if (startDateMS == null) {
-          cps.removeAllDomains(null);
-        } else {
-          cps.removeAllDomainsSince(startDateMS, null);
-        }
-
-        // Clear "Never remember passwords for this site", which is not handled by
-        // the permission manager
-        // (Note the login manager doesn't support date ranges yet, and bug
-        //  1058438 is calling for loginSaving stuff to end up in the
-        // permission manager)
-        var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
-                              .getService(Components.interfaces.nsILoginManager);
-        var hosts = pwmgr.getAllDisabledHosts();
-        for (var host of hosts) {
-          pwmgr.setLoginSavingEnabled(host, true);
-        }
-
-        // Clear site security settings - no support for ranges in this
-        // interface either, so we clearAll().
-        var sss = Cc["@mozilla.org/ssservice;1"]
-                    .getService(Ci.nsISiteSecurityService);
-        sss.clearAll();
-
-        // Clear all push notification subscriptions
-        try {
-          var push = Cc["@mozilla.org/push/Service;1"]
-                       .getService(Ci.nsIPushService);
-          push.clearForDomain("*", status => {
-            if (!Components.isSuccessCode(status)) {
-              dump("Error clearing Web Push data: " + status + "\n");
-            }
-          });
-        } catch (e) {
-          dump("Web Push may not be available.\n");
-        }
-
-        TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
-      }
-    },
-
-    openWindows: {
-      privateStateForNewWindow: "non-private",
-      _canCloseWindow: function(aWindow) {
-        if (aWindow.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;
-          return true;
-        }
-      },
-      _resetAllWindowClosures: function(aWindowList) {
-        for (let win of aWindowList) {
-          win.skipNextCanClose = false;
-        }
-      },
-      clear: Task.async(function*() {
-        // 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();
-
-        // First check if all these windows are OK with being closed:
-        let windowEnumerator = Services.wm.getEnumerator("navigator:browser");
-        let windowList = [];
-        while (windowEnumerator.hasMoreElements()) {
-          let someWin = windowEnumerator.getNext();
-          windowList.push(someWin);
-          // If someone says "no" to a beforeunload prompt, we abort here:
-          if (!this._canCloseWindow(someWin)) {
-            this._resetAllWindowClosures(windowList);
-            throw new Error("Sanitize could not close windows: cancelled by user");
-          }
-
-          // ...however, beforeunload prompts spin the event loop, and so the code here won't get
-          // hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
-          // started prompting, stop, because the user might not even remember initiating the
-          // 'forget', and the timespans will be all wrong by now anyway:
-          if (existingWindow.performance.now() > (startDate + 60 * 1000)) {
-            this._resetAllWindowClosures(windowList);
-            throw new Error("Sanitize could not close windows: timeout");
-          }
-        }
-
-        // If/once we get here, we should actually be able to close all windows.
-
-        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 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;
-            let sizemode = docEl.getAttribute("sizemode");
-            if (!newWindow.fullScreen && sizemode == "fullscreen") {
-              docEl.setAttribute("sizemode", "normal");
-              e.preventDefault();
-              e.stopPropagation();
-              return false;
-            }
-          }
-          newWindow.addEventListener("fullscreen", onFullScreen);
-        }
-
-        let promiseReady = new Promise(resolve => {
-          // Window creation and destruction is asynchronous. We need to wait
-          // until all existing windows are fully closed, and the new window is
-          // fully open, before continuing. Otherwise the rest of the sanitizer
-          // could run too early (and miss new cookies being set when a page
-          // closes) and/or run too late (and not have a fully-formed window yet
-          // in existence). See bug 1088137.
-          let newWindowOpened = false;
-          let onWindowOpened = function(subject, topic, data) {
-            if (subject != newWindow)
-              return;
-
-            Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
-            if (AppConstants.platform == "macosx") {
-              newWindow.removeEventListener("fullscreen", onFullScreen);
-            }
-            newWindowOpened = true;
-            // If we're the last thing to happen, invoke callback.
-            if (numWindowsClosing == 0) {
-              TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
-              resolve();
-            }
-          }
-
-          let numWindowsClosing = windowList.length;
-          let onWindowClosed = function() {
-            numWindowsClosing--;
-            if (numWindowsClosing == 0) {
-              Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
-              // If we're the last thing to happen, invoke callback.
-              if (newWindowOpened) {
-                TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
-                resolve();
-              }
-            }
-          }
-          Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
-          Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
-        });
-
-        // Start the process of closing windows
-        while (windowList.length) {
-          windowList.pop().close();
-        }
-        newWindow.focus();
-        yield promiseReady;
-      })
-    },
-  }
-};
-
-// "Static" members
-Sanitizer.PREF_DOMAIN = "privacy.sanitize.";
-Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
-Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
-Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
-
-// 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;
-
-// 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;
-
-  // 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];
-};
-
-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);
-});
-
-// Shows sanitization UI
-Sanitizer.showUI = function(aParentWindow)
-{
-  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
-                     .getService(Components.interfaces.nsIWindowWatcher);
-  let win = AppConstants.platform == "macosx" ?
-    null: // make this an app-modal window on Mac
-    aParentWindow;
-  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 = Task.async(function*() {
-  // Make sure that we are triggered during shutdown, at the right time.
-  let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
-     .getService(Ci.nsPIPlacesDatabase)
-     .shutdownClient
-     .jsclient;
-
-  shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
-    () => Sanitizer.onShutdown());
-
-    // One time migration to remove support for the clear saved passwords on exit feature.
-    if (!Services.prefs.getBoolPref("privacy.sanitize.migrateClearSavedPwdsOnExit")) {
-      let deprecatedPref = "privacy.clearOnShutdown.passwords";
-      let doUpdate = Services.prefs.prefHasUserValue(deprecatedPref) &&
-                     Services.prefs.getBoolPref(deprecatedPref);
-      if (doUpdate) {
-        Services.logins.removeAllLogins();
-        Services.prefs.setBoolPref("signon.rememberSignons", false);
-      }
-      Services.prefs.clearUserPref(deprecatedPref);
-      Services.prefs.setBoolPref("privacy.sanitize.migrateClearSavedPwdsOnExit", true);
-  }
-
-  // Handle incomplete sanitizations
-  if (Preferences.has(Sanitizer.PREF_SANITIZE_IN_PROGRESS)) {
-    // Firefox crashed during sanitization.
-    let s = new Sanitizer();
-    let json = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
-    let itemsToClear = JSON.parse(json);
-    yield s.sanitize(itemsToClear);
-  }
-  if (Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
-    // Firefox crashed before having a chance to sanitize during shutdown.
-    // (note that if Firefox crashed during shutdown sanitization, we
-    // will hit both `if` so we will run a second double-sanitization).
-    yield Sanitizer.onShutdown();
-  }
-});
-
-Sanitizer.onShutdown = Task.async(function*() {
-  if (!Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
-    return;
-  }
-  // Need to sanitize upon shutdown
-  let s = new Sanitizer();
-  s.prefDomain = "privacy.clearOnShutdown.";
-  yield s.sanitize();
-  Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
-});
--- a/browser/base/content/sanitize.xul
+++ b/browser/base/content/sanitize.xul
@@ -28,27 +28,25 @@
             noneverythingtitle="&sanitizeDialog2.title;"
             style="width: &sanitizeDialog2.width;;"
             ondialogaccept="return gSanitizePromptDialog.sanitize();">
 
   <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
     <stringbundle id="bundleBrowser"
                   src="chrome://browser/locale/browser.properties"/>
 
-    <script type="application/javascript"
-            src="chrome://browser/content/sanitize.js"/>
-
 #ifdef CRH_DIALOG_TREE_VIEW
     <script type="application/javascript"
             src="chrome://global/content/globalOverlay.js"/>
     <script type="application/javascript"
             src="chrome://browser/content/places/treeView.js"/>
     <script type="application/javascript"><![CDATA[
       Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
       Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
+      Components.utils.import("resource:///modules/Sanitizer.jsm");
     ]]></script>
 #endif
 
     <script type="application/javascript"
             src="chrome://browser/content/sanitizeDialog.js"/>
 
     <preferences id="sanitizePreferences">
       <preference id="privacy.cpd.history"               name="privacy.cpd.history"               type="bool"/>
--- a/browser/base/content/test/general/browser_bug409624.js
+++ b/browser/base/content/test/general/browser_bug409624.js
@@ -1,15 +1,18 @@
 /* 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/. */
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                   "resource://gre/modules/FormHistory.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
+
 add_task(function* test() {
   // This test relies on the form history being empty to start with delete
   // all the items first.
   yield new Promise(resolve => {
     FormHistory.update({ op: "remove" },
                        { handleError(error) {
                            do_throw("Error occurred updating form history: " + error);
                          },
@@ -24,20 +27,16 @@ add_task(function* test() {
   });
 
   let prefService = Cc["@mozilla.org/preferences-service;1"]
                     .getService(Components.interfaces.nsIPrefService);
 
   let findBar = gFindBar;
   let textbox = gFindBar.getElement("findbar-textbox");
 
-  let tempScope = {};
-  Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
-                                             .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-  let Sanitizer = tempScope.Sanitizer;
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
   let prefBranch = prefService.getBranch(s.prefDomain);
 
   prefBranch.setBoolPref("cache", false);
   prefBranch.setBoolPref("cookies", false);
   prefBranch.setBoolPref("downloads", false);
   prefBranch.setBoolPref("formdata", true);
--- a/browser/base/content/test/general/browser_purgehistory_clears_sh.js
+++ b/browser/base/content/test/general/browser_purgehistory_clears_sh.js
@@ -34,23 +34,19 @@ add_task(function* purgeHistoryTest() {
 
     ok(browser.webNavigation.canGoBack, true,
        "New value for webNavigation.canGoBack");
     ok(browser.webNavigation.canGoForward, true,
        "New value for webNavigation.canGoForward");
     ok(!backButton.hasAttribute("disabled"), "Back button was enabled");
     ok(!forwardButton.hasAttribute("disabled"), "Forward button was enabled");
 
+	XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
-    let tmp = {};
-    Cc["@mozilla.org/moz/jssubscript-loader;1"]
-      .getService(Ci.mozIJSSubScriptLoader)
-      .loadSubScript("chrome://browser/content/sanitize.js", tmp);
-
-    let {Sanitizer} = tmp;
     let sanitizer = new Sanitizer();
 
     yield sanitizer.sanitize(["history"]);
 
     let historyAfterPurge = yield ContentTask.spawn(browser, null, function*() {
       return content.history.length;
     });
 
--- a/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
+++ b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
@@ -1,15 +1,13 @@
 // Bug 474792 - Clear "Never remember passwords for this site" when
 // clearing site-specific settings in Clear Recent History dialog
 
-var tempScope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
-                                           .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-var Sanitizer = tempScope.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 add_task(function*() {
   var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
 
   // Add a disabled host
   pwmgr.setLoginSavingEnabled("http://example.com", false);
   // Sanity check
   is(pwmgr.getLoginSavingEnabled("http://example.com"), false,
--- a/browser/base/content/test/general/browser_sanitize-sitepermissions.js
+++ b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
@@ -1,14 +1,12 @@
 // Bug 380852 - Delete permission manager entries in Clear Recent History
 
-var tempScope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
-                                           .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-var Sanitizer = tempScope.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 function countPermissions() {
   let result = 0;
   let enumerator = Services.perms.enumerator;
   while (enumerator.hasMoreElements()) {
     result++;
     enumerator.getNext();
   }
--- a/browser/base/content/test/general/browser_sanitize-timespans.js
+++ b/browser/base/content/test/general/browser_sanitize-timespans.js
@@ -5,20 +5,18 @@ requestLongerTimeout(2);
 
 // Bug 453440 - Test the timespan-based logic of the sanitizer code
 var now_mSec = Date.now();
 var now_uSec = now_mSec * 1000;
 
 const kMsecPerMin = 60 * 1000;
 const kUsecPerMin = 60 * 1000000;
 
-var tempScope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
-                                           .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-var Sanitizer = tempScope.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 var FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
 var Downloads = (Components.utils.import("resource://gre/modules/Downloads.jsm", {})).Downloads;
 
 function promiseFormHistoryRemoved() {
   let deferred = Promise.defer();
   Services.obs.addObserver(function onfh() {
     Services.obs.removeObserver(onfh, "satchel-storage-changed", false);
--- a/browser/base/content/test/general/browser_sanitizeDialog.js
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -24,19 +24,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/FormHistory.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Timer",
                                   "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
                                   "resource://testing-common/PlacesTestUtils.jsm");
 
-var tempScope = {};
-Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-var Sanitizer = tempScope.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 const kMsecPerMin = 60 * 1000;
 const kUsecPerMin = 60 * 1000000;
 
 add_task(function* init() {
   requestLongerTimeout(3);
   yield blankSlate();
   registerCleanupFunction(function* () {
--- a/browser/base/content/test/newtab/browser_newtab_bug722273.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -1,20 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const NOW = Date.now() * 1000;
 const URL = "http://fake-site.com/";
 
-var tmp = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
-  .getService(Ci.mozIJSSubScriptLoader)
-  .loadSubScript("chrome://browser/content/sanitize.js", tmp);
-
-var {Sanitizer} = tmp;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 add_task(function*() {
   yield promiseSanitizeHistory();
   yield promiseAddFakeVisits();
   yield addNewTabPageTabPromise();
   is(getCell(0).site.url, URL, "first site is our fake site");
 
   whenPagesUpdated(() => {});
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -6,19 +6,17 @@ const PREF_NEWTAB_DIRECTORYSOURCE = "bro
 
 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
 
 var tmp = {};
 Cu.import("resource://gre/modules/Promise.jsm", tmp);
 Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
 Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
 Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
-  .getService(Ci.mozIJSSubScriptLoader)
-  .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+Cu.import("resource:///modules/Sanitizer.jsm", tmp);
 Cu.import("resource://gre/modules/Timer.jsm", tmp);
 var {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider, PlacesTestUtils} = tmp;
 
 var uri = Services.io.newURI("about:newtab", null, null);
 var principal = Services.scriptSecurityManager.createCodebasePrincipal(uri, {});
 
 var isMac = ("nsILocalFileMac" in Ci);
 var isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
--- a/browser/base/content/test/plugins/browser_clearplugindata.js
+++ b/browser/base/content/test/plugins/browser_clearplugindata.js
@@ -1,20 +1,18 @@
 var gTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
 var gTestBrowser = null;
 
-// Test clearing plugin data using sanitize.js.
+// Test clearing plugin data using Sanitize.jsm.
 const testURL1 = gTestRoot + "browser_clearplugindata.html";
 const testURL2 = gTestRoot + "browser_clearplugindata_noage.html";
 
-var tempScope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
-                                           .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
-var Sanitizer = tempScope.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                                  "resource:///modules/Sanitizer.jsm");
 
 const pluginHostIface = Ci.nsIPluginHost;
 var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
 pluginHost.QueryInterface(pluginHostIface);
 
 var pluginTag = getTestPlugin();
 var sanitizer = null;
 
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -157,17 +157,16 @@ browser.jar:
         content/browser/sync/key.xhtml                (content/sync/key.xhtml)
         content/browser/sync/utils.js                 (content/sync/utils.js)
 *       content/browser/sync/customize.xul            (content/sync/customize.xul)
         content/browser/sync/customize.js             (content/sync/customize.js)
         content/browser/sync/customize.css            (content/sync/customize.css)
         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)
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1147,18 +1147,17 @@ if (Services.prefs.getBoolPref("privacy.
   CustomizableWidgets.push({
     id: "panic-button",
     type: "view",
     viewId: "PanelUI-panicView",
     _sanitizer: null,
     _ensureSanitizer: function() {
       if (!this.sanitizer) {
         let scope = {};
-        Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js",
-                                            scope);
+        Cu.import("resource:///modules/Sanitizer.jsm", scope);
         this._Sanitizer = scope.Sanitizer;
         this._sanitizer = new scope.Sanitizer();
         this._sanitizer.ignoreTimespan = false;
       }
     },
     _getSanitizeRange: function(aDocument) {
       let group = aDocument.getElementById("PanelUI-panic-timeSpan");
       return this._Sanitizer.getClearRange(+group.value);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -212,17 +212,17 @@ function BrowserGlue() {
   XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() {
                                 Cu.import("resource:///modules/distribution.js");
                                 return new DistributionCustomizer();
                               });
 
   XPCOMUtils.defineLazyGetter(this, "_sanitizer",
     function() {
       let sanitizerScope = {};
-      Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope);
+      Cu.import("resource:///modules/Sanitizer.jsm", sanitizerScope);
       return sanitizerScope.Sanitizer;
     });
 
   this._init();
 }
 
 #ifndef XP_MACOSX
 # OS X has the concept of zero-window sessions and therefore ignores the
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_cache.js
@@ -3,23 +3,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Check about:cache after private browsing
 // This test covers MozTrap test 6047
 // bug 880621
 
 var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", null);
 
-var tmp = {};
-
-Cc["@mozilla.org/moz/jssubscript-loader;1"]
-  .getService(Ci.mozIJSSubScriptLoader)
-  .loadSubScript("chrome://browser/content/sanitize.js", tmp);
-
-var Sanitizer = tmp.Sanitizer;
+XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+    "resource:///modules/Sanitizer.jsm");
 
 function test() {
 
   waitForExplicitFinish();
 
   sanitizeCache();
 
   let nrEntriesR1 = getStorageEntryCount("regular", function(nrEntriesR1) {
--- a/browser/modules/Sanitizer.jsm
+++ b/browser/modules/Sanitizer.jsm
@@ -1,22 +1,735 @@
+// -*- 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/. */
-"use strict";
+ * 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/. */
 
-//
-// A shared module for sanitize.js
-//
-// Until bug 1167238 lands, this serves only as a way to ensure that
-// sanitize is loaded from its own compartment, rather than from that
-// of the sanitize dialog.
-//
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+                                  "resource://gre/modules/AppConstants.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+                                  "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+                                  "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+                                  "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+                                  "resource://gre/modules/TelemetryStopwatch.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+                                  "resource://gre/modules/Console.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "setTimeout",
+                                  "resource://gre/modules/Timer.jsm");
+
+/**
+ * A number of iterations after which to yield time back
+ * to the system.
+ */
+const YIELD_PERIOD = 10;
 
 this.EXPORTED_SYMBOLS = ["Sanitizer"];
 
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+function Sanitizer() {
+}
+Sanitizer.prototype = {
+  // warning to the caller: this one may raise an exception (e.g. bug #265028)
+  clearItem: function (aItemName)
+  {
+    this.items[aItemName].clear();
+  },
+
+  prefDomain: "",
+
+  getNameFromPreference: function (aPreferenceName)
+  {
+    return aPreferenceName.substr(this.prefDomain.length);
+  },
+
+  /**
+   * 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.
+   *
+   * If the consumer specifies the (optional) array parameter, only those
+   * items get cleared (irrespective of the preference settings)
+   */
+  sanitize: Task.async(function*(aItemsToClear = null) {
+    let progress = {};
+    let promise = this._sanitize(aItemsToClear, progress);
+
+    //
+    // 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.
+    //
+    let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
+       .getService(Ci.nsPIPlacesDatabase)
+       .shutdownClient
+       .jsclient;
+
+    shutdownClient.addBlocker("Sanitizer.jsm: Sanitize",
+      promise,
+      {
+        fetchState: () => {
+          return { progress };
+        }
+      }
+    );
+    try {
+      yield promise;
+    } finally {
+      Services.obs.notifyObservers(null, "sanitizer-sanitization-complete", "");
+    }
+  }),
+
+  _sanitize: Task.async(function*(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.
+    Preferences.set(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);
+      yield 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 (let itemName of itemsToClear) {
+      let item = this.items[itemName];
+      if (!("clear" in item)) {
+        progress[itemName] = "`clear` not in item";
+        continue;
+      }
+      item.range = range;
+      // Some of these clear() may raise exceptions (see bug #265028)
+      // to sanitize as much as possible, we catch and store them,
+      // rather than fail fast.
+      // Callers should check returned errors and give user feedback
+      // about items that could not be sanitized
+      let refObj = {};
+      try {
+        TelemetryStopwatch.start("FX_SANITIZE_TOTAL", refObj);
+        yield item.clear();
+        progress[itemName] = "cleared";
+      } catch(er) {
+        progress[itemName] = "failed";
+        seenError = true;
+        console.error("Error sanitizing " + itemName, er);
+      } finally {
+        TelemetryStopwatch.finish("FX_SANITIZE_TOTAL", refObj);
+      }
+    }
+
+    // Sanitization is complete.
+    Preferences.reset(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: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_CACHE", refObj);
+
+        var cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+                    getService(Ci.nsICacheStorageService);
+        try {
+          // Cache doesn't consult timespan, nor does it have the
+          // facility for timespan-based eviction.  Wipe it.
+          cache.clear();
+        } catch(er) {}
+
+        var imageCache = Cc["@mozilla.org/image/tools;1"].
+                         getService(Ci.imgITools).getImgCacheForDocument(null);
+        try {
+          imageCache.clearCache(false); // true=chrome, false=content
+        } catch(er) {}
+
+        TelemetryStopwatch.finish("FX_SANITIZE_CACHE", refObj);
+      }
+    },
+
+    cookies: {
+      clear: Task.async(function* ()
+      {
+        let yieldCounter = 0;
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_COOKIES", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
+
+        var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+                                  .getService(Ci.nsICookieManager);
+        if (this.range) {
+          // Iterate through the cookies and delete any created after our cutoff.
+          var cookiesEnum = cookieMgr.enumerator;
+          while (cookiesEnum.hasMoreElements()) {
+            var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+            if (cookie.creationTime > this.range[0]) {
+              // This cookie was created after our cutoff, clear it
+              cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
+
+              if (++yieldCounter % YIELD_PERIOD == 0) {
+                yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
+              }
+            }
+          }
+        }
+        else {
+          // Remove everything
+          cookieMgr.removeAll();
+          yield new Promise(resolve => setTimeout(resolve, 0)); // Don't block the main thread too long
+        }
+        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2", refObj);
+
+        // Clear deviceIds. Done asynchronously (returns before complete).
+        let mediaMgr = Components.classes["@mozilla.org/mediaManagerService;1"]
+                                 .getService(Ci.nsIMediaManagerService);
+        mediaMgr.sanitizeDeviceIds(this.range && this.range[0]);
+
+        // Clear plugin data.
+        TelemetryStopwatch.start("FX_SANITIZE_PLUGINS", refObj);
+        yield this.promiseClearPluginCookies();
+        TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS", refObj);
+        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES", refObj);
+      }),
+
+      promiseClearPluginCookies: Task.async(function*() {
+        const phInterface = Ci.nsIPluginHost;
+        const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
+        let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
+
+        // Determine age range in seconds. (-1 means clear all.) We don't know
+        // that this.range[1] is actually now, so we compute age range based
+        // on the lower bound. If this.range results in a negative age, do
+        // nothing.
+        let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000) : -1;
+        if (!this.range || age >= 0) {
+          let tags = ph.getPluginTags();
+          for (let tag of tags) {
+            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)
+                );
+              }
+            } catch (ex) {
+              // Ignore errors from plug-ins
+            }
+          }
+        }
+      })
+    },
+
+    offlineApps: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS", refObj);
+        Components.utils.import("resource:///modules/offlineAppCache.jsm");
+        OfflineAppCacheHelper.clear();
+        TelemetryStopwatch.finish("FX_SANITIZE_OFFLINEAPPS", refObj);
+      }
+    },
+
+    history: {
+      clear: Task.async(function* ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_HISTORY", refObj);
+        try {
+          if (this.range) {
+            yield PlacesUtils.history.removeVisitsByFilter({
+              beginDate: new Date(this.range[0] / 1000),
+              endDate: new Date(this.range[1] / 1000)
+            });
+          } else {
+            // Remove everything.
+            yield PlacesUtils.history.clear();
+          }
+
+          try {
+            let clearStartingTime = this.range ? String(this.range[0]) : "";
+            Services.obs.notifyObservers(null, "browser:purge-session-history", clearStartingTime);
+          } catch (e) { }
+
+          try {
+            let predictor = Components.classes["@mozilla.org/network/predictor;1"]
+                                      .getService(Components.interfaces.nsINetworkPredictor);
+            predictor.reset();
+          } catch (e) {
+            console.error("Error while resetting the predictor", e);
+          }
+        } finally {
+          TelemetryStopwatch.finish("FX_SANITIZE_HISTORY", refObj);
+        }
+      })
+    },
+
+    formdata: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
+
+        // Clear undo history of all searchBars
+        var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+                                      .getService(Components.interfaces.nsIWindowMediator);
+        var windows = windowManager.getEnumerator("navigator:browser");
+        while (windows.hasMoreElements()) {
+          let currentWindow = windows.getNext();
+          let currentDocument = currentWindow.document;
+          let searchBar = currentDocument.getElementById("searchbar");
+          if (searchBar)
+            searchBar.textbox.reset();
+          let tabBrowser = currentWindow.gBrowser;
+          for (let tab of tabBrowser.tabs) {
+            if (tabBrowser.isFindBarInitialized(tab))
+              tabBrowser.getFindBar(tab).clear();
+          }
+          // Clear any saved find value
+          tabBrowser._lastFindValue = "";
+        }
+
+        let change = { op: "remove" };
+        if (this.range) {
+          [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
+        }
+        FormHistory.update(change);
+
+        TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
+      }
+    },
+
+    downloads: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
+        Task.spawn(function*() {
+          let filterByTime = null;
+          if (this.range) {
+            // Convert microseconds back to milliseconds for date comparisons.
+            let rangeBeginMs = this.range[0] / 1000;
+            let rangeEndMs = this.range[1] / 1000;
+            filterByTime = download => download.startTime >= rangeBeginMs &&
+                                       download.startTime <= rangeEndMs;
+          }
+
+          // Clear all completed/cancelled downloads
+          let list = yield Downloads.getList(Downloads.ALL);
+          list.removeFinished(filterByTime);
+          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
+        }.bind(this)).then(null, error => {
+          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
+          Components.utils.reportError(error);
+        });
+      }
+    },
 
-var scope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://browser/content/sanitize.js", scope);
+    sessions: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
+
+        // clear all auth tokens
+        var sdr = Components.classes["@mozilla.org/security/sdr;1"]
+                            .getService(Components.interfaces.nsISecretDecoderRing);
+        sdr.logoutAndTeardown();
+
+        // clear FTP and plain HTTP auth sessions
+        var os = Components.classes["@mozilla.org/observer-service;1"]
+                           .getService(Components.interfaces.nsIObserverService);
+        os.notifyObservers(null, "net:clear-active-logins", null);
+
+        TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS", refObj);
+      }
+    },
+
+    siteSettings: {
+      clear: function ()
+      {
+        let refObj = {};
+        TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
+
+        // Clear site-specific permissions like "Allow this site to open popups"
+        // we ignore the "end" range and hope it is now() - none of the
+        // interfaces used here support a true range anyway.
+        let startDateMS = this.range == null ? null : this.range[0] / 1000;
+        var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+                           .getService(Components.interfaces.nsIPermissionManager);
+        if (startDateMS == null) {
+          pm.removeAll();
+        } else {
+          pm.removeAllSince(startDateMS);
+        }
+
+        // Clear site-specific settings like page-zoom level
+        var cps = Components.classes["@mozilla.org/content-pref/service;1"]
+                            .getService(Components.interfaces.nsIContentPrefService2);
+        if (startDateMS == null) {
+          cps.removeAllDomains(null);
+        } else {
+          cps.removeAllDomainsSince(startDateMS, null);
+        }
+
+        // Clear "Never remember passwords for this site", which is not handled by
+        // the permission manager
+        // (Note the login manager doesn't support date ranges yet, and bug
+        //  1058438 is calling for loginSaving stuff to end up in the
+        // permission manager)
+        var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+                              .getService(Components.interfaces.nsILoginManager);
+        var hosts = pwmgr.getAllDisabledHosts();
+        for (var host of hosts) {
+          pwmgr.setLoginSavingEnabled(host, true);
+        }
+
+        // Clear site security settings - no support for ranges in this
+        // interface either, so we clearAll().
+        var sss = Cc["@mozilla.org/ssservice;1"]
+                    .getService(Ci.nsISiteSecurityService);
+        sss.clearAll();
+
+        // Clear all push notification subscriptions
+        try {
+          var push = Cc["@mozilla.org/push/Service;1"]
+                       .getService(Ci.nsIPushService);
+          push.clearForDomain("*", status => {
+            if (!Components.isSuccessCode(status)) {
+              dump("Error clearing Web Push data: " + status + "\n");
+            }
+          });
+        } catch (e) {
+          dump("Web Push may not be available.\n");
+        }
+
+        TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
+      }
+    },
+
+    openWindows: {
+      privateStateForNewWindow: "non-private",
+      _canCloseWindow: function(aWindow) {
+        if (aWindow.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;
+          return true;
+        }
+      },
+      _resetAllWindowClosures: function(aWindowList) {
+        for (let win of aWindowList) {
+          win.skipNextCanClose = false;
+        }
+      },
+      clear: Task.async(function*() {
+        // 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();
+
+        // First check if all these windows are OK with being closed:
+        let windowEnumerator = Services.wm.getEnumerator("navigator:browser");
+        let windowList = [];
+        while (windowEnumerator.hasMoreElements()) {
+          let someWin = windowEnumerator.getNext();
+          windowList.push(someWin);
+          // If someone says "no" to a beforeunload prompt, we abort here:
+          if (!this._canCloseWindow(someWin)) {
+            this._resetAllWindowClosures(windowList);
+            throw new Error("Sanitize could not close windows: cancelled by user");
+          }
+
+          // ...however, beforeunload prompts spin the event loop, and so the code here won't get
+          // hit until the prompt has been dismissed. If more than 1 minute has elapsed since we
+          // started prompting, stop, because the user might not even remember initiating the
+          // 'forget', and the timespans will be all wrong by now anyway:
+          if (existingWindow.performance.now() > (startDate + 60 * 1000)) {
+            this._resetAllWindowClosures(windowList);
+            throw new Error("Sanitize could not close windows: timeout");
+          }
+        }
+
+        // If/once we get here, we should actually be able to close all windows.
+
+        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 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;
+            let sizemode = docEl.getAttribute("sizemode");
+            if (!newWindow.fullScreen && sizemode == "fullscreen") {
+              docEl.setAttribute("sizemode", "normal");
+              e.preventDefault();
+              e.stopPropagation();
+              return false;
+            }
+          }
+          newWindow.addEventListener("fullscreen", onFullScreen);
+        }
+
+        let promiseReady = new Promise(resolve => {
+          // Window creation and destruction is asynchronous. We need to wait
+          // until all existing windows are fully closed, and the new window is
+          // fully open, before continuing. Otherwise the rest of the sanitizer
+          // could run too early (and miss new cookies being set when a page
+          // closes) and/or run too late (and not have a fully-formed window yet
+          // in existence). See bug 1088137.
+          let newWindowOpened = false;
+          let onWindowOpened = function(subject, topic, data) {
+            if (subject != newWindow)
+              return;
 
-this.Sanitizer = scope.Sanitizer;
+            Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
+            if (AppConstants.platform == "macosx") {
+              newWindow.removeEventListener("fullscreen", onFullScreen);
+            }
+            newWindowOpened = true;
+            // If we're the last thing to happen, invoke callback.
+            if (numWindowsClosing == 0) {
+              TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
+              resolve();
+            }
+          }
+
+          let numWindowsClosing = windowList.length;
+          let onWindowClosed = function() {
+            numWindowsClosing--;
+            if (numWindowsClosing == 0) {
+              Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
+              // If we're the last thing to happen, invoke callback.
+              if (newWindowOpened) {
+                TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
+                resolve();
+              }
+            }
+          }
+          Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
+          Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
+        });
+
+        // Start the process of closing windows
+        while (windowList.length) {
+          windowList.pop().close();
+        }
+        newWindow.focus();
+        yield promiseReady;
+      })
+    },
+  }
+};
+
+// "Static" members
+Sanitizer.PREF_DOMAIN = "privacy.sanitize.";
+Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
+Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
+Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
+
+// 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;
+
+// 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;
+
+  // 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];
+};
+
+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);
+});
+
+// Shows sanitization UI
+Sanitizer.showUI = function(aParentWindow)
+{
+  var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+                     .getService(Components.interfaces.nsIWindowWatcher);
+  let win = AppConstants.platform == "macosx" ?
+    null: // make this an app-modal window on Mac
+    aParentWindow;
+  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 = Task.async(function*() {
+  // Make sure that we are triggered during shutdown, at the right time.
+  let shutdownClient = Cc["@mozilla.org/browser/nav-history-service;1"]
+     .getService(Ci.nsPIPlacesDatabase)
+     .shutdownClient
+     .jsclient;
+
+  shutdownClient.addBlocker("Sanitizer.jsm: Sanitize on shutdown",
+    () => Sanitizer.onShutdown());
+
+    // One time migration to remove support for the clear saved passwords on exit feature.
+    if (!Services.prefs.getBoolPref("privacy.sanitize.migrateClearSavedPwdsOnExit")) {
+      let deprecatedPref = "privacy.clearOnShutdown.passwords";
+      let doUpdate = Services.prefs.prefHasUserValue(deprecatedPref) &&
+                     Services.prefs.getBoolPref(deprecatedPref);
+      if (doUpdate) {
+        Services.logins.removeAllLogins();
+        Services.prefs.setBoolPref("signon.rememberSignons", false);
+      }
+      Services.prefs.clearUserPref(deprecatedPref);
+      Services.prefs.setBoolPref("privacy.sanitize.migrateClearSavedPwdsOnExit", true);
+  }
+
+  // Handle incomplete sanitizations
+  if (Preferences.has(Sanitizer.PREF_SANITIZE_IN_PROGRESS)) {
+    // Firefox crashed during sanitization.
+    let s = new Sanitizer();
+    let json = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
+    let itemsToClear = JSON.parse(json);
+    yield s.sanitize(itemsToClear);
+  }
+  if (Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
+    // Firefox crashed before having a chance to sanitize during shutdown.
+    // (note that if Firefox crashed during shutdown sanitization, we
+    // will hit both `if` so we will run a second double-sanitization).
+    yield Sanitizer.onShutdown();
+  }
+});
+
+Sanitizer.onShutdown = Task.async(function*() {
+  if (!Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
+    return;
+  }
+  // Need to sanitize upon shutdown
+  let s = new Sanitizer();
+  s.prefDomain = "privacy.clearOnShutdown.";
+  yield s.sanitize();
+  Preferences.set(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN, true);
+});
--- a/testing/puppeteer/firefox/firefox_puppeteer/api/utils.py
+++ b/testing/puppeteer/firefox/firefox_puppeteer/api/utils.py
@@ -30,17 +30,17 @@ class Utils(BaseLib):
     def sanitize(self, data_type):
         """Sanitize user data, including cache, cookies, offlineApps, history, formdata,
         downloads, passwords, sessions, siteSettings.
 
         Usage:
         sanitize():  Clears all user data.
         sanitize({ "sessions": True }): Clears only session user data.
 
-        more: https://dxr.mozilla.org/mozilla-central/source/browser/base/content/sanitize.js
+        more: https://dxr.mozilla.org/mozilla-central/source/browser/modules/Sanitizer.jsm
 
         :param data_type: optional, Information specifying data to be sanitized
         """
 
         with self.marionette.using_context('chrome'):
             result = self.marionette.execute_async_script("""
               Components.utils.import("resource://gre/modules/Services.jsm");
 
@@ -54,20 +54,18 @@ class Utils(BaseLib):
                 history: data_type.history || false,
                 offlineApps: data_type.offlineApps || false,
                 passwords: data_type.passwords || false,
                 sessions: data_type.sessions || false,
                 siteSettings: data_type.siteSettings || false
               };
 
               // Load the sanitize script
-              var tempScope = {};
-              Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
-              .getService(Components.interfaces.mozIJSSubScriptLoader)
-              .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+              XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
+                  "resource:///modules/Sanitizer.jsm");
 
               // Instantiate the Sanitizer
               var s = new tempScope.Sanitizer();
               s.prefDomain = "privacy.cpd.";
               var itemPrefs = Services.prefs.getBranch(s.prefDomain);
 
               // Apply options for what to sanitize
               for (var pref in data_type) {
--- a/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/toolkit/components/thumbnails/test/browser_thumbnails_storage.js
@@ -1,19 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const URL = "http://mochi.test:8888/";
 const URL_COPY = URL + "#copy";
 
 XPCOMUtils.defineLazyGetter(this, "Sanitizer", function () {
   let tmp = {};
-  Cc["@mozilla.org/moz/jssubscript-loader;1"]
-    .getService(Ci.mozIJSSubScriptLoader)
-    .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+  Cu.import("resource:///modules/Sanitizer.jsm", tmp);
   return tmp.Sanitizer;
 });
 
 /**
  * These tests ensure that the thumbnail storage is working as intended.
  * Newly captured thumbnails should be saved as files and they should as well
  * be removed when the user sanitizes their history.
  */