Backed out changeset 8ade38cf0509 (Bug 1089695) to revert sanitize to a sanitize-wont-timeout-abort status. a=sylvestre
authorMarco Bonardo <mbonardo@mozilla.com>
Tue, 23 Feb 2016 11:18:19 +0100
changeset 311378 30d48874cea4923cfe0e34c466894e353ef48aa2
parent 311377 ce5224000cec46e476e4af8bac57d80fe3f1064f
child 311379 bf182675fd0584774210ca1cc092ca51fef89624
child 311381 ad87067f7429d8dfac49e82bb4ba86a335f6f523
child 311383 88869e3059df6017158ead695e79587da548396b
push id5665
push usermak77@bonardo.net
push dateTue, 23 Feb 2016 16:08:14 +0000
treeherdermozilla-beta@30d48874cea4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssylvestre
bugs1089695
milestone45.0
backs out8ade38cf050915d0231d2c1a64675e3fe22ad15f
Backed out changeset 8ade38cf0509 (Bug 1089695) to revert sanitize to a sanitize-wont-timeout-abort status. a=sylvestre MozReview-Commit-ID: DPfVhsBf9t9
browser/base/content/browser.js
browser/base/content/sanitize.js
browser/base/content/sanitizeDialog.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_sanitizeDialog.js
browser/base/jar.mn
browser/components/nsBrowserGlue.js
browser/components/places/tests/unit/test_clearHistory_shutdown.js
browser/modules/Sanitizer.jsm
browser/modules/moz.build
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1228,16 +1228,19 @@ var gBrowserInit = {
 
     UpdateUrlbarSearchSplitterState();
 
     if (!(isBlankPageURL(uriToLoad) || uriToLoad == "about:privatebrowsing") ||
         !focusAndSelectUrlBar()) {
       gBrowser.selectedBrowser.focus();
     }
 
+    // Set up Sanitize Item
+    this._initializeSanitizer();
+
     // Enable/Disable auto-hide tabbar
     gBrowser.tabContainer.updateVisibility();
 
     BookmarkingUI.init();
 
     gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
 
     var homeButton = document.getElementById("home-button");
@@ -1542,16 +1545,62 @@ var gBrowserInit = {
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
           .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
           .QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIXULWindow)
           .XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
   },
+
+  _initializeSanitizer: function() {
+    const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
+    if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
+      gPrefService.clearUserPref(kDidSanitizeDomain);
+      // We need to persist this preference change, since we want to
+      // check it at next app start even if the browser exits abruptly
+      gPrefService.savePrefFile(null);
+    }
+
+    /**
+     * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
+     *
+     * a) User has customized any privacy.item prefs
+     * b) privacy.sanitize.sanitizeOnShutdown is set
+     */
+    if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
+      let itemBranch = gPrefService.getBranch("privacy.item.");
+      let itemArray = itemBranch.getChildList("");
+
+      // See if any privacy.item prefs are set
+      let doMigrate = itemArray.some(name => itemBranch.prefHasUserValue(name));
+      // Or if sanitizeOnShutdown is set
+      if (!doMigrate)
+        doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
+
+      if (doMigrate) {
+        let cpdBranch = gPrefService.getBranch("privacy.cpd.");
+        let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
+        for (let name of itemArray) {
+          try {
+            // don't migrate password or offlineApps clearing in the CRH dialog since
+            // there's no UI for those anymore. They default to false. bug 497656
+            if (name != "passwords" && name != "offlineApps")
+              cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+            clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+          }
+          catch(e) {
+            Cu.reportError("Exception thrown during privacy pref migration: " + e);
+          }
+        }
+      }
+
+      gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
+    }
+  },
 };
 
 if (AppConstants.platform == "macosx") {
   // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
   // nonBrowserWindowShutdown() are used for non-browser windows in
   // macBrowserOverlay
   gBrowserInit.nonBrowserWindowStartup = function() {
     // Disable inappropriate commands / submenus
@@ -1609,16 +1658,19 @@ if (AppConstants.platform == "macosx") {
   };
 
   gBrowserInit.nonBrowserWindowDelayedStartup = function() {
     this._delayedStartupTimeoutId = null;
 
     // initialise the offline listener
     BrowserOffline.init();
 
+    // Set up Sanitize Item
+    this._initializeSanitizer();
+
     // initialize the private browsing UI
     gPrivateBrowsingUI.init();
 
     // initialize the sync UI
     gSyncUI.init();
 
     if (AppConstants.E10S_TESTING_ONLY) {
       gRemoteTabsUI.init();
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -1,62 +1,39 @@
 // -*- 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/. */
+# 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() {
-}
+function Sanitizer() {}
 Sanitizer.prototype = {
   // warning to the caller: this one may raise an exception (e.g. bug #265028)
   clearItem: function (aItemName)
   {
     if (this.items[aItemName].canClear)
       this.items[aItemName].clear();
   },
 
-  promiseCanClearItem: function (aItemName, aArg) {
-    return new Promise(resolve => {
-      return this.canClearItem(aItemName,
-                              (_, canClear) => resolve(canClear),
-                              aArg)
-    });
-  },
-
   canClearItem: function (aItemName, aCallback, aArg)
   {
     let canClear = this.items[aItemName].canClear;
     if (typeof canClear == "function") {
       canClear(aCallback, aArg);
       return false;
     }
 
@@ -75,311 +52,311 @@ Sanitizer.prototype = {
    * 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;
+  sanitize: function (aItemsToClear)
+  {
+    var deferred = Promise.defer();
+    var seenError = false;
     if (Array.isArray(aItemsToClear)) {
-      // Shallow copy the array, as we are going to modify
-      // it in place later.
-      itemsToClear = [...aItemsToClear];
+      var 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";
+      let item = this.items.openWindows;
+
+      let ok = item.clear(() => {
+        try {
+          let clearedPromise = this.sanitize(itemsToClear);
+          clearedPromise.then(deferred.resolve, deferred.reject);
+        } catch(e) {
+          let error = "Sanitizer threw after closing windows: " + e;
+          Cu.reportError(error);
+          deferred.reject(error);
+        }
+      });
+      // When cancelled, reject immediately
+      if (!ok) {
+        deferred.reject("Sanitizer canceled closing windows");
+      }
+
+      return deferred.promise;
     }
 
+    let cookiesIndex = itemsToClear.indexOf("cookies");
+    if (cookiesIndex != -1) {
+      itemsToClear.splice(cookiesIndex, 1);
+      let item = this.items.cookies;
+      item.range = this.range;
+      let ok = item.clear(() => {
+        try {
+          if (!itemsToClear.length) {
+            // we're done
+            deferred.resolve();
+            return;
+          }
+          let clearedPromise = this.sanitize(itemsToClear);
+          clearedPromise.then(deferred.resolve, deferred.reject);
+        } catch(e) {
+          let error = "Sanitizer threw after clearing cookies: " + e;
+          Cu.reportError(error);
+          deferred.reject(error);
+        }
+      });
+      // When cancelled, reject immediately
+      if (!ok) {
+        deferred.reject("Sanitizer canceled clearing cookies");
+      }
+
+      return deferred.promise;
+    }
+
+    TelemetryStopwatch.start("FX_SANITIZE_TOTAL");
+
     // Cache the range of times to clear
-    let range = null;
-    // If we ignore timespan, clear everything,
-    // otherwise, pick a range.
-    if (!this.ignoreTimespan) {
+    if (this.ignoreTimespan)
+      var range = null;  // If we ignore timespan, clear everything
+    else
       range = this.range || Sanitizer.getClearRange();
-    }
 
+    let itemCount = Object.keys(itemsToClear).length;
+    let onItemComplete = function() {
+      if (!--itemCount) {
+        TelemetryStopwatch.finish("FX_SANITIZE_TOTAL");
+        seenError ? deferred.reject() : deferred.resolve();
+      }
+    };
     for (let itemName of itemsToClear) {
       let item = this.items[itemName];
-      if (!("clear" in item)) {
-        progress[itemName] = "`clear` not in item";
-        continue;
-      }
       item.range = range;
-      let canClear = yield this.promiseCanClearItem(itemName);
-      if (!canClear) {
-        progress[itemName] = "cannot clear item";
-        continue;
-      }
-      // 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);
+      if ("clear" in item) {
+        let clearCallback = (itemName, aCanClear) => {
+          // 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 item = this.items[itemName];
+          try {
+            if (aCanClear)
+              item.clear();
+          } catch(er) {
+            seenError = true;
+            Components.utils.reportError("Error sanitizing " + itemName +
+                                         ": " + er + "\n");
+          }
+          onItemComplete();
+        };
+        this.canClearItem(itemName, clearCallback);
+      } else {
+        onItemComplete();
       }
     }
 
-    // Sanitization is complete.
-    Preferences.reset(Sanitizer.PREF_SANITIZE_IN_PROGRESS);
-    progress = {};
-    if (seenError) {
-      throw new Error("Error sanitizing");
-    }
-  }),
+    return deferred.promise;
+  },
 
   // 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);
+        TelemetryStopwatch.start("FX_SANITIZE_CACHE");
 
         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);
+        TelemetryStopwatch.finish("FX_SANITIZE_CACHE");
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     cookies: {
-      clear: Task.async(function* ()
+      clear: function (aCallback)
       {
-        let yieldCounter = 0;
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_COOKIES", refObj);
-        TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_COOKIES");
+        TelemetryStopwatch.start("FX_SANITIZE_COOKIES_2");
 
         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]) {
+            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);
+
+        TelemetryStopwatch.finish("FX_SANITIZE_COOKIES_2");
 
         // 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);
-      }),
+        TelemetryStopwatch.start("FX_SANITIZE_PLUGINS");
+        this.clearPluginCookies().then(
+          function() {
+            TelemetryStopwatch.finish("FX_SANITIZE_PLUGINS");
+            TelemetryStopwatch.finish("FX_SANITIZE_COOKIES");
+            aCallback();
+          });
+        return true;
+      },
 
-      promiseClearPluginCookies: Task.async(function*() {
+      clearPluginCookies: 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();
+          function iterate(tag) {
+            let promise = new Promise(resolve => {
+              try {
+                let onClear = function(rv) {
+                  // If the plugin doesn't support clearing by age, clear everything.
+                  if (rv == Components.results. NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+                    ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, -1, function() {
+                      resolve();
+                    });
+                  } else {
+                    resolve();
+                  }
+                };
+                ph.clearSiteData(tag, null, FLAG_CLEAR_ALL, age, onClear);
+              } catch (ex) {
+                resolve();
+              }
+            });
+            return promise;
+          }
+          let promises = [];
           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
-            }
+            promises.push(iterate(tag));
           }
+          return Promise.all(promises);
         }
-      }),
+      },
 
       get canClear()
       {
         return true;
       }
     },
 
     offlineApps: {
       clear: function ()
       {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_OFFLINEAPPS");
         Components.utils.import("resource:///modules/offlineAppCache.jsm");
         OfflineAppCacheHelper.clear();
-        TelemetryStopwatch.finish("FX_SANITIZE_OFFLINEAPPS", refObj);
+        TelemetryStopwatch.finish("FX_SANITIZE_OFFLINEAPPS");
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     history: {
-      clear: Task.async(function* ()
+      clear: 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();
-          }
+        TelemetryStopwatch.start("FX_SANITIZE_HISTORY");
+
+        if (this.range)
+          PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]);
+        else
+          PlacesUtils.history.removeAllPages();
 
-          try {
-            let clearStartingTime = this.range ? String(this.range[0]) : "";
-            Services.obs.notifyObservers(null, "browser:purge-session-history", clearStartingTime);
-          } catch (e) { }
+        try {
+          var os = Components.classes["@mozilla.org/observer-service;1"]
+                             .getService(Components.interfaces.nsIObserverService);
+          let clearStartingTime = this.range ? String(this.range[0]) : "";
+          os.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);
-        }
-      }),
+        try {
+          var predictor = Components.classes["@mozilla.org/network/predictor;1"]
+                                    .getService(Components.interfaces.nsINetworkPredictor);
+          predictor.reset();
+        } catch (e) { }
+
+        TelemetryStopwatch.finish("FX_SANITIZE_HISTORY");
+      },
 
       get canClear()
       {
         // bug 347231: Always allow clearing history due to dependencies on
         // the browser:purge-session-history notification. (like error console)
         return true;
       }
     },
 
     formdata: {
       clear: function ()
       {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_FORMDATA", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_FORMDATA");
 
         // 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;
@@ -396,17 +373,17 @@ Sanitizer.prototype = {
         }
 
         let change = { op: "remove" };
         if (this.range) {
           [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
         }
         FormHistory.update(change);
 
-        TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA", refObj);
+        TelemetryStopwatch.finish("FX_SANITIZE_FORMDATA");
       },
 
       canClear : function(aCallback, aArg)
       {
         var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
                                       .getService(Components.interfaces.nsIWindowMediator);
         var windows = windowManager.getEnumerator("navigator:browser");
         while (windows.hasMoreElements()) {
@@ -443,75 +420,72 @@ Sanitizer.prototype = {
         FormHistory.count({}, countDone);
         return false;
       }
     },
 
     downloads: {
       clear: function ()
       {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_DOWNLOADS");
         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);
+          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS");
         }.bind(this)).then(null, error => {
-          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS", refObj);
+          TelemetryStopwatch.finish("FX_SANITIZE_DOWNLOADS");
           Components.utils.reportError(error);
         });
       },
 
       canClear : function(aCallback, aArg)
       {
         aCallback("downloads", true, aArg);
         return false;
       }
     },
 
     sessions: {
       clear: function ()
       {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_SESSIONS", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_SESSIONS");
 
         // 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);
+        TelemetryStopwatch.finish("FX_SANITIZE_SESSIONS");
       },
 
       get canClear()
       {
         return true;
       }
     },
 
     siteSettings: {
       clear: function ()
       {
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_SITESETTINGS");
 
         // 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) {
@@ -551,17 +525,17 @@ Sanitizer.prototype = {
         try {
           var push = Cc["@mozilla.org/push/NotificationService;1"]
                       .getService(Ci.nsIPushNotificationService);
           push.clearAll();
         } catch (e) {
           dump("Web Push may not be available.\n");
         }
 
-        TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS", refObj);
+        TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS");
       },
 
       get canClear()
       {
         return true;
       }
     },
     openWindows: {
@@ -575,139 +549,122 @@ Sanitizer.prototype = {
           return true;
         }
       },
       _resetAllWindowClosures: function(aWindowList) {
         for (let win of aWindowList) {
           win.skipNextCanClose = false;
         }
       },
-      clear: Task.async(function*() {
+      clear: function(aCallback)
+      {
         // NB: this closes all *browser* windows, not other windows like the library, about window,
         // browser console, etc.
 
+        if (!aCallback) {
+          throw "Sanitizer's openWindows clear() requires a callback.";
+        }
+
         // 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");
+            return false;
           }
 
           // ...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");
+            return false;
           }
         }
 
         // If/once we get here, we should actually be able to close all windows.
 
-        let refObj = {};
-        TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS", refObj);
+        TelemetryStopwatch.start("FX_SANITIZE_OPENWINDOWS");
 
         // 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;
-            }
+        // 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;
+        function onWindowOpened(subject, topic, data) {
+          if (subject != newWindow)
+            return;
+
+          Services.obs.removeObserver(onWindowOpened, "browser-delayed-startup-finished");
+          newWindowOpened = true;
+          // If we're the last thing to happen, invoke callback.
+          if (numWindowsClosing == 0) {
+            TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS");
+            aCallback();
           }
-          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;
+        let numWindowsClosing = windowList.length;
+        function onWindowClosed() {
+          numWindowsClosing--;
+          if (numWindowsClosing == 0) {
+            Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
             // If we're the last thing to happen, invoke callback.
-            if (numWindowsClosing == 0) {
-              TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
-              resolve();
+            if (newWindowOpened) {
+              TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS");
+              aCallback();
             }
           }
+        }
 
-          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);
-        });
+        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;
-      }),
+        return true;
+      },
 
       get canClear()
       {
         return true;
       }
     },
   }
 };
 
 
 
 // "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";
+Sanitizer.prefDomain          = "privacy.sanitize.";
+Sanitizer.prefShutdown        = "sanitizeOnShutdown";
+Sanitizer.prefDidShutdown     = "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;
@@ -756,84 +713,76 @@ Sanitizer.getClearRange = function (ts) 
 };
 
 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);
+                         .getBranch(Sanitizer.prefDomain);
 });
 
 // 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,
+#ifdef XP_MACOSX
+  ww.openWindow(null, // make this an app-modal window on Mac
+#else
+  ww.openWindow(aParentWindow,
+#endif
                 "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;
+Sanitizer.onStartup = function()
+{
+  // we check for unclean exit with pending sanitization
+  Sanitizer._checkAndSanitize();
+};
 
-  shutdownClient.addBlocker("sanitize.js: Sanitize on shutdown",
-    () => Sanitizer.onShutdown());
+Sanitizer.onShutdown = function()
+{
+  // we check if sanitization is needed and perform it
+  Sanitizer._checkAndSanitize();
+};
+
+// this is called on startup and shutdown, to perform pending sanitizations
+Sanitizer._checkAndSanitize = function()
+{
+  const prefs = Sanitizer.prefs;
+  if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
+      !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
 
     // 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);
+    // this is a shutdown or a startup after an unclean exit
+    var s = new Sanitizer();
+    s.prefDomain = "privacy.clearOnShutdown.";
+    s.sanitize().then(function() {
+      prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
+    });
   }
-  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/sanitizeDialog.js
+++ b/browser/base/content/sanitizeDialog.js
@@ -1,18 +1,15 @@
 /* -*- 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/. */
 
 var Cc = Components.classes;
 var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-var {Sanitizer} = Cu.import("resource:///modules/Sanitizer.jsm", {});
 
 var gSanitizePromptDialog = {
 
   get bundleBrowser()
   {
     if (!this._bundleBrowser)
       this._bundleBrowser = document.getElementById("bundleBrowser");
     return this._bundleBrowser;
@@ -41,47 +38,40 @@ var gSanitizePromptDialog = {
   init: function ()
   {
     // This is used by selectByTimespan() to determine if the window has loaded.
     this._inited = true;
 
     var s = new Sanitizer();
     s.prefDomain = "privacy.cpd.";
 
-    let tasks = [];
     let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
     for (let i = 0; i < sanitizeItemList.length; i++) {
       let prefItem = sanitizeItemList[i];
       let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
-      let promise = s.promiseCanClearItem(name).then(canClear => {
-        if (canClear) {
-          return;
+      s.canClearItem(name, function canClearCallback(aItem, aCanClear, aPrefItem) {
+        if (!aCanClear) {
+          aPrefItem.preference = null;
+          aPrefItem.checked = false;
+          aPrefItem.disabled = true;
         }
-        prefItem.preference = null;
-        prefItem.checked = false;
-        prefItem.disabled = true;
-      });
-      tasks.push(promise);
+      }, prefItem);
     }
 
     document.documentElement.getButton("accept").label =
       this.bundleBrowser.getString("sanitizeButtonOK");
 
     if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
       this.prepareWarning();
       this.warningBox.hidden = false;
       document.title =
         this.bundleBrowser.getString("sanitizeDialog2.everything.title");
     }
     else
       this.warningBox.hidden = true;
-
-    Promise.all(tasks).then(() => {
-      Services.obs.notifyObservers(null, "sanitize-dialog-setup-complete", "");
-    });
   },
 
   selectByTimespan: function ()
   {
     // This method is the onselect handler for the duration dropdown.  As a
     // result it's called a couple of times before onload calls init().
     if (!this._inited)
       return;
@@ -124,25 +114,25 @@ var gSanitizePromptDialog = {
     // once the async operation completes (either with or without errors)
     // we close the window.
     let docElt = document.documentElement;
     let acceptButton = docElt.getButton("accept");
     acceptButton.disabled = true;
     acceptButton.setAttribute("label",
                               this.bundleBrowser.getString("sanitizeButtonClearing"));
     docElt.getButton("cancel").disabled = true;
-
     try {
       s.sanitize().then(null, Components.utils.reportError)
                   .then(() => window.close())
                   .then(null, Components.utils.reportError);
     } catch (er) {
       Components.utils.reportError("Exception during sanitize: " + er);
       return true; // We *do* want to close immediately on error.
     }
+    return false;
   },
 
   /**
    * If the panel that displays a warning when the duration is "Everything" is
    * not set up, sets it up.  Otherwise does nothing.
    *
    * @param aDontShowItemList Whether only the warning message should be updated.
    *                          True means the item list visibility status should not
@@ -510,17 +500,17 @@ var gSanitizePromptDialog = {
         }
       }
       // Otherwise use the predetermined range.
       else
         s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
     }
 
     try {
-      s.sanitize(); // We ignore the resulting Promise
+      s.sanitize();
     } catch (er) {
       Components.utils.reportError("Exception during sanitize: " + er);
     }
     return true;
   },
 
   /**
    * In order to mark the custom Places tree view and its nsINavHistoryResult
--- a/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
+++ b/browser/base/content/test/general/browser_sanitize-passwordDisabledHosts.js
@@ -1,21 +1,23 @@
 // 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;
 
-add_task(function*() {
+function test() {
+
   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,
      "example.com should be disabled for password saving since we haven't cleared that yet.");
 
   // Set up the sanitizer to just clear siteSettings
   let s = new Sanitizer();
   s.ignoreTimespan = false;
   s.prefDomain = "privacy.cpd.";
@@ -24,16 +26,16 @@ add_task(function*() {
   itemPrefs.setBoolPref("downloads", false);
   itemPrefs.setBoolPref("cache", false);
   itemPrefs.setBoolPref("cookies", false);
   itemPrefs.setBoolPref("formdata", false);
   itemPrefs.setBoolPref("offlineApps", false);
   itemPrefs.setBoolPref("passwords", false);
   itemPrefs.setBoolPref("sessions", false);
   itemPrefs.setBoolPref("siteSettings", true);
-
+  
   // Clear it
-  yield s.sanitize();
-
+  s.sanitize();
+  
   // Make sure it's gone
   is(pwmgr.getLoginSavingEnabled("http://example.com"), true,
      "example.com should be enabled for password saving again now that we've cleared.");
-});
+}
--- a/browser/base/content/test/general/browser_sanitize-sitepermissions.js
+++ b/browser/base/content/test/general/browser_sanitize-sitepermissions.js
@@ -10,17 +10,17 @@ function countPermissions() {
   let enumerator = Services.perms.enumerator;
   while (enumerator.hasMoreElements()) {
     result++;
     enumerator.getNext();
   }
   return result;
 }
 
-add_task(function* test() {
+function test() {
   // sanitize before we start so we have a good baseline.
   // Set up the sanitizer to just clear siteSettings
   let s = new Sanitizer();
   s.ignoreTimespan = false;
   s.prefDomain = "privacy.cpd.";
   var itemPrefs = gPrefService.getBranch(s.prefDomain);
   itemPrefs.setBoolPref("history", false);
   itemPrefs.setBoolPref("downloads", false);
@@ -40,13 +40,13 @@ add_task(function* test() {
   // Add a permission entry
   var pm = Services.perms;
   pm.add(makeURI("http://example.com"), "testing", pm.ALLOW_ACTION);
 
   // Sanity check
   ok(pm.enumerator.hasMoreElements(), "Permission manager should have elements, since we just added one");
 
   // Clear it
-  yield s.sanitize();
+  s.sanitize();
 
   // Make sure it's gone
   is(numAtStart, countPermissions(), "Permission manager should have the same count it started with");
-});
+}
--- a/browser/base/content/test/general/browser_sanitizeDialog.js
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -19,216 +19,219 @@
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 var {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
 
 XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
                                   "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 = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
 var Sanitizer = tempScope.Sanitizer;
 
 const kMsecPerMin = 60 * 1000;
 const kUsecPerMin = 60 * 1000000;
 
-add_task(function* init() {
-  requestLongerTimeout(2);
-  blankSlate();
-  registerCleanupFunction(() => {
-    blankSlate();
-    return PlacesTestUtils.promiseAsyncUpdates();
-  });
-});
+var formEntries, downloadIDs, olderDownloadIDs;
+
+// Add tests here.  Each is a function that's called by doNextTest().
+var gAllTests = [
 
-/**
- * Initializes the dialog to its default state.
- */
-add_task(function* default_state() {
-  let wh = new WindowHelper();
-  wh.onload = function () {
-    // Select "Last Hour"
-    this.selectDuration(Sanitizer.TIMESPAN_HOUR);
-    // Hide details
-    if (!this.getItemList().collapsed)
-      this.toggleDetails();
-    this.acceptDialog();
-  };
-  wh.open();
-  return wh.promiseClosed;
-});
-
-/**
- * Cancels the dialog, makes sure history not cleared.
- */
-add_task(function* test_cancel() {
-  // Add history (within the past hour)
-  let uris = [];
-  let places = [];
-  let pURI;
-  for (let i = 0; i < 30; i++) {
-    pURI = makeURI("http://" + i + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-    uris.push(pURI);
-  }
-
-  return new Promise(resolve => {
-    PlacesTestUtils.addVisits(places).then(() => {
+  /**
+   * Initializes the dialog to its default state.
+   */
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
+      // Select "Last Hour"
       this.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      this.checkPrefCheckbox("history", false);
-      this.checkDetails(false);
-
-      // Show details
-      this.toggleDetails();
-      this.checkDetails(true);
-
       // Hide details
-      this.toggleDetails();
-      this.checkDetails(false);
-      this.cancelDialog();
+      if (!this.getItemList().collapsed)
+        this.toggleDetails();
+      this.acceptDialog();
     };
-    wh.onunload = function* () {
-      yield promiseHistoryClearedState(uris, false);
-      yield blankSlate();
-      yield promiseHistoryClearedState(uris, true);
-    };
-    wh.promiseClosed.then(resolve);
     wh.open();
-  })});
-});
+  },
+
+  /**
+   * Cancels the dialog, makes sure history not cleared.
+   */
+  function () {
+    // Add history (within the past hour)
+    let uris = [];
+    let places = [];
+    let pURI;
+    for (let i = 0; i < 30; i++) {
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
+    }
+
+    PlacesTestUtils.addVisits(places).then(() => {
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        this.checkPrefCheckbox("history", false);
+        this.checkDetails(false);
+
+        // Show details
+        this.toggleDetails();
+        this.checkDetails(true);
 
-/**
- * Ensures that the combined history-downloads checkbox clears both history
- * visits and downloads when checked; the dialog respects simple timespan.
- */
-add_task(function* test_history_downloads_checked() {
-  // Add downloads (within the past hour).
-  let downloadIDs = [];
-  for (let i = 0; i < 5; i++) {
-    yield addDownloadWithMinutesAgo(downloadIDs, i);
-  }
-  // Add downloads (over an hour ago).
-  let olderDownloadIDs = [];
-  for (let i = 0; i < 5; i++) {
-    yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
-  }
+        // Hide details
+        this.toggleDetails();
+        this.checkDetails(false);
+        this.cancelDialog();
+      };
+      wh.onunload = function () {
+        yield promiseHistoryClearedState(uris, false);
+        yield blankSlate();
+        yield promiseHistoryClearedState(uris, true);
+      };
+      wh.open();
+    });
+  },
+
+  function () {
+    // Add downloads (within the past hour).
+    Task.spawn(function () {
+      downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        yield addDownloadWithMinutesAgo(downloadIDs, i);
+      }
+      // Add downloads (over an hour ago).
+      olderDownloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        yield addDownloadWithMinutesAgo(olderDownloadIDs, 61 + i);
+      }
 
-  // Add history (within the past hour).
-  let uris = [];
-  let places = [];
-  let pURI;
-  for (let i = 0; i < 30; i++) {
-    pURI = makeURI("http://" + i + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-    uris.push(pURI);
-  }
-  // Add history (over an hour ago).
-  let olderURIs = [];
-  for (let i = 0; i < 5; i++) {
-    pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
-    olderURIs.push(pURI);
-  }
-  let promiseSanitized = promiseSanitizationComplete();
+      doNextTest();
+    }).then(null, Components.utils.reportError);
+  },
 
-  return new Promise(resolve => {
+  /**
+   * Ensures that the combined history-downloads checkbox clears both history
+   * visits and downloads when checked; the dialog respects simple timespan.
+   */
+  function () {
+    // Add history (within the past hour).
+    let uris = [];
+    let places = [];
+    let pURI;
+    for (let i = 0; i < 30; i++) {
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
+    }
+    // Add history (over an hour ago).
+    let olderURIs = [];
+    for (let i = 0; i < 5; i++) {
+      pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
+      olderURIs.push(pURI);
+    }
+
     PlacesTestUtils.addVisits(places).then(() => {
       let totalHistoryVisits = uris.length + olderURIs.length;
 
       let wh = new WindowHelper();
       wh.onload = function () {
         this.selectDuration(Sanitizer.TIMESPAN_HOUR);
         this.checkPrefCheckbox("history", true);
         this.acceptDialog();
       };
-      wh.onunload = function* () {
+      wh.onunload = function () {
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", true,
                    "history pref should be true after accepting dialog with " +
                    "history checkbox checked");
         boolPrefIs("cpd.downloads", true,
                    "downloads pref should be true after accepting dialog with " +
                    "history checkbox checked");
 
-        yield promiseSanitized;
-
         // History visits and downloads within one hour should be cleared.
         yield promiseHistoryClearedState(uris, true);
         yield ensureDownloadsClearedState(downloadIDs, true);
 
         // Visits and downloads > 1 hour should still exist.
         yield promiseHistoryClearedState(olderURIs, false);
         yield ensureDownloadsClearedState(olderDownloadIDs, false);
 
         // OK, done, cleanup after ourselves.
         yield blankSlate();
         yield promiseHistoryClearedState(olderURIs, true);
         yield ensureDownloadsClearedState(olderDownloadIDs, true);
       };
-      wh.promiseClosed.then(resolve);
       wh.open();
     });
-  });
-});
+  },
+
+  /**
+   * Add form history entries for the next test.
+   */
+  function () {
+    formEntries = [];
 
-/**
- * Ensures that the combined history-downloads checkbox removes neither
- * history visits nor downloads when not checked.
- */
-add_task(function* test_history_downloads_unchecked() {
-  // Add form entries
-  let formEntries = [];
+    let iter = function() {
+      for (let i = 0; i < 5; i++) {
+        formEntries.push(addFormEntryWithMinutesAgo(iter, i));
+        yield undefined;
+      }
+      doNextTest();
+    }();
 
-  for (let i = 0; i < 5; i++) {
-    formEntries.push((yield promiseAddFormEntryWithMinutesAgo(i)));
-  }
-
+    iter.next();
+  },
 
-  // Add downloads (within the past hour).
-  let downloadIDs = [];
-  for (let i = 0; i < 5; i++) {
-    yield addDownloadWithMinutesAgo(downloadIDs, i);
-  }
+  function () {
+    // Add downloads (within the past hour).
+    Task.spawn(function () {
+      downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        yield addDownloadWithMinutesAgo(downloadIDs, i);
+      }
+
+      doNextTest();
+    }).then(null, Components.utils.reportError);
+  },
 
-  // Add history, downloads, form entries (within the past hour).
-  let uris = [];
-  let places = [];
-  let pURI;
-  for (let i = 0; i < 5; i++) {
-    pURI = makeURI("http://" + i + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
-    uris.push(pURI);
-  }
+  /**
+   * Ensures that the combined history-downloads checkbox removes neither
+   * history visits nor downloads when not checked.
+   */
+  function () {
+    // Add history, downloads, form entries (within the past hour).
+    let uris = [];
+    let places = [];
+    let pURI;
+    for (let i = 0; i < 5; i++) {
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
+    }
 
-  return new Promise(resolve => {
     PlacesTestUtils.addVisits(places).then(() => {
       let wh = new WindowHelper();
       wh.onload = function () {
         is(this.isWarningPanelVisible(), false,
            "Warning panel should be hidden after previously accepting dialog " +
            "with a predefined timespan");
         this.selectDuration(Sanitizer.TIMESPAN_HOUR);
 
         // Remove only form entries, leave history (including downloads).
         this.checkPrefCheckbox("history", false);
         this.checkPrefCheckbox("formdata", true);
         this.acceptDialog();
       };
-      wh.onunload = function* () {
+      wh.onunload = function () {
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
                   "timeSpan pref should be hour after accepting dialog with " +
                   "hour selected");
         boolPrefIs("cpd.history", false,
                    "history pref should be false after accepting dialog with " +
                    "history checkbox unchecked");
         boolPrefIs("cpd.downloads", false,
                    "downloads pref should be false after accepting dialog with " +
@@ -243,41 +246,35 @@ add_task(function* test_history_download
           is(exists, false, "form entry " + entry + " should no longer exist");
         });
 
         // OK, done, cleanup after ourselves.
         yield blankSlate();
         yield promiseHistoryClearedState(uris, true);
         yield ensureDownloadsClearedState(downloadIDs, true);
       };
-      wh.promiseClosed.then(resolve);
       wh.open();
     });
-  });
-});
+  },
 
-/**
- * Ensures that the "Everything" duration option works.
- */
-add_task(function* test_everything() {
-  // Add history.
-  let uris = [];
-  let places = [];
-  let pURI;
-  // within past hour, within past two hours, within past four hours and 
-  // outside past four hours
-  [10, 70, 130, 250].forEach(function(aValue) {
-    pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
-    uris.push(pURI);
-  });
-
-  let promiseSanitized = promiseSanitizationComplete();
-
-  return new Promise(resolve => {
+  /**
+   * Ensures that the "Everything" duration option works.
+   */
+  function () {
+    // Add history.
+    let uris = [];
+    let places = [];
+    let pURI;
+    // within past hour, within past two hours, within past four hours and 
+    // outside past four hours
+    [10, 70, 130, 250].forEach(function(aValue) {
+      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+      uris.push(pURI);
+    });
     PlacesTestUtils.addVisits(places).then(() => {
       let wh = new WindowHelper();
       wh.onload = function () {
         is(this.isWarningPanelVisible(), false,
            "Warning panel should be hidden after previously accepting dialog " +
            "with a predefined timespan");
         this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         this.checkPrefCheckbox("history", true);
@@ -289,88 +286,84 @@ add_task(function* test_everything() {
 
         // Show details
         this.toggleDetails();
         this.checkDetails(true);
 
         this.acceptDialog();
       };
       wh.onunload = function () {
-        yield promiseSanitized;
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
 
         yield promiseHistoryClearedState(uris, true);
       };
-      wh.promiseClosed.then(resolve);
       wh.open();
     });
-  });
-});
+  },
 
-/**
- * Ensures that the "Everything" warning is visible on dialog open after
- * the previous test.
- */
-add_task(function* test_everything_warning() {
-  // Add history.
-  let uris = [];
-  let places = [];
-  let pURI;
-  // within past hour, within past two hours, within past four hours and 
-  // outside past four hours
-  [10, 70, 130, 250].forEach(function(aValue) {
-    pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
-    places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
-    uris.push(pURI);
-  });
-
-  let promiseSanitized = promiseSanitizationComplete();
-
-  return new Promise(resolve => {
+  /**
+   * Ensures that the "Everything" warning is visible on dialog open after
+   * the previous test.
+   */
+  function () {
+    // Add history.
+    let uris = [];
+    let places = [];
+    let pURI;
+    // within past hour, within past two hours, within past four hours and 
+    // outside past four hours
+    [10, 70, 130, 250].forEach(function(aValue) {
+      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+      uris.push(pURI);
+    });
     PlacesTestUtils.addVisits(places).then(() => {
       let wh = new WindowHelper();
       wh.onload = function () {
         is(this.isWarningPanelVisible(), true,
            "Warning panel should be visible after previously accepting dialog " +
            "with clearing everything");
         this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
         this.checkPrefCheckbox("history", true);
         this.acceptDialog();
       };
       wh.onunload = function () {
         intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
                   "timeSpan pref should be everything after accepting dialog " +
                   "with everything selected");
 
-        yield promiseSanitized;
-
         yield promiseHistoryClearedState(uris, true);
       };
-      wh.promiseClosed.then(resolve);
       wh.open();
     });
-  });
-});
+  },
+
+  /**
+   * Add form history entry for the next test.
+   */
+  function () {
+    let iter = function() {
+      formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
+      yield undefined;
+      doNextTest();
+    }();
 
-/**
- * The next three tests checks that when a certain history item cannot be
- * cleared then the checkbox should be both disabled and unchecked.
- * In addition, we ensure that this behavior does not modify the preferences.
- */
-add_task(function* test_cannot_clear_history() {
-  // Add form entries
-  let formEntries = [ (yield promiseAddFormEntryWithMinutesAgo(10)) ];
+    iter.next();
+  },
 
-  let promiseSanitized = promiseSanitizationComplete();
-
-  // Add history.
-  let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
-  return new Promise(resolve => {
+  /**
+   * The next three tests checks that when a certain history item cannot be
+   * cleared then the checkbox should be both disabled and unchecked.
+   * In addition, we ensure that this behavior does not modify the preferences.
+   */
+  function () {
+    // Add history.
+    let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
     PlacesTestUtils.addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}).then(() => {
       let uris = [ pURI ];
 
       let wh = new WindowHelper();
       wh.onload = function() {
         // Check that the relevant checkboxes are enabled
         var cb = this.win.document.querySelectorAll(
                    "#itemList > [preference='privacy.cpd.formdata']");
@@ -381,320 +374,312 @@ add_task(function* test_cannot_clear_his
                    "#itemList > [preference='privacy.cpd.history']");
         ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
            "clear history should be enabled.");
 
         this.checkAllCheckboxes();
         this.acceptDialog();
       };
       wh.onunload = function () {
-        yield promiseSanitized;
-
         yield promiseHistoryClearedState(uris, true);
 
         let exists = yield formNameExists(formEntries[0]);
         is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
       };
-      wh.promiseClosed.then(resolve);
       wh.open();
     });
-  });
-});
-
-add_task(function* test_no_formdata_history_to_clear() {
-  let promiseSanitized = promiseSanitizationComplete();
-  let wh = new WindowHelper();
-  wh.onload = function() {
-    boolPrefIs("cpd.history", true,
-               "history pref should be true after accepting dialog with " +
-               "history checkbox checked");
-    boolPrefIs("cpd.formdata", true,
-               "formdata pref should be true after accepting dialog with " +
-               "formdata checkbox checked");
-
-    // Even though the formdata pref is true, because there is no history
-    // left to clear, the checkbox will be disabled.
-    var cb = this.win.document.querySelectorAll(
-               "#itemList > [preference='privacy.cpd.formdata']");
-    ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
-       "There is no formdata history, checkbox should be disabled and be " +
-       "cleared to reduce user confusion (bug 497664).");
-
-    var cb = this.win.document.querySelectorAll(
-               "#itemList > [preference='privacy.cpd.history']");
-    ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
-       "There is no history, but history checkbox should always be enabled " +
-       "and will be checked from previous preference.");
-
-    this.acceptDialog();
-  }
-  wh.open();
-  yield wh.promiseClosed;
-  yield promiseSanitized;
-});
-
-add_task(function* test_form_entries() {
-  let formEntry = (yield promiseAddFormEntryWithMinutesAgo(10));
-
-  let promiseSanitized = promiseSanitizationComplete();
-
-  let wh = new WindowHelper();
-  wh.onload = function() {
-    boolPrefIs("cpd.formdata", true,
-               "formdata pref should persist previous value after accepting " +
-               "dialog where you could not clear formdata.");
-
-    var cb = this.win.document.querySelectorAll(
-               "#itemList > [preference='privacy.cpd.formdata']");
-
-    info("There exists formEntries so the checkbox should be in sync with the pref.");
-    is(cb.length, 1, "There is only one checkbox for form data");
-    ok(!cb[0].disabled, "The checkbox is enabled");
-    ok(cb[0].checked, "The checkbox is checked");
-
-    this.acceptDialog();
-  };
-  wh.onunload = function () {
-    yield promiseSanitized;
-    let exists = yield formNameExists(formEntry);
-    is(exists, false, "form entry " + formEntry + " should no longer exist");
-  };
-  wh.open();
-  return wh.promiseClosed;
-});
+  },
+  function () {
+    let wh = new WindowHelper();
+    wh.onload = function() {
+      boolPrefIs("cpd.history", true,
+                 "history pref should be true after accepting dialog with " +
+                 "history checkbox checked");
+      boolPrefIs("cpd.formdata", true,
+                 "formdata pref should be true after accepting dialog with " +
+                 "formdata checkbox checked");
 
 
-/**
- * Ensure that toggling details persists
- * across dialog openings.
- */
-add_task(function* test_toggling_details_persists() {
-  {
+      // Even though the formdata pref is true, because there is no history
+      // left to clear, the checkbox will be disabled.
+      var cb = this.win.document.querySelectorAll(
+                 "#itemList > [preference='privacy.cpd.formdata']");
+
+      // Wait until the checkbox is disabled. This is done asynchronously
+      // from Sanitizer.init() as FormHistory.count() is a purely async API.
+      promiseWaitForCondition(() => cb[0].disabled).then(() => {
+        ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
+           "There is no formdata history, checkbox should be disabled and be " +
+           "cleared to reduce user confusion (bug 497664).");
+
+        cb = this.win.document.querySelectorAll(
+                   "#itemList > [preference='privacy.cpd.history']");
+        ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+           "There is no history, but history checkbox should always be enabled " +
+           "and will be checked from previous preference.");
+
+        this.acceptDialog();
+      });
+    }
+    wh.open();
+  },
+
+  /**
+   * Add form history entry for the next test.
+   */
+  function () {
+    let iter = function() {
+      formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
+      yield undefined;
+      doNextTest();
+    }();
+
+    iter.next();
+  },
+
+  function () {
+    let wh = new WindowHelper();
+    wh.onload = function() {
+      boolPrefIs("cpd.formdata", true,
+                 "formdata pref should persist previous value after accepting " +
+                 "dialog where you could not clear formdata.");
+
+      var cb = this.win.document.querySelectorAll(
+                 "#itemList > [preference='privacy.cpd.formdata']");
+      ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+         "There exists formEntries so the checkbox should be in sync with " +
+         "the pref.");
+
+      this.acceptDialog();
+    };
+    wh.onunload = function () {
+      let exists = yield formNameExists(formEntries[0]);
+      is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
+    };
+    wh.open();
+  },
+
+
+  /**
+   * These next six tests together ensure that toggling details persists
+   * across dialog openings.
+   */
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Check all items and select "Everything"
       this.checkAllCheckboxes();
       this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
 
       // Hide details
       this.toggleDetails();
       this.checkDetails(false);
       this.acceptDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should remain closed because all items are checked.
       this.checkDetails(false);
 
       // Uncheck history.
       this.checkPrefCheckbox("history", false);
       this.acceptDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should be open because not all items are checked.
       this.checkDetails(true);
 
       // Modify the Site Preferences item state (bug 527820)
       this.checkAllCheckboxes();
       this.checkPrefCheckbox("siteSettings", false);
       this.acceptDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should be open because not all items are checked.
       this.checkDetails(true);
 
       // Hide details
       this.toggleDetails();
       this.checkDetails(false);
       this.cancelDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should be open because not all items are checked.
       this.checkDetails(true);
 
       // Select another duration
       this.selectDuration(Sanitizer.TIMESPAN_HOUR);
       // Hide details
       this.toggleDetails();
       this.checkDetails(false);
       this.acceptDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should not be open because "Last Hour" is selected
       this.checkDetails(false);
 
       this.cancelDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-  {
+  },
+  function () {
     let wh = new WindowHelper();
     wh.onload = function () {
       // Details should have remained closed
       this.checkDetails(false);
 
       // Show details
       this.toggleDetails();
       this.checkDetails(true);
       this.cancelDialog();
     };
     wh.open();
-    yield wh.promiseClosed;
-  }
-});
+  },
+  function () {
+    // Test for offline cache deletion
+
+    // Prepare stuff, we will work with www.example.com
+    var URL = "http://www.example.com";
 
-// Test for offline cache deletion
-add_task(function* test_offline_cache() {
-  // Prepare stuff, we will work with www.example.com
-  var URL = "http://www.example.com";
+    var ios = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService);
+    var URI = ios.newURI(URL, null, null);
+
+    var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+             .getService(Ci.nsIScriptSecurityManager);
+    var principal = sm.createCodebasePrincipal(URI, {});
 
-  var ios = Cc["@mozilla.org/network/io-service;1"]
-            .getService(Ci.nsIIOService);
-  var URI = ios.newURI(URL, null, null);
+    // Give www.example.com privileges to store offline data
+    var pm = Cc["@mozilla.org/permissionmanager;1"]
+             .getService(Ci.nsIPermissionManager);
+    pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+    pm.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
 
-  var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
-           .getService(Ci.nsIScriptSecurityManager);
-  var principal = sm.getNoAppCodebasePrincipal(URI);
+    // Store something to the offline cache
+    var appcacheserv = Cc["@mozilla.org/network/application-cache-service;1"]
+                       .getService(Ci.nsIApplicationCacheService);
+    var appcachegroupid = appcacheserv.buildGroupID(makeURI(URL + "/manifest"), LoadContextInfo.default);
+    var appcache = appcacheserv.createApplicationCache(appcachegroupid);
 
-  // Give www.example.com privileges to store offline data
-  var pm = Cc["@mozilla.org/permissionmanager;1"]
-           .getService(Ci.nsIPermissionManager);
-  pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
-  pm.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+    var cacheserv = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+                    .getService(Ci.nsICacheStorageService);
+    var storage = cacheserv.appCacheStorage(LoadContextInfo.default, appcache);
 
-  // Store something to the offline cache
-  var appcacheserv = Cc["@mozilla.org/network/application-cache-service;1"]
-                     .getService(Ci.nsIApplicationCacheService);
-  var appcachegroupid = appcacheserv.buildGroupID(makeURI(URL + "/manifest"), LoadContextInfo.default);
-  var appcache = appcacheserv.createApplicationCache(appcachegroupid);
-
-  var cacheserv = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
-                  .getService(Ci.nsICacheStorageService);
-  var storage = cacheserv.appCacheStorage(LoadContextInfo.default, appcache);
+    // Open the dialog
+    let wh = new WindowHelper();
+    wh.onload = function () {
+      this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+      // Show details
+      this.toggleDetails();
+      // Clear only offlineApps
+      this.uncheckAllCheckboxes();
+      this.checkPrefCheckbox("offlineApps", true);
+      this.acceptDialog();
+    };
+    wh.onunload = function () {
+      // Check if the cache has been deleted
+      var size = -1;
+      var visitor = {
+        onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+        {
+          size = aConsumption;
+        }
+      };
+      storage.asyncVisitStorage(visitor, false);
+      // Offline cache visit happens synchronously, since it's forwarded to the old code
+      is(size, 0, "offline application cache entries evicted");
+    };
 
-  // Open the dialog
-  let wh = new WindowHelper();
-  wh.onload = function () {
-    this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-    // Show details
-    this.toggleDetails();
-    // Clear only offlineApps
-    this.uncheckAllCheckboxes();
-    this.checkPrefCheckbox("offlineApps", true);
-    this.acceptDialog();
-  };
-  wh.onunload = function () {
-    // Check if the cache has been deleted
-    var size = -1;
-    var visitor = {
-      onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
-      {
-        size = aConsumption;
+    var cacheListener = {
+      onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
+      onCacheEntryAvailable: function (entry, isnew, appcache, status) {
+        is(status, Cr.NS_OK);
+        var stream = entry.openOutputStream(0);
+        var content = "content";
+        stream.write(content, content.length);
+        stream.close();
+        entry.close();
+        wh.open();
       }
     };
-    storage.asyncVisitStorage(visitor, false);
-    // Offline cache visit happens synchronously, since it's forwarded to the old code
-    is(size, 0, "offline application cache entries evicted");
-  };
+
+    storage.asyncOpenURI(makeURI(URL), "", Ci.nsICacheStorage.OPEN_TRUNCATE, cacheListener);
+  },
+  function () {
+    // Test for offline apps permission deletion
 
-  var cacheListener = {
-    onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
-    onCacheEntryAvailable: function (entry, isnew, appcache, status) {
-      is(status, Cr.NS_OK);
-      var stream = entry.openOutputStream(0);
-      var content = "content";
-      stream.write(content, content.length);
-      stream.close();
-      entry.close();
-      wh.open();
-    }
-  };
+    // Prepare stuff, we will work with www.example.com
+    var URL = "http://www.example.com";
 
-  storage.asyncOpenURI(makeURI(URL), "", Ci.nsICacheStorage.OPEN_TRUNCATE, cacheListener);
-  return wh.promiseClosed;
-});
+    var ios = Cc["@mozilla.org/network/io-service;1"]
+              .getService(Ci.nsIIOService);
+    var URI = ios.newURI(URL, null, null);
 
-// Test for offline apps permission deletion
-add_task(function* test_offline_apps_permissions() {
-  // Prepare stuff, we will work with www.example.com
-  var URL = "http://www.example.com";
+    var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+             .getService(Ci.nsIScriptSecurityManager);
+    var principal = sm.createCodebasePrincipal(URI, {});
 
-  var ios = Cc["@mozilla.org/network/io-service;1"]
-            .getService(Ci.nsIIOService);
-  var URI = ios.newURI(URL, null, null);
-
-  var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
-           .getService(Ci.nsIScriptSecurityManager);
-  var principal = sm.createCodebasePrincipal(URI, {});
-
-  let promiseSanitized = promiseSanitizationComplete();
+    // Open the dialog
+    let wh = new WindowHelper();
+    wh.onload = function () {
+      this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+      // Show details
+      this.toggleDetails();
+      // Clear only offlineApps
+      this.uncheckAllCheckboxes();
+      this.checkPrefCheckbox("siteSettings", true);
+      this.acceptDialog();
+    };
+    wh.onunload = function () {
+      // Check all has been deleted (privileges, data, cache)
+      var pm = Cc["@mozilla.org/permissionmanager;1"]
+               .getService(Ci.nsIPermissionManager);
+      is(pm.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
+    };
+    wh.open();
+  }
+];
 
-  // Open the dialog
-  let wh = new WindowHelper();
-  wh.onload = function () {
-    this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-    // Show details
-    this.toggleDetails();
-    // Clear only offlineApps
-    this.uncheckAllCheckboxes();
-    this.checkPrefCheckbox("siteSettings", true);
-    this.acceptDialog();
-  };
-  wh.onunload = function () {
-    yield promiseSanitized;
-
-    // Check all has been deleted (privileges, data, cache)
-    var pm = Cc["@mozilla.org/permissionmanager;1"]
-             .getService(Ci.nsIPermissionManager);
-    is(pm.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
-  };
-  wh.open();
-  return wh.promiseClosed;
-});
+// Index in gAllTests of the test currently being run.  Incremented for each
+// test run.  See doNextTest().
+var gCurrTest = 0;
 
 var now_mSec = Date.now();
 var now_uSec = now_mSec * 1000;
 
 ///////////////////////////////////////////////////////////////////////////////
 
 /**
  * This wraps the dialog and provides some convenience methods for interacting
  * with it.
  *
  * @param aWin
  *        The dialog's nsIDOMWindow
  */
 function WindowHelper(aWin) {
   this.win = aWin;
-  this.promiseClosed = new Promise(resolve => {this._resolveClosed = resolve});
 }
 
 WindowHelper.prototype = {
   /**
    * "Presses" the dialog's OK button.
    */
   acceptDialog: function () {
     is(this.win.document.documentElement.getButton("accept").disabled, false,
@@ -829,55 +814,69 @@ WindowHelper.prototype = {
       if (aTopic != "domwindowopened")
         return;
 
       Services.ww.unregisterNotification(windowObserver);
 
       var loaded = false;
       let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
 
-      let promiseDialogReady = promiseSanitizationDialogReady();
-
       win.addEventListener("load", function onload(event) {
         win.removeEventListener("load", onload, false);
 
         if (win.name !== "SanitizeDialog")
           return;
 
         wh.win = win;
         loaded = true;
 
-        Task.spawn(function*() {
-          yield promiseDialogReady;
-          yield new Promise(resolve => setTimeout(resolve, 0));
-          yield wh.onload();
+        executeSoon(function () {
+          // Some exceptions that reach here don't reach the test harness, but
+          // ok()/is() do...
+          try {
+            wh.onload();
+          }
+          catch (exc) {
+            win.close();
+            ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
+            finish();
+          }
         });
       }, false);
 
       win.addEventListener("unload", function onunload(event) {
         if (win.name !== "SanitizeDialog") {
           win.removeEventListener("unload", onunload, false);
           return;
         }
 
         // Why is unload fired before load?
         if (!loaded)
           return;
 
         win.removeEventListener("unload", onunload, false);
         wh.win = win;
 
-        // Some exceptions that reach here don't reach the test harness, but
-        // ok()/is() do...
-        Task.spawn(function*() {
-          if (wh.onunload) {
-            yield wh.onunload();
+        executeSoon(function () {
+          // Some exceptions that reach here don't reach the test harness, but
+          // ok()/is() do...
+          try {
+            if (wh.onunload) {
+              Task.spawn(wh.onunload).then(function() {
+                waitForAsyncUpdates(doNextTest);
+              }).then(null, Components.utils.reportError);
+            } else {
+              waitForAsyncUpdates(doNextTest);
+            }
           }
-          yield PlacesTestUtils.promiseAsyncUpdates();
-          wh._resolveClosed();
+          catch (exc) {
+            win.close();
+            ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
+            finish();
+          }
         });
       }, false);
     }
     Services.ww.registerNotification(windowObserver);
     Services.ww.openWindow(null,
                            "chrome://browser/content/sanitize.xul",
                            "SanitizeDialog",
                            "chrome,titlebar,dialog,centerscreen,modal",
@@ -905,24 +904,16 @@ WindowHelper.prototype = {
   /**
    * Toggles the details progressive disclosure button.
    */
   toggleDetails: function () {
     this.getDetailsButton().click();
   }
 };
 
-function promiseSanitizationDialogReady() {
-  return promiseTopicObserved("sanitize-dialog-setup-complete");
-}
-
-function promiseSanitizationComplete() {
-  return promiseTopicObserved("sanitizer-sanitization-complete");
-}
-
 /**
  * Adds a download to history.
  *
  * @param aMinutesAgo
  *        The download will be downloaded this many minutes ago
  */
 function addDownloadWithMinutesAgo(aExpectedPathList, aMinutesAgo) {
   let publicList = yield Downloads.getList(Downloads.PUBLIC);
@@ -944,33 +935,29 @@ function addDownloadWithMinutesAgo(aExpe
 }
 
 /**
  * Adds a form entry to history.
  *
  * @param aMinutesAgo
  *        The entry will be added this many minutes ago
  */
-function promiseAddFormEntryWithMinutesAgo(aMinutesAgo) {
+function addFormEntryWithMinutesAgo(then, aMinutesAgo) {
   let name = aMinutesAgo + "-minutes-ago";
 
   // Artifically age the entry to the proper vintage.
   let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
 
-  return new Promise((resolve, reject) =>
-    FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
+  FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
                      { handleError: function (error) {
                          do_throw("Error occurred updating form history: " + error);
-                         reject();
                        },
-                       handleCompletion: function (reason) {
-                         resolve(name);
-                       }
-                     })
-   )
+                       handleCompletion: function (reason) { then.next(); }
+                     });
+  return name;
 }
 
 /**
  * Checks if a form entry exists.
  */
 function formNameExists(name)
 {
   let deferred = Promise.defer();
@@ -1048,16 +1035,32 @@ function downloadExists(aPath)
   return Task.spawn(function() {
     let publicList = yield Downloads.getList(Downloads.PUBLIC);
     let listArray = yield publicList.getAll();
     throw new Task.Result(listArray.some(i => i.target.path == aPath));
   });
 }
 
 /**
+ * Runs the next test in the gAllTests array.  If all tests have been run,
+ * finishes the entire suite.
+ */
+function doNextTest() {
+  if (gAllTests.length <= gCurrTest) {
+    blankSlate();
+    waitForAsyncUpdates(finish);
+  }
+  else {
+    let ct = gCurrTest;
+    gCurrTest++;
+    gAllTests[ct]();
+  }
+}
+
+/**
  * Ensures that the specified downloads are either cleared or not.
  *
  * @param aDownloadIDs
  *        Array of download database IDs
  * @param aShouldBeCleared
  *        True if each download should be cleared, false otherwise
  */
 function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
@@ -1086,8 +1089,18 @@ function intPrefIs(aPrefName, aExpectedV
  * Creates a visit time.
  *
  * @param aMinutesAgo
  *        The visit will be visited this many minutes ago
  */
 function visitTimeForMinutesAgo(aMinutesAgo) {
   return now_uSec - aMinutesAgo * kUsecPerMin;
 }
+
+///////////////////////////////////////////////////////////////////////////////
+
+function test() {
+  requestLongerTimeout(2);
+  waitForExplicitFinish();
+  blankSlate();
+  // Kick off all the tests in the gAllTests array.
+  waitForAsyncUpdates(doNextTest);
+}
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -160,17 +160,17 @@ 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.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/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -500,19 +500,16 @@ BrowserGlue.prototype = {
               break;
             }
           }
         });
         break;
       case "autocomplete-did-enter-text":
         this._handleURLBarTelemetry(subject.QueryInterface(Ci.nsIAutoCompleteInput));
         break;
-      case "test-initialize-sanitizer":
-        this._sanitizer.onStartup();
-        break;
     }
   },
 
   _handleURLBarTelemetry(input) {
     if (!input ||
         input.id != "urlbar" ||
         input.inPrivateContext ||
         input.popup.selectedIndex < 0) {
@@ -1839,16 +1836,17 @@ BrowserGlue.prototype = {
   },
 
   /**
    * Places shut-down tasks
    * - finalize components depending on Places.
    * - export bookmarks as HTML, if so configured.
    */
   _onPlacesShutdown: function BG__onPlacesShutdown() {
+    this._sanitizer.onShutdown();
     PageThumbs.uninit();
 
     if (this._bookmarksBackupIdleTime) {
       this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime);
       delete this._bookmarksBackupIdleTime;
     }
   },
 
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -44,18 +44,16 @@ function run_test() {
 
 add_task(function* test_execute() {
   do_print("Initialize browserglue before Places");
 
   // Avoid default bookmarks import.
   let glue = Cc["@mozilla.org/browser/browserglue;1"].
              getService(Ci.nsIObserver);
   glue.observe(null, "initial-migration-will-import-default-bookmarks", null);
-  glue.observe(null, "test-initialize-sanitizer", null);
-
 
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cache", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.offlineApps", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.history", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.downloads", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.cookies", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true);
deleted file mode 100644
--- a/browser/modules/Sanitizer.jsm
+++ /dev/null
@@ -1,22 +0,0 @@
-/* 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";
-
-//
-// 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.
-//
-
-this.EXPORTED_SYMBOLS = ["Sanitizer"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-var scope = {};
-Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
- .loadSubScript("chrome://browser/content/sanitize.js", scope);
-
-this.Sanitizer = scope.Sanitizer;
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -31,17 +31,16 @@ EXTRA_JS_MODULES += [
     'HiddenFrame.jsm',
     'NetworkPrioritizer.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'PluginContent.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
-    'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TabGroupsMigrator.jsm',
     'TransientPrefs.jsm',
     'WebappManager.jsm',
     'webrtcUI.jsm',
 ]