Bug 1171344 - Update in-content search UI tests. r=adw
☠☠ backed out by 692034f77737 ☠ ☠
authorNihanth Subramanya <nhnt11@gmail.com>
Mon, 29 Jun 2015 13:52:22 -0700
changeset 252126 05e99d3230d8db2b294abb6994f29cfa99e4a624
parent 252125 5d8717ff74c45d5125baa41a4ed7cf17ff039575
child 252127 dfd2a128d19ce8f485543428e09328f450d96791
push id13941
push usernhnt11@gmail.com
push dateThu, 09 Jul 2015 21:58:26 +0000
treeherderfx-team@05e99d3230d8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersadw
bugs1171344
milestone42.0a1
Bug 1171344 - Update in-content search UI tests. r=adw
browser/base/content/test/general/browser.ini
browser/base/content/test/general/browser_aboutHome.js
browser/base/content/test/general/browser_contentSearchUI.js
browser/base/content/test/general/browser_searchSuggestionUI.js
browser/base/content/test/general/contentSearchUI.html
browser/base/content/test/general/contentSearchUI.js
browser/base/content/test/general/searchSuggestionEngine.xml
browser/base/content/test/general/searchSuggestionEngine2.xml
browser/base/content/test/general/searchSuggestionUI.html
browser/base/content/test/general/searchSuggestionUI.js
browser/base/content/test/newtab/browser_newtab_search.js
browser/base/content/test/newtab/head.js
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -74,16 +74,17 @@ support-files =
   parsingTestHelpers.jsm
   pinning_headers.sjs
   pinning_reports.sjs
   popup_blocker.html
   print_postdata.sjs
   redirect_bug623155.sjs
   searchSuggestionEngine.sjs
   searchSuggestionEngine.xml
+  searchSuggestionEngine2.xml
   subtst_contextmenu.html
   test-mixedcontent-securityerrors.html
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
@@ -362,20 +363,20 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_link_when_window_navigates.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet'
 [browser_save_video_frame.js]
 [browser_scope.js]
-[browser_searchSuggestionUI.js]
+[browser_contentSearchUI.js]
 support-files =
-  searchSuggestionUI.html
-  searchSuggestionUI.js
+  contentSuggestionUI.html
+  contentSuggestionUI.js
 [browser_selectpopup.js]
 run-if = e10s
 [browser_selectTabAtIndex.js]
 [browser_ssl_error_reports.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_syncui.js]
 [browser_tabDrop.js]
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -96,53 +96,45 @@ let gTests = [
       // Health Report disabled, or no SearchesProvider.
       return Promise.resolve();
     }
 
     let engine = yield promiseNewEngine("searchSuggestionEngine.xml");
     // Make this actually work in healthreport by giving it an ID:
     engine.wrappedJSObject._identifier = 'org.mozilla.testsearchsuggestions';
 
-    let promise = promiseBrowserAttributes(gBrowser.selectedTab);
+    let p = promiseContentSearchChange(engine.name);
     Services.search.currentEngine = engine;
-    yield promise;
+    yield p;
 
     let numSearchesBefore = 0;
     let searchEventDeferred = Promise.defer();
     let doc = gBrowser.contentDocument;
-    let engineName = doc.documentElement.getAttribute("searchEngineName");
+    let engineName = gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name;
     is(engine.name, engineName, "Engine name in DOM should match engine we just added");
-    let mm = gBrowser.selectedBrowser.messageManager;
-
-    mm.loadFrameScript(TEST_CONTENT_HELPER, false);
-
-    mm.addMessageListener("AboutHomeTest:CheckRecordedSearch", function (msg) {
-      let data = JSON.parse(msg.data);
-      is(data.engineName, engineName, "Detail is search engine name");
-
-      getNumberOfSearches(engineName).then(num => {
-        is(num, numSearchesBefore + 1, "One more search recorded.");
-        searchEventDeferred.resolve();
-      });
-    });
 
     // Get the current number of recorded searches.
     let searchStr = "a search";
     getNumberOfSearches(engineName).then(num => {
       numSearchesBefore = num;
 
       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 loadPromise = waitForDocLoadAndStopIt(expectedURL);
+    let loadPromise = waitForDocLoadAndStopIt(expectedURL).then(() => {
+      getNumberOfSearches(engineName).then(num => {
+        is(num, numSearchesBefore + 1, "One more search recorded.");
+        searchEventDeferred.resolve();
+      });
+    });
 
     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 {
@@ -231,67 +223,58 @@ let gTests = [
 
     Services.prefs.clearUserPref("browser.rights.override");
   }
 },
 
 {
   desc: "Check POST search engine support",
   setup: function() {},
-  run: function()
+  run: function* ()
   {
     let deferred = Promise.defer();
     let currEngine = Services.search.defaultEngine;
-    let searchObserver = function search_observer(aSubject, aTopic, aData) {
+    let searchObserver = Task.async(function* search_observer(aSubject, aTopic, aData) {
       let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
       info("Observer: " + aData + " for " + engine.name);
 
       if (aData != "engine-added")
         return;
 
       if (engine.name != "POST Search")
         return;
 
       // Ready to execute the tests!
       let needle = "Search for something awesome.";
       let document = gBrowser.selectedBrowser.contentDocument;
       let searchText = document.getElementById("searchText");
 
-      // We're about to change the search engine. Once the change has
-      // propagated to the about:home content, we want to perform a search.
-      let mutationObserver = new MutationObserver(function (mutations) {
-        for (let mutation of mutations) {
-          if (mutation.attributeName == "searchEngineName") {
-            searchText.value = needle;
-            searchText.focus();
-            EventUtils.synthesizeKey("VK_RETURN", {});
-          }
-        }
-      });
-      mutationObserver.observe(document.documentElement, { attributes: true });
+      let p = promiseContentSearchChange(engine.name);
+      Services.search.defaultEngine = engine;
+      yield p;
 
-      // Change the search engine, triggering the observer above.
-      Services.search.defaultEngine = engine;
+      searchText.value = needle;
+      searchText.focus();
+      EventUtils.synthesizeKey("VK_RETURN", {});
 
       registerCleanupFunction(function() {
-        mutationObserver.disconnect();
         Services.search.removeEngine(engine);
         Services.search.defaultEngine = currEngine;
       });
 
 
       // 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();
       });
-    };
+    });
     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",
                               Ci.nsISearchEngine.DATA_XML, null, false);
     return deferred.promise;
   }
@@ -330,31 +313,30 @@ let gTests = [
     });
 
     browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
     return deferred.promise;
   }
 },
 
 {
-  // See browser_searchSuggestionUI.js for comprehensive content search
-  // suggestion UI tests.
+  // 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 promise = promiseBrowserAttributes(gBrowser.selectedTab);
+      let p = promiseContentSearchChange(engine.name);
       Services.search.currentEngine = engine;
-      yield promise;
+      yield p;
 
       // Avoid intermittent failures.
-      gBrowser.contentWindow.wrappedJSObject.gSearchSuggestionController.remoteTimeout = 5000;
+      gBrowser.contentWindow.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
 
       // Type an X in the search input.
       let input = gBrowser.contentDocument.getElementById("searchText");
       input.focus();
       EventUtils.synthesizeKey("x", {});
 
       // Wait for the search suggestions to become visible.
       let table =
@@ -396,19 +378,21 @@ let gTests = [
           string: "x",
           clauses: [
             { length: 1, attr: EventUtils.COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         caret: { start: 1, length: 0 }
       }, gBrowser.contentWindow);
 
+      let searchController =
+        gBrowser.contentWindow.wrappedJSObject.gContentSearchController;
+
       // Wait for the search suggestions to become visible.
-      let table =
-        gBrowser.contentDocument.getElementById("searchSuggestionTable");
+      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();
         }
       });
@@ -419,19 +403,24 @@ let gTests = [
       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];
-      EventUtils.sendMouseEvent({ type: "mousedown" }, row, gBrowser.contentWindow);
+      // 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 == "xbar", "Suggestion is selected");
+      ok(input.value == "x", "Input value did not change");
     });
   }
 },
 {
   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");
@@ -472,36 +461,16 @@ let gTests = [
   run: Task.async(function* () {
     let syncButton = gBrowser.selectedBrowser.contentDocument.getElementById("sync");
     yield EventUtils.synthesizeMouseAtCenter(syncButton, {}, gBrowser.contentWindow);
 
     yield promiseTabLoadEvent(gBrowser.selectedTab, null, "load");
     is(gBrowser.currentURI.spec, "about:accounts?entrypoint=abouthome",
       "Entry point should be `abouthome`.");
   })
-},
-{
-  desc: "Clicking the icon should open the popup",
-  setup: function () {},
-  run: Task.async(function* () {
-    let doc = gBrowser.selectedBrowser.contentDocument;
-    let searchIcon = doc.getElementById("searchIcon");
-    let panel = window.document.getElementById("abouthome-search-panel");
-
-    info("Waiting for popup to open");
-    EventUtils.synthesizeMouseAtCenter(searchIcon, {}, gBrowser.selectedBrowser.contentWindow);
-    yield promiseWaitForEvent(panel, "popupshown");
-    info("Saw popup open");
-
-    let promise = promisePrefsOpen();
-    let item = window.document.getElementById("abouthome-search-panel-manage");
-    EventUtils.synthesizeMouseAtCenter(item, {});
-
-    yield promise;
-  })
 }
 
 ];
 
 function test()
 {
   waitForExplicitFinish();
   requestLongerTimeout(2);
@@ -572,48 +541,16 @@ function promiseSetupSnippetsMap(aTab, a
       aSetupFn(aSnippetsMap);
       deferred.resolve(aSnippetsMap);
     });
   }, true, true);
   return deferred.promise;
 }
 
 /**
- * Waits for the attributes being set by browser.js.
- *
- * @param aTab
- *        The tab containing about:home.
- * @return {Promise} resolved when the attributes are ready.
- */
-function promiseBrowserAttributes(aTab)
-{
-  let deferred = Promise.defer();
-
-  let docElt = aTab.linkedBrowser.contentDocument.documentElement;
-  let observer = new MutationObserver(function (mutations) {
-    for (let mutation of mutations) {
-      info("Got attribute mutation: " + mutation.attributeName +
-                                    " from " + mutation.oldValue);
-      // Now we just have to wait for the last attribute.
-      if (mutation.attributeName == "searchEngineName") {
-        info("Remove attributes observer");
-        observer.disconnect();
-        // Must be sure to continue after the page mutation observer.
-        executeSoon(function() deferred.resolve());
-        break;
-      }
-    }
-  });
-  info("Add attributes observer");
-  observer.observe(docElt, { attributes: true });
-
-  return deferred.promise;
-}
-
-/**
  * Retrieves the number of about:home searches recorded for the current day.
  *
  * @param aEngineName
  *        name of the setup search engine.
  *
  * @return {Promise} Returns a promise resolving to the number of searches.
  */
 function getNumberOfSearches(aEngineName) {
@@ -706,16 +643,28 @@ let promisePrefsOpen = Task.async(functi
             });
           });
         }
       });
     });
   }
 });
 
+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 promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
   Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
       registerCleanupFunction(() => {
rename from browser/base/content/test/general/browser_searchSuggestionUI.js
rename to browser/base/content/test/general/browser_contentSearchUI.js
--- a/browser/base/content/test/general/browser_searchSuggestionUI.js
+++ b/browser/base/content/test/general/browser_contentSearchUI.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TEST_PAGE_BASENAME = "searchSuggestionUI.html";
-const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js";
+const TEST_PAGE_BASENAME = "contentSearchUI.html";
+const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
+const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
+const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
 
-const TEST_MSG = "SearchSuggestionUIControllerTest";
+const TEST_MSG = "ContentSearchUIControllerTest";
 
 add_task(function* emptyInput() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   state = yield msg("key", "VK_BACK_SPACE");
@@ -26,233 +28,373 @@ add_task(function* blur() {
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   state = yield msg("blur");
   checkState(state, "x", [], -1);
 
   yield msg("reset");
 });
 
-add_task(function* arrowKeys() {
+add_task(function* upDownKeys() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   // Cycle down the suggestions starting from no selection.
   state = yield msg("key", "VK_DOWN");
   checkState(state, "xfoo", ["xfoo", "xbar"], 0);
 
   state = yield msg("key", "VK_DOWN");
   checkState(state, "xbar", ["xfoo", "xbar"], 1);
 
   state = yield msg("key", "VK_DOWN");
+  checkState(state, "x", ["xfoo", "xbar"], 2);
+
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "x", ["xfoo", "xbar"], 3);
+
+  state = yield msg("key", "VK_DOWN");
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   // Cycle up starting from no selection.
   state = yield msg("key", "VK_UP");
+  checkState(state, "x", ["xfoo", "xbar"], 3);
+
+  state = yield msg("key", "VK_UP");
+  checkState(state, "x", ["xfoo", "xbar"], 2);
+
+  state = yield msg("key", "VK_UP");
   checkState(state, "xbar", ["xfoo", "xbar"], 1);
 
   state = yield msg("key", "VK_UP");
   checkState(state, "xfoo", ["xfoo", "xbar"], 0);
 
   state = yield msg("key", "VK_UP");
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   yield msg("reset");
 });
 
-// The right arrow and return key function the same.
-function rightArrowOrReturn(keyName) {
-  return function* rightArrowOrReturnTest() {
-    yield setUp();
+add_task(function* rightLeftKeys() {
+  yield setUp();
+
+  let state = yield msg("key", { key: "x", waitForSuggestions: true });
+  checkState(state, "x", ["xfoo", "xbar"], -1);
 
-    let state = yield msg("key", { key: "x", waitForSuggestions: true });
-    checkState(state, "x", ["xfoo", "xbar"], -1);
+  state = yield msg("key", "VK_LEFT");
+  checkState(state, "x", ["xfoo", "xbar"], -1);
+
+  state = yield msg("key", "VK_LEFT");
+  checkState(state, "x", ["xfoo", "xbar"], -1);
 
-    state = yield msg("key", "VK_DOWN");
-    checkState(state, "xfoo", ["xfoo", "xbar"], 0);
+  state = yield msg("key", "VK_RIGHT");
+  checkState(state, "x", ["xfoo", "xbar"], -1);
+
+  state = yield msg("key", "VK_RIGHT");
+  checkState(state, "x", [], -1);
 
-    // This should make the xfoo suggestion sticky.  To make sure it sticks,
-    // trigger suggestions again and cycle through them by pressing Down until
-    // nothing is selected again.
-    state = yield msg("key", keyName);
-    checkState(state, "xfoo", [], -1);
+  state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+  checkState(state, "x", ["xfoo", "xbar"], -1);
+
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoo", ["xfoo", "xbar"], 0);
 
-    state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-    checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+  // This should make the xfoo suggestion sticky.  To make sure it sticks,
+  // trigger suggestions again and cycle through them by pressing Down until
+  // nothing is selected again.
+  state = yield msg("key", "VK_RIGHT");
+  checkState(state, "xfoo", [], 0);
 
-    state = yield msg("key", "VK_DOWN");
-    checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
+  state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
 
-    state = yield msg("key", "VK_DOWN");
-    checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
 
-    state = yield msg("key", "VK_DOWN");
-    checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoobar", ["xfoofoo", "xfoobar"], 1);
+
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 2);
 
-    yield msg("reset");
-  };
-}
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
 
-add_task(rightArrowOrReturn("VK_RIGHT"));
-add_task(rightArrowOrReturn("VK_RETURN"));
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+
+  yield msg("reset");
+});
 
 add_task(function* mouse() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
-  // Mouse over the first suggestion.
-  state = yield msg("mousemove", 0);
-  checkState(state, "x", ["xfoo", "xbar"], 0);
-
-  // Mouse over the second suggestion.
-  state = yield msg("mousemove", 1);
-  checkState(state, "x", ["xfoo", "xbar"], 1);
+  for (let i = 0; i < 4; ++i) {
+    state = yield msg("mousemove", i);
+    checkState(state, "x", ["xfoo", "xbar"], i);
+  }
 
-  // Click the second suggestion.  This should make it sticky.  To make sure it
-  // sticks, trigger suggestions again and cycle through them by pressing Down
-  // until nothing is selected again.
-  state = yield msg("mousedown", 1);
-  checkState(state, "xbar", [], -1);
-
-  state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-  checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
+  state = yield msg("mousemove", -1);
+  checkState(state, "x", ["xfoo", "xbar"], -1);
 
   yield msg("reset");
 });
 
 add_task(function* formHistory() {
   yield setUp();
 
   // Type an X and add it to form history.
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
-  yield msg("addInputValueToFormHistory");
-
   // Wait for Satchel to say it's been added to form history.
   let deferred = Promise.defer();
   Services.obs.addObserver(function onAdd(subj, topic, data) {
     if (data == "formhistory-add") {
       Services.obs.removeObserver(onAdd, "satchel-storage-changed");
       executeSoon(() => deferred.resolve());
     }
   }, "satchel-storage-changed", false);
-  yield deferred.promise;
+  yield Promise.all([msg("addInputValueToFormHistory"), deferred.promise]);
 
   // Reset the input.
   state = yield msg("reset");
   checkState(state, "", [], -1);
 
   // Type an X again.  The form history entry should appear.
   state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
              -1);
 
   // Select the form history entry and delete it.
   state = yield msg("key", "VK_DOWN");
   checkState(state, "x", [{ str: "x", type: "formHistory" }, "xfoo", "xbar"],
              0);
 
-  state = yield msg("key", "VK_DELETE");
-  checkState(state, "x", ["xfoo", "xbar"], -1);
-
   // Wait for Satchel.
   deferred = Promise.defer();
   Services.obs.addObserver(function onRemove(subj, topic, data) {
     if (data == "formhistory-remove") {
       Services.obs.removeObserver(onRemove, "satchel-storage-changed");
       executeSoon(() => deferred.resolve());
     }
   }, "satchel-storage-changed", false);
+
+  state = yield msg("key", "VK_DELETE");
+  checkState(state, "x", ["xfoo", "xbar"], -1);
+
   yield deferred.promise;
 
   // Reset the input.
   state = yield msg("reset");
   checkState(state, "", [], -1);
 
   // Type an X again.  The form history entry should still be gone.
   state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   yield msg("reset");
 });
 
-add_task(function* composition() {
+add_task(function* search() {
+  yield setUp();
+
+  let modifiers = {};
+  ["altKey", "ctrlKey", "metaKey", "shiftKey"].forEach(k => modifiers[k] = true);
+
+  // Test typing a query and pressing enter.
+  let p = msg("waitForSearch");
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+  let mesg = yield p;
+  let eventData = {
+    engineName: TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME,
+    searchString: "x",
+    healthReportKey: "test",
+    searchPurpose: "test",
+    originalEvent: modifiers,
+  };
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  // Test typing a query, then selecting a suggestion and pressing enter.
+  p = msg("waitForSearch");
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  yield msg("key", "VK_DOWN");
+  yield msg("key", "VK_DOWN");
+  yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+  mesg = yield p;
+  eventData.searchString = "xfoo";
+  eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+  eventData.selection = {
+    index: 1,
+    kind: "key",
+  }
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
   yield setUp();
 
+  // Test typing a query, then selecting a one-off button and pressing enter.
+  p = msg("waitForSearch");
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  yield msg("key", "VK_UP");
+  yield msg("key", "VK_UP");
+  yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+  mesg = yield p;
+  delete eventData.selection;
+  eventData.searchString = "x";
+  eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  // Test typing a query and clicking the search engine header.
+  p = msg("waitForSearch");
+  modifiers.button = 0;
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  yield msg("mousemove", -1);
+  yield msg("click", { eltIdx: -1, modifiers: modifiers });
+  mesg = yield p;
+  eventData.originalEvent = modifiers;
+  eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  // Test typing a query and then clicking a suggestion.
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  p = msg("waitForSearch");
+  yield msg("mousemove", 1);
+  yield msg("click", { eltIdx: 1, modifiers: modifiers });
+  mesg = yield p;
+  eventData.searchString = "xfoo";
+  eventData.selection = {
+    index: 1,
+    kind: "mouse",
+  };
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  // Test typing a query and then clicking a one-off button.
+  yield msg("key", { key: "x", waitForSuggestions: true });
+  p = msg("waitForSearch");
+  yield msg("mousemove", 3);
+  yield msg("click", { eltIdx: 3, modifiers: modifiers });
+  mesg = yield p;
+  eventData.searchString = "x";
+  eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_2_BASENAME;
+  delete eventData.selection;
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  // Test searching when using IME composition.
   let state = yield msg("startComposition", { data: "" });
   checkState(state, "", [], -1);
   state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
-  checkState(state, "x", ["xfoo", "xbar"], -1);
+  checkState(state, "x", [{ str: "x", type: "formHistory" },
+                          { str: "xfoo", type: "formHistory" }, "xbar"], -1);
+  yield msg("commitComposition");
+  delete modifiers.button;
+  p = msg("waitForSearch");
+  yield msg("key", { key: "VK_RETURN", modifiers: modifiers });
+  mesg = yield p;
+  eventData.originalEvent = modifiers;
+  eventData.engineName = TEST_ENGINE_PREFIX + " " + TEST_ENGINE_BASENAME;
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
+
+  yield promiseTab();
+  yield setUp();
+
+  state = yield msg("startComposition", { data: "" });
+  checkState(state, "", [], -1);
+  state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
+  checkState(state, "x", [{ str: "x", type: "formHistory" },
+                          { str: "xfoo", type: "formHistory" }, "xbar"], -1);
 
   // Mouse over the first suggestion.
   state = yield msg("mousemove", 0);
-  checkState(state, "x", ["xfoo", "xbar"], 0);
+  checkState(state, "x", [{ str: "x", type: "formHistory" },
+                          { str: "xfoo", type: "formHistory" }, "xbar"], 0);
 
   // Mouse over the second suggestion.
   state = yield msg("mousemove", 1);
-  checkState(state, "x", ["xfoo", "xbar"], 1);
-
-  // Click the second suggestion.  This should make it sticky.  To make sure it
-  // sticks, trigger suggestions again and cycle through them by pressing Down
-  // until nothing is selected again.
-  state = yield msg("mousedown", 1);
-
-  checkState(state, "xbar", [], -1);
+  checkState(state, "x", [{ str: "x", type: "formHistory" },
+                          { str: "xfoo", type: "formHistory" }, "xbar"], 1);
 
-  state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-  checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbarfoo", ["xbarfoo", "xbarbar"], 0);
+  modifiers.button = 0;
+  let currentTab = gBrowser.selectedTab;
+  p = msg("waitForSearch");
+  yield msg("click", { eltIdx: 1, modifiers: modifiers });
+  mesg = yield p;
+  eventData.searchString = "xfoo";
+  eventData.originalEvent = modifiers;
+  eventData.selection = {
+    index: 1,
+    kind: "mouse",
+  };
+  SimpleTest.isDeeply(eventData, mesg, "Search event data");
 
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbarbar", ["xbarfoo", "xbarbar"], 1);
-
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
-
+  yield promiseTab();
+  yield setUp();
   yield msg("reset");
 });
 
+add_task(function* settings() {
+  yield setUp();
+  yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
+  yield msg("key", "VK_UP");
+  let p = msg("waitForSearchSettings");
+  yield msg("key", "VK_RETURN");
+  yield p;
+
+  yield msg("reset");
+})
 
 let gDidInitialSetUp = false;
 
-function setUp() {
+function setUp(aNoEngine) {
   return Task.spawn(function* () {
     if (!gDidInitialSetUp) {
-      yield promiseNewEngine(TEST_ENGINE_BASENAME);
+      yield setUpEngines();
       yield promiseTab();
+      registerCleanupFunction(() => {
+        for (let tab of Array.prototype.slice.call(gBrowser.tabs, 1)) {
+          gBrowser.removeTab(tab);
+        }
+      })
       gDidInitialSetUp = true;
     }
     yield msg("focus");
   });
 }
 
 function msg(type, data=null) {
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: type,
     data: data,
   });
   let deferred = Promise.defer();
   gMsgMan.addMessageListener(TEST_MSG, function onMsg(msg) {
+    if (msg.data.type != type) {
+      return;
+    }
     gMsgMan.removeMessageListener(TEST_MSG, onMsg);
-    deferred.resolve(msg.data);
+    deferred.resolve(msg.data.data);
   });
   return deferred.promise;
 }
 
 function checkState(actualState, expectedInputVal, expectedSuggestions,
                     expectedSelectedIdx) {
   expectedSuggestions = expectedSuggestions.map(sugg => {
     return typeof(sugg) == "object" ? sugg : {
@@ -264,54 +406,42 @@ function checkState(actualState, expecte
   let expectedState = {
     selectedIndex: expectedSelectedIdx,
     numSuggestions: expectedSuggestions.length,
     suggestionAtIndex: expectedSuggestions.map(s => s.str),
     isFormHistorySuggestionAtIndex:
       expectedSuggestions.map(s => s.type == "formHistory"),
 
     tableHidden: expectedSuggestions.length == 0,
-    tableChildrenLength: expectedSuggestions.length,
-    tableChildren: expectedSuggestions.map((s, i) => {
-      let expectedClasses = new Set([s.type]);
-      if (i == expectedSelectedIdx) {
-        expectedClasses.add("selected");
-      }
-      return {
-        textContent: s.str,
-        classes: expectedClasses,
-      };
-    }),
 
     inputValue: expectedInputVal,
     ariaExpanded: expectedSuggestions.length == 0 ? "false" : "true",
   };
 
   SimpleTest.isDeeply(actualState, expectedState, "State");
 }
 
 var gMsgMan;
 
 function promiseTab() {
   let deferred = Promise.defer();
   let tab = gBrowser.addTab();
-  registerCleanupFunction(() => gBrowser.removeTab(tab));
   gBrowser.selectedTab = tab;
   let pageURL = getRootDirectory(gTestPath) + TEST_PAGE_BASENAME;
   tab.linkedBrowser.addEventListener("load", function onLoad(event) {
     tab.linkedBrowser.removeEventListener("load", onLoad, true);
     gMsgMan = tab.linkedBrowser.messageManager;
     gMsgMan.sendAsyncMessage("ContentSearch", {
       type: "AddToWhitelist",
       data: [pageURL],
     });
     promiseMsg("ContentSearch", "AddToWhitelistAck", gMsgMan).then(() => {
       let jsURL = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
       gMsgMan.loadFrameScript(jsURL, false);
-      deferred.resolve();
+      deferred.resolve(msg("init"));
     });
   }, true, true);
   openUILinkIn(pageURL, "current");
   return deferred.promise;
 }
 
 function promiseMsg(name, type, msgMan) {
   let deferred = Promise.defer();
@@ -321,24 +451,44 @@ function promiseMsg(name, type, msgMan) 
     if (msg.data.type == type) {
       msgMan.removeMessageListener(name, onMsg);
       deferred.resolve(msg);
     }
   });
   return deferred.promise;
 }
 
+function setUpEngines() {
+  return Task.spawn(function* () {
+    info("Removing default search engines");
+    let currentEngineName = Services.search.currentEngine.name;
+    let currentEngines = Services.search.getVisibleEngines();
+    info("Adding test search engines");
+    let engine1 = yield promiseNewEngine(TEST_ENGINE_BASENAME);
+    let engine2 = yield promiseNewEngine(TEST_ENGINE_2_BASENAME);
+    Services.search.currentEngine = engine1;
+    for (let engine of currentEngines) {
+      Services.search.removeEngine(engine);
+    }
+    registerCleanupFunction(() => {
+      Services.search.restoreDefaultEngines();
+      Services.search.removeEngine(engine1);
+      Services.search.removeEngine(engine2);
+      Services.search.currentEngine = Services.search.getEngineByName(currentEngineName);
+    });
+  });
+}
+
 function promiseNewEngine(basename) {
   info("Waiting for engine to be added: " + basename);
   let addDeferred = Promise.defer();
   let url = getRootDirectory(gTestPath) + basename;
   Services.search.addEngine(url, Ci.nsISearchEngine.TYPE_MOZSEARCH, "", false, {
     onSuccess: function (engine) {
       info("Search engine added: " + basename);
-      registerCleanupFunction(() => Services.search.removeEngine(engine));
       addDeferred.resolve(engine);
     },
     onError: function (errCode) {
       ok(false, "addEngine failed with error code " + errCode);
       addDeferred.reject();
     },
   });
   return addDeferred.promise;
rename from browser/base/content/test/general/searchSuggestionUI.html
rename to browser/base/content/test/general/contentSearchUI.html
--- a/browser/base/content/test/general/searchSuggestionUI.html
+++ b/browser/base/content/test/general/contentSearchUI.html
@@ -4,17 +4,18 @@
 
 <html>
 <head>
 <meta charset="utf-8">
 <script type="application/javascript;version=1.8"
         src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js">
 </script>
 <script type="application/javascript;version=1.8"
-        src="chrome://browser/content/searchSuggestionUI.js">
+        src="chrome://browser/content/contentSearchUI.js">
 </script>
+<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/>
 </head>
 <body>
 
-<input>
+<div id="container" style="position: relative;"><input type="text" value=""/></div>
 
 </body>
 </html>
rename from browser/base/content/test/general/searchSuggestionUI.js
rename to browser/base/content/test/general/contentSearchUI.js
--- a/browser/base/content/test/general/searchSuggestionUI.js
+++ b/browser/base/content/test/general/contentSearchUI.js
@@ -1,71 +1,104 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 (function () {
 
-const TEST_MSG = "SearchSuggestionUIControllerTest";
+const TEST_MSG = "ContentSearchUIControllerTest";
 const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
-
-let input = content.document.querySelector("input");
-let gController =
-  new content.SearchSuggestionUIController(input, input.parentNode);
-gController.engineName = ENGINE_NAME;
-gController.remoteTimeout = 5000;
+var gController;
 
 addMessageListener(TEST_MSG, msg => {
   messageHandlers[msg.data.type](msg.data.data);
 });
 
 let messageHandlers = {
 
+  init: function() {
+    Services.search.currentEngine = Services.search.getEngineByName(ENGINE_NAME);
+    let input = content.document.querySelector("input");
+    gController =
+      new content.ContentSearchUIController(input, input.parentNode, "test", "test");
+    content.addEventListener("ContentSearchService", function listener(aEvent) {
+      if (aEvent.detail.type == "State" &&
+          gController.defaultEngine.name == ENGINE_NAME) {
+        content.removeEventListener("ContentSearchService", listener);
+        ack("init");
+      }
+    });
+    gController.remoteTimeout = 5000;
+  },
+
   key: function (arg) {
     let keyName = typeof(arg) == "string" ? arg : arg.key;
-    content.synthesizeKey(keyName, {});
+    content.synthesizeKey(keyName, arg.modifiers || {});
     let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
-    wait(ack);
+    wait(ack.bind(null, "key"));
   },
 
   startComposition: function (arg) {
     content.synthesizeComposition({ type: "compositionstart", data: "" });
-    ack();
+    ack("startComposition");
   },
 
   changeComposition: function (arg) {
     let data = typeof(arg) == "string" ? arg : arg.data;
     content.synthesizeCompositionChange({
       composition: {
         string: data,
         clauses: [
           { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
       caret: { start: data.length, length: 0 }
     });
     let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
-    wait(ack);
+    wait(ack.bind(null, "changeComposition"));
+  },
+
+  commitComposition: function () {
+    content.synthesizeComposition({ type: "compositioncommitasis" });
+    ack("commitComposition");
   },
 
   focus: function () {
     gController.input.focus();
-    ack();
+    ack("focus");
   },
 
   blur: function () {
     gController.input.blur();
-    ack();
+    ack("blur");
+  },
+
+  waitForSearch: function () {
+    waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
+  },
+
+  waitForSearchSettings: function () {
+    waitForContentSearchEvent("ManageEngines",
+                              aData => ack("waitForSearchSettings", aData));
   },
 
-  mousemove: function (suggestionIdx) {
+  mousemove: function (itemIndex) {
+    let row;
+    if (itemIndex == -1) {
+      row = gController._table.firstChild;
+    }
+    else {
+      let allElts = [...gController._suggestionsList.children,
+                     ...gController._oneOffButtons,
+                     content.document.getElementById("contentSearchSettingsButton")];
+      row = allElts[itemIndex];
+    }
     // Copied from widget/tests/test_panel_mouse_coords.xul and
     // browser/base/content/test/newtab/head.js
-    let row = gController._table.children[suggestionIdx];
     let rect = row.getBoundingClientRect();
     let left = content.mozInnerScreenX + rect.left;
     let x = left + rect.width / 2;
     let y = content.mozInnerScreenY + rect.top + rect.height / 2;
 
     let utils = content.SpecialPowers.getDOMWindowUtils(content);
     let scale = utils.screenPixelsPerCSSPixel;
 
@@ -74,85 +107,98 @@ let messageHandlers = {
                         getService(content.SpecialPowers.Ci.nsIXULRuntime).
                         widgetToolkit;
     let nativeMsg = widgetToolkit == "cocoa" ? 5 : // NSMouseMoved
                     widgetToolkit == "windows" ? 1 : // MOUSEEVENTF_MOVE
                     3; // GDK_MOTION_NOTIFY
 
     row.addEventListener("mousemove", function onMove() {
       row.removeEventListener("mousemove", onMove);
-      ack();
+      ack("mousemove");
     });
     utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null);
   },
 
-  mousedown: function (suggestionIdx) {
-    gController.onClick = () => {
-      gController.onClick = null;
-      ack();
-    };
-    let row = gController._table.children[suggestionIdx];
-    content.sendMouseEvent({ type: "mousedown" }, row);
+  click: function (arg) {
+    let eltIdx = typeof(arg) == "object" ? arg.eltIdx : arg;
+    let row;
+    if (eltIdx == -1) {
+      row = gController._table.firstChild;
+    }
+    else {
+      let allElts = [...gController._suggestionsList.children,
+                     ...gController._oneOffButtons,
+                     content.document.getElementById("contentSearchSettingsButton")];
+      row = allElts[eltIdx];
+    }
+    let event = arg.modifiers || {};
+    content.synthesizeMouse(row, 1, 1, event);
+    ack("click");
   },
 
   addInputValueToFormHistory: function () {
     gController.addInputValueToFormHistory();
-    ack();
+    ack("addInputValueToFormHistory");
   },
 
   reset: function () {
     // Reset both the input and suggestions by select all + delete.
     gController.input.focus();
     content.synthesizeKey("a", { accelKey: true });
     content.synthesizeKey("VK_DELETE", {});
-    ack();
+    ack("reset");
   },
 };
 
-function ack() {
-  sendAsyncMessage(TEST_MSG, currentState());
+function ack(aType, aData) {
+  sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
 }
 
 function waitForSuggestions(cb) {
   let observer = new content.MutationObserver(() => {
     if (gController.input.getAttribute("aria-expanded") == "true") {
       observer.disconnect();
       cb();
     }
   });
   observer.observe(gController.input, {
     attributes: true,
     attributeFilter: ["aria-expanded"],
   });
 }
 
+function waitForContentSearchEvent(messageType, cb) {
+  let mm = content.SpecialPowers.Cc["@mozilla.org/globalmessagemanager;1"].
+    getService(content.SpecialPowers.Ci.nsIMessageListenerManager);
+  mm.addMessageListener("ContentSearch", function listener(aMsg) {
+    if (aMsg.data.type != messageType) {
+      return;
+    }
+    mm.removeMessageListener("ContentSearch", listener);
+    cb(aMsg.data.data);
+  });
+}
+
 function currentState() {
   let state = {
     selectedIndex: gController.selectedIndex,
-    numSuggestions: gController.numSuggestions,
+    numSuggestions: gController._table.hidden ? 0 : gController.numSuggestions,
     suggestionAtIndex: [],
     isFormHistorySuggestionAtIndex: [],
 
     tableHidden: gController._table.hidden,
-    tableChildrenLength: gController._table.children.length,
-    tableChildren: [],
 
     inputValue: gController.input.value,
     ariaExpanded: gController.input.getAttribute("aria-expanded"),
   };
 
-  for (let i = 0; i < gController.numSuggestions; i++) {
-    state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
-    state.isFormHistorySuggestionAtIndex.push(
-      gController.isFormHistorySuggestionAtIndex(i));
-  }
-
-  for (let child of gController._table.children) {
-    state.tableChildren.push({
-      textContent: child.textContent,
-      classes: new Set(child.className.split(/\s+/)),
-    });
+  if (state.numSuggestions) {
+    for (let i = 0; i < gController.numSuggestions; i++) {
+      state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
+      state.isFormHistorySuggestionAtIndex.push(
+        gController.isFormHistorySuggestionAtIndex(i));
+    }
   }
 
   return state;
 }
 
 })();
--- a/browser/base/content/test/general/searchSuggestionEngine.xml
+++ b/browser/base/content/test/general/searchSuggestionEngine.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
 <Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
-<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine" rel="searchform"/>
+<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine&amp;terms={searchTerms}" rel="searchform"/>
 </SearchPlugin>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/searchSuggestionEngine2.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>browser_searchSuggestionEngine searchSuggestionEngine2.xml</ShortName>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine2&amp;terms={searchTerms}" rel="searchform"/>
+</SearchPlugin>
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -71,23 +71,16 @@ let runTaskifiedTests = Task.async(funct
 
   yield addNewTabPageTabPromise();
 
   // The tab is removed at the end of the test, so there's no need to remove
   // this listener at the end of the test.
   info("Adding search event listener");
   getContentWindow().addEventListener(SERVICE_EVENT_NAME, searchEventListener);
 
-  let panel = searchPanel();
-  is(panel.state, "closed", "Search panel should be closed initially");
-
-  // The panel's animation often is not finished when the test clicks on panel
-  // children, which makes the test click the wrong children, so disable it.
-  panel.setAttribute("animate", "false");
-
   // Add the engine without any logos and switch to it.
   let noLogoEngine = yield promiseNewSearchEngine(ENGINE_NO_LOGO);
   Services.search.currentEngine = noLogoEngine;
   yield promiseSearchEvents(["CurrentEngine"]);
   yield checkCurrentEngine(ENGINE_NO_LOGO);
 
   // Add the engine with favicon and switch to it.
   let faviconEngine = yield promiseNewSearchEngine(ENGINE_FAVICON);
@@ -108,37 +101,24 @@ let runTaskifiedTests = Task.async(funct
   yield checkCurrentEngine(ENGINE_2X_LOGO);
 
   // Add the engine with 1x- and 2x-DPI logos and switch to it.
   let logo1x2xEngine = yield promiseNewSearchEngine(ENGINE_1X_2X_LOGO);
   Services.search.currentEngine = logo1x2xEngine;
   yield promiseSearchEvents(["CurrentEngine"]);
   yield checkCurrentEngine(ENGINE_1X_2X_LOGO);
 
-  // Click the logo to open the search panel.
-  yield Promise.all([
-    promisePanelShown(panel),
-    promiseClick(logoImg()),
-  ]);
-
-  let manageBox = $("manage");
-  ok(!!manageBox, "The Manage Engines box should be present in the document");
-  is(panel.childNodes.length, 1, "Search panel should only contain the Manage Engines entry");
-  is(panel.childNodes[0], manageBox, "Search panel should contain the Manage Engines entry");
-
-  panel.hidePopup();
-
   // Add the engine that provides search suggestions and switch to it.
   let suggestionEngine = yield promiseNewSearchEngine(ENGINE_SUGGESTIONS);
   Services.search.currentEngine = suggestionEngine;
   yield promiseSearchEvents(["CurrentEngine"]);
   yield checkCurrentEngine(ENGINE_SUGGESTIONS);
 
   // Avoid intermittent failures.
-  gSearch()._suggestionController.remoteTimeout = 5000;
+  gSearch().remoteTimeout = 5000;
 
   // Type an X in the search input.  This is only a smoke test.  See
   // browser_searchSuggestionUI.js for comprehensive content search suggestion
   // UI tests.
   let input = $("text");
   input.focus();
   EventUtils.synthesizeKey("x", {});
   let suggestionsPromise = promiseSearchEvents(["Suggestions"]);
@@ -304,51 +284,36 @@ function blobToBase64(blob) {
 
 let checkCurrentEngine = Task.async(function* ({name: basename, logoPrefix1x, logoPrefix2x}) {
   let engine = Services.search.currentEngine;
   ok(engine.name.includes(basename),
      "Sanity check: current engine: engine.name=" + engine.name +
      " basename=" + basename);
 
   // gSearch.currentEngineName
-  is(gSearch().currentEngineName, engine.name,
+  is(gSearch().defaultEngine.name, engine.name,
      "currentEngineName: " + engine.name);
 });
 
-function promisePanelShown(panel) {
-  let deferred = Promise.defer();
-  info("Waiting for popupshown");
-  panel.addEventListener("popupshown", function onEvent() {
-    panel.removeEventListener("popupshown", onEvent);
-    is(panel.state, "open", "Panel state");
-    deferred.resolve();
-  });
-  return deferred.promise;
-}
-
 function promiseClick(node) {
   let deferred = Promise.defer();
   let win = getContentWindow();
   SimpleTest.waitForFocus(() => {
     EventUtils.synthesizeMouseAtCenter(node, {}, win);
     deferred.resolve();
   }, win);
   return deferred.promise;
 }
 
-function searchPanel() {
-  return $("panel");
-}
-
 function logoImg() {
   return $("logo");
 }
 
 function gSearch() {
-  return getContentWindow().gSearch;
+  return getContentWindow().gSearch._contentSearchController;
 }
 
 /**
  * Waits for a load (or custom) event to finish in a given tab. If provided
  * load an uri into the tab.
  *
  * @param tab
  *        The tab to load into.
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -717,24 +717,33 @@ function whenPagesUpdated(aCallback = Te
   });
 }
 
 /**
  * Waits for the response to the page's initial search state request.
  */
 function whenSearchInitDone() {
   let deferred = Promise.defer();
-  if (getContentWindow().gSearch._initialStateReceived) {
+  let searchController = getContentWindow().gSearch._contentSearchController;
+  if (searchController.defaultEngine) {
     return Promise.resolve();
   }
   let eventName = "ContentSearchService";
   getContentWindow().addEventListener(eventName, function onEvent(event) {
     if (event.detail.type == "State") {
       getContentWindow().removeEventListener(eventName, onEvent);
-      deferred.resolve();
+      // Wait for the search controller to receive the event, then resolve.
+      let resolver = function() {
+        if (searchController.defaultEngine) {
+          deferred.resolve();
+          return;
+        }
+        executeSoon(resolver);
+      }
+      executeSoon(resolver);
     }
   });
   return deferred.promise;
 }
 
 /**
  * Changes the newtab customization option and waits for the panel to open and close
  *