Backed out changesets b2c46fdeca8b, 6b768986595f, and 78c891ba5de1 (bug 1089695) for frequent Marionette crashes.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 31 Aug 2015 22:03:20 -0400
changeset 292776 764e07ab1ad0bb25eeead8474b974588c0c1ca8c
parent 292775 fa2ee4b3a70027f91912352f54ed4693cc3ef96b
child 292777 e5d0ef8da5b08459f35cac8596081c7d16ec8f48
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1089695
milestone43.0a1
backs outb2c46fdeca8b28fe404a1ff7116b34da28689a1e
6b768986595f2166774b1e473ecffa6e99d60d36
78c891ba5de1e144c74a997653f05ded12e62504
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changesets b2c46fdeca8b, 6b768986595f, and 78c891ba5de1 (bug 1089695) for frequent Marionette crashes.
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/content/test/newtab/browser_newtab_bug722273.js
browser/base/content/test/newtab/head.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
toolkit/components/places/PlacesUtils.jsm
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1278,16 +1278,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");
@@ -1662,16 +1665,19 @@ var 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();
 
 #ifdef MOZ_SERVICES_SYNC
     // initialize the sync UI
     gSyncUI.init();
 #endif
 
@@ -1686,16 +1692,62 @@ var gBrowserInit = {
     if (this._delayedStartupTimeoutId) {
       clearTimeout(this._delayedStartupTimeoutId);
       return;
     }
 
     BrowserOffline.uninit();
   },
 #endif
+
+  _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(function (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);
+    }
+  },
 }
 
 
 /* Legacy global init functions */
 var BrowserStartup        = gBrowserInit.onLoad.bind(gBrowserInit);
 var BrowserShutdown       = gBrowserInit.onUnload.bind(gBrowserInit);
 #ifdef XP_MACOSX
 var nonBrowserWindowStartup        = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -1,62 +1,39 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
-/* 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/. */
+# -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+# 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/devtools/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,305 @@ 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";
+      itemsToClear = Object.keys(this.items).filter(itemName => branch.getBoolPref(itemName));
     }
 
     // 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 +367,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 +414,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 +519,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: {
@@ -586,138 +554,122 @@ Sanitizer.prototype = {
         }
         return true;
       },
       _resetAllWindowClosures: function(aWindowList) {
         for (let win of aWindowList) {
           win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
         }
       },
-      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);
 
-        if (AppConstants.platform == "macosx") {
-          let 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;
-          function onWindowOpened(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;
-          function onWindowClosed() {
-            numWindowsClosing--;
-            if (numWindowsClosing == 0) {
-              Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
-              // If we're the last thing to happen, invoke callback.
-              if (newWindowOpened) {
-                TelemetryStopwatch.finish("FX_SANITIZE_OPENWINDOWS", refObj);
-                resolve();
-              }
-            }
-          }
-        });
         Services.obs.addObserver(onWindowOpened, "browser-delayed-startup-finished", false);
         Services.obs.addObserver(onWindowClosed, "xul-window-destroyed", false);
 
         // Start the process of closing windows
         while (windowList.length) {
           windowList.pop().close();
         }
         newWindow.focus();
-        yield promiseReady;
-      }),
+        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;
@@ -766,84 +718,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/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-let {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
 
 let tempScope = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
 let 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");
 let {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");
 
 let tempScope = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
                                            .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
 let Sanitizer = tempScope.Sanitizer;
 
 const kMsecPerMin = 60 * 1000;
 const kUsecPerMin = 60 * 1000000;
 
-add_task(function* init() {
-  requestLongerTimeout(2);
-  blankSlate();
-  registerCleanupFunction(() => {
-    blankSlate();
-    return PlacesTestUtils.promiseAsyncUpdates();
-  });
-});
+let 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;
 
 let now_mSec = Date.now();
 let 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/content/test/newtab/browser_newtab_bug722273.js
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -6,72 +6,63 @@ const URL = "http://fake-site.com/";
 
 let tmp = {};
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 
 let {Sanitizer} = tmp;
 
-add_task(function*() {
-  yield promiseSanitizeHistory();
-  yield promiseAddFakeVisits();
-  yield addNewTabPageTabPromise();
+function runTests() {
+  sanitizeHistory();
+  yield addFakeVisits();
+  yield addNewTabPageTab();
+
   is(getCell(0).site.url, URL, "first site is our fake site");
 
-  whenPagesUpdated(() => {});
-  yield promiseSanitizeHistory();
+  whenPagesUpdated();
+  yield sanitizeHistory();
 
-  // Now wait until the grid is updated
-  while (true) {
-    if (!getCell(0).site) {
-      break;
-    }
-    info("the fake site is still present");
-    yield new Promise(resolve => setTimeout(resolve, 1000));
-  }
-  ok(!getCell(0).site, "fake site is gone");
-});
+  ok(!getCell(0).site, "the fake site is gone");
+}
 
-function promiseAddFakeVisits() {
+function addFakeVisits() {
   let visits = [];
   for (let i = 59; i > 0; i--) {
     visits.push({
       visitDate: NOW - i * 60 * 1000000,
       transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
     });
   }
   let place = {
     uri: makeURI(URL),
     title: "fake site",
     visits: visits
   };
-  return new Promise((resolve, reject) => {
-    PlacesUtils.asyncHistory.updatePlaces(place, {
-      handleError: function () reject(new Error("Couldn't add visit")),
-      handleResult: function () {},
-      handleCompletion: function () {
-        NewTabUtils.links.populateCache(function () {
-          NewTabUtils.allPages.update();
-          resolve();
-        }, true);
-      }
-    });
+  PlacesUtils.asyncHistory.updatePlaces(place, {
+    handleError: function () ok(false, "couldn't add visit"),
+    handleResult: function () {},
+    handleCompletion: function () {
+      NewTabUtils.links.populateCache(function () {
+        NewTabUtils.allPages.update();
+        TestRunner.next();
+      }, true);
+    }
   });
 }
 
-function promiseSanitizeHistory() {
+function sanitizeHistory() {
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
 
   let prefs = gPrefService.getBranch(s.prefDomain);
   prefs.setBoolPref("history", true);
   prefs.setBoolPref("downloads", false);
   prefs.setBoolPref("cache", false);
   prefs.setBoolPref("cookies", false);
   prefs.setBoolPref("formdata", false);
   prefs.setBoolPref("offlineApps", false);
   prefs.setBoolPref("passwords", false);
   prefs.setBoolPref("sessions", false);
   prefs.setBoolPref("siteSettings", false);
 
-  return s.sanitize();
+  s.sanitize();
 }
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -111,68 +111,28 @@ function watchLinksChangeOnce() {
   };
   observer.onDownloadFail = observer.onManyLinksChanged;
   DirectoryLinksProvider.addObserver(observer);
   return deferred.promise;
 };
 
 /**
  * Provide the default test function to start our test runner.
- *
- * We need different code paths for tests that are still wired for
- * `TestRunner` and tests that have been ported to `add_task` as
- * we cannot have both in the same file.
  */
-function isTestPortedToAddTask() {
-  return gTestPath.endsWith("browser_newtab_bug722273.js");
-}
-if (!isTestPortedToAddTask()) {
-  this.test = function() {
-    waitForExplicitFinish();
-    // start TestRunner.run() after directory links is downloaded and written to disk
-    watchLinksChangeOnce().then(() => {
-      // Wait for hidden page to update with the desired links
-      whenPagesUpdated(() => TestRunner.run(), true);
-    });
+function test() {
+  waitForExplicitFinish();
+  // start TestRunner.run() after directory links is downloaded and written to disk
+  watchLinksChangeOnce().then(() => {
+    // Wait for hidden page to update with the desired links
+    whenPagesUpdated(() => TestRunner.run(), true);
+  });
 
-    // Save the original directory source (which is set globally for tests)
-    gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
-    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
-  }
-} else {
-  add_task(function* setup() {
-    registerCleanupFunction(function() {
-      return new Promise(resolve => {
-        function cleanupAndFinish() {
-          PlacesTestUtils.clearHistory().then(() => {
-            whenPagesUpdated(resolve);
-            NewTabUtils.restore();
-          });
-        }
-
-        let callbacks = NewTabUtils.links._populateCallbacks;
-        let numCallbacks = callbacks.length;
-
-        if (numCallbacks)
-          callbacks.splice(0, numCallbacks, cleanupAndFinish);
-        else
-          cleanupAndFinish();
-      });
-    });
-
-    let promiseReady = Task.spawn(function*() {
-      yield watchLinksChangeOnce();
-      yield new Promise(resolve => whenPagesUpdated(resolve, true));
-    });
-
-    // Save the original directory source (which is set globally for tests)
-    gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
-    Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
-    yield promiseReady;
-  });
+  // Save the original directory source (which is set globally for tests)
+  gOrigDirectorySource = Services.prefs.getCharPref(PREF_NEWTAB_DIRECTORYSOURCE);
+  Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gDirectorySource);
 }
 
 /**
  * The test runner that controls the execution flow of our tests.
  */
 let TestRunner = {
   /**
    * Starts the test runner.
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -133,17 +133,17 @@ browser.jar:
         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)
 #endif
         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
@@ -502,19 +502,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) {
@@ -1157,17 +1154,16 @@ BrowserGlue.prototype = {
     }
     var rokuDevice = {
       id: "roku:ecp",
       target: "roku:ecp",
       factory: function(aService) {
         Cu.import("resource://gre/modules/RokuApp.jsm");
         return new RokuApp(aService);
       },
-      mirror: true,
       types: ["video/mp4"],
       extensions: ["mp4"]
     };
 
     // Register targets
     SimpleServiceDiscovery.registerDevice(rokuDevice);
 
     // Search for devices continuously every 120 seconds
@@ -1759,16 +1755,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);
@@ -68,37 +66,45 @@ add_task(function* test_execute() {
   for (let aUrl of URIS) {
     yield PlacesTestUtils.addVisits({
       uri: uri(aUrl), visitDate: timeInMicroseconds++,
       transition: PlacesUtils.history.TRANSITION_TYPED
     });
   }
   do_print("Add cache.");
   yield storeCache(FTP_URL, "testData");
+});
 
+add_task(function* run_test_continue() {
   do_print("Simulate and wait shutdown.");
   yield shutdownPlaces();
 
-  let stmt = DBConn(true).createStatement(
+  let stmt = DBConn().createStatement(
     "SELECT id FROM moz_places WHERE url = :page_url "
   );
 
   try {
     URIS.forEach(function(aUrl) {
       stmt.params.page_url = aUrl;
       do_check_false(stmt.executeStep());
       stmt.reset();
     });
   } finally {
     stmt.finalize();
   }
 
   do_print("Check cache");
   // Check cache.
-  yield checkCache(FTP_URL);
+  let promiseCacheChecked = checkCache(FTP_URL);
+
+  do_print("Shutdown the download manager");
+  // Shutdown the download manager.
+  Services.obs.notifyObservers(null, "quit-application", null);
+
+  yield promiseCacheChecked;
 });
 
 function storeCache(aURL, aContent) {
   let cache = Services.cache2;
   let storage = cache.diskCacheStorage(LoadContextInfo.default, false);
 
   return new Promise(resolve => {
     let storeCacheListener = {
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;
-
-let 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
@@ -33,17 +33,16 @@ EXTRA_JS_MODULES += [
     'NewTabURL.jsm',
     'offlineAppCache.jsm',
     'PanelFrame.jsm',
     'PluginContent.jsm',
     'ProcessHangMonitor.jsm',
     'ReaderParent.jsm',
     'RecentWindow.jsm',
     'RemotePrompt.jsm',
-    'Sanitizer.jsm',
     'SelfSupportBackend.jsm',
     'SitePermissions.jsm',
     'Social.jsm',
     'TransientPrefs.jsm',
     'WebappManager.jsm',
     'webrtcUI.jsm',
 ]
 
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -2066,82 +2066,51 @@ XPCOMUtils.defineLazyGetter(PlacesUtils,
 
 XPCOMUtils.defineLazyGetter(this, "bundle", function() {
   const PLACES_STRING_BUNDLE_URI = "chrome://places/locale/places.properties";
   return Cc["@mozilla.org/intl/stringbundle;1"].
          getService(Ci.nsIStringBundleService).
          createBundle(PLACES_STRING_BUNDLE_URI);
 });
 
-// A promise resolved once the Sqlite.jsm connections
-// can be closed.
-let promiseCanCloseConnection = function() {
-  let TOPIC = "places-will-close-connection";
-  return new Promise(resolve => {
-    let observer = function() {
-      Services.obs.removeObserver(observer, TOPIC);
-      resolve();
-    }
-    Services.obs.addObserver(observer, TOPIC, false)
-  });
-};
-
 XPCOMUtils.defineLazyGetter(this, "gAsyncDBConnPromised",
   () => new Promise((resolve) => {
     Sqlite.cloneStorageConnection({
       connection: PlacesUtils.history.DBConnection,
       readOnly:   true
     }).then(conn => {
       try {
-        let promiseReady = promiseCanCloseConnection();
-        let state = "0. not started";
-        Sqlite.shutdown.addBlocker("PlacesUtils read-only connection closing",
-          Task.async(function*() {
-            // Don't close the connection as long as it might be used.
-            state = "1. waiting for `places-will-close-connection`";
-            yield promiseReady;
-
-            // But close the connection before Sqlite shutdown.
-            state = "2. closing the connection";
-            yield conn.close();
-
-            state = "3. done";
-          }),
-          () => state);
+        Sqlite.shutdown.addBlocker(
+          "PlacesUtils read-only connection closing",
+          conn.close.bind(conn));
+        PlacesUtils.history.shutdownClient.jsclient.addBlocker(
+          "PlacesUtils read-only connection closing",
+          conn.close.bind(conn));
       } catch(ex) {
         // It's too late to block shutdown, just close the connection.
         conn.close();
         throw ex;
       }
       resolve(conn);
     });
   })
 );
 
 XPCOMUtils.defineLazyGetter(this, "gAsyncDBWrapperPromised",
   () => new Promise((resolve) => {
     Sqlite.wrapStorageConnection({
       connection: PlacesUtils.history.DBConnection,
     }).then(conn => {
       try {
-        let promiseReady = promiseCanCloseConnection();
-        let state = "0. not started";
-        Sqlite.shutdown.addBlocker("PlacesUtils wrapped connection closing",
-          Task.async(function*() {
-            // Don't close the connection as long as it might be used.
-            state = "1. waiting for `places-will-close-connection`";
-            yield promiseReady;
-
-            // But close the connection before Sqlite shutdown.
-            state = "2. closing the connection";
-            yield conn.close();
-
-            state = "3. done";
-          }),
-          () => state);
+        Sqlite.shutdown.addBlocker(
+          "PlacesUtils wrapped connection closing",
+          conn.close.bind(conn));
+        PlacesUtils.history.shutdownClient.jsclient.addBlocker(
+          "PlacesUtils wrapped connection closing",
+          conn.close.bind(conn));
       } catch(ex) {
         // It's too late to block shutdown, just close the connection.
         conn.close();
         throw ex;
       }
       resolve(conn);
     });
   })