Backed out changesets 7f2ddcfe4537 and e88770aa2160 (bug 1171344) for intermittent OSX browser_tabopen_reflows.js failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 16 Jul 2015 21:42:22 -0400
changeset 253246 15155971639cfd143cc58ee8f205c3900a2b3828
parent 253245 403f2f4cd9783f19abf010c2bde33679aaabe00e
child 253247 689cd81e0f3cdf7fb735deddf6f1761f81c011d4
push id14073
push userryanvm@gmail.com
push dateFri, 17 Jul 2015 01:42:26 +0000
treeherderfx-team@15155971639c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1171344
milestone42.0a1
backs out7f2ddcfe4537c52265ce168b3ab182892e7dbcc0
e88770aa21600f3afdefd06dc643eb83e8da6b84
Backed out changesets 7f2ddcfe4537 and e88770aa2160 (bug 1171344) for intermittent OSX browser_tabopen_reflows.js failures.
CLOBBER
browser/base/content/abouthome/aboutHome.css
browser/base/content/abouthome/aboutHome.js
browser/base/content/abouthome/aboutHome.xhtml
browser/base/content/browser.js
browser/base/content/contentSearchUI.css
browser/base/content/contentSearchUI.js
browser/base/content/newtab/newTab.css
browser/base/content/newtab/newTab.xul
browser/base/content/newtab/search.js
browser/base/content/searchSuggestionUI.css
browser/base/content/searchSuggestionUI.js
browser/base/content/tab-content.js
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
browser/base/jar.mn
browser/components/loop/test/mochitest/browser_mozLoop_context.js
browser/locales/en-US/chrome/browser/aboutHome.dtd
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/search.properties
browser/modules/AboutHome.jsm
browser/modules/ContentSearch.jsm
browser/modules/DirectoryLinksProvider.jsm
browser/modules/test/browser_ContentSearch.js
browser/themes/linux/jar.mn
browser/themes/osx/jar.mn
browser/themes/shared/search/search-arrow-go.svg
browser/themes/shared/search/search-indicator-magnifying-glass.svg
browser/themes/windows/jar.mn
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1171344 requires a clobber due to renaming of a test file.
+Bug 1178850 requires clobber for Android JNI header changes
--- a/browser/base/content/abouthome/aboutHome.css
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -44,112 +44,130 @@ a {
   width: 192px;
   margin: 22px auto 31px;
   background-image: url("chrome://branding/content/about-logo.png");
   background-size: 192px auto;
   background-position: center center;
   background-repeat: no-repeat;
 }
 
-#searchIconAndTextContainer,
+#searchForm,
 #snippets {
   width: 470px;
 }
 
-#searchIconAndTextContainer {
+#searchForm {
+  display: -moz-box;
+}
+
+#searchLogoContainer {
   display: -moz-box;
-  height: 36px;
-  position: relative;
+  -moz-box-align: center;
+  padding-top: 2px;
+  -moz-padding-end: 8px;
+}
+
+#searchLogoContainer[hidden] {
+  display: none;
+}
+
+#searchEngineLogo {
+  display: inline-block;
+  height: 28px;
+  width: 70px;
+  min-width: 70px;
 }
 
 #searchIcon {
-  border: 1px transparent;
-  padding: 0;
-  margin: 0;
-  width: 36px;
-  height: 36px;
-  background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
-  position: absolute;
+  border: 1px solid transparent;
+  -moz-margin-end: 5px;
+  height: 38px;
+  width: 38px;
+  background: url("chrome://browser/skin/magnifier.png") center center no-repeat;
+  background-size: 26px;
+}
+
+#searchIcon[active],
+#searchIcon:hover {
+  background-color: #e9e9e9;
+  border-color: rgb(226, 227, 229);
+  border-radius: 2.5px;
+}
+
+html[searchUIConfiguration="oldsearchui"] #searchIcon {
+  display: none;
+}
+
+html:not([searchUIConfiguration="oldsearchui"]) #searchText::-moz-placeholder {
+  color: transparent;
+}
+
+html:not([searchUIConfiguration="oldsearchui"]) #searchLogoContainer {
+  display: none;
 }
 
 #searchText {
-  margin-left: 0;
   -moz-box-flex: 1;
-  padding-top: 6px;
-  padding-bottom: 6px;
-  padding-left: 34px;
-  padding-right: 8px;
+  padding: 6px 8px;
   background: hsla(0,0%,100%,.9) padding-box;
   border: 1px solid;
-  border-radius: 2px 0 0 2px;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
               0 0 2px hsla(210,65%,9%,.1) inset,
               0 1px 0 hsla(0,0%,100%,.2);
+  border-radius: 2.5px 0 0 2.5px;
   color: inherit;
 }
 
 #searchText:-moz-dir(rtl) {
-  border-radius: 0 2px 2px 0;
+  border-radius: 0 2.5px 2.5px 0;
 }
 
-#searchText[aria-expanded="true"] {
-  border-radius: 2px 0 0 0;
-}
-
-#searchText[aria-expanded="true"]:-moz-dir(rtl) {
-  border-radius: 0 2px 0 0;
-}
-
-#searchText[keepfocus],
 #searchText:focus,
 #searchText[autofocus] {
   border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
 }
 
 #searchSubmit {
   -moz-margin-start: -1px;
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
-  padding: 0;
+  background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+  padding: 0 9px;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
-  border-radius: 0 2px 2px 0;
   -moz-border-start: 1px solid transparent;
+  border-radius: 0 2.5px 2.5px 0;
   box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
               0 1px 0 hsla(0,0%,100%,.2);
   color: inherit;
   cursor: pointer;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
-  width: 50px;
 }
 
 #searchSubmit:-moz-dir(rtl) {
-  border-radius: 2px 0 0 2px;
+  border-radius: 2.5px 0 0 2.5px;
 }
 
 #searchText:focus + #searchSubmit,
-#searchText[keepfocus] + #searchSubmit,
 #searchText + #searchSubmit:hover,
 #searchText[autofocus] + #searchSubmit {
   border-color: #59b5fc #45a3e7 #3294d5;
   color: white;
 }
 
 #searchText:focus + #searchSubmit,
-#searchText[keepfocus] + #searchSubmit,
 #searchText[autofocus] + #searchSubmit {
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5);
+  background-image: linear-gradient(#4cb1ff, #1793e5);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
               0 0 0 1px hsla(0,0%,100%,.1) inset,
               0 1px 0 hsla(210,54%,20%,.03);
 }
 
 #searchText + #searchSubmit:hover {
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#66bdff, #0d9eff);
+  background-image: linear-gradient(#66bdff, #0d9eff);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
               0 0 0 1px hsla(0,0%,100%,.1) inset,
               0 1px 0 hsla(210,54%,20%,.03),
               0 0 4px hsla(206,100%,20%,.2);
 }
 
 #searchText + #searchSubmit:hover:active {
   box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
@@ -157,29 +175,29 @@ a {
   transition-duration: 0ms;
 }
 
 #defaultSnippet1,
 #defaultSnippet2,
 #rightsSnippet {
   display: block;
   min-height: 38px;
-  background: 0 center no-repeat;
+  background: 30px center no-repeat;
   padding: 6px 0;
-  -moz-padding-start: 49px;
+  -moz-padding-start: 79px;
 }
 
 #rightsSnippet[hidden] {
   display: none;
 }
 
 #defaultSnippet1:-moz-dir(rtl),
 #defaultSnippet2:-moz-dir(rtl),
 #rightsSnippet:-moz-dir(rtl) {
-  background-position: right 0 center;
+  background-position: right 30px center;
 }
 
 #defaultSnippet1 {
   background-image: url("chrome://browser/content/abouthome/snippet1.png");
 }
 
 #defaultSnippet2 {
   background-image: url("chrome://browser/content/abouthome/snippet2.png");
@@ -224,17 +242,17 @@ body[narrow] #launcher[session] {
   padding: 14px 6px;
   min-width: 88px;
   max-width: 176px;
   max-height: 85px;
   vertical-align: top;
   white-space: normal;
   background: transparent padding-box;
   border: 1px solid transparent;
-  border-radius: 2px;
+  border-radius: 2.5px;
   color: #525c66;
   font-size: 75%;
   cursor: pointer;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
 }
 
 body[narrow] #launcher[session] > .launchButton {
@@ -373,16 +391,20 @@ body[narrow] #restorePreviousSession::be
  * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
  * rather than upscaling the original-size ones (bug 818940).
  */
 @media not all and (max-resolution: 1dppx) {
   #brandLogo {
     background-image: url("chrome://branding/content/about-logo@2x.png");
   }
 
+  #searchIcon {
+    background-image: url("chrome://browser/skin/magnifier@2x.png");
+  }
+
   #defaultSnippet1,
   #defaultSnippet2,
   #rightsSnippet {
     background-size: 40px;
   }
 
   #defaultSnippet1 {
     background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -1,14 +1,146 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+const SEARCH_ENGINES = {
+  "Google": {
+    // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.;
+    // it will be scaled down as necessary on lower-dpi displays.
+    // This needs to be defined in a single line to keep the JS parser from creating many
+    // intermediate strings in memory.  See bug 986672.
+    image: "data:image/png;base64,\
+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"
+  }
+};
+
 // The process of adding a new default snippet involves:
 //   * add a new entity to aboutHome.dtd
 //   * add a <span/> for it in aboutHome.xhtml
 //   * add an entry here in the proper ordering (based on spans)
 // The <a/> part of the snippet will be linked to the corresponding url.
 const DEFAULT_SNIPPETS_URLS = [
   "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet"
 , "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons"
@@ -21,32 +153,32 @@ const DATABASE_NAME = "abouthome";
 const DATABASE_VERSION = 1;
 const DATABASE_STORAGE = "persistent";
 const SNIPPETS_OBJECTSTORE_NAME = "snippets";
 
 // This global tracks if the page has been set up before, to prevent double inits
 let gInitialized = false;
 let gObserver = new MutationObserver(function (mutations) {
   for (let mutation of mutations) {
-    if (mutation.attributeName == "snippetsVersion") {
+    if (mutation.attributeName == "searchEngineName") {
+      setupSearchEngine();
       if (!gInitialized) {
         ensureSnippetsMapThen(loadSnippets);
         gInitialized = true;
       }
       return;
     }
   }
 });
 
 window.addEventListener("pageshow", function () {
   // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
   // later and may use asynchronous getters.
   window.gObserver.observe(document.documentElement, { attributes: true });
   fitToWidth();
-  setupSearch();
   window.addEventListener("resize", fitToWidth);
 
   // Ask chrome to update snippets.
   var event = new CustomEvent("AboutHomeLoad", {bubbles:true});
   document.dispatchEvent(event);
 });
 
 window.addEventListener("pagehide", function() {
@@ -163,38 +295,94 @@ function ensureSnippetsMapThen(aCallback
 
       setTimeout(invokeCallbacks, 0);
     }
   }
 }
 
 function onSearchSubmit(aEvent)
 {
-  gContentSearchController.search(aEvent);
+  let searchText = document.getElementById("searchText");
+  let searchTerms = searchText.value;
+  let engineName = document.documentElement.getAttribute("searchEngineName");
+
+  if (engineName && searchTerms.length > 0) {
+    // Send an event that will perform a search and Firefox Health Report will
+    // record that a search from about:home has occurred.
+    let eventData = {
+      engineName: engineName,
+      searchTerms: searchTerms,
+      originalEvent: {
+        target: {
+          ownerDocument: null
+        },
+        shiftKey: aEvent.shiftKey,
+        ctrlKey: aEvent.ctrlKey,
+        metaKey: aEvent.metaKey,
+        altKey: aEvent.altKey,
+        button: aEvent.button,
+      },
+    };
+
+    if (searchText.hasAttribute("selection-index")) {
+      eventData.selection = {
+        index: searchText.getAttribute("selection-index"),
+        kind: searchText.getAttribute("selection-kind")
+      };
+    }
+
+    eventData = JSON.stringify(eventData);
+
+    let event = new CustomEvent("AboutHomeSearchEvent", {detail: eventData});
+    document.dispatchEvent(event);
+  }
+
+  gSearchSuggestionController.addInputValueToFormHistory();
+
+  if (aEvent) {
+    aEvent.preventDefault();
+  }
 }
 
 
-let gContentSearchController;
+let gSearchSuggestionController;
 
-function setupSearch()
+function setupSearchEngine()
 {
   // The "autofocus" attribute doesn't focus the form element
   // immediately when the element is first drawn, so the
   // attribute is also used for styling when the page first loads.
   let searchText = document.getElementById("searchText");
   searchText.addEventListener("blur", function searchText_onBlur() {
     searchText.removeEventListener("blur", searchText_onBlur);
     searchText.removeAttribute("autofocus");
   });
+ 
+  let searchEngineName = document.documentElement.getAttribute("searchEngineName");
+  let searchEngineInfo = SEARCH_ENGINES[searchEngineName];
+  let logoElt = document.getElementById("searchEngineLogo");
 
-  if (!gContentSearchController) {
-    gContentSearchController =
-      new ContentSearchUIController(searchText, searchText.parentNode,
-                                    "abouthome", "homepage");
+  // Add search engine logo.
+  if (searchEngineInfo && searchEngineInfo.image) {
+    logoElt.parentNode.hidden = false;
+    logoElt.src = searchEngineInfo.image;
+    logoElt.alt = searchEngineName;
+    searchText.placeholder = "";
   }
+  else {
+    logoElt.parentNode.hidden = true;
+    searchText.placeholder = searchEngineName;
+  }
+
+  if (!gSearchSuggestionController) {
+    gSearchSuggestionController =
+      new SearchSuggestionUIController(searchText, searchText.parentNode,
+                                       onSearchSubmit);
+  }
+  gSearchSuggestionController.engineName = searchEngineName;
 }
 
 /**
  * Inform the test harness that we're done loading the page.
  */
 function loadCompleted()
 {
   var event = new CustomEvent("AboutHomeLoadSnippetsCompleted", {bubbles:true});
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -19,37 +19,39 @@
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&abouthome.pageTitle;</title>
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://branding/content/icon32.png"/>
     <link rel="stylesheet" type="text/css" media="all"
-          href="chrome://browser/content/contentSearchUI.css"/>
+          href="chrome://browser/content/searchSuggestionUI.css"/>
     <link rel="stylesheet" type="text/css" media="all" defer="defer"
           href="chrome://browser/content/abouthome/aboutHome.css"/>
 
     <script type="text/javascript;version=1.8"
             src="chrome://browser/content/abouthome/aboutHome.js"/>
     <script type="text/javascript;version=1.8"
-            src="chrome://browser/content/contentSearchUI.js"/>
+            src="chrome://browser/content/searchSuggestionUI.js"/>
   </head>
 
   <body dir="&locale.dir;">
     <div class="spacer"/>
     <div id="topSection">
       <div id="brandLogo"></div>
 
-      <div id="searchIconAndTextContainer">
-        <div id="searchIcon"/>
-        <input type="text" name="q" value="" id="searchText" maxlength="256"
-               aria-label="&contentSearchInput.label;" autofocus="autofocus" dir="auto"/>
-        <input id="searchSubmit" type="button" value="" onclick="onSearchSubmit(event)"
-               aria-label="&contentSearchSubmit.label;"/>
+      <div id="searchContainer">
+        <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
+          <div id="searchLogoContainer" hidden="true"><img id="searchEngineLogo"/></div>
+          <button id="searchIcon" type="button" />
+          <input type="text" name="q" value="" id="searchText" maxlength="256"
+                 autofocus="autofocus" dir="auto"/>
+          <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/>
+        </form>
       </div>
 
       <div id="snippetContainer">
         <div id="defaultSnippets" hidden="true">
           <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
           <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
         </div>
         <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3462,18 +3462,19 @@ const BrowserSearch = {
       }
       return;
     }
 #endif
     let openSearchPageIfFieldIsNotActive = function(aSearchBar) {
       if (!aSearchBar || document.activeElement != aSearchBar.textbox.inputField) {
         let url = gBrowser.currentURI.spec.toLowerCase();
         let mm = gBrowser.selectedBrowser.messageManager;
-        if (url === "about:home" ||
-            (url === "about:newtab" && NewTabUtils.allPages.enabled)) {
+        if (url === "about:home") {
+          AboutHome.focusInput(mm);
+        } else if (url === "about:newtab" && NewTabUtils.allPages.enabled) {
           ContentSearch.focusInput(mm);
         } else {
           openUILinkIn("about:home", "current");
         }
       }
     };
 
     let searchBar = this.searchBar;
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -325,116 +325,144 @@ input[type=button] {
   background-color: #fff;
   opacity: 0.01;
 }
 
 /* SEARCH */
 #newtab-search-container {
   display: -moz-box;
   position: relative;
+  -moz-box-align: center;
   -moz-box-pack: center;
 }
 
 #newtab-search-container[page-disabled] {
   opacity: 0;
   pointer-events: none;
 }
 
 #newtab-search-form {
   display: -moz-box;
-  position: relative;
-  height: 36px;
   -moz-box-flex: 1;
+  -moz-box-orient: horizontal;
+  -moz-box-align: center;
+  height: 44px; /* 32 + 6 logo top "padding" + 6 logo bottom "padding" */
+  margin: 26px 20px 10px; /* top: 32 - 6 search form top "padding", bottom: 32 - 16 tiles top margin - 6 logo bottom "padding" */
   max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */
 }
 
-#newtab-search-icon {
-  border: 1px transparent;
-  padding: 0;
-  margin: 0;
-  width: 36px;
-  height: 36px;
-  background: url("chrome://browser/skin/search-indicator-magnifying-glass.svg") center center no-repeat;
-  position: absolute;
+#newtab-search-logo {
+  display: -moz-box;
+  width: 38px;
+  height: 38px; /* 26 image height + 6 top "padding" + 6 bottom "padding" */
+  border: 1px solid transparent;
+  -moz-margin-end: 8px;
+  background-repeat: no-repeat;
+  background-position: center;
+  background-image: url(chrome://global/skin/icons/autocomplete-search.svg#search-icon);
+  background-size: 26px 26px;
+}
+
+#newtab-search-logo.magnifier {
+  width: 38px; /* 26 image width + 6 left "padding" + 6 right "padding" */
+  -moz-margin-end: 5px;
+  background-size: 26px;
+  background-image: url("chrome://browser/skin/magnifier.png");
+}
+
+@media not all and (max-resolution: 1dppx) {
+  #newtab-search-logo.magnifier {
+    background-image: url("chrome://browser/skin/magnifier@2x.png");
+  }
+}
+
+#newtab-search-logo[type="logo"] {
+  background-size: 65px 26px;
+  width: 77px; /* 65 image width + 6 left "padding" + 6 right "padding" */
+}
+
+#newtab-search-logo[type="favicon"] {
+  background-size: 16px 16px;
+}
+
+#newtab-search-logo[hidden] {
+  display: none;
+}
+
+#newtab-search-logo[active],
+#newtab-search-logo:hover {
+  background-color: #e9e9e9;
+  border: 1px solid rgb(226, 227, 229);
+  border-radius: 2.5px;
 }
 
 #newtab-search-text {
+  height: 38px; /* same height as #newtab-search-logo */
   -moz-box-flex: 1;
-  padding-top: 6px;
-  padding-bottom: 6px;
-  padding-left: 34px;
-  padding-right: 8px;
+
+  padding: 0 8px;
   background: hsla(0,0%,100%,.9) padding-box;
   border: 1px solid;
-  border-spacing: 0;
-  border-radius: 2px 0 0 2px;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
   box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
               0 0 2px hsla(210,65%,9%,.1) inset,
               0 1px 0 hsla(0,0%,100%,.2);
+  border-radius: 2.5px 0 0 2.5px;
   color: inherit;
 }
 
 #newtab-search-text:-moz-dir(rtl) {
-  border-radius: 0 2px 2px 0;
+  border-radius: 0 2.5px 2.5px 0;
 }
 
-#newtab-search-text[aria-expanded="true"] {
-  border-radius: 2px 0 0 0;
-}
-
-#newtab-search-text[aria-expanded="true"]:-moz-dir(rtl) {
-  border-radius: 0 2px 0 0;
-}
-
-#newtab-search-text[keepfocus],
 #newtab-search-text:focus,
 #newtab-search-text[autofocus] {
   border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
 }
 
 #newtab-search-submit {
+  height: 38px; /* same height as #newtab-search-logo */
+  font-size: 13px !important;
+
   -moz-margin-start: -1px;
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go") center center no-repeat, linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
-  padding: 0;
+  background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+  padding: 0 9px;
   border: 1px solid;
   border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
-  border-radius: 0 2px 2px 0;
   -moz-border-start: 1px solid transparent;
+  border-radius: 0 2.5px 2.5px 0;
   box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
               0 1px 0 hsla(0,0%,100%,.2);
   color: inherit;
   cursor: pointer;
   transition-property: background-color, border-color, box-shadow;
   transition-duration: 150ms;
-  width: 50px;
 }
 
 #newtab-search-submit:-moz-dir(rtl) {
-  border-radius: 2px 0 0 2px;
+  border-radius: 2.5px 0 0 2.5px;
 }
 
 #newtab-search-text:focus + #newtab-search-submit,
 #newtab-search-text + #newtab-search-submit:hover,
 #newtab-search-text[autofocus] + #newtab-search-submit {
   border-color: #59b5fc #45a3e7 #3294d5;
   color: white;
 }
 
 #newtab-search-text:focus + #newtab-search-submit,
-#newtab-search-text[keepfocus] + #newtab-search-submit,
 #newtab-search-text[autofocus] + #newtab-search-submit {
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5);
+  background-image: linear-gradient(#4cb1ff, #1793e5);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
               0 0 0 1px hsla(0,0%,100%,.1) inset,
               0 1px 0 hsla(210,54%,20%,.03);
 }
 
 #newtab-search-text + #newtab-search-submit:hover {
-  background: url("chrome://browser/skin/search-arrow-go.svg#search-arrow-go-inverted") center center no-repeat, linear-gradient(#4cb1ff, #1793e5);
+  background-image: linear-gradient(#66bdff, #0d9eff);
   box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
               0 0 0 1px hsla(0,0%,100%,.1) inset,
               0 1px 0 hsla(210,54%,20%,.03),
               0 0 4px hsla(206,100%,20%,.2);
 }
 
 #newtab-search-text + #newtab-search-submit:hover:active {
   box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
@@ -511,35 +539,48 @@ input[type=button] {
   display: table-cell;
   border-top: none;
 }
 
 #newtab-customize-title > label {
   cursor: default;
 }
 
-#newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent {
+#newtab-customize-panel > .panel-arrowcontainer > .panel-arrowcontent,
+#newtab-search-panel > .panel-arrowcontainer > .panel-arrowcontent {
   padding: 0;
 }
 
-.newtab-customize-panel-item {
+.newtab-customize-panel-item,
+.newtab-search-panel-engine,
+#newtab-search-manage {
   line-height: 25px;
   padding: 15px;
   -moz-padding-start: 40px;
   font-size: 14px;
   cursor: pointer;
   max-width: 300px;
 }
 
-.newtab-customize-panel-item:not(:first-child) {
+.newtab-customize-panel-item:not(:first-child),
+.newtab-search-panel-engine {
   border-top: 1px solid threedshadow;
 }
 
+.newtab-search-panel-engine > image {
+  -moz-margin-end: 8px;
+  width: 16px;
+  height: 16px;
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
 .newtab-customize-panel-subitem > label,
 .newtab-customize-panel-item > label,
+.newtab-search-panel-engine > label,
+#newtab-search-manage > label,
 .newtab-customize-complex-option {
   padding: 0;
   margin: 0;
   cursor: pointer;
 }
 
 .newtab-customize-panel-item,
 .newtab-customize-complex-option {
@@ -579,17 +620,18 @@ input[type=button] {
   background-position: 15px 15px;
   color: #171F26;
 }
 
 .newtab-customize-complex-option:hover > .selectable:not([selected]) + .newtab-customize-panel-subitem {
   background-color: #FFFFFF;
 }
 
-.newtab-customize-panel-item[selected] {
+.newtab-customize-panel-item[selected],
+.newtab-search-panel-engine[selected] {
   background: url("chrome://global/skin/menu/shared-menu-check-active.svg") no-repeat transparent;
   background-size: 16px 16px;
   background-position: 15px 15px;
   color: black;
   font-weight: 600;
 }
 
 .newtab-customize-panel-subitem > .checkbox {
@@ -624,17 +666,17 @@ input[type=button] {
 .newtab-customize-panel-superitem {
   line-height: 20px;
   border-bottom: medium none !important;
   padding: 15px 15px 10px 15px;
   -moz-padding-start: 40px;
   border-top: 1px solid threedshadow;
 }
 
-.contentSearchSuggestionTable {
+.searchSuggestionTable {
   font: message-box;
   font-size: 16px;
 }
 
 /**
  * Onboarding styling
  */
 
--- a/browser/base/content/newtab/newTab.xul
+++ b/browser/base/content/newtab/newTab.xul
@@ -1,30 +1,39 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/contentSearchUI.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/searchSuggestionUI.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
 
 <!DOCTYPE window [
   <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
   %newTabDTD;
+  <!ENTITY % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd">
+  %searchBarDTD;
   <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
   %browserDTD;
 ]>
 
 <xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
             xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
             title="&newtab.pageTitle;">
 
+  <xul:panel id="newtab-search-panel" orient="vertical" type="arrow"
+             noautohide="true" hidden="true">
+    <xul:hbox id="newtab-search-manage">
+      <xul:label>&changeSearchSettings.button;</xul:label>
+    </xul:hbox>
+  </xul:panel>
+
   <div class="newtab-customize-panel-container">
     <div id="newtab-customize-panel" orient="vertical">
         <div id="newtab-customize-panel-anchor"></div>
         <div id="newtab-customize-title" class="newtab-customize-panel-item">
             <label>&newtab.customize.cog.title2;</label>
         </div>
 
         <div class="newtab-customize-complex-option">
@@ -94,23 +103,23 @@
                       class="newtab-undo-button" />
           <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
                              class="close-icon tabbable"
                              tooltiptext="&newtab.undo.closeTooltip;" />
         </div>
       </div>
 
       <div id="newtab-search-container">
-        <div id="newtab-search-form">
-          <div id="newtab-search-icon"/>
+        <form id="newtab-search-form" name="searchForm">
+          <div id="newtab-search-logo"/>
           <input type="text" name="q" value="" id="newtab-search-text"
-                 aria-label="&contentSearchInput.label;" maxlength="256" dir="auto"/>
-          <input id="newtab-search-submit" type="button" value=""
-                 aria-label="&contentSearchSubmit.label;"/>
-        </div>
+                 maxlength="256" dir="auto"/>
+          <input id="newtab-search-submit" type="submit"
+                 value="&searchEndCap.label;"/>
+        </form>
       </div>
 
       <div id="newtab-horizontal-margin">
         <div class="newtab-side-margin"/>
 
         <div id="newtab-grid">
         </div>
 
@@ -119,12 +128,12 @@
 
       <div id="newtab-margin-bottom"/>
 
     </div>
     <input id="newtab-customize-button" type="button" title="&newtab.customize.title;"/>
   </div>
 
   <xul:script type="text/javascript;version=1.8"
-              src="chrome://browser/content/contentSearchUI.js"/>
+              src="chrome://browser/content/searchSuggestionUI.js"/>
   <xul:script type="text/javascript;version=1.8"
               src="chrome://browser/content/newtab/newTab.js"/>
 </xul:window>
--- a/browser/base/content/newtab/search.js
+++ b/browser/base/content/newtab/search.js
@@ -1,15 +1,261 @@
 #ifdef 0
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 #endif
 
 let gSearch = {
+
+  currentEngineName: null,
+
+  get useNewUI() {
+    let newUI = Services.prefs.getBoolPref("browser.search.showOneOffButtons");
+    delete this.useNewUI;
+    this.useNewUI = newUI;
+    return newUI;
+  },
+
   init: function () {
-    document.getElementById("newtab-search-submit")
-            .addEventListener("click", e => this._contentSearchController.search(e));
-    let textbox = document.getElementById("newtab-search-text");
-    this._contentSearchController =
-      new ContentSearchUIController(textbox, textbox.parentNode, "newtab", "newtab");
+    for (let idSuffix of this._nodeIDSuffixes) {
+      this._nodes[idSuffix] =
+        document.getElementById("newtab-search-" + idSuffix);
+    }
+
+    if (this.useNewUI) {
+      this._nodes.logo.classList.add("magnifier");
+    }
+
+    window.addEventListener("ContentSearchService", this);
+    this._send("GetState");
+  },
+
+  showPanel: function () {
+    let panel = this._nodes.panel;
+    let logo = this._nodes.logo;
+    panel.hidden = false;
+    panel.openPopup(logo);
+    logo.setAttribute("active", "true");
+    panel.addEventListener("popuphidden", function onHidden() {
+      panel.removeEventListener("popuphidden", onHidden);
+      panel.hidden = true;
+      logo.removeAttribute("active");
+    });
+  },
+
+  search: function (event) {
+    if (event) {
+      event.preventDefault();
+    }
+    let searchText = this._nodes.text;
+    let searchStr = searchText.value;
+    if (this.currentEngineName && searchStr.length) {
+      let useNewTab = event && event.button == 1;
+      let eventData = {
+        engineName: this.currentEngineName,
+        searchString: searchStr,
+        whence: "newtab",
+        originalEvent: {
+          target: {
+            ownerDocument: null
+          },
+          shiftKey: event.shiftKey,
+          ctrlKey: event.ctrlKey,
+          metaKey: event.metaKey,
+          altKey: event.altKey,
+          button: event.button,
+        },
+      }
+
+      if (searchText.hasAttribute("selection-index")) {
+        eventData.selection = {
+          index: searchText.getAttribute("selection-index"),
+          kind: searchText.getAttribute("selection-kind")
+        };
+      }
+
+      this._send("Search", eventData);
+    }
+    this._suggestionController.addInputValueToFormHistory();
+  },
+
+  manageEngines: function () {
+    this._nodes.panel.hidePopup();
+    this._send("ManageEngines");
+  },
+
+  handleEvent: function (event) {
+    let methodName = "on" + event.detail.type;
+    if (this.hasOwnProperty(methodName)) {
+      this[methodName](event.detail.data);
+    }
+  },
+
+  onState: function (data) {
+    this._newEngines = data.engines;
+    this._setCurrentEngine(data.currentEngine);
+    this._initWhenInitalStateReceived();
+  },
+
+  onCurrentState: function (data) {
+    if (this._initialStateReceived) {
+      this._newEngines = data.engines;
+      this._setCurrentEngine(data.currentEngine);
+    }
+  },
+
+  onCurrentEngine: function (engineName) {
+    if (this._initialStateReceived) {
+      this._nodes.panel.hidePopup();
+      this._setCurrentEngine(engineName);
+    }
+  },
+
+  onFocusInput: function () {
+    this._nodes.text.focus();
+  },
+
+  _nodeIDSuffixes: [
+    "form",
+    "logo",
+    "manage",
+    "panel",
+    "text",
+  ],
+
+  _nodes: {},
+
+  _initWhenInitalStateReceived: function () {
+    this._nodes.form.addEventListener("submit", e => this.search(e));
+    this._nodes.logo.addEventListener("click", e => this.showPanel());
+    this._nodes.manage.addEventListener("click", e => this.manageEngines());
+    this._nodes.panel.addEventListener("popupshowing", e => this._setUpPanel());
+    this._initialStateReceived = true;
+    this._initWhenInitalStateReceived = function () {};
+  },
+
+  _send: function (type, data=null) {
+    window.dispatchEvent(new CustomEvent("ContentSearchClient", {
+      detail: {
+        type: type,
+        data: data,
+      },
+    }));
+  },
+
+  _setUpPanel: function () {
+    // The new search UI only contains the "manage" engine entry in the panel
+    if (this.useNewUI) {
+      return;
+    }
+
+    // Build the panel if necessary.
+    if (this._newEngines) {
+      this._buildPanel(this._newEngines);
+      delete this._newEngines;
+    }
+
+    // Set the selected states of the engines.
+    let panel = this._nodes.panel;
+    for (let box of panel.childNodes) {
+      if (box.getAttribute("engine") == this.currentEngineName) {
+        box.setAttribute("selected", "true");
+      }
+      else {
+        box.removeAttribute("selected");
+      }
+    }
+  },
+
+  _buildPanel: function (engines) {
+    let panel = this._nodes.panel;
+
+    // Empty the panel except for the Manage Engines row.
+    let i = 0;
+    while (i < panel.childNodes.length) {
+      let node = panel.childNodes[i];
+      if (node != this._nodes.manage) {
+        panel.removeChild(node);
+      }
+      else {
+        i++;
+      }
+    }
+
+    // Add all the engines.
+    for (let engine of engines) {
+      panel.insertBefore(this._makePanelEngine(panel, engine),
+                         this._nodes.manage);
+    }
+  },
+
+  // Converts favicon array buffer into data URI of the right size and dpi.
+  _getFaviconURIFromBuffer: function (buffer) {
+    let blob = new Blob([buffer]);
+    let dpiSize = Math.round(16 * window.devicePixelRatio);
+    let sizeStr = dpiSize + "," + dpiSize;
+    return URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr;
+  },
+
+  _makePanelEngine: function (panel, engine) {
+    let box = document.createElementNS(XUL_NAMESPACE, "hbox");
+    box.className = "newtab-search-panel-engine";
+    box.setAttribute("engine", engine.name);
+
+    box.addEventListener("click", () => {
+      this._send("SetCurrentEngine", engine.name);
+      panel.hidePopup();
+      this._nodes.text.focus();
+    });
+
+    let image = document.createElementNS(XUL_NAMESPACE, "image");
+    if (engine.iconBuffer) {
+      let uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
+      image.setAttribute("src", uri);
+    }
+    box.appendChild(image);
+
+    let label = document.createElementNS(XUL_NAMESPACE, "label");
+    label.setAttribute("value", engine.name);
+    box.appendChild(label);
+
+    return box;
+  },
+
+  _setCurrentEngine: function (engine) {
+    this.currentEngineName = engine.name;
+
+    if (!this.useNewUI) {
+      let type = "";
+      let uri;
+      let logoBuf = window.devicePixelRatio >= 2 ?
+                    engine.logo2xBuffer || engine.logoBuffer :
+                    engine.logoBuffer || engine.logo2xBuffer;
+      if (logoBuf) {
+        uri = URL.createObjectURL(new Blob([logoBuf]));
+        type = "logo";
+      }
+      else if (engine.iconBuffer) {
+        uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
+        type = "favicon";
+      }
+      this._nodes.logo.setAttribute("type", type);
+
+      if (uri) {
+        this._nodes.logo.style.backgroundImage = "url(" + uri + ")";
+      }
+      else {
+        this._nodes.logo.style.backgroundImage = "";
+      }
+      this._nodes.text.placeholder = engine.placeholder;
+    }
+
+    // Set up the suggestion controller.
+    if (!this._suggestionController) {
+      let parent = document.getElementById("newtab-scrollbox");
+      this._suggestionController =
+        new SearchSuggestionUIController(this._nodes.text, parent,
+                                         event => this.search(event));
+    }
+    this._suggestionController.engineName = engine.name;
   },
 };
rename from browser/base/content/contentSearchUI.css
rename to browser/base/content/searchSuggestionUI.css
--- a/browser/base/content/contentSearchUI.css
+++ b/browser/base/content/searchSuggestionUI.css
@@ -1,152 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-.contentSearchSuggestionTable {
+.searchSuggestionTable {
   background-color: hsla(0,0%,100%,.99);
-  border: 1px solid hsla(0, 0%, 0%, .2);
-  border-top: none;
-  box-shadow: 0 5px 10px hsla(0, 0%, 0%, .1);
+  border: 1px solid;
+  border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+  border-spacing: 0;
+  border-top: 0;
+  box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+              0 0 2px hsla(210,65%,9%,.1) inset,
+              0 1px 0 hsla(0,0%,100%,.2);
+  overflow: hidden;
+  padding: 0;
   position: absolute;
-  left: 0;
+  text-align: start;
   z-index: 1001;
-  -moz-user-select: none;
-  cursor: default;
 }
 
-.contentSearchSuggestionsList {
-  border-bottom: 1px solid hsl(0, 0%, 92%);
-  width: 100%;
-  height: 100%;
-}
-
-.contentSearchSuggestionTable,
-.contentSearchSuggestionsList {
-  border-spacing: 0;
-  overflow: hidden;
-  padding: 0;
-  margin: 0;
-  text-align: start;
-}
-
-.contentSearchHeaderRow,
-.contentSearchSuggestionRow {
+.searchSuggestionRow {
+  cursor: default;
   margin: 0;
   max-width: inherit;
   padding: 0;
 }
 
-.contentSearchHeaderRow > td > img,
-.contentSearchSuggestionRow > td > .historyIcon {
-  margin-right: 8px;
-  margin-bottom: -3px;
-}
-
-.contentSearchSuggestionTable .historyIcon {
-  width: 16px;
-  height: 16px;
-  display: inline-block;
-  background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon");
-}
-
-.contentSearchSuggestionRow.selected > td > .historyIcon {
-  background-image: url("chrome://browser/skin/search-history-icon.svg#search-history-icon-active");
+.searchSuggestionRow.formHistory + .searchSuggestionRow.remote > td {
+  border-top: 1px solid GrayText;
 }
 
-.contentSearchHeader > img {
-  height: 16px;
-  width: 16px;
-  margin: 0;
-  padding: 0;
+.searchSuggestionRow.selected {
+  background-color: hsl(210,100%,40%);
+  color: hsl(0,0%,100%);
 }
 
-.contentSearchSuggestionRow.remote > td > .historyIcon {
-  visibility: hidden;
-}
-
-.contentSearchSuggestionRow.selected {
-  background-color: Highlight;
-  color: HighlightText;
-}
-
-.contentSearchHeader,
-.contentSearchSuggestionEntry {
+.searchSuggestionEntry {
   margin: 0;
   max-width: inherit;
   overflow: hidden;
-  padding: 4px 10px;
+  padding: 6px 8px;
   text-overflow: ellipsis;
   white-space: nowrap;
-  font-size: 75%;
 }
-
-.contentSearchHeader {
-  background-color: hsl(0, 0%, 97%);
-  color: #666;
-  border-bottom: 1px solid hsl(0, 0%, 92%);
-}
-
-.contentSearchSuggestionsContainer {
-  margin: 0;
-  padding: 0;
-  border-spacing: 0;
-  width: 100%;
-}
-
-.contentSearchSearchWithHeaderSearchText {
-  white-space: pre;
-  font-weight: bold;
-}
-
-.contentSearchOneOffItem {
-  -moz-appearance: none;
-  height: 32px;
-  margin: 0;
-  padding: 0;
-  border: none;
-  background: none;
-  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAWCAYAAAABxvaqAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gofECQNNVW2/AAAABBJREFUGFdjOHPmzH8GehEA/KpKg9YTf4AAAAAASUVORK5CYII=');
-  background-repeat: no-repeat;
-  background-position: right center;
-}
-
-.contentSearchOneOffItem > img {
-  width: 16px;
-  height: 16px;
-  margin-bottom: -2px;
-}
-
-.contentSearchOneOffItem:not(.last-row) {
-  border-bottom: 1px solid hsl(0, 0%, 92%);
-}
-
-.contentSearchOneOffItem.end-of-row {
-  background-image: none;
-}
-
-.contentSearchOneOffItem.selected {
-  background-color: Highlight;
-  background-image: none;
-}
-
-.contentSearchOneOffsTable {
-  width: 100%;
-}
-
-.contentSearchSettingsButton {
-  margin: 0;
-  padding: 0;
-  height: 32px;
-  border: none;
-  border-top: 1px solid hsla(0, 0%, 0%, .08);
-  text-align: center;
-  width: 100%;
-}
-
-.contentSearchSettingsButton.selected {
-  background-color: hsl(0, 0%, 90%);
-}
-
-.contentSearchSettingsButton:active {
-  background-color: hsl(0, 0%, 85%);
-}
rename from browser/base/content/contentSearchUI.js
rename to browser/base/content/searchSuggestionUI.js
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/searchSuggestionUI.js
@@ -1,20 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-this.ContentSearchUIController = (function () {
+this.SearchSuggestionUIController = (function () {
 
 const MAX_DISPLAYED_SUGGESTIONS = 6;
 const SUGGESTION_ID_PREFIX = "searchSuggestion";
-const ONE_OFF_ID_PREFIX = "oneOff";
-const CSS_URI = "chrome://browser/content/contentSearchUI.css";
+const CSS_URI = "chrome://browser/content/searchSuggestionUI.css";
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 /**
  * Creates a new object that manages search suggestions and their UI for a text
  * box.
  *
  * The UI consists of an html:table that's inserted into the DOM after the given
@@ -25,372 +24,267 @@ const HTML_NS = "http://www.w3.org/1999/
  *        Assumed to be an html:input.  xul:textbox is untested but might work.
  * @param tableParent
  *        The suggestion table is appended as a child to this element.  Since
  *        the table is absolutely positioned and its top and left values are set
  *        to be relative to the top and left of the page, either the parent and
  *        all its ancestors should not be positioned elements (i.e., their
  *        positions should be "static"), or the parent's position should be the
  *        top left of the page.
- * @param healthReportKey
- *        This will be sent with the search data for FHR to record the search.
- * @param searchPurpose
- *        Sent with search data, see nsISearchEngine.getSubmission.
+ * @param onClick
+ *        A function that's called when a search suggestion is clicked.  Ideally
+ *        we could call submit() on inputElement's ancestor form, but that
+ *        doesn't trigger submit listeners. The function is passed one argument,
+ *        the click event.
  * @param idPrefix
  *        The IDs of elements created by the object will be prefixed with this
  *        string.
  */
-function ContentSearchUIController(inputElement, tableParent, healthReportKey,
-                                   searchPurpose, idPrefix="") {
+function SearchSuggestionUIController(inputElement, tableParent, onClick=null,
+                                      idPrefix="") {
   this.input = inputElement;
+  this.onClick = onClick;
   this._idPrefix = idPrefix;
-  this._healthReportKey = healthReportKey;
-  this._searchPurpose = searchPurpose;
 
   let tableID = idPrefix + "searchSuggestionTable";
   this.input.autocomplete = "off";
   this.input.setAttribute("aria-autocomplete", "true");
   this.input.setAttribute("aria-controls", tableID);
   tableParent.appendChild(this._makeTable(tableID));
 
   this.input.addEventListener("keypress", this);
   this.input.addEventListener("input", this);
   this.input.addEventListener("focus", this);
   this.input.addEventListener("blur", this);
   window.addEventListener("ContentSearchService", this);
 
   this._stickyInputValue = "";
   this._hideSuggestions();
 
-  this._getSearchEngines();
-  this._getStrings();
+  this._ignoreInputEvent = false;
 }
 
-ContentSearchUIController.prototype = {
+SearchSuggestionUIController.prototype = {
 
   // The timeout (ms) of the remote suggestions.  Corresponds to
   // SearchSuggestionController.remoteTimeout.  Uses
   // SearchSuggestionController's default timeout if falsey.
   remoteTimeout: undefined,
-  _oneOffButtons: [],
 
-  get defaultEngine() {
-    return this._defaultEngine;
+  get engineName() {
+    return this._engineName;
   },
 
-  set defaultEngine(val) {
-    this._defaultEngine = val;
-    this._updateDefaultEngineHeader();
-
+  set engineName(val) {
+    this._engineName = val;
     if (val && document.activeElement == this.input) {
       this._speculativeConnect();
     }
   },
 
-  get engines() {
-    return this._engines;
-  },
-
-  set engines(val) {
-    this._engines = val;
-    this._setUpOneOffButtons();
-  },
-
-  // The selectedIndex is the index of the element with the "selected" class in
-  // the list obtained by concatenating the suggestion rows, one-off buttons, and
-  // search settings button.
   get selectedIndex() {
-    let allElts = [...this._suggestionsList.children,
-                   ...this._oneOffButtons,
-                   document.getElementById("contentSearchSettingsButton")];
-    for (let i = 0; i < allElts.length; ++i) {
-      let elt = allElts[i];
-      if (elt.classList.contains("selected")) {
+    for (let i = 0; i < this._table.children.length; i++) {
+      let row = this._table.children[i];
+      if (row.classList.contains("selected")) {
         return i;
       }
     }
     return -1;
   },
 
   set selectedIndex(idx) {
     // Update the table's rows, and the input when there is a selection.
     this._table.removeAttribute("aria-activedescendant");
-    this.input.removeAttribute("aria-activedescendant");
-
-    let allElts = [...this._suggestionsList.children,
-                   ...this._oneOffButtons,
-                   document.getElementById("contentSearchSettingsButton")];
-    for (let i = 0; i < allElts.length; ++i) {
-      let elt = allElts[i];
-      let ariaSelectedElt = i < this.numSuggestions ? elt.firstChild : elt;
+    for (let i = 0; i < this._table.children.length; i++) {
+      let row = this._table.children[i];
       if (i == idx) {
-        elt.classList.add("selected");
-        ariaSelectedElt.setAttribute("aria-selected", "true");
-        this.input.setAttribute("aria-activedescendant", ariaSelectedElt.id);
+        row.classList.add("selected");
+        row.firstChild.setAttribute("aria-selected", "true");
+        this._table.setAttribute("aria-activedescendant", row.firstChild.id);
       }
       else {
-        elt.classList.remove("selected");
-        ariaSelectedElt.setAttribute("aria-selected", "false");
+        row.classList.remove("selected");
+        row.firstChild.setAttribute("aria-selected", "false");
       }
     }
   },
 
-  get selectedEngineName() {
-    let selectedElt = this._table.querySelector(".selected");
-    if (selectedElt && selectedElt.engineName) {
-      return selectedElt.engineName;
-    }
-    return this.defaultEngine.name;
-  },
-
   get numSuggestions() {
-    return this._suggestionsList.children.length;
+    return this._table.children.length;
   },
 
   selectAndUpdateInput: function (idx) {
     this.selectedIndex = idx;
-    let newValue = this.suggestionAtIndex(idx) || this._stickyInputValue;
-    // Setting the input value when the value has not changed commits the current
-    // IME composition, which we don't want to do.
-    if (this.input.value != newValue) {
-      this.input.value = newValue;
-    }
-    this._updateSearchWithHeader();
+    this.input.value = idx >= 0 ? this.suggestionAtIndex(idx) :
+                       this._stickyInputValue;
   },
 
   suggestionAtIndex: function (idx) {
-    let row = this._suggestionsList.children[idx];
+    let row = this._table.children[idx];
     return row ? row.textContent : null;
   },
 
   deleteSuggestionAtIndex: function (idx) {
     // Only form history suggestions can be deleted.
     if (this.isFormHistorySuggestionAtIndex(idx)) {
       let suggestionStr = this.suggestionAtIndex(idx);
       this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
-      this._suggestionsList.children[idx].remove();
+      this._table.children[idx].remove();
       this.selectAndUpdateInput(-1);
     }
   },
 
   isFormHistorySuggestionAtIndex: function (idx) {
-    let row = this._suggestionsList.children[idx];
+    let row = this._table.children[idx];
     return row && row.classList.contains("formHistory");
   },
 
   addInputValueToFormHistory: function () {
     this._sendMsg("AddFormHistoryEntry", this.input.value);
   },
 
   handleEvent: function (event) {
     this["_on" + event.type[0].toUpperCase() + event.type.substr(1)](event);
   },
 
-  _onCommand: function(aEvent) {
-    if (this.selectedIndex == this.numSuggestions + this._oneOffButtons.length) {
-      // Settings button was selected.
-      this._sendMsg("ManageEngines");
+  _onInput: function (event) {
+    if (this._ignoreInputEvent) {
       return;
     }
-
-    this.search(aEvent);
-
-    if (aEvent) {
-      aEvent.preventDefault();
-    }
-  },
-
-  search: function (aEvent) {
-    if (!this.defaultEngine) {
-      return; // Not initialized yet.
-    }
-
-    let searchText = this.input;
-    let searchTerms;
-    if (this._table.hidden ||
-        aEvent.originalTarget.id == "contentSearchDefaultEngineHeader") {
-      searchTerms = searchText.value;
+    if (this.input.value) {
+      this._getSuggestions();
     }
     else {
-      searchTerms = this.suggestionAtIndex(this.selectedIndex) || searchText.value;
-    }
-    // Send an event that will perform a search and Firefox Health Report will
-    // record that a search from the healthReportKey passed to the constructor.
-    let eventData = {
-      engineName: this.selectedEngineName,
-      searchString: searchTerms,
-      healthReportKey: this._healthReportKey,
-      searchPurpose: this._searchPurpose,
-      originalEvent: {
-        shiftKey: aEvent.shiftKey,
-        ctrlKey: aEvent.ctrlKey,
-        metaKey: aEvent.metaKey,
-        altKey: aEvent.altKey,
-        button: aEvent.button,
-      },
-    };
-
-    if (this.suggestionAtIndex(this.selectedIndex)) {
-      eventData.selection = {
-        index: this.selectedIndex,
-        kind: aEvent instanceof MouseEvent ? "mouse" :
-              aEvent instanceof KeyboardEvent ? "key" : undefined,
-      };
-    }
-
-    this._sendMsg("Search", eventData);
-    this.addInputValueToFormHistory();
-  },
-
-  _onInput: function () {
-    if (!this.input.value) {
       this._stickyInputValue = "";
       this._hideSuggestions();
     }
-    else if (this.input.value != this._stickyInputValue) {
-      // Only fetch new suggestions if the input value has changed.
-      this._getSuggestions();
-      this.selectAndUpdateInput(-1);
-    }
-    this._updateSearchWithHeader();
+    this.selectedIndex = -1;
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
     switch (event.keyCode) {
     case event.DOM_VK_UP:
-      if (!this._table.hidden) {
+      if (this.numSuggestions) {
         selectedIndexDelta = -1;
       }
       break;
     case event.DOM_VK_DOWN:
-      if (this._table.hidden) {
-        this._getSuggestions();
+      if (this.numSuggestions) {
+        selectedIndexDelta = 1;
       }
       else {
-        selectedIndexDelta = 1;
+        this._getSuggestions();
       }
       break;
     case event.DOM_VK_RIGHT:
       // Allow normal caret movement until the caret is at the end of the input.
       if (this.input.selectionStart != this.input.selectionEnd ||
           this.input.selectionEnd != this.input.value.length) {
         return;
       }
-      if (this.numSuggestions && this.selectedIndex >= 0 &&
-          this.selectedIndex < this.numSuggestions) {
+      // else, fall through
+    case event.DOM_VK_RETURN:
+      if (this.selectedIndex >= 0) {
         this.input.value = this.suggestionAtIndex(this.selectedIndex);
         this.input.setAttribute("selection-index", this.selectedIndex);
         this.input.setAttribute("selection-kind", "key");
       } else {
         // If we didn't select anything, make sure to remove the attributes
         // in case they were populated last time.
         this.input.removeAttribute("selection-index");
         this.input.removeAttribute("selection-kind");
       }
       this._stickyInputValue = this.input.value;
       this._hideSuggestions();
       break;
-    case event.DOM_VK_RETURN:
-      this._onCommand(event);
-      break;
     case event.DOM_VK_DELETE:
       if (this.selectedIndex >= 0) {
         this.deleteSuggestionAtIndex(this.selectedIndex);
       }
       break;
-    case event.DOM_VK_ESCAPE:
-      if (!this._table.hidden) {
-        this._hideSuggestions();
-      }
     default:
       return;
     }
 
     if (selectedIndexDelta) {
       // Update the selection.
       let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
       if (newSelectedIndex < -1) {
-        newSelectedIndex = this.numSuggestions + this._oneOffButtons.length;
+        newSelectedIndex = this.numSuggestions - 1;
       }
-      else if (this.numSuggestions + this._oneOffButtons.length < newSelectedIndex) {
+      else if (this.numSuggestions <= newSelectedIndex) {
         newSelectedIndex = -1;
       }
       this.selectAndUpdateInput(newSelectedIndex);
 
       // Prevent the input's caret from moving.
       event.preventDefault();
     }
   },
 
   _onFocus: function () {
-    if (this._mousedown) {
-      return;
-    }
-    // When the input box loses focus to something in our table, we refocus it
-    // immediately. This causes the focus highlight to flicker, so we set a
-    // custom attribute which consumers should use for focus highlighting. This
-    // attribute is removed only when we do not immediately refocus the input
-    // box, thus eliminating flicker.
-    this.input.setAttribute("keepfocus", "true");
     this._speculativeConnect();
   },
 
   _onBlur: function () {
-    if (this._mousedown) {
-      // At this point, this.input has lost focus, but a new element has not yet
-      // received it. If we re-focus this.input directly, the new element will
-      // steal focus immediately, so we queue it instead.
-      setTimeout(() => this.input.focus(), 0);
-      return;
-    }
-    this.input.removeAttribute("keepfocus");
     this._hideSuggestions();
   },
 
   _onMousemove: function (event) {
-    this.selectedIndex = this._indexOfTableItem(event.target);
+    this.selectedIndex = this._indexOfTableRowOrDescendent(event.target);
   },
 
-  _onMouseup: function (event) {
+  _onMousedown: function (event) {
     if (event.button == 2) {
       return;
     }
-    this._onCommand(event);
-  },
+    let idx = this._indexOfTableRowOrDescendent(event.target);
+    let suggestion = this.suggestionAtIndex(idx);
+    this._stickyInputValue = suggestion;
 
-  _onClick: function (event) {
-    this._onMouseup(event);
+    // Setting value commits composition string forcibly.  While IME commits
+    // composition, this needs to ignore input event at committed composition
+    // string which will be overwritten by the suggestion.
+    this._ignoreInputEvent = true;
+    this.input.value = suggestion;
+    this._ignoreInputEvent = false;
+    this.input.setAttribute("selection-index", idx);
+    this.input.setAttribute("selection-kind", "mouse");
+    this._hideSuggestions();
+    if (this.onClick) {
+      this.onClick.call(null, event);
+    }
   },
 
   _onContentSearchService: function (event) {
     let methodName = "_onMsg" + event.detail.type;
     if (methodName in this) {
       this[methodName](event.detail.data);
     }
   },
 
-  _onMsgFocusInput: function (event) {
-    this.input.focus();
-  },
-
   _onMsgSuggestions: function (suggestions) {
     // Ignore the suggestions if their search string or engine doesn't match
     // ours.  Due to the async nature of message passing, this can easily happen
     // when the user types quickly.
     if (this._stickyInputValue != suggestions.searchString ||
-        this.defaultEngine.name != suggestions.engineName) {
+        this.engineName != suggestions.engineName) {
       return;
     }
 
-    this._clearSuggestionRows();
+    // Empty the table.
+    while (this._table.firstElementChild) {
+      this._table.firstElementChild.remove();
+    }
 
     // Position and size the table.
-    let { left } = this.input.getBoundingClientRect();
-    this._table.style.top = this.input.offsetHeight + "px";
+    let { left, bottom } = this.input.getBoundingClientRect();
+    this._table.style.left = (left + window.scrollX) + "px";
+    this._table.style.top = (bottom + window.scrollY) + "px";
     this._table.style.minWidth = this.input.offsetWidth + "px";
     this._table.style.maxWidth = (window.innerWidth - left - 40) + "px";
 
     // Add the suggestions to the table.
     let searchWords =
       new Set(suggestions.searchString.trim().toLowerCase().split(/\s+/));
     for (let i = 0; i < MAX_DISPLAYED_SUGGESTIONS; i++) {
       let type, idx;
@@ -401,108 +295,41 @@ ContentSearchUIController.prototype = {
         let j = i - suggestions.formHistory.length;
         if (j < suggestions.remote.length) {
           [type, idx] = ["remote", j];
         }
         else {
           break;
         }
       }
-      this._suggestionsList.appendChild(
-        this._makeTableRow(type, suggestions[type][idx], i, searchWords));
+      this._table.appendChild(this._makeTableRow(type, suggestions[type][idx],
+                                                 i, searchWords));
     }
 
-    if (this._table.hidden) {
-      this.selectedIndex = -1;
-      this._table.hidden = false;
-      this.input.setAttribute("aria-expanded", "true");
-    }
-  },
-
-  _onMsgState: function (state) {
-    this.defaultEngine = {
-      name: state.currentEngine.name,
-      icon: this._getFaviconURIFromBuffer(state.currentEngine.iconBuffer),
-    };
-    this.engines = state.engines;
-  },
-
-  _onMsgCurrentState: function (state) {
-    this._onMsgState(state);
-  },
-
-  _onMsgCurrentEngine: function (engine) {
-    this.defaultEngine = {
-      name: engine.name,
-      icon: this._getFaviconURIFromBuffer(engine.iconBuffer),
-    };
-    this._setUpOneOffButtons();
-  },
-
-  _onMsgStrings: function (strings) {
-    this._strings = strings;
-    this._updateDefaultEngineHeader();
-    this._updateSearchWithHeader();
-  },
-
-  _updateDefaultEngineHeader: function () {
-    let header = document.getElementById("contentSearchDefaultEngineHeader");
-    if (this.defaultEngine.icon) {
-      header.firstChild.setAttribute("src", this.defaultEngine.icon);
-    }
-    if (!this._strings) {
-      return;
-    }
-    while (header.firstChild.nextSibling) {
-      header.firstChild.nextSibling.remove();
-    }
-    header.appendChild(document.createTextNode(
-      this._strings.searchHeader.replace("%S", this.defaultEngine.name)));
-  },
-
-  _updateSearchWithHeader: function () {
-    if (!this._strings) {
-      return;
-    }
-    let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
-    while (searchWithHeader.firstChild) {
-      searchWithHeader.firstChild.remove();
-    }
-    if (this.input.value) {
-      searchWithHeader.appendChild(document.createTextNode(this._strings.searchFor));
-      let span = document.createElementNS(HTML_NS, "span");
-      span.setAttribute("class", "contentSearchSearchWithHeaderSearchText");
-      span.appendChild(document.createTextNode(" " + this.input.value + " "));
-      searchWithHeader.appendChild(span);
-      searchWithHeader.appendChild(document.createTextNode(this._strings.searchWith));
-      return;
-    }
-    searchWithHeader.appendChild(document.createTextNode(this._strings.searchWithHeader));
+    this._table.hidden = false;
+    this.input.setAttribute("aria-expanded", "true");
   },
 
   _speculativeConnect: function () {
-    if (this.defaultEngine) {
-      this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
+    if (this.engineName) {
+      this._sendMsg("SpeculativeConnect", this.engineName);
     }
   },
 
   _makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
     let row = document.createElementNS(HTML_NS, "tr");
     row.dir = "auto";
-    row.classList.add("contentSearchSuggestionRow");
+    row.classList.add("searchSuggestionRow");
     row.classList.add(type);
     row.setAttribute("role", "presentation");
     row.addEventListener("mousemove", this);
-    row.addEventListener("mouseup", this);
+    row.addEventListener("mousedown", this);
 
     let entry = document.createElementNS(HTML_NS, "td");
-    let img = document.createElementNS(HTML_NS, "div");
-    img.setAttribute("class", "historyIcon");
-    entry.appendChild(img);
-    entry.classList.add("contentSearchSuggestionEntry");
+    entry.classList.add("searchSuggestionEntry");
     entry.setAttribute("role", "option");
     entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
     entry.setAttribute("aria-selected", "false");
 
     let suggestionWords = suggestionStr.trim().toLowerCase().split(/\s+/);
     for (let i = 0; i < suggestionWords.length; i++) {
       let word = suggestionWords[i];
       let wordSpan = document.createElementNS(HTML_NS, "span");
@@ -515,221 +342,59 @@ ContentSearchUIController.prototype = {
         entry.appendChild(document.createTextNode(" "));
       }
     }
 
     row.appendChild(entry);
     return row;
   },
 
-  // Converts favicon array buffer into data URI of the right size and dpi.
-  _getFaviconURIFromBuffer: function (buffer) {
-    let blob = new Blob([buffer]);
-    let dpiSize = Math.round(16 * window.devicePixelRatio);
-    let sizeStr = dpiSize + "," + dpiSize;
-    return URL.createObjectURL(blob) + "#-moz-resolution=" + sizeStr;
-  },
-
-  _getSearchEngines: function () {
-    this._sendMsg("GetState");
-  },
-
-  _getStrings: function () {
-    this._sendMsg("GetStrings");
-  },
-
   _getSuggestions: function () {
     this._stickyInputValue = this.input.value;
-    if (this.defaultEngine) {
+    if (this.engineName) {
       this._sendMsg("GetSuggestions", {
-        engineName: this.defaultEngine.name,
+        engineName: this.engineName,
         searchString: this.input.value,
         remoteTimeout: this.remoteTimeout,
       });
     }
   },
 
-  _clearSuggestionRows: function() {
-    while (this._suggestionsList.firstElementChild) {
-      this._suggestionsList.firstElementChild.remove();
-    }
-  },
-
   _hideSuggestions: function () {
     this.input.setAttribute("aria-expanded", "false");
     this._table.hidden = true;
+    while (this._table.firstElementChild) {
+      this._table.firstElementChild.remove();
+    }
+    this.selectAndUpdateInput(-1);
   },
 
-  _indexOfTableItem: function (elt) {
-    if (elt.classList.contains("contentSearchOneOffItem")) {
-      return this.numSuggestions + this._oneOffButtons.indexOf(elt);
+  _indexOfTableRowOrDescendent: function (row) {
+    while (row && row.localName != "tr") {
+      row = row.parentNode;
     }
-    if (elt.classList.contains("contentSearchSettingsButton")) {
-      return this.numSuggestions + this._oneOffButtons.length;
-    }
-    while (elt && elt.localName != "tr") {
-      elt = elt.parentNode;
-    }
-    if (!elt) {
+    if (!row) {
       throw new Error("Element is not a row");
     }
-    return elt.rowIndex;
+    return row.rowIndex;
   },
 
   _makeTable: function (id) {
     this._table = document.createElementNS(HTML_NS, "table");
     this._table.id = id;
     this._table.hidden = true;
-    this._table.classList.add("contentSearchSuggestionTable");
-    this._table.setAttribute("role", "presentation");
-
-    // When the search input box loses focus, we want to immediately give focus
-    // back to it if the blur was because the user clicked somewhere in the table.
-    // onBlur uses the _mousedown flag to detect this.
-    this._table.addEventListener("mousedown", () => { this._mousedown = true; });
-    document.addEventListener("mouseup", () => { delete this._mousedown; });
-
-    // Deselect the selected element on mouseout if it wasn't a suggestion.
-    this._table.addEventListener("mouseout", () => {
-      if (this.selectedIndex >= this.numSuggestions) {
-        this.selectAndUpdateInput(-1);
-      }
-    });
-
-    // If a search is loaded in the same tab, ensure the suggestions dropdown
-    // is hidden immediately when the page starts loading and not when it first
-    // appears, in order to provide timely feedback to the user.
-    window.addEventListener("beforeunload", () => { this._hideSuggestions(); });
-
-    let headerRow = document.createElementNS(HTML_NS, "tr");
-    let header = document.createElementNS(HTML_NS, "td");
-    headerRow.setAttribute("class", "contentSearchHeaderRow");
-    header.setAttribute("class", "contentSearchHeader");
-    let img = document.createElementNS(HTML_NS, "img");
-    img.setAttribute("src", "chrome://browser/skin/search-engine-placeholder.png");
-    header.appendChild(img);
-    header.id = "contentSearchDefaultEngineHeader";
-    headerRow.appendChild(header);
-    headerRow.addEventListener("click", this);
-    this._table.appendChild(headerRow);
-
-    let row = document.createElementNS(HTML_NS, "tr");
-    row.setAttribute("class", "contentSearchSuggestionsContainer");
-    let cell = document.createElementNS(HTML_NS, "td");
-    cell.setAttribute("class", "contentSearchSuggestionsContainer");
-    this._suggestionsList = document.createElementNS(HTML_NS, "table");
-    this._suggestionsList.setAttribute("class", "contentSearchSuggestionsList");
-    cell.appendChild(this._suggestionsList);
-    row.appendChild(cell);
-    this._table.appendChild(row);
-    this._suggestionsList.setAttribute("role", "listbox");
-
-    this._oneOffsTable = document.createElementNS(HTML_NS, "table");
-    this._oneOffsTable.setAttribute("class", "contentSearchOneOffsTable");
-    this._oneOffsTable.classList.add("contentSearchSuggestionsContainer");
-    this._oneOffsTable.setAttribute("role", "group");
-    this._table.appendChild(this._oneOffsTable);
-
-    headerRow = document.createElementNS(HTML_NS, "tr");
-    header = document.createElementNS(HTML_NS, "td");
-    headerRow.setAttribute("class", "contentSearchHeaderRow");
-    header.setAttribute("class", "contentSearchHeader");
-    headerRow.appendChild(header);
-    header.id = "contentSearchSearchWithHeader";
-    this._oneOffsTable.appendChild(headerRow);
-
-    let button = document.createElementNS(HTML_NS, "button");
-    button.appendChild(document.createTextNode("Change Search Settings"));
-    button.setAttribute("class", "contentSearchSettingsButton");
-    button.classList.add("contentSearchHeaderRow");
-    button.classList.add("contentSearchHeader");
-    button.id = "contentSearchSettingsButton";
-    button.addEventListener("click", this);
-    button.addEventListener("mousemove", this);
-    this._table.appendChild(button);
-
+    this._table.classList.add("searchSuggestionTable");
+    this._table.setAttribute("role", "listbox");
     return this._table;
   },
 
-  _setUpOneOffButtons: function () {
-    // Sometimes we receive a CurrentEngine message from the ContentSearch service
-    // before we've received a State message - i.e. before we have our engines.
-    if (!this._engines) {
-      return;
-    }
-
-    while (this._oneOffsTable.firstChild.nextSibling) {
-      this._oneOffsTable.firstChild.nextSibling.remove();
-    }
-
-    this._oneOffButtons = [];
-
-    let engines = this._engines.filter(aEngine => aEngine.name != this.defaultEngine.name);
-    if (!engines.length) {
-      this._oneOffsTable.hidden = true;
-      return;
-    }
-
-    const kDefaultButtonWidth = 49; // 48px + 1px border.
-    let rowWidth = this.input.offsetWidth - 2; // 2px border.
-    let enginesPerRow = Math.floor(rowWidth / kDefaultButtonWidth);
-    let buttonWidth = Math.floor(rowWidth / enginesPerRow);
-
-    let row = document.createElementNS(HTML_NS, "tr");
-    let cell = document.createElementNS(HTML_NS, "td");
-    row.setAttribute("class", "contentSearchSuggestionsContainer");
-    cell.setAttribute("class", "contentSearchSuggestionsContainer");
-
-    for (let i = 0; i < engines.length; ++i) {
-      let engine = engines[i];
-      if (i > 0 && i % enginesPerRow == 0) {
-        row.appendChild(cell);
-        this._oneOffsTable.appendChild(row);
-        row = document.createElementNS(HTML_NS, "tr");
-        cell = document.createElementNS(HTML_NS, "td");
-        row.setAttribute("class", "contentSearchSuggestionsContainer");
-        cell.setAttribute("class", "contentSearchSuggestionsContainer");
-      }
-      let button = document.createElementNS(HTML_NS, "button");
-      button.setAttribute("class", "contentSearchOneOffItem");
-      let img = document.createElementNS(HTML_NS, "img");
-      let uri = "chrome://browser/skin/search-engine-placeholder.png";
-      if (engine.iconBuffer) {
-        uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
-      }
-      img.setAttribute("src", uri);
-      button.appendChild(img);
-      button.style.width = buttonWidth + "px";
-      button.setAttribute("title", engine.name);
-
-      button.engineName = engine.name;
-      button.addEventListener("click", this);
-      button.addEventListener("mousemove", this);
-
-      if (engines.length - i <= enginesPerRow - (i % enginesPerRow)) {
-        button.classList.add("last-row");
-      }
-
-      if ((i + 1) % enginesPerRow == 0) {
-        button.classList.add("end-of-row");
-      }
-
-      button.id = ONE_OFF_ID_PREFIX + i;
-      cell.appendChild(button);
-      this._oneOffButtons.push(button);
-    }
-    row.appendChild(cell);
-    this._oneOffsTable.appendChild(row);
-    this._oneOffsTable.hidden = false;
-  },
-
   _sendMsg: function (type, data=null) {
     dispatchEvent(new CustomEvent("ContentSearchClient", {
       detail: {
         type: type,
         data: data,
       },
     }));
   },
 };
 
-return ContentSearchUIController;
+return SearchSuggestionUIController;
 })();
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -109,16 +109,22 @@ let AboutHomeListener = {
   handleEvent: function(aEvent) {
     if (!this.isAboutHome) {
       return;
     }
     switch (aEvent.type) {
       case "AboutHomeLoad":
         this.onPageLoad();
         break;
+      case "AboutHomeSearchEvent":
+        this.onSearch(aEvent);
+        break;
+      case "AboutHomeSearchPanel":
+        this.onOpenSearchPanel(aEvent);
+        break;
       case "click":
         this.onClick(aEvent);
         break;
       case "pagehide":
         this.onPageHide(aEvent);
         break;
     }
   },
@@ -126,49 +132,57 @@ let AboutHomeListener = {
   receiveMessage: function(aMessage) {
     if (!this.isAboutHome) {
       return;
     }
     switch (aMessage.name) {
       case "AboutHome:Update":
         this.onUpdate(aMessage.data);
         break;
+      case "AboutHome:FocusInput":
+        this.onFocusInput();
+        break;
     }
   },
 
   onUpdate: function(aData) {
     let doc = content.document;
     if (aData.showRestoreLastSession && !PrivateBrowsingUtils.isContentWindowPrivate(content))
       doc.getElementById("launcher").setAttribute("session", "true");
 
     // Inject search engine and snippets URL.
     let docElt = doc.documentElement;
-    // Set snippetsVersion last, which triggers to show the snippets when it's set.
+    // set the following attributes BEFORE searchEngineName, which triggers to
+    // show the snippets when it's set.
     docElt.setAttribute("snippetsURL", aData.snippetsURL);
     if (aData.showKnowYourRights)
       docElt.setAttribute("showKnowYourRights", "true");
     docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
+    docElt.setAttribute("searchEngineName", aData.defaultEngineName);
   },
 
   onPageLoad: function() {
     let doc = content.document;
     if (doc.documentElement.hasAttribute("hasBrowserHandlers")) {
       return;
     }
 
     doc.documentElement.setAttribute("hasBrowserHandlers", "true");
     addMessageListener("AboutHome:Update", this);
+    addMessageListener("AboutHome:FocusInput", this);
     addEventListener("click", this, true);
     addEventListener("pagehide", this, true);
 
     if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
       doc.documentElement.setAttribute("searchUIConfiguration", "oldsearchui");
     }
 
     sendAsyncMessage("AboutHome:RequestUpdate");
+    doc.addEventListener("AboutHomeSearchEvent", this, true, true);
+    doc.addEventListener("AboutHomeSearchPanel", this, true, true);
   },
 
   onClick: function(aEvent) {
     if (!aEvent.isTrusted || // Don't trust synthetic events
         aEvent.button == 2 || aEvent.target.localName != "button") {
       return;
     }
 
@@ -209,30 +223,49 @@ let AboutHomeListener = {
 
       case "sync":
         sendAsyncMessage("AboutHome:Sync");
         break;
 
       case "settings":
         sendAsyncMessage("AboutHome:Settings");
         break;
+
+      case "searchIcon":
+        sendAsyncMessage("AboutHome:OpenSearchPanel", null, { anchor: originalTarget });
+        break;
     }
   },
 
   onPageHide: function(aEvent) {
     if (aEvent.target.defaultView.frameElement) {
       return;
     }
     removeMessageListener("AboutHome:Update", this);
     removeEventListener("click", this, true);
     removeEventListener("pagehide", this, true);
     if (aEvent.target.documentElement) {
       aEvent.target.documentElement.removeAttribute("hasBrowserHandlers");
     }
   },
+
+  onSearch: function(aEvent) {
+    sendAsyncMessage("AboutHome:Search", { searchData: aEvent.detail });
+  },
+
+  onOpenSearchPanel: function(aEvent) {
+    sendAsyncMessage("AboutHome:OpenSearchPanel");
+  },
+
+  onFocusInput: function () {
+    let searchInput = content.document.getElementById("searchText");
+    if (searchInput) {
+      searchInput.focus();
+    }
+  },
 };
 AboutHomeListener.init(this);
 
 let AboutPrivateBrowsingListener = {
   init(chromeGlobal) {
     chromeGlobal.addEventListener("AboutPrivateBrowsingOpenWindow", this,
                                   false, true);
   },
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -75,17 +75,16 @@ 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
@@ -365,20 +364,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_contentSearchUI.js]
+[browser_searchSuggestionUI.js]
 support-files =
-  contentSearchUI.html
-  contentSearchUI.js
+  searchSuggestionUI.html
+  searchSuggestionUI.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,45 +96,53 @@ 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 p = promiseContentSearchChange(engine.name);
+    let promise = promiseBrowserAttributes(gBrowser.selectedTab);
     Services.search.currentEngine = engine;
-    yield p;
+    yield promise;
 
     let numSearchesBefore = 0;
     let searchEventDeferred = Promise.defer();
     let doc = gBrowser.contentDocument;
-    let engineName = gBrowser.contentWindow.wrappedJSObject.gContentSearchController.defaultEngine.name;
+    let engineName = doc.documentElement.getAttribute("searchEngineName");
     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).then(() => {
-      getNumberOfSearches(engineName).then(num => {
-        is(num, numSearchesBefore + 1, "One more search recorded.");
-        searchEventDeferred.resolve();
-      });
-    });
+    let loadPromise = waitForDocLoadAndStopIt(expectedURL);
 
     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 {
@@ -223,58 +231,67 @@ 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 = Task.async(function* search_observer(aSubject, aTopic, aData) {
+    let searchObserver = 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");
 
-      let p = promiseContentSearchChange(engine.name);
-      Services.search.defaultEngine = engine;
-      yield p;
+      // 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 });
 
-      searchText.value = needle;
-      searchText.focus();
-      EventUtils.synthesizeKey("VK_RETURN", {});
+      // Change the search engine, triggering the observer above.
+      Services.search.defaultEngine = engine;
 
       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;
   }
@@ -313,30 +330,31 @@ let gTests = [
     });
 
     browser.loadURI("https://example.com/browser/browser/base/content/test/general/test_bug959531.html");
     return deferred.promise;
   }
 },
 
 {
-  // See browser_contentSearchUI.js for comprehensive content search UI tests.
+  // See browser_searchSuggestionUI.js for comprehensive content search
+  // suggestion 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);
+      let promise = promiseBrowserAttributes(gBrowser.selectedTab);
       Services.search.currentEngine = engine;
-      yield p;
+      yield promise;
 
       // Avoid intermittent failures.
-      gBrowser.contentWindow.wrappedJSObject.gContentSearchController.remoteTimeout = 5000;
+      gBrowser.contentWindow.wrappedJSObject.gSearchSuggestionController.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 =
@@ -378,21 +396,19 @@ 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 = searchController._suggestionsList;
+      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();
         }
       });
@@ -403,24 +419,19 @@ 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];
-      // 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);
+      EventUtils.sendMouseEvent({ type: "mousedown" }, row, gBrowser.contentWindow);
       yield loadPromise;
-      ok(input.value == "x", "Input value did not change");
+      ok(input.value == "xbar", "Suggestion is selected");
     });
   }
 },
 {
   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");
@@ -461,16 +472,36 @@ 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);
@@ -541,16 +572,48 @@ 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) {
@@ -643,28 +706,16 @@ 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_contentSearchUI.js
rename to browser/base/content/test/general/browser_searchSuggestionUI.js
--- a/browser/base/content/test/general/browser_contentSearchUI.js
+++ b/browser/base/content/test/general/browser_searchSuggestionUI.js
@@ -1,18 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const TEST_PAGE_BASENAME = "contentSearchUI.html";
-const TEST_CONTENT_SCRIPT_BASENAME = "contentSearchUI.js";
-const TEST_ENGINE_PREFIX = "browser_searchSuggestionEngine";
+const TEST_PAGE_BASENAME = "searchSuggestionUI.html";
+const TEST_CONTENT_SCRIPT_BASENAME = "searchSuggestionUI.js";
 const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
-const TEST_ENGINE_2_BASENAME = "searchSuggestionEngine2.xml";
 
-const TEST_MSG = "ContentSearchUIControllerTest";
+const TEST_MSG = "SearchSuggestionUIControllerTest";
 
 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");
@@ -28,406 +26,233 @@ add_task(function* blur() {
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   state = yield msg("blur");
   checkState(state, "x", [], -1);
 
   yield msg("reset");
 });
 
-add_task(function* upDownKeys() {
+add_task(function* arrowKeys() {
   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");
 });
 
-add_task(function* rightLeftKeys() {
-  yield setUp();
-
-  let state = yield msg("key", { key: "x", waitForSuggestions: true });
-  checkState(state, "x", ["xfoo", "xbar"], -1);
+// The right arrow and return key function the same.
+function rightArrowOrReturn(keyName) {
+  return function* rightArrowOrReturnTest() {
+    yield setUp();
 
-  state = yield msg("key", "VK_LEFT");
-  checkState(state, "x", ["xfoo", "xbar"], -1);
-
-  state = yield msg("key", "VK_LEFT");
-  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_RIGHT");
-  checkState(state, "x", ["xfoo", "xbar"], -1);
-
-  state = yield msg("key", "VK_RIGHT");
-  checkState(state, "x", [], -1);
+    state = yield msg("key", "VK_DOWN");
+    checkState(state, "xfoo", ["xfoo", "xbar"], 0);
 
-  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);
+    // 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);
 
-  // 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", { key: "VK_DOWN", waitForSuggestions: true });
+    checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
 
-  state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
-  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
+    state = yield msg("key", "VK_DOWN");
+    checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
 
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xfoofoo", ["xfoofoo", "xfoobar"], 0);
+    state = yield msg("key", "VK_DOWN");
+    checkState(state, "xfoobar", ["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);
+    state = yield msg("key", "VK_DOWN");
+    checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
 
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], 3);
+    yield msg("reset");
+  };
+}
 
-  state = yield msg("key", "VK_DOWN");
-  checkState(state, "xfoo", ["xfoofoo", "xfoobar"], -1);
-
-  yield msg("reset");
-});
+add_task(rightArrowOrReturn("VK_RIGHT"));
+add_task(rightArrowOrReturn("VK_RETURN"));
 
 add_task(function* mouse() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
-  for (let i = 0; i < 4; ++i) {
-    state = yield msg("mousemove", i);
-    checkState(state, "x", ["xfoo", "xbar"], i);
-  }
+  // 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);
 
-  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);
+
+  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);
 
   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 Promise.all([msg("addInputValueToFormHistory"), deferred.promise]);
+  yield 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* 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();
+add_task(function* composition() {
   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", [{ 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);
+  checkState(state, "x", ["xfoo", "xbar"], -1);
 
   // Mouse over the first suggestion.
   state = yield msg("mousemove", 0);
-  checkState(state, "x", [{ str: "x", type: "formHistory" },
-                          { str: "xfoo", type: "formHistory" }, "xbar"], 0);
+  checkState(state, "x", ["xfoo", "xbar"], 0);
 
   // Mouse over the second suggestion.
   state = yield msg("mousemove", 1);
-  checkState(state, "x", [{ str: "x", type: "formHistory" },
-                          { str: "xfoo", type: "formHistory" }, "xbar"], 1);
+  checkState(state, "x", ["xfoo", "xbar"], 1);
 
-  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");
+  // 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);
 
-  yield promiseTab();
-  yield setUp();
+  checkState(state, "xbar", [], -1);
 
-  // Remove form history entries.
-  // Wait for Satchel.
-  let deferred = Promise.defer();
-  let historyCount = 2;
-  Services.obs.addObserver(function onRemove(subj, topic, data) {
-    if (data == "formhistory-remove") {
-      if (--historyCount) {
-        return;
-      }
-      Services.obs.removeObserver(onRemove, "satchel-storage-changed");
-      executeSoon(() => deferred.resolve());
-    }
-  }, "satchel-storage-changed", false);
+  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);
 
-  yield msg("key", { key: "x", waitForSuggestions: true });
-  yield msg("key", "VK_DOWN");
-  yield msg("key", "VK_DOWN");
-  yield msg("key", "VK_DELETE");
-  yield msg("key", "VK_DOWN");
-  yield msg("key", "VK_DELETE");
-  yield deferred.promise;
-
-  yield msg("reset");
-  state = yield msg("key", { key: "x", waitForSuggestions: true });
-  checkState(state, "x", ["xfoo", "xbar"], -1);
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xbarbar", ["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;
+  state = yield msg("key", "VK_DOWN");
+  checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
 
   yield msg("reset");
 });
 
+
 let gDidInitialSetUp = false;
 
-function setUp(aNoEngine) {
+function setUp() {
   return Task.spawn(function* () {
     if (!gDidInitialSetUp) {
-      Cu.import("resource:///modules/ContentSearch.jsm");
-      let originalOnMessageSearch = ContentSearch._onMessageSearch;
-      let originalOnMessageManageEngines = ContentSearch._onMessageManageEngines;
-      ContentSearch._onMessageSearch = () => {};
-      ContentSearch._onMessageManageEngines = () => {};
-      registerCleanupFunction(() => {
-        ContentSearch._onMessageSearch = originalOnMessageSearch;
-        ContentSearch._onMessageManageEngines = originalOnMessageManageEngines;
-      });
-      yield setUpEngines();
+      yield promiseNewEngine(TEST_ENGINE_BASENAME);
       yield promiseTab();
       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.data);
+    deferred.resolve(msg.data);
   });
   return deferred.promise;
 }
 
 function checkState(actualState, expectedInputVal, expectedSuggestions,
                     expectedSelectedIdx) {
   expectedSuggestions = expectedSuggestions.map(sugg => {
     return typeof(sugg) == "object" ? sugg : {
@@ -439,16 +264,27 @@ 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");
 }
 
@@ -465,17 +301,17 @@ function promiseTab() {
     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(msg("init"));
+      deferred.resolve();
     });
   }, true, true);
   openUILinkIn(pageURL, "current");
   return deferred.promise;
 }
 
 function promiseMsg(name, type, msgMan) {
   let deferred = Promise.defer();
@@ -485,44 +321,24 @@ 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;
--- 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&amp;terms={searchTerms}" rel="searchform"/>
+<Url type="text/html" method="GET" template="http://www.browser-searchSuggestionEngine.com/searchSuggestionEngine" rel="searchform"/>
 </SearchPlugin>
deleted file mode 100644
--- a/browser/base/content/test/general/searchSuggestionEngine2.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<?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>
rename from browser/base/content/test/general/contentSearchUI.html
rename to browser/base/content/test/general/searchSuggestionUI.html
--- a/browser/base/content/test/general/contentSearchUI.html
+++ b/browser/base/content/test/general/searchSuggestionUI.html
@@ -4,18 +4,17 @@
 
 <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/contentSearchUI.js">
+        src="chrome://browser/content/searchSuggestionUI.js">
 </script>
-<link rel="stylesheet" href="chrome://browser/content/contentSearchUI.css"/>
 </head>
 <body>
 
-<div id="container" style="position: relative;"><input type="text" value=""/></div>
+<input>
 
 </body>
 </html>
rename from browser/base/content/test/general/contentSearchUI.js
rename to browser/base/content/test/general/searchSuggestionUI.js
--- a/browser/base/content/test/general/contentSearchUI.js
+++ b/browser/base/content/test/general/searchSuggestionUI.js
@@ -1,189 +1,158 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 (function () {
 
-const TEST_MSG = "ContentSearchUIControllerTest";
+const TEST_MSG = "SearchSuggestionUIControllerTest";
 const ENGINE_NAME = "browser_searchSuggestionEngine searchSuggestionEngine.xml";
-var gController;
+
+let input = content.document.querySelector("input");
+let gController =
+  new content.SearchSuggestionUIController(input, input.parentNode);
+gController.engineName = ENGINE_NAME;
+gController.remoteTimeout = 5000;
 
 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, arg.modifiers || {});
+    content.synthesizeKey(keyName, {});
     let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
-    wait(ack.bind(null, "key"));
+    wait(ack);
   },
 
   startComposition: function (arg) {
     content.synthesizeComposition({ type: "compositionstart", data: "" });
-    ack("startComposition");
+    ack();
   },
 
   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.bind(null, "changeComposition"));
-  },
-
-  commitComposition: function () {
-    content.synthesizeComposition({ type: "compositioncommitasis" });
-    ack("commitComposition");
+    wait(ack);
   },
 
   focus: function () {
     gController.input.focus();
-    ack("focus");
+    ack();
   },
 
   blur: function () {
     gController.input.blur();
-    ack("blur");
-  },
-
-  waitForSearch: function () {
-    waitForContentSearchEvent("Search", aData => ack("waitForSearch", aData));
-  },
-
-  waitForSearchSettings: function () {
-    waitForContentSearchEvent("ManageEngines",
-                              aData => ack("waitForSearchSettings", aData));
+    ack();
   },
 
-  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];
-    }
-    let event = {
-      type: "mousemove",
-      clickcount: 0,
-    }
-    content.synthesizeMouseAtCenter(row, event);
-    ack("mousemove");
+  mousemove: function (suggestionIdx) {
+    // 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;
+
+    let widgetToolkit = content.SpecialPowers.
+                        Cc["@mozilla.org/xre/app-info;1"].
+                        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();
+    });
+    utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null);
   },
 
-  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 || {};
-    // synthesizeMouseAtCenter defaults to sending a mousedown followed by a
-    // mouseup if the event type is not specified.
-    content.synthesizeMouseAtCenter(row, event);
-    ack("click");
+  mousedown: function (suggestionIdx) {
+    gController.onClick = () => {
+      gController.onClick = null;
+      ack();
+    };
+    let row = gController._table.children[suggestionIdx];
+    content.sendMouseEvent({ type: "mousedown" }, row);
   },
 
   addInputValueToFormHistory: function () {
     gController.addInputValueToFormHistory();
-    ack("addInputValueToFormHistory");
+    ack();
   },
 
   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("reset");
+    ack();
   },
 };
 
-function ack(aType, aData) {
-  sendAsyncMessage(TEST_MSG, { type: aType, data: aData || currentState() });
+function ack() {
+  sendAsyncMessage(TEST_MSG, 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._table.hidden ? 0 : gController.numSuggestions,
+    numSuggestions: gController.numSuggestions,
     suggestionAtIndex: [],
     isFormHistorySuggestionAtIndex: [],
 
     tableHidden: gController._table.hidden,
+    tableChildrenLength: gController._table.children.length,
+    tableChildren: [],
 
     inputValue: gController.input.value,
     ariaExpanded: gController.input.getAttribute("aria-expanded"),
   };
 
-  if (state.numSuggestions) {
-    for (let i = 0; i < gController.numSuggestions; i++) {
-      state.suggestionAtIndex.push(gController.suggestionAtIndex(i));
-      state.isFormHistorySuggestionAtIndex.push(
-        gController.isFormHistorySuggestionAtIndex(i));
-    }
+  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+/)),
+    });
   }
 
   return state;
 }
 
 })();
--- a/browser/base/content/test/newtab/browser_newtab_search.js
+++ b/browser/base/content/test/newtab/browser_newtab_search.js
@@ -71,16 +71,23 @@ 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);
@@ -101,24 +108,37 @@ 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().remoteTimeout = 5000;
+  gSearch()._suggestionController.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"]);
@@ -284,36 +304,51 @@ 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().defaultEngine.name, engine.name,
+  is(gSearch().currentEngineName, 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._contentSearchController;
+  return getContentWindow().gSearch;
 }
 
 /**
  * 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,33 +717,24 @@ function whenPagesUpdated(aCallback = Te
   });
 }
 
 /**
  * Waits for the response to the page's initial search state request.
  */
 function whenSearchInitDone() {
   let deferred = Promise.defer();
-  let searchController = getContentWindow().gSearch._contentSearchController;
-  if (searchController.defaultEngine) {
+  if (getContentWindow().gSearch._initialStateReceived) {
     return Promise.resolve();
   }
   let eventName = "ContentSearchService";
   getContentWindow().addEventListener(eventName, function onEvent(event) {
     if (event.detail.type == "State") {
       getContentWindow().removeEventListener(eventName, onEvent);
-      // Wait for the search controller to receive the event, then resolve.
-      let resolver = function() {
-        if (searchController.defaultEngine) {
-          deferred.resolve();
-          return;
-        }
-        executeSoon(resolver);
-      }
-      executeSoon(resolver);
+      deferred.resolve();
     }
   });
   return deferred.promise;
 }
 
 /**
  * Changes the newtab customization option and waits for the panel to open and close
  *
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -139,18 +139,18 @@ browser.jar:
 #endif
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
         content/browser/safeMode.xul                  (content/safeMode.xul)
 *       content/browser/sanitize.js                   (content/sanitize.js)
 *       content/browser/sanitize.xul                  (content/sanitize.xul)
 *       content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
-        content/browser/contentSearchUI.js            (content/contentSearchUI.js)
-        content/browser/contentSearchUI.css           (content/contentSearchUI.css)
+        content/browser/searchSuggestionUI.js         (content/searchSuggestionUI.js)
+        content/browser/searchSuggestionUI.css        (content/searchSuggestionUI.css)
         content/browser/tabbrowser.css                (content/tabbrowser.css)
         content/browser/tabbrowser.xml                (content/tabbrowser.xml)
         content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
 *       content/browser/utilityOverlay.js             (content/utilityOverlay.js)
         content/browser/web-panels.js                 (content/web-panels.js)
 *       content/browser/web-panels.xul                (content/web-panels.xul)
 *       content/browser/baseMenuOverlay.xul           (content/baseMenuOverlay.xul)
 *       content/browser/nsContextMenu.js              (content/nsContextMenu.js)
--- a/browser/components/loop/test/mochitest/browser_mozLoop_context.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_context.js
@@ -25,19 +25,17 @@ add_task(function* test_mozLoop_getSelec
 
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   yield promiseTabLoadEvent(tab, "about:home");
   metadata = yield promiseGetMetadata();
 
   Assert.strictEqual(metadata.url, null, "URL should be empty for about:home");
   Assert.strictEqual(metadata.favicon, null, "Favicon should be empty for about:home");
   Assert.ok(metadata.title, "Title should be set for about:home");
-  // Filter out null elements in the previews - contentSearchUI adds some img
-  // elements with chrome:// srcs, which show up as null in metadata.previews.
-  Assert.deepEqual(metadata.previews.filter(e => e), [], "No previews available for about:home");
+  Assert.deepEqual(metadata.previews, [], "No previews available for about:home");
 
   gBrowser.removeTab(tab);
 });
 
 add_task(function* test_mozLoop_getSelectedTabMetadata_defaultIcon() {
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   yield promiseTabLoadEvent(tab, "http://example.com/");
   let metadata = yield promiseGetMetadata();
--- a/browser/locales/en-US/chrome/browser/aboutHome.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutHome.dtd
@@ -6,17 +6,17 @@
 %brandDTD;
 <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
 %syncBrandDTD;
 
 <!-- These strings are used in the about:home page -->
 
 <!ENTITY abouthome.pageTitle "&brandFullName; Start Page">
 
-<!ENTITY abouthome.search.placeholder    "Search">
+<!ENTITY abouthome.searchEngineButton.label "Search">
 
 <!-- LOCALIZATION NOTE (abouthome.defaultSnippet1.v1):
      text in <a/> will be linked to the Firefox features page on mozilla.com
 -->
 <!ENTITY abouthome.defaultSnippet1.v1 "Thanks for choosing Firefox! To get the most out of your browser, learn more about the <a>latest features</a>.">
 <!-- LOCALIZATION NOTE (abouthome.defaultSnippet2.v1):
      text in <a/> will be linked to the featured add-ons on addons.mozilla.org
 -->
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -416,22 +416,16 @@ These should match what Safari and other
   is a fundamental keybinding and we are maintaining a XP binding so that it is easy
   for people to switch to Linux.
 
  -->
 <!ENTITY searchFocus.commandkey       "k">
 <!ENTITY searchFocus.commandkey2      "e">
 <!ENTITY searchFocusUnix.commandkey   "j">
 
-<!-- LOCALIZATION NOTE (contentSearchInput.label, contentSearchSubmit.label):
-     These are set as the aria-label attribute for the search input box and
-     submit button in the in-content search UI, to be used by screen readers. -->
-<!ENTITY contentSearchInput.label     "Search query">
-<!ENTITY contentSearchSubmit.label    "Submit search">
-
 <!-- LOCALIZATION NOTE (searchFor.label, searchWith.label):
      These two strings are used to build the header above the list of one-click
      search providers:  "Search for <used typed keywords> with:" -->
 <!ENTITY searchFor.label              "Search for ">
 <!ENTITY searchWith.label             " with:">
 
 <!-- LOCALIZATION NOTE (search.label, searchAfter.label):
      This string is used to build the header above the list of one-click search
--- a/browser/locales/en-US/chrome/browser/search.properties
+++ b/browser/locales/en-US/chrome/browser/search.properties
@@ -27,20 +27,8 @@ cmd_showSuggestions_accesskey=S
 # a search engine offered by a web page. Each engine is displayed as a
 # menuitem at the bottom of the search panel.
 cmd_addFoundEngine=Add "%S"
 # LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines
 # are offered by a web page, instead of listing all of them in the
 # search panel using the cmd_addFoundEngine string, they will be
 # grouped in a submenu using cmd_addFoundEngineMenu as a label.
 cmd_addFoundEngineMenu=Add search engine
-
-# LOCALIZATION NOTE (searchFor, searchWith):
-# These two strings are used to build the header above the list of one-click
-# search providers:  "Search for <user-typed keywords> with:"
-searchFor=Search for 
-searchWith= with:
-
-# LOCALIZATION NOTE (searchWithHeader):
-# The wording of this string should be as close as possible to
-# searchFor and searchWith. This string will be used instead of
-# them when the user has not typed any keyword.
-searchWithHeader=Search with:
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -95,24 +95,36 @@ let AboutHome = {
     "AboutHome:Downloads",
     "AboutHome:Bookmarks",
     "AboutHome:History",
     "AboutHome:Apps",
     "AboutHome:Addons",
     "AboutHome:Sync",
     "AboutHome:Settings",
     "AboutHome:RequestUpdate",
+    "AboutHome:Search",
+    "AboutHome:OpenSearchPanel",
   ],
 
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
 
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
     }
+
+    Services.obs.addObserver(this, "browser-search-engine-modified", false);
+  },
+
+  observe: function(aEngine, aTopic, aVerb) {
+    switch (aTopic) {
+      case "browser-search-engine-modified":
+        this.sendAboutHomeData(null);
+        break;
+    }
   },
 
   receiveMessage: function(aMessage) {
     let window = aMessage.target.ownerDocument.defaultView;
 
     switch (aMessage.name) {
       case "AboutHome:RestorePreviousSession":
         let ss = Cc["@mozilla.org/browser/sessionstore;1"].
@@ -162,33 +174,105 @@ let AboutHome = {
 
       case "AboutHome:Settings":
         window.openPreferences();
         break;
 
       case "AboutHome:RequestUpdate":
         this.sendAboutHomeData(aMessage.target);
         break;
+
+      case "AboutHome:Search":
+        let data;
+        try {
+          data = JSON.parse(aMessage.data.searchData);
+        } catch(ex) {
+          Cu.reportError(ex);
+          break;
+        }
+
+        Services.search.init(function(status) {
+          if (!Components.isSuccessCode(status)) {
+            return;
+          }
+
+          let engine = Services.search.currentEngine;
+          if (AppConstants.MOZ_SERVICES_HEALTHREPORT) {
+            window.BrowserSearch.recordSearchInHealthReport(engine, "abouthome", data.selection);
+          }
+
+          // Trigger a search through nsISearchEngine.getSubmission()
+          let submission = engine.getSubmission(data.searchTerms, null, "homepage");
+          let where = window.whereToOpenLink(data.originalEvent);
+
+          // There is a chance that by the time we receive the search message, the
+          // user has switched away from the tab that triggered the search. If,
+          // based on the event, we need to load the search in the same tab that
+          // triggered it (i.e. where == "current"), openUILinkIn will not work
+          // because that tab is no longer the current one. For this case we
+          // manually load the URI in the target browser.
+          if (where == "current") {
+            aMessage.target.loadURIWithFlags(submission.uri.spec,
+                                             Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+                                             null, null, submission.postData);
+          } else {
+            let params = {
+              postData: submission.postData,
+              inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
+            };
+            window.openLinkIn(submission.uri.spec, where, params);
+          }
+          // Used for testing
+          let mm = aMessage.target.messageManager;
+          mm.sendAsyncMessage("AboutHome:SearchTriggered", aMessage.data.searchData);
+        });
+
+        break;
+
+      case "AboutHome:OpenSearchPanel":
+        let panel = window.document.getElementById("abouthome-search-panel");
+        let anchor = aMessage.objects.anchor;
+        panel.hidden = false;
+        panel.openPopup(anchor);
+        anchor.setAttribute("active", "true");
+        panel.addEventListener("popuphidden", function onHidden() {
+          panel.removeEventListener("popuphidden", onHidden);
+          anchor.removeAttribute("active");
+        });
+        break;
     }
   },
 
   // Send all the chrome-privileged data needed by about:home. This
   // gets re-sent when the search engine changes.
   sendAboutHomeData: function(target) {
     let wrapper = {};
     Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
       wrapper);
     let ss = wrapper.SessionStore;
 
     ss.promiseInitialized.then(function() {
+      let deferred = Promise.defer();
+
+      Services.search.init(function (status){
+        if (!Components.isSuccessCode(status)) {
+          deferred.reject(status);
+        } else {
+          deferred.resolve(Services.search.defaultEngine.name);
+        }
+      });
+
+      return deferred.promise;
+    }).then(function(engineName) {
       let data = {
         showRestoreLastSession: ss.canRestoreLastSession,
         snippetsURL: AboutHomeUtils.snippetsURL,
         showKnowYourRights: AboutHomeUtils.showKnowYourRights,
         snippetsVersion: AboutHomeUtils.snippetsVersion,
+        defaultEngineName: engineName
       };
 
       if (AboutHomeUtils.showKnowYourRights) {
         // Set pref to indicate we've shown the notification.
         let currentVersion = Services.prefs.getIntPref("browser.rights.version");
         Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
       }
 
@@ -196,10 +280,19 @@ let AboutHome = {
         target.messageManager.sendAsyncMessage("AboutHome:Update", data);
       } else {
         let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
         mm.broadcastAsyncMessage("AboutHome:Update", data);
       }
     }).then(null, function onError(x) {
       Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
     });
+  },
+
+  /**
+   * Focuses the search input in the page with the given message manager.
+   * @param  messageManager
+   *         The MessageManager object of the selected browser.
+   */
+  focusInput: function (messageManager) {
+    messageManager.sendAsyncMessage("AboutHome:FocusInput");
   }
 };
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -40,28 +40,25 @@ const MAX_SUGGESTIONS = 6;
  *     Adds an entry to the search form history.
  *     data: the entry, a string
  *   GetSuggestions
  *     Retrieves an array of search suggestions given a search string.
  *     data: { engineName, searchString, [remoteTimeout] }
  *   GetState
  *     Retrieves the current search engine state.
  *     data: null
- *   GetStrings
- *     Retrieves localized search UI strings.
- *     data: null
  *   ManageEngines
  *     Opens the search engine management window.
  *     data: null
  *   RemoveFormHistoryEntry
  *     Removes an entry from the search form history.
  *     data: the entry, a string
  *   Search
  *     Performs a search.
- *     data: { engineName, searchString, healthReportKey, searchPurpose }
+ *     data: { engineName, searchString, whence }
  *   SetCurrentEngine
  *     Sets the current engine.
  *     data: the name of the engine
  *   SpeculativeConnect
  *     Speculatively connects to an engine.
  *     data: the name of the engine
  *
  * Outbound messages have the following types:
@@ -70,19 +67,16 @@ const MAX_SUGGESTIONS = 6;
  *     Broadcast when the current engine changes.
  *     data: see _currentEngineObj
  *   CurrentState
  *     Broadcast when the current search state changes.
  *     data: see _currentStateObj
  *   State
  *     Sent in reply to GetState.
  *     data: see _currentStateObj
- *   Strings
- *     Sent in reply to GetStrings
- *     data: Object containing string names and values for the current locale.
  *   Suggestions
  *     Sent in reply to GetSuggestions.
  *     data: see _onMessageGetSuggestions
  */
 
 this.ContentSearch = {
 
   // Inbound events are queued and processed in FIFO order instead of handling
@@ -99,34 +93,19 @@ this.ContentSearch = {
   _destroyedPromise: null,
 
   init: function () {
     Cc["@mozilla.org/globalmessagemanager;1"].
       getService(Ci.nsIMessageListenerManager).
       addMessageListener(INBOUND_MESSAGE, this);
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
     Services.obs.addObserver(this, "shutdown-leaks-before-check", false);
-    Services.prefs.addObserver("browser.search.hiddenOneOffs", this, false);
     this._stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
   },
 
-  get searchSuggestionUIStrings() {
-    if (this._searchSuggestionUIStrings) {
-      return this._searchSuggestionUIStrings;
-    }
-    this._searchSuggestionUIStrings = {};
-    let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
-    let stringNames = ["searchHeader", "searchPlaceholder", "searchFor",
-                       "searchWith", "searchWithHeader"];
-    for (let name of stringNames) {
-      this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
-    }
-    return this._searchSuggestionUIStrings;
-  },
-
   destroy: function () {
     if (this._destroyedPromise) {
       return this._destroyedPromise;
     }
 
     Cc["@mozilla.org/globalmessagemanager;1"].
       getService(Ci.nsIMessageListenerManager).
       removeMessageListener(INBOUND_MESSAGE, this);
@@ -169,17 +148,16 @@ this.ContentSearch = {
       type: "Message",
       data: msg,
     });
     this._processEventQueue();
   },
 
   observe: function (subj, topic, data) {
     switch (topic) {
-    case "nsPref:changed":
     case "browser-search-engine-modified":
       this._eventQueue.push({
         type: "Observe",
         data: data,
       });
       this._processEventQueue();
       break;
     case "shutdown-leaks-before-check":
@@ -218,29 +196,24 @@ this.ContentSearch = {
   }),
 
   _onMessageGetState: function (msg, data) {
     return this._currentStateObj().then(state => {
       this._reply(msg, "State", state);
     });
   },
 
-  _onMessageGetStrings: function (msg, data) {
-    this._reply(msg, "Strings", this.searchSuggestionUIStrings);
-  },
-
   _onMessageSearch: function (msg, data) {
     this._ensureDataHasProperties(data, [
       "engineName",
       "searchString",
-      "healthReportKey",
-      "searchPurpose",
+      "whence",
     ]);
     let engine = Services.search.getEngineByName(data.engineName);
-    let submission = engine.getSubmission(data.searchString, "", data.searchPurpose);
+    let submission = engine.getSubmission(data.searchString, "", data.whence);
     let browser = msg.target;
     let win;
     try {
       win = browser.ownerDocument.defaultView;
     }
     catch (err) {
       // The browser may have been closed between the time its content sent the
       // message and the time we handle it.  In that case, trying to call any
@@ -261,17 +234,17 @@ this.ContentSearch = {
                                submission.postData);
     } else {
       let params = {
         postData: submission.postData,
         inBackground: Services.prefs.getBoolPref("browser.tabs.loadInBackground"),
       };
       win.openUILinkIn(submission.uri.spec, where, params);
     }
-    win.BrowserSearch.recordSearchInHealthReport(engine, data.healthReportKey,
+    win.BrowserSearch.recordSearchInHealthReport(engine, data.whence,
                                                  data.selection || null);
     return Promise.resolve();
   },
 
   _onMessageSetCurrentEngine: function (msg, data) {
     Services.search.currentEngine = Services.search.getEngineByName(data);
     return Promise.resolve();
   },
@@ -436,22 +409,17 @@ this.ContentSearch = {
     }];
   },
 
   _currentStateObj: Task.async(function* () {
     let state = {
       engines: [],
       currentEngine: yield this._currentEngineObj(),
     };
-    let pref = Services.prefs.getCharPref("browser.search.hiddenOneOffs");
-    let hiddenList = pref ? pref.split(",") : [];
     for (let engine of Services.search.getVisibleEngines()) {
-      if (hiddenList.indexOf(engine.name) != -1) {
-        continue;
-      }
       let uri = engine.getIconURLBySize(16, 16);
       state.engines.push({
         name: engine.name,
         iconBuffer: yield this._arrayBufferFromDataURI(uri),
       });
     }
     return state;
   }),
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -668,17 +668,17 @@ let DirectoryLinksProvider = {
    * @param action String of the behavior to report
    * @param triggeringSiteIndex optional Int index of the site triggering action
    * @return download promise
    */
   reportSitesAction: function DirectoryLinksProvider_reportSitesAction(sites, action, triggeringSiteIndex) {
     let pastImpressions;
     // Check if the suggested tile was shown
     if (action == "view") {
-      sites.slice(0, triggeringSiteIndex + 1).filter(s => s).forEach(site => {
+      sites.slice(0, triggeringSiteIndex + 1).forEach(site => {
         let {targetedSite, url} = site.link;
         if (targetedSite) {
           this._addFrequencyCapView(url);
         }
       });
     }
     // any click action on a suggested tile should stop that tile suggestion
     // click/block - user either removed a tile or went to a landing page
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -89,18 +89,17 @@ add_task(function* modifyEngine() {
 });
 
 add_task(function* search() {
   yield addTab();
   let engine = Services.search.currentEngine;
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
-    healthReportKey: "ContentSearchTest",
-    searchPurpose: "ContentSearchTest",
+    whence: "ContentSearchTest",
   };
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: "Search",
     data: data,
   });
   let submissionURL =
     engine.getSubmission(data.searchString, "", data.whence).uri.spec;
   yield waitForLoadAndStopIt(gBrowser.selectedBrowser, submissionURL);
@@ -112,18 +111,17 @@ add_task(function* searchInBackgroundTab
   // search page should be loaded in the same tab that performed the search, in
   // the background tab.
   yield addTab();
   let searchBrowser = gBrowser.selectedBrowser;
   let engine = Services.search.currentEngine;
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
-    healthReportKey: "ContentSearchTest",
-    searchPurpose: "ContentSearchTest",
+    whence: "ContentSearchTest",
   };
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: "Search",
     data: data,
   });
 
   let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -88,18 +88,16 @@ browser.jar:
   skin/classic/browser/reload-stop-go@2x.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/search-pref.png                      (../shared/search/search-pref.png)
   skin/classic/browser/search-indicator.png                 (../shared/search/search-indicator.png)
   skin/classic/browser/search-engine-placeholder.png        (../shared/search/search-engine-placeholder.png)
   skin/classic/browser/badge-add-engine.png                 (../shared/search/badge-add-engine.png)
   skin/classic/browser/search-indicator-badge-add.png       (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-history-icon.svg              (../shared/search/history-icon.svg)
-  skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
-  skin/classic/browser/search-arrow-go.svg                  (../shared/search/search-arrow-go.svg)
   skin/classic/browser/Security-broken.png
   skin/classic/browser/setDesktopBackground.css
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/theme-switcher-icon.png              (../shared/theme-switcher-icon.png)
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar-inverted.png
   skin/classic/browser/Toolbar-small.png
   skin/classic/browser/undoCloseTab.png                        (../shared/undoCloseTab.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -119,18 +119,16 @@ browser.jar:
   skin/classic/browser/search-indicator@2x.png                 (../shared/search/search-indicator@2x.png)
   skin/classic/browser/search-engine-placeholder.png           (../shared/search/search-engine-placeholder.png)
   skin/classic/browser/search-engine-placeholder@2x.png        (../shared/search/search-engine-placeholder@2x.png)
   skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
   skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
   skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
   skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
   skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
-  skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
-  skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
   skin/classic/browser/slowStartup-16.png
   skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
   skin/classic/browser/theme-switcher-icon@2x.png              (../shared/theme-switcher-icon@2x.png)
   skin/classic/browser/Toolbar.png
   skin/classic/browser/Toolbar@2x.png
   skin/classic/browser/Toolbar-inverted.png
   skin/classic/browser/Toolbar-inverted@2x.png
   skin/classic/browser/toolbarbutton-dropmarker.png
deleted file mode 100644
--- a/browser/themes/shared/search/search-arrow-go.svg
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-  <style>
-    use:not(:target) {
-      display: none;
-    }
-    use {
-      fill: #616366;
-    }
-    use[id$="-inverted"] {
-      fill: #fff;
-    }
-  </style>
-  <defs>
-    <path id="search-arrow-go-glyph" d="M1,7v2.2C1,9.8,1.4,10,2,10h7.5l-3,3.1c-0.4,0.3-0.4,1,0,1.4l0.8,0.8 c0.4,0.4,1,0.4,1.4,0l6.6-6.6c0.4-0.4,0.4-1,0-1.4L8.7,0.7c-0.4-0.4-1-0.4-1.4,0L6.5,1.6C6.1,2,6.1,2.6,6.5,3l3,3H2C1.4,6,1,6.4,1,7z"/>
-  </defs>
-  <use id="search-arrow-go" xlink:href="#search-arrow-go-glyph"/>
-  <use id="search-arrow-go-inverted" xlink:href="#search-arrow-go-glyph"/>
-</svg>
deleted file mode 100644
--- a/browser/themes/shared/search/search-indicator-magnifying-glass.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
-  <path fill="#808080" d="M21.7,20.3l-1.4,1.4l-5.4-5.4c-1.3,1-3,1.7-4.9,1.7 c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8c4.4,0,8,3.6,8,8c0,1.8-0.6,3.5-1.7,4.9L21.7,20.3z M10,4c-3.3,0-6,2.7-6,6s2.7,6,6,6s6-2.7,6-6 S13.3,4,10,4z"/>
-</svg>
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -112,18 +112,16 @@ browser.jar:
         skin/classic/browser/search-indicator@2x.png                 (../shared/search/search-indicator@2x.png)
         skin/classic/browser/search-engine-placeholder.png           (../shared/search/search-engine-placeholder.png)
         skin/classic/browser/search-engine-placeholder@2x.png        (../shared/search/search-engine-placeholder@2x.png)
         skin/classic/browser/badge-add-engine.png                    (../shared/search/badge-add-engine.png)
         skin/classic/browser/badge-add-engine@2x.png                 (../shared/search/badge-add-engine@2x.png)
         skin/classic/browser/search-indicator-badge-add.png          (../shared/search/search-indicator-badge-add.png)
         skin/classic/browser/search-indicator-badge-add@2x.png       (../shared/search/search-indicator-badge-add@2x.png)
         skin/classic/browser/search-history-icon.svg                 (../shared/search/history-icon.svg)
-        skin/classic/browser/search-indicator-magnifying-glass.svg   (../shared/search/search-indicator-magnifying-glass.svg)
-        skin/classic/browser/search-arrow-go.svg                     (../shared/search/search-arrow-go.svg)
         skin/classic/browser/setDesktopBackground.css
         skin/classic/browser/slowStartup-16.png
         skin/classic/browser/theme-switcher-icon.png                 (../shared/theme-switcher-icon.png)
         skin/classic/browser/Toolbar.png
         skin/classic/browser/Toolbar@2x.png
         skin/classic/browser/Toolbar-aero.png
         skin/classic/browser/Toolbar-aero@2x.png
         skin/classic/browser/Toolbar-inverted.png