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 284896 15155971639cfd143cc58ee8f205c3900a2b3828
parent 284895 403f2f4cd9783f19abf010c2bde33679aaabe00e
child 284897 689cd81e0f3cdf7fb735deddf6f1761f81c011d4
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1171344
milestone42.0a1
backs out7f2ddcfe4537c52265ce168b3ab182892e7dbcc0
e88770aa21600f3afdefd06dc643eb83e8da6b84
first release with
nightly linux32
15155971639c / 42.0a1 / 20150717030202 / files
nightly linux64
15155971639c / 42.0a1 / 20150717030202 / files
nightly mac
15155971639c / 42.0a1 / 20150717030202 / files
nightly win32
15155971639c / 42.0a1 / 20150717030202 / files
nightly win64
15155971639c / 42.0a1 / 20150717030202 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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