Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Wed, 21 Jan 2015 15:28:31 -0800
changeset 224982 34e2d2bd7ec44a260350c2c8ec574ef6cb8b367d
parent 224966 06b590bf59f47dc0de3be8f1a525732f80552666 (current diff)
parent 224981 efcb9eadd748f6d11f85e22b7016d6ef329f3b36 (diff)
child 224985 4472136fbb5f64d4929adacbf22be68b4ce25e07
child 225013 1ccb6100d2bdb60cec0d49b89665353f1af3a3c0
child 225069 29e5830d4b21a1e186518bc5c734fc8b4736c99e
push id28149
push userkwierso@gmail.com
push dateWed, 21 Jan 2015 23:28:39 +0000
treeherdermozilla-central@34e2d2bd7ec4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone38.0a1
first release with
nightly linux32
34e2d2bd7ec4 / 38.0a1 / 20150122030202 / files
nightly linux64
34e2d2bd7ec4 / 38.0a1 / 20150122030202 / files
nightly mac
34e2d2bd7ec4 / 38.0a1 / 20150122030202 / files
nightly win32
34e2d2bd7ec4 / 38.0a1 / 20150122030202 / files
nightly win64
34e2d2bd7ec4 / 38.0a1 / 20150122030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge
browser/components/loop/test/functional/test_1_browser_call.py
toolkit/components/places/tests/expiration/test_removeAllPages.js
toolkit/components/places/tests/unit/test_history_removeAllPages.js
--- a/browser/base/content/test/general/browser_action_searchengine.js
+++ b/browser/base/content/test/general/browser_action_searchengine.js
@@ -21,17 +21,17 @@ add_task(function* () {
     Services.search.currentEngine = originalEngine;
     let engine = Services.search.getEngineByName("MozSearch");
     Services.search.removeEngine(engine);
 
     try {
       gBrowser.removeTab(tab);
     } catch(ex) { /* tab may have already been closed in case of failure */ }
 
-    return promiseClearHistory();
+    return PlacesTestUtils.clearHistory();
   });
 
   yield promiseAutocompleteResultPopup("open a search");
   let result = gURLBar.popup.richlistbox.firstChild;
 
   isnot(result, null, "Should have a result");
   is(result.getAttribute("url"),
      `moz-action:searchengine,{"engineName":"MozSearch","input":"open a search","searchQuery":"open a search"}`,
--- a/browser/base/content/test/general/browser_action_searchengine_alias.js
+++ b/browser/base/content/test/general/browser_action_searchengine_alias.js
@@ -21,17 +21,17 @@ add_task(function* () {
     let engine = Services.search.getEngineByName("MozSearch");
     Services.search.removeEngine(engine);
     Services.prefs.clearUserPref("browser.urlbar.unifiedcomplete");
 
     try {
       gBrowser.removeTab(tab);
     } catch(ex) { /* tab may have already been closed in case of failure */ }
 
-    return promiseClearHistory();
+    return PlacesTestUtils.clearHistory();
   });
 
   yield promiseAutocompleteResultPopup("moz open a search");
 
   let result = gURLBar.popup.richlistbox.children[0];
   ok(result.hasAttribute("image"), "Result should have an image attribute");
   // Image attribute gets a suffix (-moz-resolution) added in the value.
   ok(result.getAttribute("image").startsWith(engine.iconURI.spec),
--- a/browser/base/content/test/general/browser_autocomplete_autoselect.js
+++ b/browser/base/content/test/general/browser_autocomplete_autoselect.js
@@ -10,17 +10,17 @@ function is_selected(index) {
 
 add_task(function*() {
   // This test is only relevant if UnifiedComplete is enabled.
   if (!Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) {
     todo(false, "Stop supporting old autocomplete components.");
     return;
   }
 
-  registerCleanupFunction(promiseClearHistory);
+  registerCleanupFunction(() => PlacesTestUtils.clearHistory());
 
   let visits = [];
   repeat(10, i => {
     visits.push({
       uri: makeURI("http://example.com/autocomplete/?" + i),
     });
   });
   yield PlacesTestUtils.addVisits(visits);
--- a/browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js
+++ b/browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js
@@ -10,17 +10,17 @@ function is_selected(index) {
 
 add_task(function*() {
   // This test is only relevant if UnifiedComplete is *disabled*.
   if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete")) {
     ok(true, "Don't run this test with UnifiedComplete enabled.")
     return;
   }
 
-  registerCleanupFunction(promiseClearHistory);
+  registerCleanupFunction(() => PlacesTestUtils.clearHistory());
 
   let visits = [];
   repeat(10, i => {
     visits.push({
       uri: makeURI("http://example.com/autocomplete/?" + i),
     });
   });
   yield PlacesTestUtils.addVisits(visits);
--- a/browser/base/content/test/general/browser_bug1003461-switchtab-override.js
+++ b/browser/base/content/test/general/browser_bug1003461-switchtab-override.js
@@ -61,10 +61,10 @@ add_task(function* test_switchtab_overri
   whenTabLoaded(secondTab, deferred.resolve);
 
   EventUtils.synthesizeKey("VK_SHIFT" , { type: "keydown" });
   EventUtils.synthesizeKey("VK_RETURN" , { });
   info(`gURLBar.value = ${gURLBar.value}`);
   EventUtils.synthesizeKey("VK_SHIFT" , { type: "keyup" });
   yield deferred.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/browser/base/content/test/general/browser_bug1024133-switchtab-override-keynav.js
+++ b/browser/base/content/test/general/browser_bug1024133-switchtab-override-keynav.js
@@ -13,17 +13,17 @@ add_task(function* test_switchtab_overri
 
   info("Opening and selecting second tab");
   let secondTab = gBrowser.selectedTab = gBrowser.addTab();
   registerCleanupFunction(() => {
     try {
       gBrowser.removeTab(tab);
       gBrowser.removeTab(secondTab);
     } catch(ex) { /* tabs may have already been closed in case of failure */ }
-    return promiseClearHistory();
+    return PlacesTestUtils.clearHistory();
   });
 
   gURLBar.focus();
   gURLBar.value = "dummy_pag";
   EventUtils.synthesizeKey("e" , {});
   yield promiseSearchComplete();
 
   info("Select second autocomplete popup entry");
--- a/browser/base/content/test/general/browser_bug581253.js
+++ b/browser/base/content/test/general/browser_bug581253.js
@@ -65,38 +65,22 @@ function onPanelShown(aEvent) {
     tagsField.focus();
 
     StarUI.panel.addEventListener("popuphidden", onPanelHidden, false);
     let removeButton = document.getElementById("editBookmarkPanelRemoveButton");
     removeButton.click();
   }
 }
 
-/**
- * Clears history invoking callback when done.
- */
-function waitForClearHistory(aCallback)
-{
-  let observer = {
-    observe: function(aSubject, aTopic, aData)
-    {
-      Services.obs.removeObserver(this, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-      aCallback(aSubject, aTopic, aData);
-    }
-  };
-  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}
-
 function onPanelHidden(aEvent) {
   if (aEvent.target == StarUI.panel) {
     StarUI.panel.removeEventListener("popuphidden", arguments.callee, false);
 
     executeSoon(function() {
       ok(!PlacesUtils.bookmarks.isBookmarked(makeURI(testURL)),
          "the bookmark for the test url has been removed");
       is(BookmarkingUI.status, BookmarkingUI.STATUS_UNSTARRED,
          "star button indicates that the bookmark has been removed");
       gBrowser.removeCurrentTab();
-      waitForClearHistory(finish);
+      PlacesTestUtils.clearHistory().then(finish);
     });
   }
 }
--- a/browser/base/content/test/general/browser_sanitizeDialog.js
+++ b/browser/base/content/test/general/browser_sanitizeDialog.js
@@ -971,50 +971,42 @@ function formNameExists(name)
 
   return deferred.promise;
 }
 
 /**
  * Removes all history visits, downloads, and form entries.
  */
 function blankSlate() {
-  PlacesUtils.bhistory.removeAllPages();
-
-  // The promise is resolved only when removing both downloads and form history are done.
-  let deferred = Promise.defer();
-  let formHistoryDone = false, downloadsDone = false;
-
-  Task.spawn(function deleteAllDownloads() {
+  let deleteDownloads = Task.spawn(function* deleteAllDownloads() {
     let publicList = yield Downloads.getList(Downloads.PUBLIC);
     let downloads = yield publicList.getAll();
     for (let download of downloads) {
       yield publicList.remove(download);
       yield download.finalize(true);
     }
-    downloadsDone = true;
-    if (formHistoryDone) {
-      deferred.resolve();
-    }
   }).then(null, Components.utils.reportError);
 
-  FormHistory.update({ op: "remove" },
-                     { handleError: function (error) {
-                         do_throw("Error occurred updating form history: " + error);
-                         deferred.reject(error);
-                       },
-                       handleCompletion: function (reason) {
-                         if (!reason) {
-                           formHistoryDone = true;
-                           if (downloadsDone) {
-                             deferred.resolve();
-                           }
-                         }
-                       }
-                     });
-  return deferred.promise;
+  let updateFormHistory = new Promise((resolve, reject) => {
+    FormHistory.update({op: "remove"}, {
+      handleCompletion(reason) {
+        if (!reason) {
+          resolve();
+        }
+      },
+
+      handleError(error) {
+        do_throw("Error occurred updating form history: " + error);
+        reject(error);
+      }
+    });
+  });
+
+  return Promise.all([
+    PlacesTestUtils.clearHistory(), deleteDownloads, updateFormHistory]);
 }
 
 /**
  * Ensures that the given pref is the expected value.
  *
  * @param aPrefName
  *        The pref's sub-branch under the privacy branch
  * @param aExpectedVal
--- a/browser/base/content/test/general/browser_search_favicon.js
+++ b/browser/base/content/test/general/browser_search_favicon.js
@@ -67,17 +67,17 @@ function promiseAddVisits(aPlaceInfo) {
   });
 }
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref(gUnifiedCompletePref);
   Services.prefs.clearUserPref(gRestyleSearchesPref);
   Services.search.currentEngine = gOriginalEngine;
   Services.search.removeEngine(gEngine);
-  return promiseClearHistory();
+  return PlacesTestUtils.clearHistory();
 });
 
 add_task(function*() {
   Services.prefs.setBoolPref(gUnifiedCompletePref, true);
   Services.prefs.setBoolPref(gRestyleSearchesPref, true);
 });
 
 add_task(function*() {
--- a/browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
@@ -145,18 +145,17 @@ function waitForRestoredTab(tab) {
 
 function nextStep() {
   if (gTestSteps.length == 0) {
     while (gBrowser.tabs.length > 1) {
       gBrowser.selectTabAtIndex(1);
       gBrowser.removeCurrentTab();
     }
 
-    waitForClearHistory(finish);
-
+    PlacesTestUtils.clearHistory().then(finish);
     return;
   }
 
   var stepFunc = gTestSteps.shift();
   stepFunc();
 }
 
 function ensure_opentabs_match_db(aCallback) {
@@ -179,32 +178,16 @@ function ensure_opentabs_match_db(aCallb
       else
         tabs[url]++;
     }
   }
 
   checkAutocompleteResults(tabs, aCallback);
 }
 
-/**
- * Clears history invoking callback when done.
- */
-function waitForClearHistory(aCallback) {
-  const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
-  let observer = {
-    observe: function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
-      aCallback();
-    }
-  };
-  Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
-
-  PlacesUtils.bhistory.removeAllPages();
-}
-
 function checkAutocompleteResults(aExpected, aCallback)
 {
   gController.input = {
     timeout: 10,
     textValue: "",
     searches: ["history"],
     searchParam: "enable-actions",
     popupOpen: false,
--- a/browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
+++ b/browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
@@ -56,30 +56,22 @@ function continue_test() {
         // Now ensure selecting from the popup correctly trims.
         if (Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete"))
           is(gURLBar.controller.matchCount, 2, "Found the expected number of matches");
         else
           is(gURLBar.controller.matchCount, 1, "Found the expected number of matches");
         EventUtils.synthesizeKey("VK_DOWN", {});
         is(gURLBar.textValue, "www.autofilltrimurl.com/whatever", "trim was applied correctly");
         gURLBar.closePopup();
-        waitForClearHistory(finish);
+        PlacesTestUtils.clearHistory().then(finish);
       });
     });
   });
 }
 
-function waitForClearHistory(aCallback) {
-  Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-    aCallback();
-  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}
-
 let gOnSearchComplete = null;
 function waitForSearchComplete(aCallback) {
   info("Waiting for onSearchComplete");
   if (!gOnSearchComplete) {
     gOnSearchComplete = gURLBar.onSearchComplete;
     registerCleanupFunction(() => {
       gURLBar.onSearchComplete = gOnSearchComplete;
     });
--- a/browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js
+++ b/browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js
@@ -11,19 +11,19 @@ function* promiseAutoComplete(inputText)
   yield promiseSearchComplete();
 }
 
 function is_selected(index) {
   is(gURLBar.popup.richlistbox.selectedIndex, index, `Item ${index + 1} should be selected`);
 }
 
 add_task(function*() {
-  registerCleanupFunction(promiseClearHistory);
+  registerCleanupFunction(() => PlacesTestUtils.clearHistory());
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   let tabCount = gBrowser.tabs.length;
 
   let visits = [];
   repeat(10, i => {
     visits.push({
       uri: makeURI("http://example.com/autocomplete/?" + i),
     });
   });
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -379,50 +379,16 @@ function promiseHistoryClearedState(aURI
       callbackDone();
     });
   });
 
   return deferred.promise;
 }
 
 /**
- * Allows waiting for an observer notification once.
- *
- * @param topic
- *        Notification topic to observe.
- *
- * @return {Promise}
- * @resolves The array [subject, data] from the observed notification.
- * @rejects Never.
- */
-function promiseTopicObserved(topic)
-{
-  let deferred = Promise.defer();
-  info("Waiting for observer topic " + topic);
-  Services.obs.addObserver(function PTO_observe(subject, topic, data) {
-    Services.obs.removeObserver(PTO_observe, topic);
-    deferred.resolve([subject, data]);
-  }, topic, false);
-  return deferred.promise;
-}
-
-/**
- * Clears history asynchronously.
- *
- * @return {Promise}
- * @resolves When history has been cleared.
- * @rejects Never.
- */
-function promiseClearHistory() {
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  PlacesUtils.bhistory.removeAllPages();
-  return promise;
-}
-
-/**
  * Waits for the next top-level document load in the current browser.  The URI
  * of the document is compared against aExpectedURL.  The load is then stopped
  * before it actually starts.
  *
  * @param aExpectedURL
  *        The URL of the document that is expected to load.
  * @return promise
  */
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -5,21 +5,22 @@ const PREF_NEWTAB_ENABLED = "browser.new
 const PREF_NEWTAB_DIRECTORYSOURCE = "browser.newtabpage.directory.source";
 
 Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
 
 let tmp = {};
 Cu.import("resource://gre/modules/Promise.jsm", tmp);
 Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
 Cu.import("resource:///modules/DirectoryLinksProvider.jsm", tmp);
+Cu.import("resource://testing-common/PlacesTestUtils.jsm", tmp);
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://browser/content/sanitize.js", tmp);
 Cu.import("resource://gre/modules/Timer.jsm", tmp);
-let {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider} = tmp;
+let {Promise, NewTabUtils, Sanitizer, clearTimeout, setTimeout, DirectoryLinksProvider, PlacesTestUtils} = tmp;
 
 let uri = Services.io.newURI("about:newtab", null, null);
 let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
 
 let isMac = ("nsILocalFileMac" in Ci);
 let isLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc);
 let isWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 let gWindow = window;
@@ -149,17 +150,17 @@ let TestRunner = {
     }
   },
 
   /**
    * Finishes all tests and cleans up.
    */
   finish: function () {
     function cleanupAndFinish() {
-      clearHistory(function () {
+      PlacesTestUtils.clearHistory().then(() => {
         whenPagesUpdated(finish);
         NewTabUtils.restore();
       });
     }
 
     let callbacks = NewTabUtils.links._populateCallbacks;
     let numCallbacks = callbacks.length;
 
@@ -224,36 +225,27 @@ function setLinks(aLinks, aCallback = Te
     });
   }
 
   // Call populateCache() once to make sure that all link fetching that is
   // currently in progress has ended. We clear the history, fill it with the
   // given entries and call populateCache() now again to make sure the cache
   // has the desired contents.
   NewTabUtils.links.populateCache(function () {
-    clearHistory(function () {
+    PlacesTestUtils.clearHistory().then(() => {
       fillHistory(links, function () {
         NewTabUtils.links.populateCache(function () {
           NewTabUtils.allPages.update();
           aCallback();
         }, true);
       });
     });
   });
 }
 
-function clearHistory(aCallback) {
-  Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(observe, aTopic);
-    executeSoon(aCallback);
-  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-
-  PlacesUtils.history.removeAllPages();
-}
-
 function fillHistory(aLinks, aCallback = TestRunner.next) {
   let numLinks = aLinks.length;
   if (!numLinks) {
     if (aCallback)
       executeSoon(aCallback);
     return;
   }
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -2206,17 +2206,16 @@
             Ci.nsIWebProgressListener.STATE_BLOCKED_TRACKING_CONTENT;
         ]]></getter>
       </property>
       <constructor><![CDATA[
         // default title
         _doorhangerTitle.value =
           gNavigatorBundle.getFormattedString(
             "badContentBlocked.notblocked.message", [this._brandShortName]);
-
         if (this.notification.options.state &
             Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT) {
           _doorhangerTitle.value =
             gNavigatorBundle.getFormattedString(
               "badContentBlocked.blocked.message", [this._brandShortName]);
           _mixedContent.hidden = false;
           _mixedContentUnblock.hidden = false;
           _mixedContentHelpLink.href =
@@ -2247,16 +2246,20 @@
             Ci.nsIWebProgressListener.STATE_LOADED_TRACKING_CONTENT) {
           this.setAttribute("trackingblockdisabled", true);
           _trackingContent.hidden = false;
           _trackingContentBlock.hidden = false;
           _trackingContentHelpLink.href =
             Services.urlFormatter.formatURLPref("app.support.baseURL")
               + "tracking-protection";
         }
+        if (Services.prefs.getBoolPref("privacy.trackingprotection.enabled")) {
+          let histogram = Services.telemetry.getHistogramById("TRACKING_PROTECTION_EVENTS");
+          histogram.add(0);
+        }
       ]]></constructor>
       <method name="disableMixedContentProtection">
         <body><![CDATA[
           // Use telemetry to measure how often unblocking happens
           const kMIXED_CONTENT_UNBLOCK_EVENT = 2;
           let histogram =
             Services.telemetry.getHistogramById(
               "MIXED_CONTENT_UNBLOCK_COUNTER");
@@ -2278,31 +2281,31 @@
           // convert document URI into the format used by
           // nsChannelClassifier::ShouldEnableTrackingProtection
           // (any scheme turned into https is correct)
           let normalizedUrl = Services.io.newURI(
             "https://" + gBrowser.selectedBrowser.currentURI.hostPort,
             null, null);
           // Add the current host in the 'trackingprotection' consumer of
           // the permission manager using a normalized URI. This effectively
-          // places this host on the tracking protection white list.
+          // places this host on the tracking protection allowlist.
           Services.perms.add(normalizedUrl,
             "trackingprotection", Services.perms.ALLOW_ACTION);
           // Telemetry for disable protection
           let histogram = Services.telemetry.getHistogramById(
               "TRACKING_PROTECTION_EVENTS");
           histogram.add(1);
           BrowserReload();
         ]]></body>
       </method>
       <method name="enableTrackingContentProtection">
         <body><![CDATA[
           // Remove the current host from the 'trackingprotection' consumer
           // of the permission manager. This effectively removes this host
-          // from the tracking protection white list (any list actually).
+          // from the tracking protection allowlist.
           Services.perms.remove(gBrowser.selectedBrowser.currentURI.host,
             "trackingprotection");
           // Telemetry for enable protection
           let histogram = Services.telemetry.getHistogramById(
               "TRACKING_PROTECTION_EVENTS");
           histogram.add(2);
           BrowserReload();
         ]]></body>
--- a/browser/components/loop/standalone/content/index.html
+++ b/browser/components/loop/standalone/content/index.html
@@ -8,16 +8,27 @@
     <meta name="locales" content="en-US" />
     <meta name="default_locale" content="en-US" />
 
     <link rel="stylesheet" type="text/css" href="shared/css/reset.css">
     <link rel="stylesheet" type="text/css" href="shared/css/common.css">
     <link rel="stylesheet" type="text/css" href="shared/css/conversation.css">
     <link rel="stylesheet" type="text/css" href="css/webapp.css">
     <link rel="localization" href="l10n/{locale}/loop.properties">
+
+    <script>
+      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
+        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
+        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
+      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
+
+      ga('create', 'UA-36116321-15', 'auto');
+      ga('send', 'pageview');
+    </script>
+
   </head>
   <body class="standalone">
 
     <div id="main"></div>
 
     <!-- libs -->
     <script>
       // IE9, 10, and 11 lack the window.CustomEvent constructor. IE11 has the
--- a/browser/components/loop/standalone/content/js/webapp.js
+++ b/browser/components/loop/standalone/content/js/webapp.js
@@ -14,16 +14,17 @@ loop.webapp = (function($, _, OT, mozL10
   loop.config = loop.config || {};
   loop.config.serverUrl = loop.config.serverUrl || "http://localhost:5000";
 
   var sharedActions = loop.shared.actions;
   var sharedMixins = loop.shared.mixins;
   var sharedModels = loop.shared.models;
   var sharedViews = loop.shared.views;
   var sharedUtils = loop.shared.utils;
+  var WEBSOCKET_REASONS = loop.shared.utils.WEBSOCKET_REASONS;
 
   var multiplexGum = loop.standaloneMedia.multiplexGum;
 
   /**
    * Homepage view.
    */
   var HomeView = React.createClass({displayName: "HomeView",
     render: function() {
@@ -887,17 +888,17 @@ loop.webapp = (function($, _, OT, mozL10
      * Handles call rejection.
      *
      * @param {String} reason The reason the call was terminated (reject, busy,
      *                        timeout, cancel, media-fail, user-unknown, closed)
      */
     _handleCallTerminated: function(reason) {
       multiplexGum.reset();
 
-      if (reason === "cancel") {
+      if (reason === WEBSOCKET_REASONS.CANCEL) {
         this.setState({callStatus: "start"});
         return;
       }
       // XXX later, we'll want to display more meaningfull messages (needs UX)
       this.props.notifications.errorL10n("call_timeout_notification_text");
       this.setState({callStatus: "failure"});
     },
 
--- a/browser/components/loop/test/functional/config.py
+++ b/browser/components/loop/test/functional/config.py
@@ -2,17 +2,17 @@
 CONTENT_SERVER_PORT = 3001
 LOOP_SERVER_PORT = 5001
 FIREFOX_PREFERENCES = {
     "loop.server": "http://localhost:" + str(LOOP_SERVER_PORT),
     "browser.dom.window.dump.enabled": True,
     # Some more changes might be necesarry to have this working in offline mode
     "media.peerconnection.default_iceservers": "[]",
     "media.peerconnection.use_document_iceservers": False,
-    "stun.allow_loopback": True,
+    "media.peerconnection.ice.loopback": True,
     "devtools.chrome.enabled": True,
     "devtools.debugger.prompt-connection": False,
     "devtools.debugger.remote-enabled": True,
     "media.volume_scale": "0",
     "loop.gettingStarted.seen": True,
     "loop.seenToS": "seen",
 
     # this dialog is fragile, and likely to introduce intermittent failures
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -5,16 +5,18 @@ from errors import NoSuchElementExceptio
 # noinspection PyUnresolvedReferences
 from wait import Wait
 from time import sleep
 
 import os
 import sys
 sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)))
 
+import pyperclip
+
 from serversetup import LoopTestServers
 from config import *
 
 
 class Test1BrowserCall(MarionetteTestCase):
     # XXX Move this to setup class so it doesn't restart the server
     # after every test.
     def setUp(self):
@@ -34,114 +36,144 @@ class Test1BrowserCall(MarionetteTestCas
     # taken from https://github.com/mozilla-b2g/gaia/blob/master/tests/python/gaia-ui-tests/gaiatest/gaia_test.py#L858
     # XXX factor out into utility object for use by other tests
     def wait_for_element_displayed(self, by, locator, timeout=None):
         Wait(self.marionette, timeout,
              ignored_exceptions=[NoSuchElementException, StaleElementException])\
             .until(lambda m: m.find_element(by, locator).is_displayed())
         return self.marionette.find_element(by, locator)
 
-    # XXX workaround for Marionette bug 1055309
+    def wait_for_subelement_displayed(self, parent, by, locator, timeout=None):
+        Wait(self.marionette, timeout,
+             ignored_exceptions=[NoSuchElementException, StaleElementException])\
+            .until(lambda m: parent.find_element(by, locator).is_displayed())
+        return parent.find_element(by, locator)
+
+    # XXX workaround for Marionette bug 1094246
     def wait_for_element_exists(self, by, locator, timeout=None):
         Wait(self.marionette, timeout,
              ignored_exceptions=[NoSuchElementException, StaleElementException]) \
             .until(lambda m: m.find_element(by, locator))
         return self.marionette.find_element(by, locator)
 
+    def wait_for_element_enabled(self, element, timeout=10):
+        Wait(self.marionette, timeout) \
+            .until(lambda e: element.is_enabled(),
+                   message="Timed out waiting for element to be enabled")
+
+    def wait_for_element_attribute_to_be_false(self, element, attribute, timeout=10):
+        Wait(self.marionette, timeout) \
+            .until(lambda e: element.get_attribute(attribute) == "false",
+                   message="Timeout out waiting for " + attribute + " to be false")
+
     def switch_to_panel(self):
         button = self.marionette.find_element(By.ID, "loop-button")
 
         # click the element
         button.click()
 
         # switch to the frame
         frame = self.marionette.find_element(By.ID, "loop-panel-iframe")
         self.marionette.switch_to_frame(frame)
 
-    def load_and_verify_standalone_ui(self, url):
-        self.marionette.set_context("content")
-        self.marionette.navigate(url)
-
-    def start_a_conversation(self):
-        # TODO: wait for react elements
-        sleep(2)
-        button = self.marionette.find_element(By.CSS_SELECTOR, ".rooms .btn-info")
-
-        # click the element
-        button.click()
-
-    def get_and_verify_call_url(self):
-        # in the new room model we have to first start a conversation
-        self.start_a_conversation()
-
-        # TODO: wait for react elements
-        sleep(2)
-        call_url = self.marionette.find_element(By.CLASS_NAME, \
-                                                "room-url-link").text
-
-        self.assertIn(urlparse.urlparse(call_url).scheme, ['http', 'https'],
-                      "call URL returned by server " + call_url +
-                      " has invalid scheme")
-        return call_url
-
-    def start_and_verify_outgoing_call(self):
-        # TODO: wait for react elements
-        sleep(2)
-        # make the call!
-        call_button = self.marionette.find_element(By.CLASS_NAME,
-                                                   "btn-join")
-        call_button.click()
-
-    def accept_and_verify_incoming_call(self):
+    def switch_to_chatbox(self):
         self.marionette.set_context("chrome")
         self.marionette.switch_to_frame()
 
         # XXX should be using wait_for_element_displayed, but need to wait
-        # for Marionette bug 1055309 to be fixed.
+        # for Marionette bug 1094246 to be fixed.
         chatbox = self.wait_for_element_exists(By.TAG_NAME, 'chatbox')
         script = ("return document.getAnonymousElementByAttribute("
                   "arguments[0], 'class', 'chat-frame');")
         frame = self.marionette.execute_script(script, [chatbox])
         self.marionette.switch_to_frame(frame)
 
+    def switch_to_standalone(self):
+        self.marionette.set_context("content")
+
+    def local_start_a_conversation(self):
+        button = self.marionette.find_element(By.CSS_SELECTOR, ".rooms .btn-info")
+
+        self.wait_for_element_enabled(button, 120)
+
+        button.click()
+
+    def local_check_room_self_video(self):
+        self.switch_to_chatbox()
+
         # expect a video container on desktop side
-        video = self.wait_for_element_displayed(By.CLASS_NAME, "media")
-        self.assertEqual(video.tag_name, "div", "expect a video container")
+        media_container = self.wait_for_element_displayed(By.CLASS_NAME, "media")
+        self.assertEqual(media_container.tag_name, "div", "expect a video container")
+
+    def local_get_and_verify_room_url(self):
+        button = self.wait_for_element_displayed(By.CLASS_NAME, "btn-copy")
+
+        button.click()
+
+        # click the element
+        room_url = pyperclip.paste()
+
+        self.assertIn(urlparse.urlparse(room_url).scheme, ['http', 'https'],
+                      "room URL returned by server " + room_url +
+                      " has invalid scheme")
+        return room_url
+
+    def standalone_load_and_join_room(self, url):
+        self.switch_to_standalone()
+        self.marionette.navigate(url)
 
-    def hangup_call_and_verify_feedback(self):
-        self.marionette.set_context("chrome")
+        # Join the room
+        join_button = self.wait_for_element_displayed(By.CLASS_NAME,
+                                                      "btn-join")
+        join_button.click()
+
+    # Assumes the standlone or the conversation window is selected first.
+    def check_remote_video(self):
+        # TODO: This is disabled currently due to bug 1122486
+        # video_wrapper = self.wait_for_element_displayed(By.CSS_SELECTOR, ".media .OT_subscriber .OT_video-container", 20)
+        # video = self.wait_for_subelement_displayed(video_wrapper, By.TAG_NAME, "video")
+
+        # self.wait_for_element_attribute_to_be_false(video, "paused")
+        # self.assertEqual(video.get_attribute("ended"), "false")
+
+        # Due to the above waits being disabled, we do a sleep.
+        sleep(15)
+
+    def standalone_check_remote_video(self):
+        self.switch_to_standalone()
+        self.check_remote_video()
+
+    def local_check_remote_video(self):
+        self.switch_to_chatbox()
+        self.check_remote_video()
+
+    def local_leave_room_and_verify_feedback(self):
         button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
 
-        # XXX bug 1080095 For whatever reason, the click doesn't take effect
-        # unless we wait for a bit (even if we wait for the element to
-        # actually be displayed first, which we're not currently bothering
-        # with).  It's not entirely clear whether the click is being
-        # delivered in this case, or whether there's a Marionette bug here.
-        sleep(5)
         button.click()
 
         # check that the feedback form is displayed
         feedback_form = self.wait_for_element_displayed(By.CLASS_NAME, "faces")
         self.assertEqual(feedback_form.tag_name, "div", "expect feedback form")
 
     def test_1_browser_call(self):
         self.switch_to_panel()
 
-        call_url = self.get_and_verify_call_url()
+        self.local_start_a_conversation()
+
+        # Check the self video in the conversation window
+        self.local_check_room_self_video()
+
+        room_url = self.local_get_and_verify_room_url()
 
         # load the link clicker interface into the current content browser
-        self.load_and_verify_standalone_ui(call_url)
-
-        self.start_and_verify_outgoing_call()
+        self.standalone_load_and_join_room(room_url)
 
-        # Switch to the conversation window and answer
-        self.accept_and_verify_incoming_call()
-
-        # Let's wait for the call/media to get established.
-        # TODO: replace this with some media detection
-        sleep(5)
+        # Check we get the video streams
+        self.standalone_check_remote_video()
+        self.local_check_remote_video()
 
         # hangup the call
-        self.hangup_call_and_verify_feedback()
+        self.local_leave_room_and_verify_feedback()
 
     def tearDown(self):
         self.loop_test_servers.shutdown()
         MarionetteTestCase.tearDown(self)
--- a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
+++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
@@ -52,17 +52,17 @@ function onClipboardReady() {
 
   // Remove new Places data we created.
   PlacesUtils.tagging.untagURI(NetUtil.newURI(MOZURISPEC), ["foo"]);
   PlacesUtils.tagging.untagURI(NetUtil.newURI(TEST_URL), ["foo"]);
   let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
   is(tags.length, 0, "tags are gone");
   PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
 
-  waitForClearHistory(finish);
+  PlacesTestUtils.clearHistory().then(finish);
 }
 
 let tests = {
 
   makeHistVisit: function(aCallback) {
     // need to add a history object
     let testURI1 = NetUtil.newURI(MOZURISPEC);
     isnot(testURI1, null, "testURI is not null");
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -469,18 +469,17 @@ gTests.push({
   },
 
   finish: function() {
     toggleSidebar(this.sidebar, false);
     runNextTest();
   },
 
   cleanup: function() {
-    var bh = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-    bh.removeAllPages();
+    return PlacesTestUtils.clearHistory();
   }
 });
 
 //------------------------------------------------------------------------------
 
 function test() {
   waitForExplicitFinish();
   // This test can take some time, if we timeout too early it could run
@@ -493,20 +492,21 @@ function test() {
 
   // kick off tests
   runNextTest();
 }
 
 function runNextTest() {
   // Cleanup from previous test.
   if (gCurrentTest) {
-    gCurrentTest.cleanup();
-    info("End of test: " + gCurrentTest.desc);
-    gCurrentTest = null;
-    waitForAsyncUpdates(runNextTest);
+    Promise.resolve(gCurrentTest.cleanup()).then(() => {
+      info("End of test: " + gCurrentTest.desc);
+      gCurrentTest = null;
+      waitForAsyncUpdates(runNextTest);
+    });
     return;
   }
 
   if (gTests.length > 0) {
     // Goto next tests.
     gCurrentTest = gTests.shift();
     info("Start of test: " + gCurrentTest.desc);
     gCurrentTest.setup(function() {
--- a/browser/components/places/tests/browser/browser_forgetthissite_single.js
+++ b/browser/components/places/tests/browser/browser_forgetthissite_single.js
@@ -15,17 +15,17 @@ function test() {
   TEST_URIs.forEach(function(TEST_URI) {
     places.push({uri: PlacesUtils._uri(TEST_URI),
                  transition: PlacesUtils.history.TRANSITION_TYPED});
   });
   addVisits(places, window, function() {
     testForgetThisSiteVisibility(1, function() {
       testForgetThisSiteVisibility(2, function() {
         // Cleanup
-        waitForClearHistory(finish);
+        PlacesTestUtils.clearHistory().then(finish);
       });
     });
   });
 
   function testForgetThisSiteVisibility(selectionCount, funcNext) {
     openLibrary(function (organizer) {
           // Select History in the left pane.
           organizer.PlacesOrganizer.selectLeftPaneQuery('History');
--- a/browser/components/places/tests/browser/browser_history_sidebar_search.js
+++ b/browser/components/places/tests/browser/browser_history_sidebar_search.js
@@ -26,17 +26,17 @@ var pages = [
 ];
 // Number of pages that will be filtered out by the search.
 const FILTERED_COUNT = 1;
 
 function test() {
   waitForExplicitFinish();
 
   // Cleanup.
-  waitForClearHistory(continue_test);
+  PlacesTestUtils.clearHistory().then(continue_test);
 }
 
 function continue_test() {
   // Add some visited page.
   var time = Date.now();
   var pagesLength = pages.length;
   var places = [];
   for (var i = 0; i < pagesLength; i++) {
@@ -59,17 +59,17 @@ function continue_test() {
       searchBox.doCommand();
       check_sidebar_tree_order(pages.length - FILTERED_COUNT);
       searchBox.value = "";
       searchBox.doCommand();
       check_sidebar_tree_order(pages.length);
 
       // Cleanup.
       toggleSidebar("viewHistorySidebar", false);
-      waitForClearHistory(finish);
+      PlacesTestUtils.clearHistory().then(finish);
     });
   }, true);
 }
 
 function check_sidebar_tree_order(aExpectedRows) {
   var tree = sidebar.contentDocument.getElementById("historyTree");
   var treeView = tree.view;
   var rc = treeView.rowCount;
--- a/browser/components/places/tests/browser/browser_library_commands.js
+++ b/browser/components/places/tests/browser/browser_library_commands.js
@@ -7,17 +7,17 @@
 /**
  *  Test enabled commands in the left pane folder of the Library.
  */
 
 const TEST_URI = NetUtil.newURI("http://www.mozilla.org/");
 
 registerCleanupFunction(function* () {
   yield PlacesUtils.bookmarks.eraseEverything();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function* test_date_container() {
   let library = yield promiseLibrary();
   info("Ensure date containers under History cannot be cut but can be deleted");
 
   yield promiseAddVisits(TEST_URI);
 
--- a/browser/components/places/tests/browser/browser_library_downloads.js
+++ b/browser/components/places/tests/browser/browser_library_downloads.js
@@ -47,17 +47,17 @@ function test() {
         let len = contentRoot.childCount;
         const TEST_URIS = ["http://ubuntu.org/", "http://google.com/"];
         for (let i = 0; i < len; i++) {
           is(contentRoot.getChild(i).uri, TEST_URIS[i],
               "Comparing downloads shown at index " + i);
         }
 
         win.close();
-        waitForClearHistory(finish);
+        PlacesTestUtils.clearHistory().then(finish);
       }
     })
   }
 
   openLibrary(onLibraryReady, "Downloads");
 }
 
 function VisitInfo(aTransitionType)
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -103,17 +103,17 @@ gTests.push({
       checkInfoBoxSelected(PO);
       ok(!infoBoxExpanderWrapper.hidden,
          "Expander button is not hidden for bookmark item.");
       checkAddInfoFieldsNotCollapsed(PO);
       checkAddInfoFields(PO, "bookmark item");
 
       menuNode.containerOpen = false;
 
-      waitForClearHistory(nextTest);
+      PlacesTestUtils.clearHistory().then(nextTest);
     }
     // add a visit to browser history
     addVisits(
       { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
         transition: PlacesUtils.history.TRANSITION_TYPED },
       window,
       addVisitsCallback);
   }
--- a/browser/components/places/tests/browser/browser_library_panel_leak.js
+++ b/browser/components/places/tests/browser/browser_library_panel_leak.js
@@ -34,17 +34,17 @@ function test() {
     let selection = contentTree.view.selection;
     selection.clearSelection();
     selection.rangedSelect(0, 0, true);
     // Check the panel is editing the history entry.
     is(organizer.gEditItemOverlay.itemId, -1, "Editing an history entry");
     // Close Library window.
     organizer.close();
     // Clean up history.
-    waitForClearHistory(finish);
+    PlacesTestUtils.clearHistory().then(finish);
   }
 
   waitForExplicitFinish();
   // Add an history entry.
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
   addVisits(
     {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
       transition: PlacesUtils.history.TRANSITION_TYPED},
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -152,17 +152,17 @@ function onLibraryAvailable() {
   testCases.forEach(function (aTest) aTest());
 
   gLibrary.close();
   gLibrary = null;
 
   // Cleanup.
   PlacesUtils.tagging.untagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
   PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
-  waitForClearHistory(finish);
+  PlacesTestUtils.clearHistory().then(finish);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 function test() {
   waitForExplicitFinish();
 
   // Sanity:
--- a/browser/components/places/tests/browser/browser_sidebarpanels_click.js
+++ b/browser/components/places/tests/browser/browser_sidebarpanels_click.js
@@ -63,17 +63,17 @@ function test() {
       sidebar.contentDocument.getElementById("byvisited").doCommand();
     },
     selectNode: function(tree) {
       tree.selectNode(tree.view.nodeForTreeIndex(0));
       is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
       is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
     },
     cleanup: function(aCallback) {
-      waitForClearHistory(aCallback);
+      PlacesTestUtils.clearHistory().then(aCallback);
     },
     sidebarName: HISTORY_SIDEBAR_ID,
     treeName: HISTORY_SIDEBAR_TREE_ID,
     desc: "History sidebar test"
   });
 
   function testPlacesPanel(preFunc, postFunc) {
     currentTest.init(function() {
@@ -149,10 +149,10 @@ function test() {
                         function() {
                           runNextTest();
                         });
                       });
     }
   }
 
   // Ensure history is clean before starting the test.
-  waitForClearHistory(runNextTest);
+  PlacesTestUtils.clearHistory().then(runNextTest);
 }
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
 
 // We need to cache this before test runs...
 let cachedLeftPaneFolderIdGetter;
 let (getter = PlacesUIUtils.__lookupGetter__("leftPaneFolderId")) {
   if (!cachedLeftPaneFolderIdGetter && typeof(getter) == "function")
     cachedLeftPaneFolderIdGetter = getter;
 }
 // ...And restore it when test ends.
@@ -69,31 +71,16 @@ function promiseClipboard(aPopulateClipb
   waitForClipboard(function (aData) !!aData,
                    aPopulateClipboardFn,
                    function () { deferred.resolve(); },
                    aFlavor);
   return deferred.promise;
 }
 
 /**
- * Waits for completion of a clear history operation, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- */
-function waitForClearHistory(aCallback) {
-  Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-    aCallback();
-  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}
-
-/**
  * Waits for all pending async statements on the default connection, before
  * proceeding with aCallback.
  *
  * @param aCallback
  *        Function to be called when done.
  * @param aScope
  *        Scope for the callback.
  * @param aArguments
@@ -367,42 +354,8 @@ function promiseHistoryNotification(noti
     });
     PlacesUtils.history.addObserver(proxifiedObserver, false);
     let timeout = setTimeout(() => {
       PlacesUtils.history.removeObserver(proxifiedObserver, false);
       reject(new Error("Timed out while waiting for history notification"));
     }, 2000);
   });
 }
-
-/**
- * Clears history asynchronously.
- *
- * @return {Promise}
- * @resolves When history has been cleared.
- * @rejects Never.
- */
-function promiseClearHistory() {
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  PlacesUtils.bhistory.removeAllPages();
-  return promise;
-}
-
-/**
- * Allows waiting for an observer notification once.
- *
- * @param topic
- *        Notification topic to observe.
- *
- * @return {Promise}
- * @resolves The array [subject, data] from the observed notification.
- * @rejects Never.
- */
-function promiseTopicObserved(topic)
-{
-  let deferred = Promise.defer();
-  info("Waiting for observer topic " + topic);
-  Services.obs.addObserver(function PTO_observe(subject, topic, data) {
-    Services.obs.removeObserver(PTO_observe, topic);
-    deferred.resolve([subject, data]);
-  }, topic, false);
-  return deferred.promise;
-}
--- a/browser/components/places/tests/chrome/head.js
+++ b/browser/components/places/tests/chrome/head.js
@@ -1,8 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
+
 /**
  * Asynchronously adds visits to a page, invoking a callback function when done.
  *
  * @param aPlaceInfo
  *        Can be an nsIURI, in such a case a single LINK visit will be added.
  *        Otherwise can be an object describing the visit to add, or an array
  *        of these objects:
  *          { uri: nsIURI of the page,
@@ -48,22 +56,8 @@ function addVisits(aPlaceInfo, aCallback
       handleResult: function () {},
       handleCompletion: function UP_handleCompletion() {
         if (aCallback)
           aCallback();
       }
     }
   );
 }
-
-/**
- * Clears history invoking callback when done.
- */
-function waitForClearHistory(aCallback) {
-  const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
-  Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(observer, TOPIC_EXPIRATION_FINISHED);
-    aCallback();
-  }, TOPIC_EXPIRATION_FINISHED, false);
-  Cc["@mozilla.org/browser/nav-history-service;1"]
-    .getService(Ci.nsINavHistoryService)
-    .QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
-}
--- a/browser/components/places/tests/chrome/test_bug549192.xul
+++ b/browser/components/places/tests/chrome/test_bug549192.xul
@@ -40,17 +40,17 @@
      * Bug 549192
      * Ensures that history views are updated after deleting entries.
      */
 
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
       // The mochitest page is added to history.
-      waitForClearHistory(continue_test);
+      PlacesTestUtils.clearHistory().then(continue_test);
     }
 
     function continue_test() {
       // Add some visits.
       let vtime = Date.now() * 1000;
       const ttype = PlacesUtils.history.TRANSITION_TYPED;
       let places =
         [{ uri: Services.io.newURI("http://example.tld/", null, null),
@@ -101,15 +101,15 @@
             let node = tree.selectedNode;
             tree.controller.remove("Removing page");
             ok(treeView.treeIndexForNode(node) == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE,
               node.uri + " removed.");
             ok(treeView.rowCount == rc - i - 1, "Rows count decreased");
           }
 
           // Cleanup.
-          waitForClearHistory(SimpleTest.finish);
+          PlacesTestUtils.clearHistory().then(SimpleTest.finish);
         });
       });
     }
 
   ]]></script>
 </window>
--- a/browser/components/places/tests/chrome/test_bug549491.xul
+++ b/browser/components/places/tests/chrome/test_bug549491.xul
@@ -43,17 +43,17 @@
      * Ensures that changing the details of places tree's root-node doesn't
      * throw.
      */
 
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
       // The mochitest page is added to history.
-      waitForClearHistory(continue_test);
+      PlacesTestUtils.clearHistory().then(continue_test);
     }
 
     function continue_test() {
       addVisits(
         {uri: Services.io.newURI("http://example.tld/", null, null),
           transition: PlacesUtils.history.TRANSITION_TYPED},
         function() {
           // Make a history query.
@@ -67,31 +67,14 @@
 
           let rootNode = tree.result.root;
           let obs = tree.view.QueryInterface(Ci.nsINavHistoryResultObserver);
           obs.nodeHistoryDetailsChanged(rootNode, rootNode.time, rootNode.accessCount);
           obs.nodeTitleChanged(rootNode, rootNode.title);
           ok(true, "No exceptions thrown");
 
           // Cleanup.
-          waitForClearHistory(SimpleTest.finish);
+          PlacesTestUtils.clearHistory().then(SimpleTest.finish);
         });
     }
 
-    /**
-     * Clears history invoking callback when done.
-     */
-    function waitForClearHistory(aCallback) {
-      const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
-      let observer = {
-        observe: function(aSubject, aTopic, aData) {
-          Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
-          aCallback();
-        }
-      };
-      Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
-      let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
-               getService(Ci.nsINavHistoryService);
-      hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
-   }
-
   ]]></script>
 </window>
--- a/browser/components/places/tests/chrome/test_treeview_date.xul
+++ b/browser/components/places/tests/chrome/test_treeview_date.xul
@@ -49,17 +49,17 @@
      *
      * Ensures that date in places treeviews is correctly formatted.
      */
 
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
       // The mochitest page is added to history.
-      waitForClearHistory(continue_test);
+      PlacesTestUtils.clearHistory().then(continue_test);
     }
 
     function continue_test() {
       var hs = Cc["@mozilla.org/browser/nav-history-service;1"].
                getService(Ci.nsINavHistoryService);
       var bh = hs.QueryInterface(Ci.nsIBrowserHistory);
       var bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
                getService(Ci.nsINavBookmarksService);
@@ -143,17 +143,17 @@
               case "visitCount":
                 is(text, 1, "Visit count is correct");
                 break;
             }
           }
         }
         // Cleanup.
         bs.removeItem(itemId);
-        waitForClearHistory(SimpleTest.finish);
+        PlacesTestUtils.clearHistory().then(SimpleTest.finish);
       }
 
       // Add a visit 1ms before midnight, a visit at midnight, and
       // a visit 1ms after midnight.
       addVisits(
         [{uri: uri("http://before.midnight.com/"),
            visitDate: (midnight.getTime() - 1) * 1000,
            transition: hs.TRANSITION_TYPED},
@@ -161,28 +161,11 @@
            visitDate: (midnight.getTime()) * 1000,
            transition: hs.TRANSITION_TYPED},
          {uri: uri("http://after.midnight.com/"),
            visitDate: (midnight.getTime() + 1) * 1000,
            transition: hs.TRANSITION_TYPED}],
         addVisitsCallback);
     }
 
-    /**
-     * Clears history invoking callback when done.
-     */
-    function waitForClearHistory(aCallback) {
-      const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
-      let observer = {
-        observe: function(aSubject, aTopic, aData) {
-          Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
-          aCallback();
-        }
-      };
-      Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
-      let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
-               getService(Ci.nsINavHistoryService);
-      hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
-   }
-
   ]]>
   </script>
 </window>
--- a/browser/components/preferences/tests/browser_chunk_permissions.js
+++ b/browser/components/preferences/tests/browser_chunk_permissions.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
 
 const ABOUT_PERMISSIONS_SPEC = "about:permissions";
 
 const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/");
 const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/");
 const TEST_URI_3 = NetUtil.newURI("http://wikipedia.org/");
@@ -59,17 +58,17 @@ function cleanUp() {
       Services.perms.remove(TEST_URI_2.host, type);
       Services.perms.remove(TEST_URI_3.host, type);
     }
   }
 }
 
 function runNextTest() {
   if (gTestIndex == tests.length) {
-    waitForClearHistory(finish);
+    PlacesTestUtils.clearHistory().then(finish);
     return;
   }
 
   let nextTest = tests[gTestIndex++];
   info(nextTest.desc);
 
   function preinit_observer() {
     Services.obs.removeObserver(preinit_observer, "browser-permissions-preinit");
@@ -131,20 +130,8 @@ var tests = [
     }
   }
 ];
 
 function getSiteItem(aHost) {
   return gBrowser.contentDocument.
                   querySelector(".site[value='" + aHost + "']");
 }
-
-// copied from toolkit/components/places/tests/head_common.js
-function waitForClearHistory(aCallback) {
-  let observer = {
-    observe: function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-      aCallback();
-    }
-  };
-  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}
--- a/browser/components/preferences/tests/browser_permissions.js
+++ b/browser/components/preferences/tests/browser_permissions.js
@@ -1,12 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 const ABOUT_PERMISSIONS_SPEC = "about:permissions";
 
 const TEST_URI_1 = NetUtil.newURI("http://mozilla.com/");
 const TEST_URI_2 = NetUtil.newURI("http://mozilla.org/");
 
 const TEST_PRINCIPAL_1 = Services.scriptSecurityManager.getNoAppCodebasePrincipal(TEST_URI_1);
@@ -76,17 +75,17 @@ function cleanUp() {
     }
   }
 
   gBrowser.removeTab(gBrowser.selectedTab);
 }
 
 function runNextTest() {
   if (gTestIndex == tests.length) {
-    waitForClearHistory(finish);
+    PlacesTestUtils.clearHistory().then(finish);
     return;
   }
 
   let nextTest = tests[gTestIndex++];
   info("[" + nextTest.name + "] running test");
   nextTest();
 }
 
@@ -270,17 +269,17 @@ var tests = [
        "are allowed");
 
     runNextTest();
   },
 
   function test_forget_site() {
     // click "Forget About This Site" button
     gBrowser.contentDocument.getElementById("forget-site-button").doCommand();
-    waitForClearHistory(function() {
+    PlacesTestUtils.clearHistory().then(() => {
       is(gSiteLabel.value, "", "site label cleared");
 
       let allSitesItem = gBrowser.contentDocument.getElementById("all-sites-item");
       is(gSitesList.selectedItem, allSitesItem,
          "all sites item selected after forgetting selected site");
 
       // check to make sure site is gone from sites list
       let testSiteItem = getSiteItem(TEST_URI_2.host);
@@ -324,20 +323,8 @@ function addWindowListener(aURL, aCallba
         domwindow.close();
         aCallback();
       }, domwindow);
     },
     onCloseWindow: function(aXULWindow) { },
     onWindowTitleChange: function(aXULWindow, aNewTitle) { }
   });
 }
-
-// copied from toolkit/components/places/tests/head_common.js
-function waitForClearHistory(aCallback) {
-  let observer = {
-    observe: function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-      aCallback();
-    }
-  };
-  Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}
--- a/browser/components/preferences/tests/head.js
+++ b/browser/components/preferences/tests/head.js
@@ -1,10 +1,13 @@
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
+
 /**
  * Asynchronously adds visits to a page, invoking a callback function when done.
  *
  * @param aPlaceInfo
  *        Can be an nsIURI, in such a case a single LINK visit will be added.
  *        Otherwise can be an object describing the visit to add, or an array
  *        of these objects:
  *          { uri: nsIURI of the page,
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placesTitleNoUpdate.js
@@ -26,17 +26,17 @@ function test() {
     PlacesUtils.history.removeObserver(historyObserver, false);
     windowsToClose.forEach(function(aWin) {
       aWin.close();
     });
     gBrowser.removeTab(tabToClose);
   });
 
 
-  waitForClearHistory(function () {
+  PlacesTestUtils.clearHistory().then(() => {
     historyObserver = {
       onTitleChanged: function(aURI, aPageTitle) {
         switch (++testNumber) {
           case 1:
             afterFirstVisit();
           break;
           case 2:
             afterUpdateVisit();
@@ -84,17 +84,17 @@ function test() {
       whenPageLoad(aWin, function() {
         executeSoon(afterFirstVisitInPrivateWindow);
       });
     });
   }
 
   function afterFirstVisitInPrivateWindow() {
      is(PlacesUtils.history.getPageTitle(TEST_URI), TITLE_2, "The title remains the same after visiting in private window");
-     waitForClearHistory(finish);
+     PlacesTestUtils.clearHistory().then(finish);
   }
 
   function whenPageLoad(aWin, aCallback) {
     aWin.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
       aWin.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
       aCallback();
     }, true);
     aWin.gBrowser.selectedBrowser.loadURI(TEST_URL);
@@ -102,22 +102,10 @@ function test() {
 
   function testOnWindow(aPrivate, aCallback) {
     whenNewWindowLoaded({ private: aPrivate }, function(aWin) {
       selectedWin = aWin;
       windowsToClose.push(aWin);
       executeSoon(function() { aCallback(aWin) });
     });
   }
-
-  function waitForClearHistory(aCallback) {
-    let observer = {
-      observe: function(aSubject, aTopic, aData) {
-        Services.obs.removeObserver(this, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-        aCallback();
-      }
-    };
-    Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-
-    PlacesUtils.bhistory.removeAllPages();
-  }
 }
 
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_placestitle.js
@@ -11,21 +11,17 @@ function test() {
   const TEST_URL = "http://mochi.test:8888/browser/browser/components/" +
                    "privatebrowsing/test/browser/title.sjs";
   let cm = Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager);
 
   function waitForCleanup(aCallback) {
     // delete all cookies
     cm.removeAll();
     // delete all history items
-    Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-      aCallback();
-    }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-    PlacesUtils.bhistory.removeAllPages();
+    PlacesTestUtils.clearHistory().then(aCallback);
   }
 
   let testNumber = 0;
   let historyObserver = {
     onTitleChanged: function(aURI, aPageTitle) {
       if (aURI.spec != TEST_URL)
         return;
       switch (++testNumber) {
--- a/browser/components/privatebrowsing/test/browser/head.js
+++ b/browser/components/privatebrowsing/test/browser/head.js
@@ -1,11 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
+
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   let gotLoad = false;
   let gotActivate = Services.focus.activeWindow == win;
 
   function maybeRunCallback() {
     if (gotLoad && gotActivate) {
       executeSoon(function() { aCallback(win); });
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -4,18 +4,18 @@ support-files =
   image.png
   uitour.html
   ../UITour-lib.js
 
 [browser_UITour.js]
 skip-if = os == "linux" || e10s # Intermittent failures, bug 951965
 [browser_UITour2.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
-[browser_UITour3.js]
-skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
+# [browser_UITour3.js] Bug 1113038
+# skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_loop.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -413,16 +413,17 @@ gbjar.sources += [
     'Tab.java',
     'Tabs.java',
     'tabs/PrivateTabsPanel.java',
     'tabs/TabCurve.java',
     'tabs/TabHistoryController.java',
     'tabs/TabHistoryFragment.java',
     'tabs/TabHistoryItemRow.java',
     'tabs/TabHistoryPage.java',
+    'tabs/TabPanelBackButton.java',
     'tabs/TabsGridLayout.java',
     'tabs/TabsLayoutAdapter.java',
     'tabs/TabsLayoutItemView.java',
     'tabs/TabsListLayout.java',
     'tabs/TabsPanel.java',
     'Telemetry.java',
     'TelemetryContract.java',
     'TextSelection.java',
--- a/mobile/android/base/resources/layout-large-v11/new_tablet_tabs_panel_back_button.xml
+++ b/mobile/android/base/resources/layout-large-v11/new_tablet_tabs_panel_back_button.xml
@@ -1,14 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
-<ImageButton xmlns:android="http://schemas.android.com/apk/res/android"
-             android:id="@+id/nav_back"
-             android:layout_width="@dimen/tabs_panel_indicator_width"
-             android:layout_height="match_parent"
-             android:minWidth="@dimen/tabs_panel_indicator_width"
-             android:src="@drawable/new_tablet_nav_back"
-             android:contentDescription="@string/back"
-             android:background="@drawable/action_bar_button_inverse"/>
+<org.mozilla.gecko.tabs.TabPanelBackButton xmlns:android="http://schemas.android.com/apk/res/android"
+                                           xmlns:gecko="http://schemas.android.com/apk/res-auto"
+                                           android:id="@+id/nav_back"
+                                           android:layout_width="@dimen/tabs_panel_indicator_width"
+                                           android:layout_height="match_parent"
+                                           android:minWidth="@dimen/tabs_panel_indicator_width"
+                                           android:src="@drawable/new_tablet_nav_back"
+                                           android:contentDescription="@string/back"
+                                           android:background="@drawable/action_bar_button_inverse"
+                                           gecko:dividerVerticalPadding="@dimen/new_tablet_tab_panel_divider_vertical_padding"
+                                           gecko:rightDivider="@drawable/tab_indicator_divider"/>
 
--- a/mobile/android/base/resources/layout/tabs_panel_default.xml
+++ b/mobile/android/base/resources/layout/tabs_panel_default.xml
@@ -23,16 +23,17 @@
                       android:layout_width="wrap_content"
                       android:layout_height="match_parent"/>
 
             <org.mozilla.gecko.widget.IconTabWidget android:id="@+id/tab_widget"
                                                     android:layout_width="wrap_content"
                                                     android:layout_height="match_parent"
                                                     android:tabStripEnabled="false"
                                                     android:divider="@drawable/tab_indicator_divider"
+                                                    android:dividerPadding="@dimen/new_tablet_tab_panel_divider_vertical_padding"
                                                     android:layout="@layout/tabs_panel_indicator"/>
 
             <View android:layout_width="0dip"
                   android:layout_height="match_parent"
                   android:layout_weight="1.0"/>
 
             <ImageButton android:id="@+id/add_tab"
                          style="@style/UrlBar.ImageButton"
--- a/mobile/android/base/resources/values/attrs.xml
+++ b/mobile/android/base/resources/values/attrs.xml
@@ -175,16 +175,21 @@
         <attr name="android:horizontalSpacing"/>
         <attr name="android:verticalSpacing"/>
     </declare-styleable>
 
     <declare-styleable name="TabMenuStrip">
         <attr name="strip" format="reference"/>
     </declare-styleable>
 
+    <declare-styleable name="TabPanelBackButton">
+        <attr name="rightDivider" format="reference"/>
+        <attr name="dividerVerticalPadding" format="dimension"/>
+    </declare-styleable>
+
     <declare-styleable name="EllipsisTextView">
         <attr name="ellipsizeAtLine" format="integer"/>
     </declare-styleable>
 
     <declare-styleable name="FloatingHintEditText">
         <attr name="floatingHintEditTextStyle" format="reference" />
     </declare-styleable>
 
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -17,16 +17,20 @@
     <dimen name="browser_toolbar_favicon_size">21.33dip</dimen>
     <dimen name="browser_toolbar_shadow_size">2dp</dimen>
 
     <!-- If you update one of these values, update the others. -->
     <dimen name="new_tablet_nav_button_width">42dp</dimen>
     <dimen name="new_tablet_nav_button_width_half">21dp</dimen>
     <dimen name="new_tablet_nav_button_width_plus_half">63dp</dimen>
 
+    <!-- This is the system default for the vertical padding for the divider of the TabWidget.
+         Used to mimic the divider padding on the tablet tabs panel back button. -->
+    <dimen name="new_tablet_tab_panel_divider_vertical_padding">12dp</dimen>
+
     <dimen name="new_tablet_tab_strip_height">48dp</dimen>
     <dimen name="new_tablet_tab_strip_item_width">208dp</dimen>
     <dimen name="new_tablet_tab_strip_item_margin">-28dp</dimen>
     <dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
     <dimen name="new_tablet_tab_strip_fading_edge_size">15dp</dimen>
     <dimen name="new_tablet_site_security_height">60dp</dimen>
     <dimen name="new_tablet_site_security_width">34dp</dimen>
     <!-- We primarily use padding (instead of margins) to increase the hit area. -->
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tabs/TabPanelBackButton.java
@@ -0,0 +1,55 @@
+/* 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/. */
+
+package org.mozilla.gecko.tabs;
+
+import org.mozilla.gecko.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ViewGroup;
+import android.widget.ImageButton;
+
+public class TabPanelBackButton extends ImageButton {
+
+    private int dividerWidth = 0;
+
+    private final Drawable divider;
+    private final int dividerPadding;
+
+    public TabPanelBackButton(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabPanelBackButton);
+        divider = a.getDrawable(R.styleable.TabPanelBackButton_rightDivider);
+        dividerPadding = (int) a.getDimension(R.styleable.TabPanelBackButton_dividerVerticalPadding, 0);
+        a.recycle();
+
+        if (divider != null) {
+            dividerWidth = divider.getIntrinsicWidth();
+        }
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(getMeasuredWidth() + dividerWidth, getMeasuredHeight());
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+        if (divider != null) {
+            final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
+            final int left = getRight() - lp.rightMargin - dividerWidth;
+
+            divider.setBounds(left, getPaddingTop() + dividerPadding,
+                    left + dividerWidth, getHeight() - getPaddingBottom() - dividerPadding);
+            divider.draw(canvas);
+        }
+    }
+}
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -2111,45 +2111,45 @@ add_task(function test_platform_integrat
 /**
  * Checks that downloads are added to browsing history when they start.
  */
 add_task(function test_history()
 {
   mustInterruptResponses();
 
   // We will wait for the visit to be notified during the download.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   let promiseVisit = promiseWaitForVisit(httpUrl("interruptible.txt"));
 
   // Start a download that is not allowed to finish yet.
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
   // The history notifications should be received before the download completes.
   let [time, transitionType] = yield promiseVisit;
   do_check_eq(time, download.startTime.getTime() * 1000);
   do_check_eq(transitionType, Ci.nsINavHistoryService.TRANSITION_DOWNLOAD);
 
   // Restart and complete the download after clearing history.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   download.cancel();
   continueResponses();
   yield download.start();
 
   // The restart should not have added a new history visit.
   do_check_false(yield promiseIsURIVisited(httpUrl("interruptible.txt")));
 });
 
 /**
  * Checks that downloads started by nsIHelperAppService are added to the
  * browsing history when they start.
  */
 add_task(function test_history_tryToKeepPartialData()
 {
   // We will wait for the visit to be notified during the download.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   let promiseVisit =
       promiseWaitForVisit(httpUrl("interruptible_resumable.txt"));
 
   // Start a download that is not allowed to finish yet.
   let beforeStartTimeMs = Date.now();
   let download = yield promiseStartDownload_tryToKeepPartialData();
 
   // The history notifications should be received before the download completes.
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -26,16 +26,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
                                   "resource://gre/modules/Downloads.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "HttpServer",
                                   "resource://testing-common/httpd.js");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
@@ -165,53 +167,16 @@ function promiseExecuteSoon()
 function promiseTimeout(aTime)
 {
   let deferred = Promise.defer();
   do_timeout(aTime, deferred.resolve);
   return deferred.promise;
 }
 
 /**
- * Allows waiting for an observer notification once.
- *
- * @param aTopic
- *        Notification topic to observe.
- *
- * @return {Promise}
- * @resolves The array [aSubject, aData] from the observed notification.
- * @rejects Never.
- */
-function promiseTopicObserved(aTopic)
-{
-  let deferred = Promise.defer();
-
-  Services.obs.addObserver(
-    function PTO_observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(PTO_observe, aTopic);
-      deferred.resolve([aSubject, aData]);
-    }, aTopic, false);
-
-  return deferred.promise;
-}
-
-/**
- * Clears history asynchronously.
- *
- * @return {Promise}
- * @resolves When history has been cleared.
- * @rejects Never.
- */
-function promiseClearHistory()
-{
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
-  return promise;
-}
-
-/**
  * Waits for a new history visit to be notified for the specified URI.
  *
  * @param aUrl
  *        String containing the URI that will be visited.
  *
  * @return {Promise}
  * @resolves Array [aTime, aTransitionType] from nsINavHistoryObserver.onVisit.
  * @rejects Never.
--- a/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
+++ b/toolkit/components/jsdownloads/test/unit/test_DownloadList.js
@@ -348,17 +348,17 @@ add_task(function test_history_expiratio
 
   // Work with one finished download and one canceled download.
   yield downloadOne.start();
   downloadTwo.start();
   yield downloadTwo.cancel();
 
   // We must replace the visits added while executing the downloads with visits
   // that are older than 7 days, otherwise they will not be expired.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   yield promiseExpirableDownloadVisit();
   yield promiseExpirableDownloadVisit(httpUrl("interruptible.txt"));
 
   // After clearing history, we can add the downloads to be removed to the list.
   yield list.add(downloadOne);
   yield list.add(downloadTwo);
 
   // Force a history expiration.
@@ -391,17 +391,17 @@ add_task(function test_history_clear()
       }
     },
   };
   yield list.addView(downloadView);
 
   yield downloadOne.start();
   yield downloadTwo.start();
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
   // Wait for the removal notifications that may still be pending.
   yield deferred.promise;
 });
 
 /**
  * Tests the removeFinished method to ensure that it only removes
  * finished downloads.
--- a/toolkit/components/places/tests/PlacesTestUtils.jsm
+++ b/toolkit/components/places/tests/PlacesTestUtils.jsm
@@ -2,16 +2,17 @@
 
 this.EXPORTED_SYMBOLS = [
   "PlacesTestUtils",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 
 
 this.PlacesTestUtils = Object.freeze({
   /**
    * Asynchronously adds visits to a page.
@@ -68,9 +69,27 @@ this.PlacesTestUtils = Object.freeze({
           handleResult: function () {},
           handleCompletion: function UP_handleCompletion() {
             resolve();
           }
         }
       );
     });
   },
+
+  /**
+   * Clear all history.
+   *
+   * @return {Promise}
+   * @resolves When history was cleared successfully.
+   * @rejects JavaScript exception.
+   */
+  clearHistory() {
+    let expirationFinished = new Promise(resolve => {
+      Services.obs.addObserver(function observe(subj, topic, data) {
+        Services.obs.removeObserver(observe, topic);
+        resolve();
+      }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+    });
+
+    return Promise.all([expirationFinished, PlacesUtils.history.clear()]);
+  }
 });
--- a/toolkit/components/places/tests/browser/browser_bug248970.js
+++ b/toolkit/components/places/tests/browser/browser_bug248970.js
@@ -22,17 +22,17 @@ add_task(function () {
   let placeItemsCount = 0;
 
   registerCleanupFunction(function() {
     windowsToClose.forEach(function(win) {
       win.close();
     });
   });
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
    // Ensure we wait for the default bookmarks import.
   let bookmarksDeferred = Promise.defer();
   waitForCondition(() => {
     placeItemsCount = getPlacesItemsCount();
     return placeItemsCount > 0
   }, bookmarksDeferred.resolve, "Should have default bookmarks");
   yield bookmarksDeferred.promise;
--- a/toolkit/components/places/tests/browser/browser_bug399606.js
+++ b/toolkit/components/places/tests/browser/browser_bug399606.js
@@ -42,17 +42,17 @@ function test() {
 
   function confirm_results() {
     gBrowser.removeCurrentTab();
     hs.removeObserver(historyObserver, false);
     for (let aURI in historyObserver.visitCount) {
       is(historyObserver.visitCount[aURI], 1,
          "onVisit has been received right number of times for " + aURI);
     }
-    promiseClearHistory().then(finish);
+    PlacesTestUtils.clearHistory().then(finish);
   }
 
   var loadCount = 0;
   function handleLoad(aEvent) {
     loadCount++;
     info("new load count is " + loadCount);
 
     if (loadCount == 3) {
--- a/toolkit/components/places/tests/browser/browser_bug680727.js
+++ b/toolkit/components/places/tests/browser/browser_bug680727.js
@@ -93,17 +93,17 @@ function reloadListener() {
   // Check if global history remembers the successfully-requested URI.
   promiseAsyncUpdates().then(function() {
     gAsyncHistory.isURIVisited(kUniqueURI, reloadAsyncListener);
   });
 }
 
 function reloadAsyncListener(aURI, aIsVisited) {
   ok(kUniqueURI.equals(aURI) && aIsVisited, "We have visited the URI.");
-  promiseClearHistory().then(finish);
+  PlacesTestUtils.clearHistory().then(finish);
 }
 
 registerCleanupFunction(function() {
   Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
   Services.io.offline = false;
   window.removeEventListener("DOMContentLoaded", errorListener, false);
   window.removeEventListener("DOMContentLoaded", reloadListener, false);
   gBrowser.removeCurrentTab();
--- a/toolkit/components/places/tests/browser/browser_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_notfound.js
@@ -21,17 +21,17 @@ function test() {
       PlacesUtils.history.removeObserver(historyObserver);
       info("Received onVisit: " + aURI.spec);
       fieldForUrl(aURI, "frecency", function (aFrecency) {
         is(aFrecency, 0, "Frecency should be 0");
         fieldForUrl(aURI, "hidden", function (aHidden) {
           is(aHidden, 0, "Page should not be hidden");
           fieldForUrl(aURI, "typed", function (aTyped) {
             is(aTyped, 0, "page should not be marked as typed");
-            promiseClearHistory().then(finish);
+            PlacesTestUtils.clearHistory().then(finish);
           });
         });
       });
     },
     onBeginUpdateBatch: function () {},
     onEndUpdateBatch: function () {},
     onTitleChanged: function () {},
     onDeleteURI: function () {},
--- a/toolkit/components/places/tests/browser/browser_redirect.js
+++ b/toolkit/components/places/tests/browser/browser_redirect.js
@@ -38,17 +38,17 @@ function test() {
           is(aHidden, 1, "The redirecting page should be hidden");
 
           fieldForUrl(TARGET_URI, "frecency", function (aFrecency) {
             ok(aFrecency != 0, "Frecency of the target page should not be 0");
 
             fieldForUrl(TARGET_URI, "hidden", function (aHidden) {
               is(aHidden, 0, "The target page should not be hidden");
 
-              promiseClearHistory().then(finish);
+              PlacesTestUtils.clearHistory().then(finish);
             });
           });
         });
       });
     },
     onBeginUpdateBatch: function () {},
     onEndUpdateBatch: function () {},
     onTitleChanged: function () {},
--- a/toolkit/components/places/tests/browser/browser_settitle.js
+++ b/toolkit/components/places/tests/browser/browser_settitle.js
@@ -3,17 +3,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 gBrowser.selectedTab = gBrowser.addTab();
 
 function finishAndCleanUp()
 {
   gBrowser.removeCurrentTab();
-  promiseClearHistory().then(finish);
+  PlacesTestUtils.clearHistory().then(finish);
 }
 
 /**
  * One-time DOMContentLoaded callback.
  */
 function load(href, callback)
 {
   content.location.href = href;
--- a/toolkit/components/places/tests/browser/browser_visited_notfound.js
+++ b/toolkit/components/places/tests/browser/browser_visited_notfound.js
@@ -36,16 +36,16 @@ function continueTest(aOldFrecency) {
       PlacesUtils.history.removeObserver(historyObserver);
       info("Received onVisit: " + aURI.spec);
       fieldForUrl(aURI, "frecency", function (aFrecency) {
         is(aFrecency, aOldFrecency, "Frecency should be unchanged");
         fieldForUrl(aURI, "hidden", function (aHidden) {
           is(aHidden, 0, "Page should not be hidden");
           fieldForUrl(aURI, "typed", function (aTyped) {
             is(aTyped, 0, "page should not be marked as typed");
-            promiseClearHistory().then(finish);
+            PlacesTestUtils.clearHistory().then(finish);
           });
         });
       });
     }
   };
   PlacesUtils.history.addObserver(historyObserver, false);
 }
--- a/toolkit/components/places/tests/browser/browser_visituri.js
+++ b/toolkit/components/places/tests/browser/browser_visituri.js
@@ -3,17 +3,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 gBrowser.selectedTab = gBrowser.addTab();
 
 function finishAndCleanUp()
 {
   gBrowser.removeCurrentTab();
-  promiseClearHistory().then(finish);
+  PlacesTestUtils.clearHistory().then(finish);
 }
 
 /**
  * One-time observer callback.
  */
 function waitForObserve(name, callback)
 {
   var observerService = Cc["@mozilla.org/observer-service;1"]
--- a/toolkit/components/places/tests/browser/browser_visituri_nohistory.js
+++ b/toolkit/components/places/tests/browser/browser_visituri_nohistory.js
@@ -45,17 +45,17 @@ function test()
 
   waitForObserve("uri-visit-saved", function(subject, topic, data)
   {
     let uri = subject.QueryInterface(Ci.nsIURI);
     is(uri.spec, FINAL_URL, "received expected visit");
     if (uri.spec != FINAL_URL)
       return;
     gBrowser.removeCurrentTab();
-    promiseClearHistory().then(finish);
+    PlacesTestUtils.clearHistory().then(finish);
   });
 
   Services.prefs.setBoolPref("places.history.enabled", false);
   content.location.href = INITIAL_URL;
   waitForLoad(function()
   {
     try {
       Services.prefs.clearUserPref("places.history.enabled");
--- a/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
+++ b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
@@ -71,14 +71,14 @@ function test() {
   });
 
   // test first when on private mode
   testOnWindow({private: true}, function(aWin) {
     doTest(true, aWin, initialURL, function() {
       // then test when not on private mode
       testOnWindow({}, function(aWin) {
         doTest(false, aWin, finalURL, function () {
-          promiseClearHistory().then(finish);
+          PlacesTestUtils.clearHistory().then(finish);
         });
       });
     });
   });
 }
--- a/toolkit/components/places/tests/browser/head.js
+++ b/toolkit/components/places/tests/browser/head.js
@@ -6,58 +6,24 @@ const TRANSITION_BOOKMARK = Ci.nsINavHis
 const TRANSITION_REDIRECT_PERMANENT = Ci.nsINavHistoryService.TRANSITION_REDIRECT_PERMANENT;
 const TRANSITION_REDIRECT_TEMPORARY = Ci.nsINavHistoryService.TRANSITION_REDIRECT_TEMPORARY;
 const TRANSITION_EMBED = Ci.nsINavHistoryService.TRANSITION_EMBED;
 const TRANSITION_FRAMED_LINK = Ci.nsINavHistoryService.TRANSITION_FRAMED_LINK;
 const TRANSITION_DOWNLOAD = Ci.nsINavHistoryService.TRANSITION_DOWNLOAD;
 
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 /**
- * Allows waiting for an observer notification once.
- *
- * @param aTopic
- *        Notification topic to observe.
- *
- * @return {Promise}
- * @resolves The array [aSubject, aData] from the observed notification.
- * @rejects Never.
- */
-function promiseTopicObserved(aTopic)
-{
-  let deferred = Promise.defer();
-
-  Services.obs.addObserver(
-    function PTO_observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(PTO_observe, aTopic);
-      deferred.resolve([aSubject, aData]);
-    }, aTopic, false);
-
-  return deferred.promise;
-}
-
-/**
- * Clears history asynchronously.
- *
- * @return {Promise}
- * @resolves When history has been cleared.
- * @rejects Never.
- */
-function promiseClearHistory() {
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  PlacesUtils.bhistory.removeAllPages();
-  return promise;
-}
-
-/**
  * Waits for all pending async statements on the default connection.
  *
  * @return {Promise}
  * @resolves When all pending async statements finished.
  * @rejects Never.
  *
  * @note The result is achieved by asynchronously executing a query requiring
  *       a write lock.  Since all statements on the same connection are
rename from toolkit/components/places/tests/expiration/test_removeAllPages.js
rename to toolkit/components/places/tests/expiration/test_clearHistory.js
--- a/toolkit/components/places/tests/expiration/test_removeAllPages.js
+++ b/toolkit/components/places/tests/expiration/test_clearHistory.js
@@ -2,17 +2,17 @@
  * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
  * 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/. */
 
 /**
  * What this is aimed to test:
  *
- * bh.removeAllPages should expire everything but bookmarked pages and valid
+ * History.clear() should expire everything but bookmarked pages and valid
  * annos.
  */
 
 let hs = PlacesUtils.history;
 let bs = PlacesUtils.bookmarks;
 let as = PlacesUtils.annotations;
 
 /**
@@ -77,17 +77,17 @@ function add_old_anno(aIdentifier, aName
     stmt.finalize();
   }
 }
 
 function run_test() {
   run_next_test();
 }
 
-add_task(function test_removeAllPages() {
+add_task(function test_historyClear() {
   // Set interval to a large value so we don't expire on it.
   setInterval(3600); // 1h
 
   // Expire all expirable pages.
   setMaxPages(0);
 
   // Add some bookmarked page with visit and annotations.
   for (let i = 0; i < 5; i++) {
@@ -120,23 +120,18 @@ add_task(function test_removeAllPages() 
     yield promiseAddVisits({ uri: pageURI });
     as.setPageAnnotation(pageURI, "expire", "test", 0, as.EXPIRE_NEVER);
     as.setPageAnnotation(pageURI, "expire_session", "test", 0, as.EXPIRE_SESSION);
     add_old_anno(pageURI, "expire_days", "test", as.EXPIRE_DAYS, 8);
     add_old_anno(pageURI, "expire_weeks", "test", as.EXPIRE_WEEKS, 31);
     add_old_anno(pageURI, "expire_months", "test", as.EXPIRE_MONTHS, 181);
   }
 
-  // Expire all visits for the bookmarks.  This does the same thing as the
-  // promiseClearHistory helper, but it is made explicit here because
-  // removeAllPages is the function we are testing.
-  let promise =
-      promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
-  yield promise;
+  // Expire all visits for the bookmarks
+  yield PlacesUtils.history.clear();
 
   ["expire_days", "expire_weeks", "expire_months", "expire_session",
    "expire"].forEach(function(aAnno) {
     let pages = as.getPagesWithAnnotation(aAnno);
     do_check_eq(pages.length, 0);
   });
 
   ["expire_days", "expire_weeks", "expire_months", "expire_session",
--- a/toolkit/components/places/tests/expiration/test_debug_expiration.js
+++ b/toolkit/components/places/tests/expiration/test_debug_expiration.js
@@ -27,17 +27,17 @@ add_task(function test_expire_orphans()
   yield promiseForceExpirationStep(0);
 
   // Check that visits survived.
   do_check_eq(visits_in_database("http://page1.mozilla.org/"), 1);
   do_check_eq(visits_in_database("http://page2.mozilla.org/"), 1);
   do_check_false(page_in_database("http://page3.mozilla.org/"));
 
   // Clean up.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_expire_orphans_optionalarg()
 {
   // Add visits to 2 pages and force a orphan expiration. Visits should survive.
   yield promiseAddVisits({ uri: uri("http://page1.mozilla.org/"),
                            visitDate: gNow++ });
   yield promiseAddVisits({ uri: uri("http://page2.mozilla.org/"),
@@ -52,17 +52,17 @@ add_task(function test_expire_orphans_op
   yield promiseForceExpirationStep();
 
   // Check that visits survived.
   do_check_eq(visits_in_database("http://page1.mozilla.org/"), 1);
   do_check_eq(visits_in_database("http://page2.mozilla.org/"), 1);
   do_check_false(page_in_database("http://page3.mozilla.org/"));
 
   // Clean up.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_expire_limited()
 {
   // Add visits to 2 pages and force a single expiration.
   // Only 1 page should survive.
   yield promiseAddVisits({ uri: uri("http://page1.mozilla.org/"),
                            visitDate: gNow++ });
@@ -72,17 +72,17 @@ add_task(function test_expire_limited()
   // Expire now.
   yield promiseForceExpirationStep(1);
 
   // Check that visits to the more recent page survived.
   do_check_false(page_in_database("http://page1.mozilla.org/"));
   do_check_eq(visits_in_database("http://page2.mozilla.org/"), 1);
 
   // Clean up.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_expire_unlimited()
 {
   // Add visits to 2 pages and force a single expiration.
   // Only 1 page should survive.
   yield promiseAddVisits({ uri: uri("http://page1.mozilla.org/"),
                            visitDate: gNow++ });
@@ -92,17 +92,17 @@ add_task(function test_expire_unlimited(
   // Expire now.
   yield promiseForceExpirationStep(-1);
 
   // Check that visits to the more recent page survived.
   do_check_false(page_in_database("http://page1.mozilla.org/"));
   do_check_false(page_in_database("http://page2.mozilla.org/"));
 
   // Clean up.
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 function run_test()
 {
   // Set interval to a large value so we don't expire on it.
   setInterval(3600); // 1h
   // Set maxPages to a low value, so it's easy to go over it.
   setMaxPages(1);
--- a/toolkit/components/places/tests/expiration/test_notifications.js
+++ b/toolkit/components/places/tests/expiration/test_notifications.js
@@ -7,32 +7,30 @@
 /**
  * What this is aimed to test:
  *
  * Ensure that History (through category cache) notifies us just once.
  */
 
 let os = Cc["@mozilla.org/observer-service;1"].
          getService(Ci.nsIObserverService);
-let hs = Cc["@mozilla.org/browser/nav-history-service;1"].
-         getService(Ci.nsINavHistoryService);
 
 let gObserver = {
   notifications: 0,
   observe: function(aSubject, aTopic, aData) {
     this.notifications++;
   }
 };
 os.addObserver(gObserver, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
 
 function run_test() {
   // Set interval to a large value so we don't expire on it.
   setInterval(3600); // 1h
 
-  hs.QueryInterface(Ci.nsIBrowserHistory).removeAllPages();
+  PlacesTestUtils.clearHistory();
 
   do_timeout(2000, check_result);
   do_test_pending();
 }
 
 function check_result() {
   os.removeObserver(gObserver, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
   do_check_eq(gObserver.notifications, 1);
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteURI.js
@@ -99,15 +99,15 @@ add_task(function test_notifications_onD
 
     hs.removeObserver(historyObserver, false);
 
     do_check_eq(currentTest.receivedNotifications,
                 currentTest.expectedNotifications);
 
     // Clean up.
     bs.removeFolderChildren(bs.unfiledBookmarksFolder);
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
   }
 
   clearMaxPages();
   bs.removeFolderChildren(bs.unfiledBookmarksFolder);
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
+++ b/toolkit/components/places/tests/expiration/test_notifications_onDeleteVisits.js
@@ -121,15 +121,15 @@ add_task(function test_notifications_onD
 
     hs.removeObserver(historyObserver, false);
 
     do_check_eq(currentTest.receivedNotifications,
                 currentTest.expectedNotifications);
 
     // Clean up.
     bs.removeFolderChildren(bs.unfiledBookmarksFolder);
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
   }
 
   clearMaxPages();
   bs.removeFolderChildren(bs.unfiledBookmarksFolder);
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/expiration/test_pref_maxpages.js
+++ b/toolkit/components/places/tests/expiration/test_pref_maxpages.js
@@ -111,14 +111,14 @@ add_task(function test_pref_maxpages() {
     yield promiseForceExpirationStep(-1);
 
     hs.removeObserver(historyObserver, false);
 
     do_check_eq(currentTest.receivedNotifications,
                 currentTest.expectedNotifications);
 
     // Clean up.
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
   }
 
   clearMaxPages();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/expiration/xpcshell.ini
+++ b/toolkit/components/places/tests/expiration/xpcshell.ini
@@ -5,19 +5,19 @@ skip-if = toolkit == 'android' || toolki
 
 [test_analyze_runs.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_annos_expire_history.js]
 [test_annos_expire_never.js]
 [test_annos_expire_policy.js]
 [test_annos_expire_session.js]
+[test_clearHistory.js]
 [test_debug_expiration.js]
 [test_idle_daily.js]
 [test_notifications.js]
 [test_notifications_onDeleteURI.js]
 [test_notifications_onDeleteVisits.js]
 [test_outdated_analyze.js]
 [test_pref_interval.js]
 # Crashes when timer is used on non-main thread due to JS implemetation in this test
 skip-if = "JS implementation of nsITimer"
 [test_pref_maxpages.js]
-[test_removeAllPages.js]
--- a/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
+++ b/toolkit/components/places/tests/favicons/test_replaceFaviconData.js
@@ -78,17 +78,17 @@ add_task(function test_replaceFaviconDat
         pageURI, favicon.mimetype, favicon.data,
         function test_replaceFaviconData_validHistoryURI_callback() {
           favicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconData_overrideDefaultFavicon() {
   do_log_info("test replaceFaviconData to override a later setAndFetchFaviconForPage");
 
   let pageURI = uri("http://test2.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -110,17 +110,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconData_overrideDefaultFavicon_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconData_replaceExisting() {
   do_log_info("test replaceFaviconData to override a previous setAndFetchFaviconForPage");
 
   let pageURI = uri("http://test3.bar");
   yield promiseAddVisits(pageURI);
 
@@ -147,17 +147,17 @@ add_task(function test_replaceFaviconDat
                 secondFavicon.file.remove(false);
                 deferSetAndFetchFavicon.resolve();
               });
           });
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconData_unrelatedReplace() {
   do_log_info("test replaceFaviconData to not make unrelated changes");
 
   let pageURI = uri("http://test4.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -179,17 +179,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconData_unrelatedReplace_callback() {
           favicon.file.remove(false);
           unrelatedFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconData_badInputs() {
   do_log_info("test replaceFaviconData to throw on bad inputs");
 
   let favicon = createFavicon("favicon8.png");
 
   let ex = null;
@@ -219,17 +219,17 @@ add_task(function test_replaceFaviconDat
   } catch (e) {
     ex = e;
   } finally {
     do_check_true(!!ex);
   }
 
   favicon.file.remove(false);
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconData_twiceReplace() {
   do_log_info("test replaceFaviconData on multiple replacements");
 
   let pageURI = uri("http://test5.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -254,10 +254,10 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconData_twiceReplace_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
+++ b/toolkit/components/places/tests/favicons/test_replaceFaviconDataFromDataURL.js
@@ -80,17 +80,17 @@ add_task(function test_replaceFaviconDat
         pageURI, favicon.mimetype, favicon.data,
         function test_replaceFaviconDataFromDataURL_validHistoryURI_callback() {
           favicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() {
   do_log_info("test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage");
 
   let pageURI = uri("http://test2.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -110,17 +110,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_replaceExisting() {
   do_log_info("test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage");
 
   let pageURI = uri("http://test3.bar");
   yield promiseAddVisits(pageURI);
 
@@ -143,17 +143,17 @@ add_task(function test_replaceFaviconDat
               firstFavicon.file.remove(false);
               secondFavicon.file.remove(false);
               deferSetAndFetchFavicon.resolve();
             });
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_unrelatedReplace() {
   do_log_info("test replaceFaviconDataFromDataURL to not make unrelated changes");
 
   let pageURI = uri("http://test4.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -173,17 +173,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconDataFromDataURL_unrelatedReplace_callback() {
           favicon.file.remove(false);
           unrelatedFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_badInputs() {
   do_log_info("test replaceFaviconDataFromDataURL to throw on bad inputs");
 
   let favicon = createFavicon("favicon8.png");
 
   let ex = null;
@@ -201,17 +201,17 @@ add_task(function test_replaceFaviconDat
   } catch (e) {
     ex = e;
   } finally {
     do_check_true(!!ex);
   }
 
   favicon.file.remove(false);
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_twiceReplace() {
   do_log_info("test replaceFaviconDataFromDataURL on multiple replacements");
 
   let pageURI = uri("http://test5.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -232,17 +232,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconDataFromDataURL_twiceReplace_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_afterRegularAssign() {
   do_log_info("test replaceFaviconDataFromDataURL after replaceFaviconData");
 
   let pageURI = uri("http://test6.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -265,17 +265,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconDataFromDataURL_afterRegularAssign_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_replaceFaviconDataFromDataURL_beforeRegularAssign() {
   do_log_info("test replaceFaviconDataFromDataURL before replaceFaviconData");
 
   let pageURI = uri("http://test7.bar/");
   yield promiseAddVisits(pageURI);
 
@@ -298,17 +298,17 @@ add_task(function test_replaceFaviconDat
         function test_replaceFaviconDataFromDataURL_beforeRegularAssign_callback() {
           firstFavicon.file.remove(false);
           secondFavicon.file.remove(false);
           deferSetAndFetchFavicon.resolve();
         });
     });
   yield deferSetAndFetchFavicon.promise;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 /* toBase64 copied from image/test/unit/test_encoder_png.js */
 
 /* Convert data (an array of integers) to a Base64 string. */
 const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' +
     '0123456789+/';
 const base64Pad = '=';
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -35,16 +35,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
                                   "resource://gre/modules/BookmarkJSONUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
                                   "resource://gre/modules/PlacesBackups.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
                                   "resource://gre/modules/PlacesTransactions.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS",
                                   "resource://gre/modules/osfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 
 // This imports various other objects in addition to PlacesUtils.
@@ -384,30 +386,16 @@ function promiseTopicObserved(aTopic)
       Services.obs.removeObserver(PTO_observe, aTopic);
       deferred.resolve([aSubject, aData]);
     }, aTopic, false);
 
   return deferred.promise;
 }
 
 /**
- * Clears history asynchronously.
- *
- * @return {Promise}
- * @resolves When history has been cleared.
- * @rejects Never.
- */
-function promiseClearHistory() {
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
-  return promise;
-}
-
-
-/**
  * Simulates a Places shutdown.
  */
 function shutdownPlaces(aKeepAliveConnection)
 {
   let hs = PlacesUtils.history.QueryInterface(Ci.nsIObserver);
   hs.observe(null, "profile-change-teardown", null);
   hs.observe(null, "profile-before-change", null);
 }
--- a/toolkit/components/places/tests/history/test_remove.js
+++ b/toolkit/components/places/tests/history/test_remove.js
@@ -125,17 +125,17 @@ add_task(function* test_remove_single() 
         yield remover("Testing History.remove() with a single string url", x => x.spec, options);
         yield remover("Testing History.remove() with a single string guid", x => do_get_guid_for_uri(x), options);
         yield remover("Testing History.remove() with a single URI in an array", x => [x], options);
         yield remover("Testing History.remove() with a single string url in an array", x => [x.spec], options);
         yield remover("Testing History.remove() with a single string guid in an array", x => [do_get_guid_for_uri(x)], options);
       }
     }
   } finally {
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
   }
   return;
 });
 
 // Test removing a list of pages
 add_task(function* test_remove_many() {
   const SIZE = 10;
 
@@ -263,17 +263,17 @@ add_task(function* test_remove_many() {
     Assert.equal(page.onFrecencyChangedCalled, page.onDeleteVisitsCalled, "onDeleteVisits was called iff onFrecencyChanged was called");
     Assert.ok(page.onFrecencyChangedCalled ^ page.onDeleteURICalled, "Either onFrecencyChanged or onDeleteURI was called");
   }
 
   Assert.notEqual(visits_in_database(WITNESS_URI), 0, "Witness URI still has visits");
   Assert.notEqual(page_in_database(WITNESS_URI), 0, "Witness URI is still here");
 
   do_print("Cleaning up");
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
 });
 
 // Test the various error cases
 add_task(function* test_error_cases() {
   Assert.throws(
     () =>  PlacesUtils.history.remove(),
     /TypeError: Invalid url/,
--- a/toolkit/components/places/tests/inline/head_autocomplete.js
+++ b/toolkit/components/places/tests/inline/head_autocomplete.js
@@ -176,17 +176,17 @@ function run_test() {
 
 let gAutoCompleteTests = [];
 function add_autocomplete_test(aTestData) {
   gAutoCompleteTests.push(aTestData);
 }
 
 function waitForCleanup(aCallback) {
   remove_all_bookmarks();
-  promiseClearHistory().then(aCallback);
+  PlacesTestUtils.clearHistory().then(aCallback);
 }
 
 function addBookmark(aBookmarkObj) {
   do_check_true(!!aBookmarkObj.url);
   let parentId = aBookmarkObj.parentId ? aBookmarkObj.parentId
                                        : PlacesUtils.unfiledBookmarksFolderId;
   let itemId = PlacesUtils.bookmarks
                           .insertBookmark(parentId,
--- a/toolkit/components/places/tests/queries/test_history_queries_titles_liveUpdate.js
+++ b/toolkit/components/places/tests/queries/test_history_queries_titles_liveUpdate.js
@@ -63,17 +63,17 @@ add_task(function pages_query()
     let uri = NetUtil.newURI(node.uri);
     yield promiseAddVisits({uri: uri, title: "changedTitle"});
     do_check_eq(node.title, "changedTitle");
     yield promiseAddVisits({uri: uri, title: gTestData[i].title});
     do_check_eq(node.title, gTestData[i].title);
   }
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function visits_query()
 {
   yield task_populateDB(gTestData);
 
   let [query, options] = newQueryWithOptions();
   options.resultType = Ci.nsINavHistoryQueryOptions.RESULTS_AS_VISIT;
@@ -90,17 +90,17 @@ add_task(function visits_query()
     node = searchNodeHavingUrl(root, testData.uri);
     do_check_eq(node.title, "changedTitle");
     yield promiseAddVisits({uri: uri, title: testData.title});
     node = searchNodeHavingUrl(root, testData.uri);
     do_check_eq(node.title, testData.title);
   }
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function pages_searchterm_query()
 {
   yield task_populateDB(gTestData);
 
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "example";
@@ -114,17 +114,17 @@ add_task(function pages_searchterm_query
     do_check_eq(node.title, gTestData[i].title);
     yield promiseAddVisits({uri: uri, title: "changedTitle"});
     do_check_eq(node.title, "changedTitle");
     yield promiseAddVisits({uri: uri, title: gTestData[i].title});
     do_check_eq(node.title, gTestData[i].title);
   }
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function visits_searchterm_query()
 {
   yield task_populateDB(gTestData);
 
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "example";
@@ -141,17 +141,17 @@ add_task(function visits_searchterm_quer
     node = searchNodeHavingUrl(root, testData.uri);
     do_check_eq(node.title, "changedTitle");
     yield promiseAddVisits({uri: uri, title: testData.title});
     node = searchNodeHavingUrl(root, testData.uri);
     do_check_eq(node.title, testData.title);
   }
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function pages_searchterm_is_title_query()
 {
   yield task_populateDB(gTestData);
 
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "match";
@@ -165,17 +165,17 @@ add_task(function pages_searchterm_is_ti
     yield promiseAddVisits({uri: uri, title: data.title});
     compareArrayToResult([data], root);
     data.title = origTitle;
     yield promiseAddVisits({uri: uri, title: data.title});
     compareArrayToResult([], root);
   });
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function visits_searchterm_is_title_query()
 {
   yield task_populateDB(gTestData);
 
   let [query, options] = newQueryWithOptions();
   query.searchTerms = "match";
@@ -190,10 +190,10 @@ add_task(function visits_searchterm_is_t
     yield promiseAddVisits({uri: uri, title: data.title});
     compareArrayToResult([data], root);
     data.title = origTitle;
     yield promiseAddVisits({uri: uri, title: data.title});
     compareArrayToResult([], root);
   });
 
   root.containerOpen = false;
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/queries/test_redirects.js
+++ b/toolkit/components/places/tests/queries/test_redirects.js
@@ -295,10 +295,10 @@ add_task(function test_redirects()
                          Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
                          Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING];
   // Will execute check_results_callback() for each generated combination.
   cartProd([includeHidden_options, maxResults_options, sorting_options],
            check_results_callback);
 
   remove_all_bookmarks();
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
--- a/toolkit/components/places/tests/queries/test_sorting.js
+++ b/toolkit/components/places/tests/queries/test_sorting.js
@@ -1267,11 +1267,11 @@ add_task(function test_sorting()
   for (let [, test] in Iterator(tests)) {
     yield test.setup();
     yield promiseAsyncUpdates();
     test.check();
     // sorting reversed, usually SORT_BY have ASC and DESC
     test.check_reverse();
     // Execute cleanup tasks
     remove_all_bookmarks();
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
   }
 });
--- a/toolkit/components/places/tests/queries/test_tags.js
+++ b/toolkit/components/places/tests/queries/test_tags.js
@@ -572,17 +572,17 @@ function addBookmark(aURI) {
   do_check_true(bmId > 0);
 }
 
 /**
  * Asynchronous task that removes all pages from history and bookmarks.
  */
 function task_cleanDatabase(aCallback) {
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 }
 
 /**
  * Sets up a query with the specified tags, converts it to a URI, and makes sure
  * the URI is what we expect it to be.
  *
  * @param aTags
  *        The query's tags will be set to those in this array
--- a/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
+++ b/toolkit/components/places/tests/unifiedcomplete/head_autocomplete.js
@@ -27,17 +27,17 @@ function* cleanup() {
   Services.prefs.clearUserPref("browser.urlbar.autocomplete.enabled");
   Services.prefs.clearUserPref("browser.urlbar.autoFill");
   Services.prefs.clearUserPref("browser.urlbar.autoFill.typed");
   Services.prefs.clearUserPref("browser.urlbar.autoFill.searchEngines");
   for (let type of ["history", "bookmark", "history.onlyTyped", "openpage"]) {
     Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
   }
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 }
 do_register_cleanup(cleanup);
 
 /**
  * @param aSearches
  *        Array of AutoCompleteSearch names.
  */
 function AutoCompleteInput(aSearches) {
--- a/toolkit/components/places/tests/unit/test_317472.js
+++ b/toolkit/components/places/tests/unit/test_317472.js
@@ -40,17 +40,17 @@ add_task(function test_execute()
   do_check_eq(PlacesUtils.annotations.getPageAnnotation(TEST_URI, CHARSET_ANNO), charset);
 
   // get charset from not-bookmarked page
   do_check_eq((yield PlacesUtils.getCharsetForURI(TEST_URI)), charset);
 
   // get charset from bookmarked page
   do_check_eq((yield PlacesUtils.getCharsetForURI(TEST_BOOKMARKED_URI)), charset);
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
   // ensure that charset has gone for not-bookmarked page
   do_check_neq((yield PlacesUtils.getCharsetForURI(TEST_URI)), charset);
 
   // check that page annotation has been removed
   try {
     PlacesUtils.annotations.getPageAnnotation(TEST_URI, CHARSET_ANNO);
     do_throw("Charset page annotation has not been removed correctly");
--- a/toolkit/components/places/tests/unit/test_412132.js
+++ b/toolkit/components/places/tests/unit/test_412132.js
@@ -29,17 +29,17 @@ add_task(function changeuri_unvisited_bo
   PlacesUtils.bookmarks.changeBookmarkURI(id, uri("http://example.com/2"));
 
   yield promiseAsyncUpdates();
 
   do_log_info("Unvisited URI no longer bookmarked => frecency should = 0");
   do_check_eq(frecencyForUrl(TEST_URI), 0);
 
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function changeuri_visited_bookmark()
 {
   do_log_info("After changing URI of bookmark, frecency of bookmark's " +
               "original URI should not be zero if original URI is visited.");
   const TEST_URI = NetUtil.newURI("http://example.com/1");
   let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
@@ -59,17 +59,17 @@ add_task(function changeuri_visited_book
   PlacesUtils.bookmarks.changeBookmarkURI(id, uri("http://example.com/2"));
 
   yield promiseAsyncUpdates();
 
   do_log_info("*Visited* URI no longer bookmarked => frecency should != 0");
   do_check_neq(frecencyForUrl(TEST_URI), 0);
 
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function changeuri_bookmark_still_bookmarked()
 {
   do_log_info("After changing URI of bookmark, frecency of bookmark's " +
               "original URI should not be zero if original URI is still " +
               "bookmarked.");
   const TEST_URI = NetUtil.newURI("http://example.com/1");
@@ -90,17 +90,17 @@ add_task(function changeuri_bookmark_sti
   PlacesUtils.bookmarks.changeBookmarkURI(id1, uri("http://example.com/2"));
 
   yield promiseAsyncUpdates();
 
   do_log_info("URI still bookmarked => frecency should != 0");
   do_check_neq(frecencyForUrl(TEST_URI), 0);
 
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function changeuri_nonexistent_bookmark()
 {
   do_log_info("Changing the URI of a nonexistent bookmark should fail.");
   function tryChange(itemId)
   {
     try {
@@ -122,17 +122,17 @@ add_task(function changeuri_nonexistent_
   let id = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                                 uri("http://example.com/"),
                                                 PlacesUtils.bookmarks.DEFAULT_INDEX,
                                                 "bookmark title");
   PlacesUtils.bookmarks.removeItem(id);
   tryChange(id);
 
   remove_all_bookmarks();
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 ///////////////////////////////////////////////////////////////////////////////
 
 function run_test()
 {
   run_next_test();
 }
--- a/toolkit/components/places/tests/unit/test_adaptive.js
+++ b/toolkit/components/places/tests/unit/test_adaptive.js
@@ -368,17 +368,17 @@ add_task(function test_adaptive()
     PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.tagsFolderId);
     observer.runCount = -1;
 
     let types = ["history", "bookmark", "openpage"];
     for (let type of types) {
       Services.prefs.clearUserPref("browser.urlbar.suggest." + type);
     }
 
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
 
     deferEnsureResults = Promise.defer();
     yield test();
     yield deferEnsureResults.promise;
   }
 
   Services.obs.removeObserver(observer, PlacesUtils.TOPIC_FEEDBACK_UPDATED);
 });
--- a/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js
+++ b/toolkit/components/places/tests/unit/test_asyncExecuteLegacyQueries.js
@@ -84,13 +84,13 @@ function run_test()
 
 function run_next_test() {
   if (tests.length == 0) {
     do_test_finished();
     return;
   }
 
   let test = tests.shift();
-  promiseClearHistory().then(function() {
+  PlacesTestUtils.clearHistory().then(function() {
     remove_all_bookmarks();
     do_execute_soon(test);
   });
 }
--- a/toolkit/components/places/tests/unit/test_browserhistory.js
+++ b/toolkit/components/places/tests/unit/test_browserhistory.js
@@ -51,17 +51,17 @@ add_task(function* test_removePages() {
   // Check the annotation on the non-bookmarked page does not exist anymore.
   try {
     PlacesUtils.annotations.getPageAnnotation(pages[ANNO_INDEX], ANNO_NAME);
     do_throw("did not expire expire_never anno on a not bookmarked item");
   } catch(ex) {}
 
   // Cleanup.
   PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function* test_removePagesByTimeframe() {
   let visits = [];
   let startDate = Date.now() * 1000;
   for (let i = 0; i < 10; i++) {
     visits.push({
       uri: NetUtil.newURI(TEST_URI.spec + i),
@@ -92,18 +92,18 @@ add_task(function* test_removePagesFromH
 });
 
 add_task(function* test_removePagesFromHost_keepSubdomains() {
   yield promiseAddVisits([{ uri: TEST_URI }, { uri: TEST_SUBDOMAIN_URI }]);
   PlacesUtils.bhistory.removePagesFromHost("mozilla.com", false);
   do_check_eq(1, PlacesUtils.history.hasHistoryEntries);
 });
 
-add_task(function* test_removeAllPages() {
-  PlacesUtils.bhistory.removeAllPages();
+add_task(function* test_history_clear() {
+  yield PlacesTestUtils.clearHistory();
   do_check_eq(0, PlacesUtils.history.hasHistoryEntries);
 });
 
 add_task(function* test_getObservers() {
   // Ensure that getObservers() invalidates the hasHistoryEntries cache.
   yield promiseAddVisits(TEST_URI);
   do_check_eq(1, PlacesUtils.history.hasHistoryEntries);
   // This is just for testing purposes, never do it.
--- a/toolkit/components/places/tests/unit/test_download_history.js
+++ b/toolkit/components/places/tests/unit/test_download_history.js
@@ -106,17 +106,17 @@ add_test(function test_dh_addMultiRemove
     waitForOnVisit(function DHAD_onVisit(aURI) {
       do_check_true(aURI.equals(DOWNLOAD_URI));
       do_check_true(!!page_in_database(DOWNLOAD_URI));
 
       waitForOnDeleteVisits(function DHRAD_onDeleteVisits(aURI) {
         do_check_true(aURI.equals(DOWNLOAD_URI));
         do_check_true(!!page_in_database(DOWNLOAD_URI));
 
-        promiseClearHistory().then(run_next_test);
+        PlacesTestUtils.clearHistory().then(run_next_test);
       });
       gDownloadHistory.removeAllDownloads();
     });
 
     gDownloadHistory.addDownload(DOWNLOAD_URI, null, Date.now() * 1000);
   });
 });
 
@@ -129,17 +129,17 @@ add_test(function test_dh_addBookmarkRem
   waitForOnVisit(function DHAD_onVisit(aURI) {
     do_check_true(aURI.equals(DOWNLOAD_URI));
     do_check_true(!!page_in_database(DOWNLOAD_URI));
 
     waitForOnDeleteVisits(function DHRAD_onDeleteVisits(aURI) {
       do_check_true(aURI.equals(DOWNLOAD_URI));
       do_check_true(!!page_in_database(DOWNLOAD_URI));
 
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     });
     gDownloadHistory.removeAllDownloads();
   });
 
   gDownloadHistory.addDownload(DOWNLOAD_URI, null, Date.now() * 1000);
 });
 
 add_test(function test_dh_addDownload_referrer()
@@ -151,17 +151,17 @@ add_test(function test_dh_addDownload_re
     waitForOnVisit(function DHAD_onVisit(aURI, aVisitID, aTime, aSessionID,
                                               aReferringID) {
       do_check_true(aURI.equals(DOWNLOAD_URI));
       do_check_eq(aReferringID, referrerVisitId);
 
       // Verify that the URI is already available in results at this time.
       do_check_true(!!page_in_database(DOWNLOAD_URI));
 
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     });
 
     gDownloadHistory.addDownload(DOWNLOAD_URI, REFERRER_URI, Date.now() * 1000);
   });
 
   // Note that we don't pass the optional callback argument here because we must
   // ensure that we receive the onVisit notification before we call addDownload.
   PlacesUtils.asyncHistory.updatePlaces({
@@ -180,17 +180,17 @@ add_test(function test_dh_addDownload_di
     // test is based on the assumption that visit notifications are received in
     // the same order of the addDownload calls, which is currently true because
     // database access is serialized on the same worker thread.
     do_check_true(aURI.equals(DOWNLOAD_URI));
 
     do_check_true(!!page_in_database(DOWNLOAD_URI));
     do_check_false(!!page_in_database(PRIVATE_URI));
 
-    promiseClearHistory().then(run_next_test);
+    PlacesTestUtils.clearHistory().then(run_next_test);
   });
 
   Services.prefs.setBoolPref("places.history.enabled", false);
   gDownloadHistory.addDownload(PRIVATE_URI, REFERRER_URI, Date.now() * 1000);
 
   // The addDownload functions calls CanAddURI synchronously, thus we can set
   // the preference back to true immediately (not all apps enable places by
   // default).
@@ -216,17 +216,17 @@ add_test(function test_dh_details()
   let destinationFileNameSet = false;
 
   function checkFinished()
   {
     if (titleSet && destinationFileUriSet && destinationFileNameSet) {
       PlacesUtils.annotations.removeObserver(annoObserver);
       PlacesUtils.history.removeObserver(historyObserver);
 
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     }
   };
 
   let annoObserver = {
     onPageAnnotationSet: function AO_onPageAnnotationSet(aPage, aName)
     {
       if (aPage.equals(SOURCE_URI)) {
         let value = PlacesUtils.annotations.getPageAnnotation(aPage, aName);
--- a/toolkit/components/places/tests/unit/test_frecency.js
+++ b/toolkit/components/places/tests/unit/test_frecency.js
@@ -282,17 +282,17 @@ add_task(function test_frecency()
   var prefs = Cc["@mozilla.org/preferences-service;1"].
               getService(Ci.nsIPrefBranch);
 
   prefs.setBoolPref("browser.urlbar.suggest.history", true);
   prefs.setBoolPref("browser.urlbar.suggest.bookmark", true);
   prefs.setBoolPref("browser.urlbar.suggest.openpage", false);
   for (let [, test] in Iterator(tests)) {
     remove_all_bookmarks();
-    yield promiseClearHistory();
+    yield PlacesTestUtils.clearHistory();
 
     deferEnsureResults = Promise.defer();
     yield test();
     yield deferEnsureResults.promise;
   }
   for (let type of ["history", "bookmark", "openpage"]) {
     prefs.clearUserPref("browser.urlbar.suggest." + type);
   }
--- a/toolkit/components/places/tests/unit/test_frecency_observers.js
+++ b/toolkit/components/places/tests/unit/test_frecency_observers.js
@@ -39,18 +39,17 @@ add_task(function test_nsNavHistory_inva
   bm.insertBookmark(bm.unfiledBookmarksFolder, uri,
                     Ci.nsINavBookmarksService.DEFAULT_INDEX, "test");
   PlacesUtils.history.removePagesFromHost(uri.host, false);
   yield onFrecencyChanged(uri);
 });
 
 // nsNavHistory::invalidateFrecencies for all pages
 add_task(function test_nsNavHistory_invalidateFrecencies_allPages() {
-  PlacesUtils.history.removeAllPages();
-  yield onManyFrecenciesChanged();
+  yield Promise.all([onManyFrecenciesChanged(), PlacesTestUtils.clearHistory()]);
 });
 
 // nsNavHistory::DecayFrecency and nsNavHistory::FixInvalidFrecencies
 add_task(function test_nsNavHistory_DecayFrecency_and_nsNavHistory_FixInvalidFrecencies() {
   // FixInvalidFrecencies is at the end of a path that DecayFrecency is also on,
   // so expect two notifications.  Trigger the path by making nsNavHistory
   // observe the idle-daily notification.
   PlacesUtils.history.QueryInterface(Ci.nsIObserver).
rename from toolkit/components/places/tests/unit/test_history_removeAllPages.js
rename to toolkit/components/places/tests/unit/test_history_clear.js
--- a/toolkit/components/places/tests/unit/test_history_removeAllPages.js
+++ b/toolkit/components/places/tests/unit/test_history_clear.js
@@ -41,17 +41,17 @@ function run_test() {
   // places-init-complete is notified after run_test, and it will
   // run a first frecency fix through async statements.
   // To avoid random failures we have to run after all of this.
   promiseInit = promiseTopicObserved(PlacesUtils.TOPIC_INIT_COMPLETE);
 
   run_next_test();
 }
 
-add_task(function test_history_removeAllPages()
+add_task(function* test_history_clear()
 {
   yield promiseInit;
 
   yield promiseAddVisits([
     { uri: uri("http://typed.mozilla.org/"),
       transition: TRANSITION_TYPED },
     { uri: uri("http://link.mozilla.org/"),
       transition: TRANSITION_LINK },
@@ -90,17 +90,17 @@ add_task(function test_history_removeAll
       transition: TRANSITION_BOOKMARK },
     { uri: uri("http://frecency.mozilla.org/"),
       transition: TRANSITION_LINK },
   ]);
   yield promiseAsyncUpdates();
 
   // Clear history and wait for the onClearHistory notification.
   let promiseWaitClearHistory = promiseOnClearHistoryObserved();
-  PlacesUtils.bhistory.removeAllPages();
+  PlacesUtils.history.clear();
   yield promiseWaitClearHistory;
 
   // check browserHistory returns no entries
   do_check_eq(0, PlacesUtils.history.hasHistoryEntries);
 
   yield promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
   yield promiseAsyncUpdates();
 
--- a/toolkit/components/places/tests/unit/test_hosts_triggers.js
+++ b/toolkit/components/places/tests/unit/test_hosts_triggers.js
@@ -86,17 +86,17 @@ add_task(function test_moz_hosts_update(
 });
 
 add_task(function test_remove_places()
 {
   for (let idx in urls) {
     PlacesUtils.history.removePage(urls[idx].uri);
   }
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
   for (let idx in urls) {
     do_check_false(isHostInMozHosts(urls[idx].uri, urls[idx].typed, urls[idx].prefix));
   }
 });
 
 add_task(function test_bookmark_changes()
 {
@@ -107,31 +107,31 @@ add_task(function test_bookmark_changes(
                                                      PlacesUtils.bookmarks.DEFAULT_INDEX,
                                                      "bookmark title");
 
   do_check_true(isHostInMozPlaces(testUri));
 
   // Change the hostname
   PlacesUtils.bookmarks.changeBookmarkURI(itemId, NetUtil.newURI(NEW_URL));
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
   let newUri = NetUtil.newURI(NEW_URL);
   do_check_true(isHostInMozPlaces(newUri));
   do_check_true(isHostInMozHosts(newUri, false, null));
   do_check_false(isHostInMozHosts(NetUtil.newURI("http://test.mozilla.org"), false, null));
 });
 
 add_task(function test_bookmark_removal()
 {
   let itemId = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId,
                                                     PlacesUtils.bookmarks.DEFAULT_INDEX);
   let newUri = NetUtil.newURI(NEW_URL);
   PlacesUtils.bookmarks.removeItem(itemId);
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 
   do_check_false(isHostInMozHosts(newUri, false, null));
 });
 
 add_task(function test_moz_hosts_typed_update()
 {
   const TEST_URI = NetUtil.newURI("http://typed.mozilla.com");
   let places = [{ uri: TEST_URI
@@ -140,17 +140,17 @@ add_task(function test_moz_hosts_typed_u
                 { uri: TEST_URI
                 , title: "test for " + TEST_URI.spec
                 , transition: TRANSITION_TYPED
                 }];
 
   yield promiseAddVisits(places);
 
   do_check_true(isHostInMozHosts(TEST_URI, true, null));
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_moz_hosts_www_remove()
 {
   function test_removal(aURIToRemove, aURIToKeep, aCallback) {
     let places = [{ uri: aURIToRemove
                   , title: "test for " + aURIToRemove.spec
                   , transition: TRANSITION_TYPED
@@ -170,17 +170,17 @@ add_task(function test_moz_hosts_www_rem
     dump_table("moz_places");
     do_check_true(isHostInMozHosts(aURIToKeep, true, prefix));
   }
 
   const TEST_URI = NetUtil.newURI("http://rem.mozilla.com");
   const TEST_WWW_URI = NetUtil.newURI("http://www.rem.mozilla.com");
   yield test_removal(TEST_URI, TEST_WWW_URI);
   yield test_removal(TEST_WWW_URI, TEST_URI);
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_moz_hosts_ftp_matchall()
 {
   const TEST_URI_1 = NetUtil.newURI("ftp://www.mozilla.com/");
   const TEST_URI_2 = NetUtil.newURI("ftp://mozilla.com/");
 
   yield promiseAddVisits([{ uri: TEST_URI_1, transition: TRANSITION_TYPED },
--- a/toolkit/components/places/tests/unit/test_isURIVisited.js
+++ b/toolkit/components/places/tests/unit/test_isURIVisited.js
@@ -65,17 +65,17 @@ function step()
           handleCompletion: function () {
             do_log_info("Added visit to " + uri.spec);
 
             history.isURIVisited(uri, function (aURI, aIsVisited) {
               do_check_true(uri.equals(aURI));
               let checker = SCHEMES[scheme] ? do_check_true : do_check_false;
               checker(aIsVisited);
 
-              promiseClearHistory().then(function () {
+              PlacesTestUtils.clearHistory().then(function () {
                 history.isURIVisited(uri, function(aURI, aIsVisited) {
                   do_check_true(uri.equals(aURI));
                   do_check_false(aIsVisited);
                   gRunner.next();
                 });
               });
             });
           },
--- a/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
+++ b/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
@@ -124,39 +124,40 @@ add_test(function check_history_query() 
 
         // nsINavHistoryResultObserver.sortingChanged
         resultObserver.invalidatedContainer = null;
         result.sortingMode = options.SORT_BY_TITLE_ASCENDING;
         do_check_eq(resultObserver.sortingMode, options.SORT_BY_TITLE_ASCENDING);
         do_check_eq(resultObserver.invalidatedContainer, result.root);
 
         // nsINavHistoryResultObserver.invalidateContainer
-        bhist.removeAllPages();
-        do_check_eq(root.uri, resultObserver.invalidatedContainer.uri);
+        PlacesTestUtils.clearHistoryEnabled().then(() => {
+          do_check_eq(root.uri, resultObserver.invalidatedContainer.uri);
 
-        // nsINavHistoryResultObserver.batching
-        do_check_false(resultObserver.inBatchMode);
-        histsvc.runInBatchMode({
-          runBatched: function (aUserData) {
-            do_check_true(resultObserver.inBatchMode);
-          }
-        }, null);
-        do_check_false(resultObserver.inBatchMode);
-        bmsvc.runInBatchMode({
-          runBatched: function (aUserData) {
-            do_check_true(resultObserver.inBatchMode);
-          }
-        }, null);
-        do_check_false(resultObserver.inBatchMode);
+          // nsINavHistoryResultObserver.batching
+          do_check_false(resultObserver.inBatchMode);
+          histsvc.runInBatchMode({
+            runBatched: function (aUserData) {
+              do_check_true(resultObserver.inBatchMode);
+            }
+          }, null);
+          do_check_false(resultObserver.inBatchMode);
+          bmsvc.runInBatchMode({
+            runBatched: function (aUserData) {
+              do_check_true(resultObserver.inBatchMode);
+            }
+          }, null);
+          do_check_false(resultObserver.inBatchMode);
 
-        root.containerOpen = false;
-        do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
-        result.removeObserver(resultObserver);
-        resultObserver.reset();
-        promiseAsyncUpdates().then(run_next_test);
+          root.containerOpen = false;
+          do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
+          result.removeObserver(resultObserver);
+          resultObserver.reset();
+          promiseAsyncUpdates().then(run_next_test);
+        });
       });
     });
   });
 });
 
 add_test(function check_bookmarks_query() {
   var options = histsvc.getNewQueryOptions();
   var query = histsvc.getNewQuery();
--- a/toolkit/components/places/tests/unit/test_pageGuid_bookmarkGuid.js
+++ b/toolkit/components/places/tests/unit/test_pageGuid_bookmarkGuid.js
@@ -101,17 +101,17 @@ add_task(function test_addVisitAndCheckG
   let root = histsvc.executeQuery(query, options).root;
   root.containerOpen = true;
   do_check_eq(root.childCount, 1);
 
   do_check_valid_places_guid(root.getChild(0).pageGuid);
   do_check_eq(root.getChild(0).bookmarkGuid, "");
   root.containerOpen = false;
 
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
 });
 
 add_task(function test_addItemsWithInvalidGUIDsFails() {
   const INVALID_GUID = "XYZ";
   try {
     bmsvc.createFolder(bmsvc.placesRoot, "XYZ folder",
                        bmsvc.DEFAULT_INDEX, INVALID_GUID);
     do_throw("Adding a folder with an invalid guid should fail");
--- a/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js
+++ b/toolkit/components/places/tests/unit/test_removeVisitsByTimeframe.js
@@ -4,17 +4,17 @@
  * 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 NOW = Date.now() * 1000;
 const TEST_URI = uri("http://example.com/");
 const PLACE_URI = uri("place:queryType=0&sort=8&maxResults=10");
 
 function* cleanup() {
-  yield promiseClearHistory();
+  yield PlacesTestUtils.clearHistory();
   remove_all_bookmarks();
   // This is needed to remove place: entries.
   DBConn().executeSimpleSQL("DELETE FROM moz_places");
 }
 
 add_task(function remove_visits_outside_unbookmarked_uri() {
   do_log_info("*** TEST: Remove some visits outside valid timeframe from an unbookmarked URI");
  
--- a/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js
+++ b/toolkit/components/places/tests/unit/test_update_frecency_after_delete.js
@@ -29,17 +29,17 @@ add_test(function removed_bookmark()
     PlacesUtils.bookmarks.removeItem(id);
 
     promiseAsyncUpdates().then(function ()
     {
       do_log_info("Unvisited URI no longer bookmarked => frecency should = 0");
       do_check_eq(frecencyForUrl(TEST_URI), 0);
 
       remove_all_bookmarks();
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     });
   });
 });
 
 add_test(function removed_but_visited_bookmark()
 {
   do_log_info("After removing bookmark, frecency of bookmark's URI should " +
               "not be zero if URI is visited.");
@@ -57,17 +57,17 @@ add_test(function removed_but_visited_bo
       PlacesUtils.bookmarks.removeItem(id);
 
       promiseAsyncUpdates().then(function ()
       {
         do_log_info("*Visited* URI no longer bookmarked => frecency should != 0");
         do_check_neq(frecencyForUrl(TEST_URI), 0);
 
         remove_all_bookmarks();
-        promiseClearHistory().then(run_next_test);
+        PlacesTestUtils.clearHistory().then(run_next_test);
       });
     });
   });
 });
 
 add_test(function remove_bookmark_still_bookmarked()
 {
   do_log_info("After removing bookmark, frecency of bookmark's URI should ",
@@ -89,17 +89,17 @@ add_test(function remove_bookmark_still_
     PlacesUtils.bookmarks.removeItem(id1);
 
     promiseAsyncUpdates().then(function ()
     {
       do_log_info("URI still bookmarked => frecency should != 0");
       do_check_neq(frecencyForUrl(TEST_URI), 0);
 
       remove_all_bookmarks();
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     });
   });
 });
 
 add_test(function cleared_parent_of_visited_bookmark()
 {
   do_log_info("After removing all children from bookmark's parent, frecency " +
               "of bookmark's URI should not be zero if URI is visited.");
@@ -117,17 +117,17 @@ add_test(function cleared_parent_of_visi
       PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
 
       promiseAsyncUpdates().then(function ()
       {
         do_log_info("*Visited* URI no longer bookmarked => frecency should != 0");
         do_check_neq(frecencyForUrl(TEST_URI), 0);
 
         remove_all_bookmarks();
-        promiseClearHistory().then(run_next_test);
+        PlacesTestUtils.clearHistory().then(run_next_test);
       });
     });
   });
 });
 
 add_test(function cleared_parent_of_bookmark_still_bookmarked()
 {
   do_log_info("After removing all children from bookmark's parent, frecency " +
@@ -151,17 +151,17 @@ add_test(function cleared_parent_of_book
     PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
 
     promiseAsyncUpdates().then(function ()
     {
       // URI still bookmarked => frecency should != 0.
       do_check_neq(frecencyForUrl(TEST_URI), 0);
 
       remove_all_bookmarks();
-      promiseClearHistory().then(run_next_test);
+      PlacesTestUtils.clearHistory().then(run_next_test);
     });
   });
 });
 
 ///////////////////////////////////////////////////////////////////////////////
 
 function run_test()
 {
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -90,19 +90,19 @@ fail-if = os == "android"
 [test_frecency_zero_updated.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_getChildIndex.js]
 [test_getPlacesInfo.js]
 [test_history.js]
 [test_history_autocomplete_tags.js]
 [test_history_catobs.js]
+[test_history_clear.js]
 [test_history_notifications.js]
 [test_history_observer.js]
-[test_history_removeAllPages.js]
 # Bug 676989: test hangs consistently on Android
 skip-if = os == "android"
 [test_history_sidebar.js]
 [test_hosts_triggers.js]
 [test_isURIVisited.js]
 [test_isvisited.js]
 [test_lastModified.js]
 [test_markpageas.js]
--- a/toolkit/components/social/test/xpcshell/head.js
+++ b/toolkit/components/social/test/xpcshell/head.js
@@ -200,27 +200,8 @@ function promiseAddVisits(aPlaceInfo)
       handleCompletion: function handleCompletion() {
         deferred.resolve();
       }
     }
   );
 
   return deferred.promise;
 }
-
-function promiseTopicObserved(aTopic)
-{
-  let deferred = Promise.defer();
-
-  Services.obs.addObserver(
-    function PTO_observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(PTO_observe, aTopic);
-      deferred.resolve([aSubject, aData]);
-    }, aTopic, false);
-
-  return deferred.promise;
-}
-
-function promiseClearHistory() {
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  do_execute_soon(function() PlacesUtils.bhistory.removeAllPages());
-  return promise;
-}
--- a/toolkit/components/social/test/xpcshell/test_SocialService.js
+++ b/toolkit/components/social/test/xpcshell/test_SocialService.js
@@ -1,14 +1,16 @@
 /* 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/. */
 
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+  "resource://testing-common/PlacesTestUtils.jsm");
 
 function run_test() {
   initApp();
 
   // NOTE: none of the manifests here can have a workerURL set, or we attempt
   // to create a FrameWorker and that fails under xpcshell...
   let manifests = [
     { // normal provider
@@ -156,11 +158,11 @@ function testOrderedProviders(manifests,
   }
 
   promiseAddVisits(visits).then(next);
   yield;
   let orderedProviders = yield SocialService.getOrderedProviderList(next);
   do_check_eq(orderedProviders[0], providers[1]);
   do_check_eq(orderedProviders[1], providers[0]);
   do_check_true(orderedProviders[0].frecency > orderedProviders[1].frecency);
-  promiseClearHistory().then(next);
+  PlacesTestUtils.clearHistory().then(next);
   yield;
 }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6974,17 +6974,17 @@
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether or not a session has tracking protection enabled"
   },
   "TRACKING_PROTECTION_SHIELD": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 4,
-    "description": "Tracking protection shield (0 = not shown, 1 = blocked, 2 = loaded, 3 = due to mixed content"
+    "description": "Tracking protection shield (0 = not shown, 1 = loaded, 2 = blocked, 3 = due to mixed content"
   },
   "TRACKING_PROTECTION_EVENTS": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 3,
     "description": "Doorhanger shown = 0, Disable = 1, Enable = 2"
   },
   "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS": {
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -14,16 +14,18 @@
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
 
 const COOKIE_EXPIRY = Math.round(Date.now() / 1000) + 60;
 const COOKIE_NAME = "testcookie";
 const COOKIE_PATH = "/";
 
 const LOGIN_USERNAME = "username";
 const LOGIN_PASSWORD = "password";
 const LOGIN_USERNAME_FIELD = "username_field";
@@ -409,17 +411,17 @@ function test_history_not_cleared_with_u
   const TEST_URI = uri("http://ilovemozilla.org/foo");
   do_check_false(yield promiseIsURIVisited(TEST_URI));
   yield promiseAddVisits(TEST_URI);
   do_check_true(yield promiseIsURIVisited(TEST_URI));
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   do_check_true(yield promiseIsURIVisited(TEST_URI));
 
   // Clear history since we left something there from this test.
-  PlacesUtils.bhistory.removeAllPages();
+  yield PlacesTestUtils.clearHistory();
 }
 
 // Cookie Service
 function test_cookie_cleared_with_direct_match()
 {
   const TEST_DOMAIN = "mozilla.org";
   add_cookie(TEST_DOMAIN);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
--- a/toolkit/mozapps/downloads/tests/chrome/test_destinationURI_annotation.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_destinationURI_annotation.xul
@@ -34,16 +34,17 @@ let ww = Cc["@mozilla.org/embedcomp/wind
 let dm = Cc["@mozilla.org/download-manager;1"]
            .getService(Ci.nsIDownloadManager);
 
 let os = Cc["@mozilla.org/observer-service;1"]
            .getService(Ci.nsIObserverService);
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+Components.utils.import("resource://testing-common/PlacesTestUtils.jsm");
 
 let checkDestination = false,
     checkFileName = false;
 
 SimpleTest.waitForExplicitFinish();
 
 let annoObserver = {
   onPageAnnotationSet: function AO_onPageAnnotationSet(aPage, aName){
@@ -180,17 +181,17 @@ function endTest() {
 
   Cc["@mozilla.org/appshell/window-mediator;1"]
     .getService(Ci.nsIWindowMediator)
     .getMostRecentWindow("Download:Manager")
     .close();
 
   Services.prefs.clearUserPref("browser.download.useToolkitUI");
 
-  waitForClearHistory(SimpleTest.finish);
+  PlacesTestUtils.clearHistory().then(SimpleTest.finish);
 }
 
   ]]>
   </script>
 
   <body xmlns="http://www.w3.org/1999/xhtml">
     <p id="display"></p>
     <div id="content" style="display:none;"></div>
--- a/toolkit/mozapps/downloads/tests/chrome/utils.js
+++ b/toolkit/mozapps/downloads/tests/chrome/utils.js
@@ -141,20 +141,8 @@ function setCleanState()
            getService(Ci.nsIDownloadManager);
 
   // Clean the dm
   dm.DBConnection.executeSimpleSQL("DELETE FROM moz_downloads");
 
   let win = getDMWindow();
   if (win) win.close();
 }
-
-/**
- * Clears history invoking callback when done.
- */
-function waitForClearHistory(aCallback) {
-  Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
-  Services.obs.addObserver(function observeClearHistory(aSubject, aTopic) {
-    Services.obs.removeObserver(observeClearHistory, aTopic);
-    aCallback();
-  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
-  PlacesUtils.bhistory.removeAllPages();
-}