Bug 1093153 - enabled and re-factored browser_aboutHome.js to work correctly in e10s mode. r=mak, a=ritu
authorMike de Boer <mdeboer@mozilla.com>
Tue, 15 Mar 2016 11:09:34 +0100
changeset 323466 5781664ff8a9a00fadb1349af3ae1487fdea94df
parent 323465 7a9e58c8c8b87c42ed95c5dd776bb28d8d4d7f81
child 323467 7a0624f58e6f3691eac904678d0a803595220d56
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak, ritu
bugs1093153
milestone47.0a2
Bug 1093153 - enabled and re-factored browser_aboutHome.js to work correctly in e10s mode. r=mak, a=ritu
browser/base/content/abouthome/aboutHome.js
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_aboutHome.js
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -147,18 +147,26 @@ function ensureSnippetsMapThen(aCallback
     }
 
     db.onversionchange = function (event) {
       event.target.close();
       invokeCallbacks();
     }
 
     let cache = new Map();
-    let cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME)
-                          .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
+    let cursorRequest;
+    try {
+      cursorRequest = db.transaction(SNIPPETS_OBJECTSTORE_NAME)
+                        .objectStore(SNIPPETS_OBJECTSTORE_NAME).openCursor();
+    } catch(ex) {
+      console.error(ex);
+      invokeCallbacks();
+      return;
+    }
+
     cursorRequest.onerror = function (event) {
       invokeCallbacks();
     }
 
     cursorRequest.onsuccess = function(event) {
       let cursor = event.target.result;
 
       // Populate the cache from the persistent storage.
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -127,17 +127,16 @@ skip-if = (os == "linux" || os == "mac")
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
 [browser_aboutCertError.js]
 [browser_aboutSupport_newtab_security_state.js]
 [browser_aboutHealthReport.js]
 skip-if = os == "linux" # Bug 924307
 [browser_aboutHome.js]
-skip-if = e10s # Bug 1093153 - no about:home support yet
 [browser_aboutHome_wrapsCorrectly.js]
 [browser_action_keyword.js]
 [browser_action_keyword_override.js]
 [browser_action_searchengine.js]
 [browser_action_searchengine_alias.js]
 [browser_addKeywordSearch.js]
 [browser_search_favicon.js]
 [browser_alltabslistener.js]
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -1,662 +1,675 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-///////////////////
-//
-// Whitelisting this test.
-// As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
-thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: Assert is null");
+requestLongerTimeout(2);
+ignoreAllUncaughtExceptions();
 
-
-XPCOMUtils.defineLazyModuleGetter(this, "Promise",
-  "resource://gre/modules/Promise.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task",
-  "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
   "resource:///modules/AboutHome.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+  "resource://gre/modules/AppConstants.jsm");
 
-const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/content/test/general/aboutHome_content_script.js";
+const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/" +
+  "content/test/general/aboutHome_content_script.js";
 var gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
 
 registerCleanupFunction(function() {
   // Ensure we don't pollute prefs for next tests.
   Services.prefs.clearUserPref("network.cookies.cookieBehavior");
   Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
   Services.prefs.clearUserPref("browser.rights.override");
   Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
 });
 
-var gTests = [
+add_task(function* () {
+  info("Check that clearing cookies does not clear storage");
 
-{
-  desc: "Check that clearing cookies does not clear storage",
-  setup: function ()
-  {
-    Cc["@mozilla.org/observer-service;1"]
-      .getService(Ci.nsIObserverService)
-      .notifyObservers(null, "cookie-changed", "cleared");
-  },
-  run: function (aSnippetsMap)
-  {
-    isnot(aSnippetsMap.get("snippets-last-update"), null,
-          "snippets-last-update should have a value");
-  }
-},
+  yield withSnippetsMap(
+    () => {
+      Cc["@mozilla.org/observer-service;1"]
+        .getService(Ci.nsIObserverService)
+        .notifyObservers(null, "cookie-changed", "cleared");
+    },
+    function* () {
+      isnot(content.gSnippetsMap.get("snippets-last-update"), null,
+            "snippets-last-update should have a value");
+    });
+});
 
-{
-  desc: "Check default snippets are shown",
-  setup: function () { },
-  run: function ()
-  {
-    let doc = gBrowser.selectedBrowser.contentDocument;
+add_task(function* () {
+  info("Check default snippets are shown");
+
+  yield withSnippetsMap(null, function* () {
+    let doc = content.document;
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element")
     is(snippetsElt.getElementsByTagName("span").length, 1,
        "A default snippet is present.");
-  }
-},
+  });
+});
 
-{
-  desc: "Check default snippets are shown if snippets are invalid xml",
-  setup: function (aSnippetsMap)
-  {
-    // This must be some incorrect xhtml code.
-    aSnippetsMap.set("snippets", "<p><b></p></b>");
-  },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedBrowser.contentDocument;
+add_task(function* () {
+  info("Check default snippets are shown if snippets are invalid xml");
 
-    let snippetsElt = doc.getElementById("snippets");
-    ok(snippetsElt, "Found snippets element");
-    is(snippetsElt.getElementsByTagName("span").length, 1,
-       "A default snippet is present.");
+  yield withSnippetsMap(
+    // This must set some incorrect xhtml code.
+    snippetsMap => snippetsMap.set("snippets", "<p><b></p></b>"),
+    function* () {
+      let doc = content.document;
+      let snippetsElt = doc.getElementById("snippets");
+      ok(snippetsElt, "Found snippets element");
+      is(snippetsElt.getElementsByTagName("span").length, 1,
+         "A default snippet is present.");
 
-    aSnippetsMap.delete("snippets");
-  }
-},
+      content.gSnippetsMap.delete("snippets");
+    });
+});
 
-{
-  desc: "Check that performing a search fires a search event and records to " +
-        "Telemetry.",
-  setup: function () { },
-  run: function* () {
+add_task(function* () {
+  info("Check that performing a search fires a search event and records to Telemetry.");
 
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
     let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
     // Make this actually work in healthreport by giving it an ID:
     Object.defineProperty(engine.wrappedJSObject, "identifier",
-                          {value: "org.mozilla.testsearchsuggestions"});
+                          { value: "org.mozilla.testsearchsuggestions" });
 
-    let p = promiseContentSearchChange(engine.name);
+    let p = promiseContentSearchChange(browser, engine.name);
     Services.search.currentEngine = engine;
     yield p;
 
+    yield ContentTask.spawn(browser, { expectedName: engine.name }, function* (args) {
+      let engineName = content.wrappedJSObject.gContentSearchController.defaultEngine.name;
+      is(engineName, args.expectedName, "Engine name in DOM should match engine we just added");
+    });
+
     let numSearchesBefore = 0;
-    let searchEventDeferred = Promise.defer();
-    let doc = gBrowser.contentDocument;
-    let engineName = gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name;
-    is(engine.name, engineName, "Engine name in DOM should match engine we just added");
-
     // Get the current number of recorded searches.
     let histogramKey = engine.identifier + ".abouthome";
     try {
       let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
       if (histogramKey in hs) {
         numSearchesBefore = hs[histogramKey].sum;
       }
     } catch (ex) {
       // No searches performed yet, not a problem, |numSearchesBefore| is 0.
     }
 
-    // Perform a search to increase the SEARCH_COUNT histogram.
     let searchStr = "a search";
-    info("Perform a search.");
-    doc.getElementById("searchText").value = searchStr;
-    doc.getElementById("searchSubmit").click();
+
+    let expectedURL = Services.search.currentEngine
+      .getSubmission(searchStr, null, "homepage").uri.spec;
+    let promise = waitForDocLoadAndStopIt(expectedURL, browser);
 
-    let expectedURL = Services.search.currentEngine.
-                      getSubmission(searchStr, null, "homepage").
-                      uri.spec;
-    let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => {
-      // Make sure the SEARCH_COUNTS histogram has the right key and count.
-      let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
-      Assert.ok(histogramKey in hs, "histogram with key should be recorded");
-      Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
-                   "histogram sum should be incremented");
-      searchEventDeferred.resolve();
+    // Perform a search to increase the SEARCH_COUNT histogram.
+    yield ContentTask.spawn(browser, { searchStr }, function* (args) {
+      let doc = content.document;
+      info("Perform a search.");
+      doc.getElementById("searchText").value = args.searchStr;
+      doc.getElementById("searchSubmit").click();
     });
 
+    yield promise;
+
+    // Make sure the SEARCH_COUNTS histogram has the right key and count.
+    let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
+    Assert.ok(histogramKey in hs, "histogram with key should be recorded");
+    Assert.equal(hs[histogramKey].sum, numSearchesBefore + 1,
+                 "histogram sum should be incremented");
+
     try {
-      yield Promise.all([searchEventDeferred.promise, loadPromise]);
-    } catch (ex) {
-      Cu.reportError(ex);
-      ok(false, "An error occurred waiting for the search to be performed: " + ex);
-    } finally {
-      try {
-        Services.search.removeEngine(engine);
-      } catch (ex) {}
-    }
-  }
-},
+      Services.search.removeEngine(engine);
+    } catch (ex) {}
+  });
+});
+
+add_task(function* () {
+  info("Check snippets map is cleared if cached version is old");
 
-{
-  desc: "Check snippets map is cleared if cached version is old",
-  setup: function (aSnippetsMap)
-  {
-    aSnippetsMap.set("snippets", "test");
-    aSnippetsMap.set("snippets-cached-version", 0);
-  },
-  run: function (aSnippetsMap)
-  {
-    ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared");
-    ok(!aSnippetsMap.has("snippets-cached-version"),
-       "cached-version has been properly cleared");
-  }
-},
+  yield withSnippetsMap(
+    snippetsMap => {
+      snippetsMap.set("snippets", "test");
+      snippetsMap.set("snippets-cached-version", 0);
+    },
+    function* () {
+      let snippetsMap = content.gSnippetsMap;
+      ok(!snippetsMap.has("snippets"), "snippets have been properly cleared");
+      ok(!snippetsMap.has("snippets-cached-version"),
+         "cached-version has been properly cleared");
+    });
+});
+
+add_task(function* () {
+  info("Check cached snippets are shown if cached version is current");
 
-{
-  desc: "Check cached snippets are shown if cached version is current",
-  setup: function (aSnippetsMap)
-  {
-    aSnippetsMap.set("snippets", "test");
-  },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedBrowser.contentDocument;
+  yield withSnippetsMap(
+    snippetsMap => snippetsMap.set("snippets", "test"),
+    function* (args) {
+      let doc = content.document;
+      let snippetsMap = content.gSnippetsMap
 
-    let snippetsElt = doc.getElementById("snippets");
-    ok(snippetsElt, "Found snippets element");
-    is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
+      let snippetsElt = doc.getElementById("snippets");
+      ok(snippetsElt, "Found snippets element");
+      is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
 
-    is(aSnippetsMap.get("snippets"), "test", "snippets still cached");
-    is(aSnippetsMap.get("snippets-cached-version"),
-       AboutHomeUtils.snippetsVersion,
-       "cached-version is correct");
-    ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists");
-  }
-},
+      is(snippetsMap.get("snippets"), "test", "snippets still cached");
+      is(snippetsMap.get("snippets-cached-version"),
+         args.expectedVersion,
+         "cached-version is correct");
+      ok(snippetsMap.has("snippets-last-update"), "last-update still exists");
+    }, { expectedVersion: AboutHomeUtils.snippetsVersion });
+});
 
-{
-  desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set",
-  beforeRun: function ()
-  {
-    Services.prefs.setBoolPref("browser.rights.override", false);
-  },
-  setup: function () { },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let showRights = AboutHomeUtils.showKnowYourRights;
+add_task(function* () {
+  info("Check if the 'Know Your Rights default snippet is shown when " +
+    "'browser.rights.override' pref is set");
 
-    ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE");
+  Services.prefs.setBoolPref("browser.rights.override", false);
 
+  ok(AboutHomeUtils.showKnowYourRights, "AboutHomeUtils.showKnowYourRights should be TRUE");
+
+  yield withSnippetsMap(null, function* () {
+    let doc = content.document;
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element");
-    is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present.");
+    is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights",
+      "Snippet link is present.");
+  });
 
-    Services.prefs.clearUserPref("browser.rights.override");
-  }
-},
+  Services.prefs.clearUserPref("browser.rights.override");
+});
 
-{
-  desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set",
-  beforeRun: function ()
-  {
-    Services.prefs.setBoolPref("browser.rights.override", true);
-  },
-  setup: function () { },
-  run: function (aSnippetsMap)
-  {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let rightsData = AboutHomeUtils.knowYourRightsData;
+add_task(function* () {
+  info("Check if the 'Know Your Rights default snippet is NOT shown when " +
+    "'browser.rights.override' pref is NOT set");
+
+  Services.prefs.setBoolPref("browser.rights.override", true);
 
-    ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE");
+  let rightsData = AboutHomeUtils.knowYourRightsData;
+  ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE");
 
+  yield withSnippetsMap(null, function*() {
+    let doc = content.document;
     let snippetsElt = doc.getElementById("snippets");
     ok(snippetsElt, "Found snippets element");
-    ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights.");
+    ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights",
+      "Snippet link should not point to about:rights.");
+  });
 
-    Services.prefs.clearUserPref("browser.rights.override");
-  }
-},
+  Services.prefs.clearUserPref("browser.rights.override");
+});
 
-{
-  desc: "Check POST search engine support",
-  setup: function() {},
-  run: function* ()
-  {
-    let deferred = Promise.defer();
-    let currEngine = Services.search.defaultEngine;
-    let searchObserver = Task.async(function* search_observer(aSubject, aTopic, aData) {
-      let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
-      info("Observer: " + aData + " for " + engine.name);
+add_task(function* () {
+  info("Check POST search engine support");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    return new Promise(resolve => {
+      let currEngine = Services.search.defaultEngine;
+      let searchObserver = Task.async(function* search_observer(subject, topic, data) {
+        let engine = subject.QueryInterface(Ci.nsISearchEngine);
+        info("Observer: " + data + " for " + engine.name);
 
-      if (aData != "engine-added")
-        return;
+        if (data != "engine-added")
+          return;
 
-      if (engine.name != "POST Search")
-        return;
+        if (engine.name != "POST Search")
+          return;
+
+        // Ready to execute the tests!
+        let needle = "Search for something awesome.";
 
-      // Ready to execute the tests!
-      let needle = "Search for something awesome.";
-      let document = gBrowser.selectedBrowser.contentDocument;
-      let searchText = document.getElementById("searchText");
+        let p = promiseContentSearchChange(browser, engine.name);
+        Services.search.defaultEngine = engine;
+        yield p;
 
-      let p = promiseContentSearchChange(engine.name);
-      Services.search.defaultEngine = engine;
-      yield p;
+        registerCleanupFunction(function() {
+          Services.search.removeEngine(engine);
+          Services.search.defaultEngine = currEngine;
+        });
 
-      searchText.value = needle;
-      searchText.focus();
-      EventUtils.synthesizeKey("VK_RETURN", {});
+        let promise = BrowserTestUtils.browserLoaded(browser);
 
-      registerCleanupFunction(function() {
-        Services.search.removeEngine(engine);
-        Services.search.defaultEngine = currEngine;
-      });
+        yield ContentTask.spawn(browser, { needle }, function* (args) {
+          let doc = content.document;
+          doc.getElementById("searchText").value = args.needle;
+          doc.getElementById("searchSubmit").click();
+        });
 
+        yield promise;
 
-      // When the search results load, check them for correctness.
-      waitForLoad(function() {
-        let loadedText = gBrowser.contentDocument.body.textContent;
-        ok(loadedText, "search page loaded");
-        is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")),
-           "Search text should arrive correctly");
-        deferred.resolve();
+        // When the search results load, check them for correctness.
+        yield ContentTask.spawn(browser, { needle }, function* (args) {
+          let loadedText = content.document.body.textContent;
+          ok(loadedText, "search page loaded");
+          is(loadedText, "searchterms=" + escape(args.needle.replace(/\s/g, "+")),
+             "Search text should arrive correctly");
+        });
+
+        resolve();
       });
+      Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+      registerCleanupFunction(function () {
+        Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+      });
+      Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
+                                null, null, false);
     });
-    Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
-    registerCleanupFunction(function () {
-      Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
-    });
-    Services.search.addEngine("http://test:80/browser/browser/base/content/test/general/POSTSearchEngine.xml",
-                              null, null, false);
-    return deferred.promise;
-  }
-},
+  });
+});
+
+add_task(function* () {
+  info("Make sure that a page can't imitate about:home");
 
-{
-  desc: "Make sure that a page can't imitate about:home",
-  setup: function () { },
-  run: function (aSnippetsMap)
-  {
-    let deferred = Promise.defer();
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    let promise = BrowserTestUtils.browserLoaded(browser);
+    browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
+    yield promise;
 
-    let browser = gBrowser.selectedBrowser;
-    waitForLoad(() => {
-      let button = browser.contentDocument.getElementById("settings");
+    yield ContentTask.spawn(browser, null, function* () {
+      let button = content.document.getElementById("settings");
       ok(button, "Found settings button in test page");
       button.click();
+    });
 
+    yield new Promise(resolve => {
       // It may take a few turns of the event loop before the window
       // is displayed, so we wait.
       function check(n) {
         let win = Services.wm.getMostRecentWindow("Browser:Preferences");
         ok(!win, "Preferences window not showing");
         if (win) {
           win.close();
         }
 
         if (n > 0) {
           executeSoon(() => check(n-1));
         } else {
-          deferred.resolve();
+          resolve();
         }
       }
 
       check(5);
     });
+  });
+});
 
-    browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
-    return deferred.promise;
-  }
-},
-
-{
+add_task(function* () {
   // See browser_contentSearchUI.js for comprehensive content search UI tests.
-  desc: "Search suggestion smoke test",
-  setup: function() {},
-  run: function()
-  {
-    return Task.spawn(function* () {
-      // Add a test engine that provides suggestions and switch to it.
-      let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
-      let p = promiseContentSearchChange(engine.name);
-      Services.search.currentEngine = engine;
-      yield p;
+  info("Search suggestion smoke test");
 
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    // Add a test engine that provides suggestions and switch to it.
+    let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+    let p = promiseContentSearchChange(browser, engine.name);
+    Services.search.currentEngine = engine;
+    yield p;
+
+    yield ContentTask.spawn(browser, null, function* () {
       // Avoid intermittent failures.
-      gBrowser.contentWindow.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
+      content.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
 
       // Type an X in the search input.
-      let input = gBrowser.contentDocument.getElementById("searchText");
+      let input = content.document.getElementById("searchText");
       input.focus();
-      EventUtils.synthesizeKey("x", {});
+    });
 
+    yield BrowserTestUtils.synthesizeKey("x", {}, browser);
+
+    yield ContentTask.spawn(browser, null, function* () {
       // Wait for the search suggestions to become visible.
-      let table =
-        gBrowser.contentDocument.getElementById("searchSuggestionTable");
-      let deferred = Promise.defer();
-      let observer = new MutationObserver(() => {
-        if (input.getAttribute("aria-expanded") == "true") {
-          observer.disconnect();
-          ok(!table.hidden, "Search suggestion table unhidden");
-          deferred.resolve();
-        }
+      let table = content.document.getElementById("searchSuggestionTable");
+      let input = content.document.getElementById("searchText");
+
+      yield new Promise(resolve => {
+        let observer = new content.MutationObserver(() => {
+          if (input.getAttribute("aria-expanded") == "true") {
+            observer.disconnect();
+            ok(!table.hidden, "Search suggestion table unhidden");
+            resolve();
+          }
+        });
+        observer.observe(input, {
+          attributes: true,
+          attributeFilter: ["aria-expanded"],
+        });
       });
-      observer.observe(input, {
-        attributes: true,
-        attributeFilter: ["aria-expanded"],
-      });
-      yield deferred.promise;
+    });
 
-      // Empty the search input, causing the suggestions to be hidden.
-      EventUtils.synthesizeKey("a", { accelKey: true });
-      EventUtils.synthesizeKey("VK_DELETE", {});
-      ok(table.hidden, "Search suggestion table hidden");
+    // Empty the search input, causing the suggestions to be hidden.
+    yield BrowserTestUtils.synthesizeKey("a", { accelKey: true }, browser);
+    yield BrowserTestUtils.synthesizeKey("VK_DELETE", {}, browser);
 
-      try {
-        Services.search.removeEngine(engine);
-      } catch (ex) { }
+    yield ContentTask.spawn(browser, null, function* () {
+      let table = content.document.getElementById("searchSuggestionTable");
+      yield ContentTaskUtils.waitForCondition(() => table.hidden,
+        "Search suggestion table hidden");
     });
-  }
-},
-{
-  desc: "Clicking suggestion list while composing",
-  setup: function() {},
-  run: function()
-  {
-    return Task.spawn(function* () {
-      // Add a test engine that provides suggestions and switch to it.
-      let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
-      let p = promiseContentSearchChange(engine.name);
-      Services.search.currentEngine = engine;
-      yield p;
+
+    try {
+      Services.search.removeEngine(engine);
+    } catch (ex) { }
+  });
+});
 
+add_task(function* () {
+  info("Clicking suggestion list while composing");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    // Add a test engine that provides suggestions and switch to it.
+    let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
+    let p = promiseContentSearchChange(browser, engine.name);
+    Services.search.currentEngine = engine;
+    yield p;
+
+    yield ContentTask.spawn(browser, null, function* () {
       // Start composition and type "x"
-      let input = gBrowser.contentDocument.getElementById("searchText");
+      let input = content.document.getElementById("searchText");
       input.focus();
-      EventUtils.synthesizeComposition({ type: "compositionstart", data: "" },
-                                       gBrowser.contentWindow);
-      EventUtils.synthesizeCompositionChange({
-        composition: {
-          string: "x",
-          clauses: [
-            { length: 1, attr: EventUtils.COMPOSITION_ATTR_RAW_CLAUSE }
-          ]
-        },
-        caret: { start: 1, length: 0 }
-      }, gBrowser.contentWindow);
+    });
 
-      let searchController =
-        gBrowser.contentWindow.wrappedJSObject.gContentSearchController;
+    yield BrowserTestUtils.synthesizeComposition({
+      type: "compositionstart",
+      data: ""
+    }, browser);
+    yield BrowserTestUtils.synthesizeCompositionChange({
+      composition: {
+        string: "x",
+        clauses: [
+          { length: 1, attr: Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE }
+        ]
+      },
+      caret: { start: 1, length: 0 }
+    }, browser);
+
+    yield ContentTask.spawn(browser, null, function* () {
+      let searchController = content.wrappedJSObject.gContentSearchController;
 
       // Wait for the search suggestions to become visible.
       let table = searchController._suggestionsList;
-      let deferred = Promise.defer();
-      let observer = new MutationObserver(() => {
-        if (input.getAttribute("aria-expanded") == "true") {
-          observer.disconnect();
-          ok(!table.hidden, "Search suggestion table unhidden");
-          deferred.resolve();
-        }
+      let input = content.document.getElementById("searchText");
+
+      yield new Promise(resolve => {
+        let observer = new content.MutationObserver(() => {
+          if (input.getAttribute("aria-expanded") == "true") {
+            observer.disconnect();
+            ok(!table.hidden, "Search suggestion table unhidden");
+            resolve();
+          }
+        });
+        observer.observe(input, {
+          attributes: true,
+          attributeFilter: ["aria-expanded"],
+        });
       });
-      observer.observe(input, {
-        attributes: true,
-        attributeFilter: ["aria-expanded"],
-      });
-      yield deferred.promise;
 
-      // Click the second suggestion.
-      let expectedURL = Services.search.currentEngine.
-                        getSubmission("xbar", null, "homepage").
-                        uri.spec;
-      let loadPromise = waitForDocLoadAndStopIt(expectedURL);
       let row = table.children[1];
+      row.setAttribute("id", "TEMPID");
+
       // ContentSearchUIController looks at the current selectedIndex when
       // performing a search. Synthesizing the mouse event on the suggestion
       // doesn't actually mouseover the suggestion and trigger it to be flagged
       // as selected, so we manually select it first.
       searchController.selectedIndex = 1;
-      EventUtils.synthesizeMouseAtCenter(row, {button: 0}, gBrowser.contentWindow);
-      yield loadPromise;
-      ok(input.value == "x", "Input value did not change");
     });
-  }
-},
-{
-  desc: "Pressing any key should focus the search box in the page, and send the key to it",
-  setup: function () {},
-  run: Task.async(function* () {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let logo = doc.getElementById("brandLogo");
-    let searchInput = doc.getElementById("searchText");
+
+    // Click the second suggestion.
+    let expectedURL = Services.search.currentEngine
+      .getSubmission("xbar", null, "homepage").uri.spec;
+    let loadPromise = waitForDocLoadAndStopIt(expectedURL);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#TEMPID", {
+      button: 0
+    }, browser);
+    yield loadPromise;
+
+    yield ContentTask.spawn(browser, null, function* () {
+      let input = content.document.getElementById("searchText");
+      ok(input.value == "x", "Input value did not change");
 
-    EventUtils.synthesizeMouseAtCenter(logo, {});
-    isnot(searchInput, doc.activeElement, "Search input should not be the active element.");
+      let row = content.document.getElementById("TEMPID");
+      if (row) {
+        row.removeAttribute("id");
+      }
+    });
+  });
+});
+
+add_task(function* () {
+  info("Pressing any key should focus the search box in the page, and send the key to it");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
 
-    EventUtils.synthesizeKey("a", {});
-    yield promiseWaitForCondition(() => doc.activeElement === searchInput);
-    is(searchInput, doc.activeElement, "Search input should be the active element.");
-    is(searchInput.value, "a", "Search input should be 'a'.");
-  })
-},
-{
-  desc: "Cmd+k should focus the search box in the page when the search box in the toolbar is absent",
-  setup: function () {
-    // Remove the search bar from toolbar
-    CustomizableUI.removeWidgetFromArea("search-container");
-  },
-  run: Task.async(function* () {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let logo = doc.getElementById("brandLogo");
-    let searchInput = doc.getElementById("searchText");
+    yield ContentTask.spawn(browser, null, function* () {
+      let doc = content.document;
+      isnot(doc.getElementById("searchText"), doc.activeElement,
+        "Search input should not be the active element.");
+    });
+
+    yield BrowserTestUtils.synthesizeKey("a", {}, browser);
 
-    EventUtils.synthesizeMouseAtCenter(logo, {});
-    isnot(searchInput, doc.activeElement, "Search input should not be the active element.");
+    yield ContentTask.spawn(browser, null, function* () {
+      let doc = content.document;
+      let searchInput = doc.getElementById("searchText");
+      yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
+        "Search input should be the active element.");
+      is(searchInput.value, "a", "Search input should be 'a'.");
+    });
+  });
+});
+
+add_task(function* () {
+  info("Cmd+k should focus the search box in the page when the search box in the toolbar is absent");
+
+  // Remove the search bar from toolbar
+  CustomizableUI.removeWidgetFromArea("search-container");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+    yield ContentTask.spawn(browser, null, function* () {
+      let doc = content.document;
+      isnot(doc.getElementById("searchText"), doc.activeElement,
+        "Search input should not be the active element.");
+    });
 
     EventUtils.synthesizeKey("k", { accelKey: true });
-    yield promiseWaitForCondition(() => doc.activeElement === searchInput);
-    is(searchInput, doc.activeElement, "Search input should be the active element.");
-    CustomizableUI.reset();
-  })
-},
-{
-  desc: "Cmd+k should focus the search box in the toolbar when it's present",
-  setup: function () {},
-  run: Task.async(function* () {
-    let logo = gBrowser.selectedBrowser.contentDocument.getElementById("brandLogo");
+
+    yield ContentTask.spawn(browser, null, function* () {
+      let doc = content.document;
+      let searchInput = doc.getElementById("searchText");
+
+      yield ContentTaskUtils.waitForCondition(() => doc.activeElement === searchInput,
+        "Search input should be the active element.");
+    });
+  });
+
+  CustomizableUI.reset();
+});
+
+add_task(function* () {
+  info("Cmd+k should focus the search box in the toolbar when it's present");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#brandLogo", {}, browser);
+
     let doc = window.document;
     let searchInput = doc.getElementById("searchbar").textbox.inputField;
-
-    EventUtils.synthesizeMouseAtCenter(logo, {});
     isnot(searchInput, doc.activeElement, "Search bar should not be the active element.");
 
     EventUtils.synthesizeKey("k", { accelKey: true });
     yield promiseWaitForCondition(() => doc.activeElement === searchInput);
     is(searchInput, doc.activeElement, "Search bar should be the active element.");
-  })
-},
-{
-  desc: "Sync button should open about:preferences#sync",
-  setup: function () {},
-  run: Task.async(function* () {
-    let syncButton = gBrowser.selectedBrowser.contentDocument.getElementById("sync");
+  });
+});
+
+add_task(function* () {
+  info("Sync button should open about:preferences#sync");
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
     let oldOpenPrefs = window.openPreferences;
     let openPrefsPromise = new Promise(resolve => {
       window.openPreferences = function (pane, params) {
         resolve({ pane: pane, params: params });
       };
     });
 
-    yield EventUtils.synthesizeMouseAtCenter(syncButton, {}, gBrowser.contentWindow);
+    yield BrowserTestUtils.synthesizeMouseAtCenter("#sync", {}, browser);
 
     let result = yield openPrefsPromise;
     window.openPreferences = oldOpenPrefs;
 
     is(result.pane, "paneSync", "openPreferences should be called with paneSync");
-    is(result.params.urlParams.entrypoint, "abouthome", "openPreferences should be called with abouthome entrypoint");
-  })
-},
-{
-  desc: "Pressing Space while the Addons button is focussed should activate it",
-  setup: function () {},
-  run: Task.async(function* () {
-    // Skip this test on Mac, because Space doesn't activate the button there.
-    if (navigator.platform.indexOf("Mac") == 0) {
-      return Promise.resolve();
-    }
+    is(result.params.urlParams.entrypoint, "abouthome",
+      "openPreferences should be called with abouthome entrypoint");
+  });
+});
 
+add_task(function* () {
+  info("Pressing Space while the Addons button is focused should activate it");
+
+  // Skip this test on Mac, because Space doesn't activate the button there.
+  if (AppConstants.platform == "macosx") {
+    return;
+  }
+
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:home" }, function* (browser) {
     info("Waiting for about:addons tab to open...");
     let promiseTabOpened = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
-    let addOnsButton = gBrowser.selectedBrowser.contentDocument.getElementById("addons");
-    addOnsButton.focus();
-    EventUtils.synthesizeKey(" ", {});
-    let tab = yield promiseTabOpened;
-    is(tab.linkedBrowser.currentURI.spec, "about:addons", "Should have seen the about:addons tab");
-    yield BrowserTestUtils.removeTab(tab);
-  })
-}
 
-];
-
-function test()
-{
-  waitForExplicitFinish();
-  requestLongerTimeout(2);
-  ignoreAllUncaughtExceptions();
-
-  Task.spawn(function () {
-    for (let test of gTests) {
-      info(test.desc);
-
-      if (test.beforeRun)
-        yield test.beforeRun();
+    yield ContentTask.spawn(browser, null, function* () {
+      let addOnsButton = content.document.getElementById("addons");
+      addOnsButton.focus();
+    });
+    yield BrowserTestUtils.synthesizeKey(" ", {}, browser);
 
-      // Create a tab to run the test.
-      let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
-
-      // Add an event handler to modify the snippets map once it's ready.
-      let snippetsPromise = promiseSetupSnippetsMap(tab, test.setup);
-
-      // Start loading about:home and wait for it to complete.
-      yield promiseTabLoadEvent(tab, "about:home", "AboutHomeLoadSnippetsCompleted");
-
-      // This promise should already be resolved since the page is done,
-      // but we still want to get the snippets map out of it.
-      let snippetsMap = yield snippetsPromise;
-
-      info("Running test");
-      yield test.run(snippetsMap);
-      info("Cleanup");
-      gBrowser.removeCurrentTab();
-    }
-  }).then(finish, ex => {
-    ok(false, "Unexpected Exception: " + ex);
-    finish();
+    let tab = yield promiseTabOpened;
+    is(tab.linkedBrowser.currentURI.spec, "about:addons",
+      "Should have seen the about:addons tab");
+    yield BrowserTestUtils.removeTab(tab);
   });
-}
+});
 
 /**
  * Cleans up snippets and ensures that by default we don't try to check for
  * remote snippets since that may cause network bustage or slowness.
  *
- * @param aTab
- *        The tab containing about:home.
  * @param aSetupFn
  *        The setup function to be run.
  * @return {Promise} resolved when the snippets are ready.  Gets the snippets map.
  */
-function promiseSetupSnippetsMap(aTab, aSetupFn)
-{
-  let deferred = Promise.defer();
-  info("Waiting for snippets map");
-  aTab.linkedBrowser.addEventListener("AboutHomeLoadSnippets", function load(event) {
-    aTab.linkedBrowser.removeEventListener("AboutHomeLoadSnippets", load, true);
+function* withSnippetsMap(setupFn, testFn, testArgs = null) {
+  let setupFnSource;
+  if (setupFn) {
+    setupFnSource = setupFn.toSource();
+  }
 
-    let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
+  yield BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function* (browser) {
     // The snippets should already be ready by this point. Here we're
     // just obtaining a reference to the snippets map.
-    cw.ensureSnippetsMapThen(function (aSnippetsMap) {
-      aSnippetsMap = Cu.waiveXrays(aSnippetsMap);
-      info("Got snippets map: " +
-           "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
-           ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
-           " }");
-      // Don't try to update.
-      aSnippetsMap.set("snippets-last-update", Date.now());
-      aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
-      // Clear snippets.
-      aSnippetsMap.delete("snippets");
-      aSetupFn(aSnippetsMap);
-      deferred.resolve(aSnippetsMap);
-    });
-  }, true, true);
-  return deferred.promise;
-}
+    yield ContentTask.spawn(browser, {
+      setupFnSource,
+      version: AboutHomeUtils.snippetsVersion,
+    }, function* (args) {
+      yield new Promise(resolve => {
+        let onAfterLocationChange = () => {
+          let document = content.document;
+          // We're not using Promise-based listeners, because they resolve asynchronously.
+          // The snippets test setup code relies on synchronous behaviour here.
+          document.addEventListener("AboutHomeLoadSnippets", function loadSnippets() {
+            document.removeEventListener("AboutHomeLoadSnippets", loadSnippets);
+
+            let updateSnippets;
+            if (args.setupFnSource) {
+              updateSnippets = eval(`(() => (${args.setupFnSource}))()`);
+            }
+
+            content.wrappedJSObject.ensureSnippetsMapThen(snippetsMap => {
+              snippetsMap = Cu.waiveXrays(snippetsMap);
+              info("Got snippets map: " +
+                   "{ last-update: " + snippetsMap.get("snippets-last-update") +
+                   ", cached-version: " + snippetsMap.get("snippets-cached-version") +
+                   " }");
+              // Don't try to update.
+              snippetsMap.set("snippets-last-update", Date.now());
+              snippetsMap.set("snippets-cached-version", args.version);
+              // Clear snippets.
+              snippetsMap.delete("snippets");
+
+              if (updateSnippets) {
+                updateSnippets(snippetsMap);
+              }
 
-function waitForLoad(cb) {
-  let browser = gBrowser.selectedBrowser;
-  browser.addEventListener("load", function listener() {
-    if (browser.currentURI.spec == "about:blank")
-      return;
-    info("Page loaded: " + browser.currentURI.spec);
-    browser.removeEventListener("load", listener, true);
+              // Tack it to the global object
+              content.gSnippetsMap = snippetsMap;
+
+              resolve();
+            });
+          });
+        };
 
-    cb();
-  }, true);
-}
+        // We'd like to listen to the 'AboutHomeLoadSnippets' event on a fresh
+        // document as soon as technically possible, so we use webProgress.
+        let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIWebProgress);
+        let wpl = {
+          onLocationChange: () => {
+            onAfterLocationChange();
+            webProgress.removeProgressListener(wpl);
+          },
+          onProgressChange: () => {},
+          onStatusChange: () => {},
+          onSecurityChange: () => {},
+          QueryInterface: XPCOMUtils.generateQI([
+            Ci.nsIWebProgressListener,
+            Ci.nsISupportsWeakReference,
+            Ci.nsISupports
+          ])
+        };
+        webProgress.addProgressListener(wpl, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+          Ci.nsIWebProgress.NOTIFY_LOCATION);
 
-function promiseWaitForEvent(node, type, capturing) {
-  return new Promise((resolve) => {
-    node.addEventListener(type, function listener(event) {
-      node.removeEventListener(type, listener, capturing);
-      resolve(event);
-    }, capturing);
+        // Set the URL to 'about:home' here to allow capturing the 'AboutHomeLoadSnippets'
+        // event.
+        content.document.location.href = "about:home";
+      });
+    });
+
+    yield ContentTask.spawn(browser, testArgs, testFn);
   });
 }
 
-var promisePrefsOpen = Task.async(function*() {
-  info("Waiting for the preferences tab to open...");
-  let event = yield promiseWaitForEvent(gBrowser.tabContainer, "TabOpen", true);
-  let tab = event.target;
-  yield promiseTabLoadEvent(tab);
-  is(tab.linkedBrowser.currentURI.spec, "about:preferences#search", "Should have seen the prefs tab");
-  gBrowser.removeTab(tab);
-});
-
-function promiseContentSearchChange(newEngineName) {
-  return new Promise(resolve => {
-    content.addEventListener("ContentSearchService", function listener(aEvent) {
-      if (aEvent.detail.type == "CurrentState" &&
-          gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name == newEngineName) {
-        content.removeEventListener("ContentSearchService", listener);
-        resolve();
-      }
+function promiseContentSearchChange(browser, newEngineName) {
+  return ContentTask.spawn(browser, { newEngineName }, function* (args) {
+    return new Promise(resolve => {
+      content.addEventListener("ContentSearchService", function listener(aEvent) {
+        if (aEvent.detail.type == "CurrentState" &&
+            content.wrappedJSObject.gContentSearchController.defaultEngine.name == args.newEngineName) {
+          content.removeEventListener("ContentSearchService", listener);
+          resolve();
+        }
+      });
     });
   });
 }
 
 function promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
-  let addDeferred = Promise.defer();
-  let url = getRootDirectory(gTestPath) + basename;
-  Services.search.addEngine(url, null, "", false, {
-    onSuccess: function (engine) {
-      info("Search engine added: " + basename);
-      registerCleanupFunction(() => {
-        try {
-          Services.search.removeEngine(engine);
-        } catch (ex) { /* Can't remove the engine more than once */ }
-      });
-      addDeferred.resolve(engine);
-    },
-    onError: function (errCode) {
-      ok(false, "addEngine failed with error code " + errCode);
-      addDeferred.reject();
-    },
+  return new Promise((resolve, reject) => {
+    let url = getRootDirectory(gTestPath) + basename;
+    Services.search.addEngine(url, null, "", false, {
+      onSuccess: function (engine) {
+        info("Search engine added: " + basename);
+        registerCleanupFunction(() => {
+          try {
+            Services.search.removeEngine(engine);
+          } catch (ex) { /* Can't remove the engine more than once */ }
+        });
+        resolve(engine);
+      },
+      onError: function (errCode) {
+        ok(false, "addEngine failed with error code " + errCode);
+        reject();
+      },
+    });
   });
-  return addDeferred.promise;
 }