Bug 1561894 - Implement simplified one-off search button design. r=dao
authorharry <htwyford@mozilla.com>
Tue, 13 Aug 2019 19:03:20 +0000
changeset 487742 7af558091d18077a0a09d8528027c73a0b7eab9f
parent 487741 b134d9db39a9da2cdb1a4b7c19e6023063fde7f4
child 487743 08e2e7007fe16fc8fe8c253c20b75114752f1c03
push id36430
push userdvarga@mozilla.com
push dateWed, 14 Aug 2019 04:09:17 +0000
treeherdermozilla-central@d3deef805f92 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao
bugs1561894
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1561894 - Implement simplified one-off search button design. r=dao Differential Revision: https://phabricator.services.mozilla.com/D39751
browser/components/search/content/search-one-offs.js
browser/components/search/test/browser/browser.ini
browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
browser/components/search/test/browser/browser_oneOffHeader.js
browser/components/search/test/browser/head.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/osx/searchbar.css
browser/themes/shared/searchbar.inc.css
browser/themes/shared/urlbar-autocomplete.inc.css
browser/themes/windows/browser.css
--- a/browser/components/search/content/search-one-offs.js
+++ b/browser/components/search/content/search-one-offs.js
@@ -13,34 +13,23 @@
  */
 class SearchOneOffs {
   constructor(container) {
     this.container = container;
 
     this.container.appendChild(
       MozXULElement.parseXULToFragment(
         `
-      <deck class="search-panel-one-offs-header search-panel-header search-panel-current-input">
-        <label class="searchbar-oneoffheader-search" value="&searchWithHeader.label;"/>
-        <hbox class="search-panel-searchforwith search-panel-current-input">
-          <label value="&searchFor.label;"/>
-          <label class="searchbar-oneoffheader-searchtext search-panel-input-value" flex="1" crop="end"/>
-          <label flex="10000" value="&searchWith.label;"/>
-        </hbox>
-        <hbox class="search-panel-searchonengine search-panel-current-input">
-          <label value="&search.label;"/>
-          <label class="searchbar-oneoffheader-engine search-panel-input-value" flex="1" crop="end"/>
-          <label flex="10000" value="&searchAfter.label;"/>
-        </hbox>
-      </deck>
-      <description role="group" class="search-panel-one-offs">
-        <button class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"/>
-      </description>
+      <div class="search-panel-one-offs-header search-panel-header search-panel-current-input">
+        <label class="searchbar-oneoffheader-search" value="&searchWithDesc.label;"/>
+      </div>
+      <div class="search-panel-one-offs"/>
       <vbox class="search-add-engines"/>
-      <button class="search-setting-button search-panel-header" label="&changeSearchSettings.button;"/>
+      <button class="searchbar-engine-one-off-item search-setting-button-compact" tooltiptext="&changeSearchSettings.tooltip;"/>
+      <button class="search-setting-button" label="&changeSearchSettings.button;"/>
       <menupopup class="search-one-offs-context-menu">
         <menuitem class="search-one-offs-context-open-in-new-tab" label="&searchInNewTab.label;" accesskey="&searchInNewTab.accesskey;"/>
         <menuitem class="search-one-offs-context-set-default" label="&searchSetAsDefault.label;" accesskey="&searchSetAsDefault.accesskey;"/>
       </menupopup>
       `,
         ["chrome://browser/locale/browser.dtd"]
       )
     );
@@ -174,22 +163,27 @@ class SearchOneOffs {
     if (methodName in this) {
       this[methodName](event);
     } else {
       throw new Error("Unrecognized search-one-offs event: " + event.type);
     }
   }
 
   /**
-   * Width in pixels of the one-off buttons.  49px is the min-width of
-   * each search engine button, adapt this const when changing the css.
-   * It's actually 48px + 1px of right border.
+   * Width in pixels of the one-off buttons.
    */
   get buttonWidth() {
-    return 49;
+    return this.compact ? 40 : 48;
+  }
+
+  /**
+   * Height in pixels of the one-off buttons.
+   */
+  get buttonHeight() {
+    return 32;
   }
 
   /**
    * @param {UrlbarView} val
    */
   set view(val) {
     if (this._view) {
       this._view.controller.removeQueryListener(this);
@@ -269,37 +263,40 @@ class SearchOneOffs {
    *        The new query string to set.
    */
   set query(val) {
     this._query = val;
     if (
       (this._view && this._view.isOpen) ||
       (this.popup && this.popup.popupOpen)
     ) {
-      this._updateAfterQueryChanged();
+      let isOneOffSelected =
+        this.selectedButton &&
+        this.selectedButton.classList.contains("searchbar-engine-one-off-item");
+      // Typing de-selects the settings or opensearch buttons at the bottom
+      // of the search panel, as typing shows the user intends to search.
+      if (this.selectedButton && !isOneOffSelected) {
+        this.selectedButton = null;
+      }
     }
     return val;
   }
 
   get query() {
     return this._query;
   }
 
   /**
    * The selected one-off, a xul:button, including the add-engine button
    * and the search-settings button.
    *
    * @param {DOMElement|null} val
    *        The selected one-off button. Null if no one-off is selected.
    */
   set selectedButton(val) {
-    if (val && val.classList.contains("dummy")) {
-      // Never select dummy buttons.
-      val = null;
-    }
     let previousButton = this._selectedButton;
     if (previousButton) {
       previousButton.removeAttribute("selected");
     }
     if (val) {
       val.setAttribute("selected", "true");
     }
     this._selectedButton = val;
@@ -388,53 +385,16 @@ class SearchOneOffs {
   }
 
   observe(aEngine, aTopic, aData) {
     // Make sure the engine list is refetched next time it's needed.
     this._engines = null;
   }
 
   /**
-   * Updates the parts of the UI that show the query string.
-   */
-  _updateAfterQueryChanged() {
-    let headerSearchText = this.querySelector(
-      ".searchbar-oneoffheader-searchtext"
-    );
-    headerSearchText.setAttribute("value", this.query);
-    let groupText;
-    let isOneOffSelected =
-      this.selectedButton &&
-      this.selectedButton.classList.contains("searchbar-engine-one-off-item");
-    // Typing de-selects the settings or opensearch buttons at the bottom
-    // of the search panel, as typing shows the user intends to search.
-    if (this.selectedButton && !isOneOffSelected) {
-      this.selectedButton = null;
-    }
-    if (this.query) {
-      groupText =
-        headerSearchText.previousElementSibling.value +
-        '"' +
-        headerSearchText.value +
-        '"' +
-        headerSearchText.nextElementSibling.value;
-      if (!isOneOffSelected) {
-        this.header.selectedIndex = 1;
-      }
-    } else {
-      let noSearchHeader = this.querySelector(".searchbar-oneoffheader-search");
-      groupText = noSearchHeader.value;
-      if (!isOneOffSelected) {
-        this.header.selectedIndex = 0;
-      }
-    }
-    this.buttons.setAttribute("aria-label", groupText);
-  }
-
-  /**
    * Infallible, non-re-entrant version of `__rebuild()`.
    */
   async _rebuild() {
     if (this._rebuilding) {
       return;
     }
 
     this._rebuilding = true;
@@ -449,19 +409,16 @@ class SearchOneOffs {
 
   /**
    * Builds all the UI.
    */
   async __rebuild() {
     this.selectedButton = null;
     this._contextEngine = null;
 
-    // Update the 'Search for <keywords> with:" header.
-    this._updateAfterQueryChanged();
-
     // Handle opensearch items. This needs to be done before building the
     // list of one off providers, as that code will return early if all the
     // alternative engines are hidden.
     // Skip this in compact mode, ie. for the urlbar.
     if (!this.compact) {
       this._rebuildAddEngineList();
     }
 
@@ -475,25 +432,24 @@ class SearchOneOffs {
       // width has changed.
       if (this._engines && this._textboxWidth == textboxWidth) {
         return;
       }
       this._textboxWidth = textboxWidth;
     }
 
     // Finally, build the list of one-off buttons.
-    while (this.buttons.firstElementChild != this.settingsButtonCompact) {
+    while (this.buttons.firstElementChild) {
       this.buttons.firstElementChild.remove();
     }
 
-    // Remove the trailing empty text node introduced by the binding's
-    // content markup above.
-    if (this.settingsButtonCompact.nextElementSibling) {
-      this.settingsButtonCompact.nextElementSibling.remove();
-    }
+    let headerText = this.header.querySelector(
+      ".searchbar-oneoffheader-search"
+    );
+    this.buttons.setAttribute("aria-label", headerText.value);
 
     let engines = await this.getEngines();
     let defaultEngine = await Services.search.getDefault();
     let oneOffCount = engines.length;
     let hideOneOffs =
       !oneOffCount ||
       (oneOffCount == 1 && engines[0].name == defaultEngine.name);
 
@@ -503,112 +459,79 @@ class SearchOneOffs {
       // Hide everything except the settings button.
       this.header.hidden = this.buttons.hidden = hideOneOffs;
     }
 
     if (hideOneOffs) {
       return;
     }
 
-    let panelWidth = parseInt((this.popup || this._view.panel).clientWidth);
+    // this.buttonWidth is for the compact settings button.
+    let buttonsWidth = this.compact
+      ? this._textboxWidth - this.buttonWidth - this.header.clientWidth
+      : this.popup.clientWidth;
 
     // There's one weird thing to guard against: when layout pixels
     // aren't an integral multiple of device pixels, the last button
     // of each row sometimes gets pushed to the next row, depending on the
     // panel and button widths.
     // This is likely because the clientWidth getter rounds the value, but
     // the panel's border width is not an integer.
     // As a workaround, decrement the width if the scale is not an integer.
     let scale = window.windowUtils.screenPixelsPerCSSPixel;
     if (Math.floor(scale) != scale) {
-      --panelWidth;
+      --buttonsWidth;
     }
 
-    // The + 1 is because the last button doesn't have a right border.
-    let enginesPerRow = Math.floor((panelWidth + 1) / this.buttonWidth);
-    let buttonWidth = Math.floor(panelWidth / enginesPerRow);
-    // There will be an emtpy area of:
-    //   panelWidth - enginesPerRow * buttonWidth  px
+    // If the header string is very long, then the searchbar buttons will
+    // overflow their container unless max-width is set.
+    this.buttons.style.setProperty(
+      this.compact ? "width" : "max-width",
+      `${buttonsWidth}px`
+    );
+
+    // 24: 12px left padding + 12px right padding.
+    if (this.compact) {
+      buttonsWidth -= 24;
+    }
+
+    // In very narrow windows, we should always have at least one button
+    // per row.
+    buttonsWidth = Math.max(buttonsWidth, this.buttonWidth);
+
+    let enginesPerRow = Math.floor(buttonsWidth / this.buttonWidth);
+    // There will be an empty area of:
+    //   buttonsWidth - enginesPerRow * this.buttonWidth  px
     // at the end of each row.
 
-    // If the <description> tag with the list of search engines doesn't have
+    // If the <div> with the list of search engines doesn't have
     // a fixed height, the panel will be sized incorrectly, causing the bottom
     // of the suggestion <tree> to be hidden.
-    if (this.compact) {
-      ++oneOffCount;
-    }
     let rowCount = Math.ceil(oneOffCount / enginesPerRow);
-    let height = rowCount * 33; // 32px per row, 1px border.
-    this.buttons.setAttribute("height", height + "px");
-
+    let height = rowCount * this.buttonHeight;
+    this.buttons.style.setProperty("height", `${height}px`);
     // Ensure we can refer to the settings buttons by ID:
     let origin = this.telemetryOrigin;
     this.settingsButton.id = origin + "-anon-search-settings";
     this.settingsButtonCompact.id = origin + "-anon-search-settings-compact";
 
-    let dummyItems =
-      enginesPerRow - (oneOffCount % enginesPerRow || enginesPerRow);
     for (let i = 0; i < engines.length; ++i) {
       let engine = engines[i];
       let button = document.createXULElement("button");
       button.id = this._buttonIDForEngine(engine);
       let uri = "chrome://browser/skin/search-engine-placeholder.png";
       if (engine.iconURI) {
         uri = engine.iconURI.spec;
       }
       button.setAttribute("image", uri);
       button.setAttribute("class", "searchbar-engine-one-off-item");
       button.setAttribute("tooltiptext", engine.name);
-      button.setAttribute("width", buttonWidth);
       button.engine = engine;
 
-      if ((i + 1) % enginesPerRow == 0) {
-        button.classList.add("last-of-row");
-      }
-
-      if (i + 1 == engines.length) {
-        button.classList.add("last-engine");
-      }
-
-      if (i >= oneOffCount + dummyItems - enginesPerRow) {
-        button.classList.add("last-row");
-      }
-
-      this.buttons.insertBefore(button, this.settingsButtonCompact);
-    }
-
-    let hasDummyItems = !!dummyItems;
-    while (dummyItems) {
-      let button = document.createXULElement("button");
-      button.setAttribute(
-        "class",
-        "searchbar-engine-one-off-item dummy last-row"
-      );
-      button.setAttribute("width", buttonWidth);
-
-      if (!--dummyItems) {
-        button.classList.add("last-of-row");
-      }
-
-      this.buttons.insertBefore(button, this.settingsButtonCompact);
-    }
-
-    if (this.compact) {
-      this.settingsButtonCompact.setAttribute("width", buttonWidth);
-      if (rowCount == 1 && hasDummyItems) {
-        // When there's only one row, make the compact settings button
-        // hug the right edge of the panel.  It may not due to the panel's
-        // width not being an integral multiple of the button width.  (See
-        // the "There will be an emtpy area" comment above.)  Increase the
-        // width of the last dummy item by the remainder.
-        let remainder = panelWidth - enginesPerRow * buttonWidth;
-        let width = remainder + buttonWidth;
-        let lastDummyItem = this.settingsButtonCompact.previousElementSibling;
-        lastDummyItem.setAttribute("width", width);
-      }
+      this.buttons.appendChild(button);
     }
   }
 
   _rebuildAddEngineList() {
     let list = this.addEngines;
     while (list.firstChild) {
       list.firstChild.remove();
     }
@@ -727,70 +650,37 @@ class SearchOneOffs {
    * button.
    *
    * @param {DOMElement} mousedOverButton
    *        The currently moused-over button, or null if there isn't one.
    */
   _updateStateForButton(mousedOverButton) {
     let button = mousedOverButton;
 
-    // Ignore dummy buttons.
-    if (button && button.classList.contains("dummy")) {
-      button = null;
-    }
-
     // If there's no moused-over button, then the one-offs should reflect
     // the selected button, if any.
     button = button || this.selectedButton;
 
-    if (!button) {
-      this.header.selectedIndex = this.query ? 1 : 0;
-      if (this.textbox) {
+    if (this.textbox) {
+      if (!button) {
         this.textbox.removeAttribute("aria-activedescendant");
+      } else {
+        this.textbox.setAttribute("aria-activedescendant", button.id);
       }
-      return;
-    }
-
-    if (
-      button.classList.contains("searchbar-engine-one-off-item") &&
-      button.engine
-    ) {
-      let headerEngineText = this.querySelector(
-        ".searchbar-oneoffheader-engine"
-      );
-      this.header.selectedIndex = 2;
-      headerEngineText.value = button.engine.name;
-    } else {
-      this.header.selectedIndex = this.query ? 1 : 0;
-    }
-    if (this.textbox) {
-      this.textbox.setAttribute("aria-activedescendant", button.id);
     }
   }
 
   getSelectableButtons(aIncludeNonEngineButtons) {
     let buttons = [];
     for (
       let oneOff = this.buttons.firstElementChild;
       oneOff;
       oneOff = oneOff.nextElementSibling
     ) {
-      // oneOff may be a text node since the list xul:description contains
-      // whitespace and the compact settings button.  See the markup
-      // above.  _rebuild removes text nodes, but it may not have been
-      // called yet (because e.g. the popup hasn't been opened yet).
-      if (oneOff.nodeType == Node.ELEMENT_NODE) {
-        if (
-          oneOff.classList.contains("dummy") ||
-          oneOff.classList.contains("search-setting-button-compact")
-        ) {
-          break;
-        }
-        buttons.push(oneOff);
-      }
+      buttons.push(oneOff);
     }
 
     if (aIncludeNonEngineButtons) {
       for (
         let addEngine = this.addEngines.firstElementChild;
         addEngine;
         addEngine = addEngine.nextElementSibling
       ) {
@@ -845,17 +735,17 @@ class SearchOneOffs {
 
   /**
    * Increments or decrements the index of the currently selected one-off.
    *
    * @param {boolean} aForward
    *        If true, the index is incremented, and if false, the index is
    *        decremented.
    * @param {boolean} aIncludeNonEngineButtons
-   *        If true, non-dummy buttons that do not have engines are included.
+   *        If true, buttons that do not have engines are included.
    *        These buttons include the OpenSearch and settings buttons.  For
    *        example, if the currently selected button is an engine button,
    *        the next button is the settings button, and you pass true for
    *        aForward, then passing true for this value would cause the
    *        settings to be selected.  Passing false for this value would
    *        cause the selection to clear or wrap around, depending on what
    *        value you passed for the aWrapAround parameter.
    * @param {boolean} aWrapAround
@@ -1197,19 +1087,17 @@ class SearchOneOffs {
       return;
     }
 
     // Ignore mouse events when the context menu is open.
     if (this._ignoreMouseEvents) {
       return;
     }
 
-    let isOneOff =
-      target.classList.contains("searchbar-engine-one-off-item") &&
-      !target.classList.contains("dummy");
+    let isOneOff = target.classList.contains("searchbar-engine-one-off-item");
     if (
       isOneOff ||
       target.classList.contains("addengine-item") ||
       target.classList.contains("search-setting-button")
     ) {
       this._updateStateForButton(target);
     }
   }
@@ -1344,18 +1232,17 @@ class SearchOneOffs {
     }
   }
 
   _on_contextmenu(event) {
     let target = event.originalTarget;
     // Prevent the context menu from appearing except on the one off buttons.
     if (
       !target.classList.contains("searchbar-engine-one-off-item") ||
-      target.classList.contains("search-setting-button-compact") ||
-      target.classList.contains("dummy")
+      target.classList.contains("search-setting-button-compact")
     ) {
       event.preventDefault();
       return;
     }
     this.querySelector(".search-one-offs-context-set-default").setAttribute(
       "disabled",
       target.engine == Services.search.defaultEngine
     );
--- a/browser/components/search/test/browser/browser.ini
+++ b/browser/components/search/test/browser/browser.ini
@@ -34,18 +34,16 @@ skip-if = verify && debug && os == 'win'
 [browser_google_behavior.js]
 [browser_healthreport.js]
 skip-if = (verify && debug && (os == 'win' || os == 'linux'))
 [browser_hiddenOneOffs_cleanup.js]
 [browser_hiddenOneOffs_diacritics.js]
 [browser_oneOffContextMenu.js]
 skip-if = verify
 [browser_oneOffContextMenu_setDefault.js]
-[browser_oneOffHeader.js]
-skip-if = os == "mac" #1421238
 [browser_private_search_perwindowpb.js]
 [browser_searchbar_openpopup.js]
 skip-if = os == "linux" # Linux has different focus behaviours.
 [browser_searchbar_keyboard_navigation.js]
 [browser_searchbar_smallpanel_keyboard_navigation.js]
 [browser_searchEngine_behaviors.js]
 [browser_searchTelemetry.js]
 skip-if = !debug && (os == 'linux') # Bug 1515466
--- a/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
+++ b/browser/components/search/test/browser/browser_oneOffContextMenu_setDefault.js
@@ -184,26 +184,33 @@ async function openPopupAndGetEngineButt
     await UrlbarTestUtils.promiseAutocompleteResultPopup({
       window,
       waitForFocus,
       value: "a",
     });
   }
 
   const contextMenu = oneOffInstance.contextMenuPopup;
-  const oneOffButtons = oneOffInstance.buttons;
+  let oneOffButton = oneOffInstance.buttons;
 
   // Get the one-off button for the test engine.
-  let oneOffButton;
-  for (let node of oneOffButtons.children) {
-    if (node.engine && node.engine.name == TEST_ENGINE_NAME) {
-      oneOffButton = node;
+  for (
+    oneOffButton = oneOffButton.firstChild;
+    oneOffButton;
+    oneOffButton = oneOffButton.nextSibling
+  ) {
+    if (
+      oneOffButton.nodeType == Node.ELEMENT_NODE &&
+      oneOffButton.engine &&
+      oneOffButton.engine.name == TEST_ENGINE_NAME
+    ) {
       break;
     }
   }
+
   Assert.notEqual(
     oneOffButton,
     undefined,
     "One-off for test engine should exist"
   );
   Assert.equal(
     oneOffButton.getAttribute("tooltiptext"),
     TEST_ENGINE_NAME,
deleted file mode 100644
--- a/browser/components/search/test/browser/browser_oneOffHeader.js
+++ /dev/null
@@ -1,176 +0,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/. */
-// Tests that keyboard navigation in the search panel works as designed.
-
-const isMac = "nsILocalFileMac" in Ci;
-
-const searchPopup = document.getElementById("PopupSearchAutoComplete");
-
-const oneOffsContainer = searchPopup.searchOneOffsContainer;
-const searchSettings = oneOffsContainer.querySelector(".search-setting-button");
-
-var header = oneOffsContainer.querySelector(".search-panel-one-offs-header");
-
-function getHeaderText() {
-  let headerChild = header.selectedPanel;
-  while (headerChild.hasChildNodes()) {
-    headerChild = headerChild.firstElementChild;
-  }
-  let headerStrings = [];
-  for (let label = headerChild; label; label = label.nextElementSibling) {
-    headerStrings.push(label.value);
-  }
-  return headerStrings.join("");
-}
-
-const msg = isMac ? 5 : 1;
-const utils = window.windowUtils;
-const scale = utils.screenPixelsPerCSSPixel;
-function synthesizeNativeMouseMove(aElement) {
-  let rect = aElement.getBoundingClientRect();
-  let win = aElement.ownerGlobal;
-  let x = win.mozInnerScreenX + (rect.left + rect.right) / 2;
-  let y = win.mozInnerScreenY + (rect.top + rect.bottom) / 2;
-
-  // Wait for the mousemove event to occur before continuing.
-  return new Promise((resolve, reject) => {
-    function eventOccurred(e) {
-      aElement.removeEventListener("mousemove", eventOccurred, true);
-      SimpleTest.executeSoon(resolve);
-    }
-
-    aElement.addEventListener("mousemove", eventOccurred, true);
-
-    utils.sendNativeMouseEvent(x * scale, y * scale, msg, 0, null);
-  });
-}
-
-let searchbar;
-let searchIcon;
-
-add_task(async function init() {
-  searchbar = await gCUITestUtils.addSearchBar();
-  registerCleanupFunction(() => {
-    gCUITestUtils.removeSearchBar();
-  });
-  searchIcon = searchbar.querySelector(".searchbar-search-button");
-
-  await promiseNewEngine("testEngine.xml");
-});
-
-add_task(async function test_notext() {
-  let promise = promiseEvent(searchPopup, "popupshown");
-  info("Opening search panel");
-  EventUtils.synthesizeMouseAtCenter(searchIcon, {});
-  await promise;
-
-  is(
-    header.getAttribute("selectedIndex"),
-    0,
-    "Header has the correct index selected with no search terms."
-  );
-
-  is(
-    getHeaderText(),
-    "Search with:",
-    "Search header string is correct when no search terms have been entered"
-  );
-
-  await synthesizeNativeMouseMove(searchSettings);
-  is(
-    header.getAttribute("selectedIndex"),
-    0,
-    "Header has the correct index when no search terms have been entered and the Change Search Settings button is selected."
-  );
-  is(
-    getHeaderText(),
-    "Search with:",
-    "Header has the correct text when no search terms have been entered and the Change Search Settings button is selected."
-  );
-
-  let buttons = getOneOffs();
-  await synthesizeNativeMouseMove(buttons[0]);
-  is(
-    header.getAttribute("selectedIndex"),
-    2,
-    "Header has the correct index selected when a search engine has been selected"
-  );
-  is(
-    getHeaderText(),
-    "Search " + buttons[0].engine.name,
-    "Is the header text correct when a search engine is selected and no terms have been entered."
-  );
-
-  promise = promiseEvent(searchPopup, "popuphidden");
-  info("Closing search panel");
-  EventUtils.synthesizeKey("KEY_Escape");
-  await promise;
-});
-
-add_task(async function test_text() {
-  searchbar.textbox.value = "foo";
-
-  let promise = promiseEvent(searchPopup, "popupshown");
-  info("Opening search panel");
-  SimpleTest.executeSoon(() => {
-    EventUtils.synthesizeMouseAtCenter(searchIcon, {});
-  });
-  await promise;
-
-  is(
-    header.getAttribute("selectedIndex"),
-    1,
-    "Header has the correct index selected with a search term."
-  );
-  is(
-    getHeaderText(),
-    "Search for foo with:",
-    "Search header string is correct when a search term has been entered"
-  );
-
-  let buttons = getOneOffs();
-  await synthesizeNativeMouseMove(buttons[0]);
-  is(
-    header.getAttribute("selectedIndex"),
-    2,
-    "Header has the correct index selected when a search engine has been selected"
-  );
-  is(
-    getHeaderText(),
-    "Search " + buttons[0].engine.name,
-    "Is the header text correct when search terms are entered after a search engine has been selected."
-  );
-
-  await synthesizeNativeMouseMove(searchSettings);
-  is(
-    header.getAttribute("selectedIndex"),
-    1,
-    "Header has the correct index selected when search terms have been entered and the Change Search Settings button is selected."
-  );
-  is(
-    getHeaderText(),
-    "Search for foo with:",
-    "Header has the correct text when search terms have been entered and the Change Search Settings button is selected."
-  );
-
-  // Click the "Foo Search" header at the top of the popup and make sure it
-  // loads the search results.
-  let searchbarEngine = searchPopup.searchbarEngine;
-  await synthesizeNativeMouseMove(searchbarEngine);
-  SimpleTest.executeSoon(() => {
-    EventUtils.synthesizeMouseAtCenter(searchbarEngine, {});
-  });
-
-  let url = (await Services.search.getDefault()).getSubmission(
-    searchbar.textbox.value
-  ).uri.spec;
-  await promiseTabLoadEvent(gBrowser.selectedTab, url);
-
-  // Move the cursor out of the panel area to avoid messing with other tests.
-  await synthesizeNativeMouseMove(searchbar);
-});
-
-add_task(async function cleanup() {
-  searchbar.textbox.value = "";
-});
--- a/browser/components/search/test/browser/head.js
+++ b/browser/components/search/test/browser/head.js
@@ -202,19 +202,13 @@ function promiseTabLoadEvent(tab, url) {
 // Get an array of the one-off buttons.
 function getOneOffs() {
   let oneOffs = [];
   let searchPopup = document.getElementById("PopupSearchAutoComplete");
   let oneOffsContainer = searchPopup.searchOneOffsContainer;
   let oneOff = oneOffsContainer.querySelector(".search-panel-one-offs");
   for (oneOff = oneOff.firstChild; oneOff; oneOff = oneOff.nextSibling) {
     if (oneOff.nodeType == Node.ELEMENT_NODE) {
-      if (
-        oneOff.classList.contains("dummy") ||
-        oneOff.classList.contains("search-setting-button-compact")
-      ) {
-        break;
-      }
       oneOffs.push(oneOff);
     }
   }
   return oneOffs;
 }
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -426,36 +426,21 @@ convenience of Safari and Chrome users o
 <!ENTITY contentSearchInput.label     "Search query">
 <!ENTITY contentSearchSubmit.tooltip  "Submit search">
 
 <!-- LOCALIZATION NOTE (searchInput.placeholder):
      This string is displayed in the search box when the input field is empty. -->
 <!ENTITY searchInput.placeholder      "Search">
 <!ENTITY searchIcon.tooltip           "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 (searchWithDesc.label):
+     This string prompts the user to use the list of one-click search engines in
+     the Urlbar and searchbar. -->
+<!ENTITY searchWithDesc.label         "This time, search with:">
 
-<!-- LOCALIZATION NOTE (search.label, searchAfter.label):
-     This string is used to build the header above the list of one-click search
-     providers when a one off engine has been selected.  The searchAfter text is
-     intentionally left empty for en-US and can be used by other localizations to
-     display a string after the search engine name.  This string will be displayed
-     as:  "Search <selected engine name><searchAfter.label text>" -->
-<!ENTITY search.label                 "Search ">
-<!ENTITY searchAfter.label            "">
-
-<!-- LOCALIZATION NOTE (searchWithHeader.label):
-     The wording of this string should be as close as possible to
-     searchFor.label and searchWith.label. This string will be used instead of
-     them when the user has not typed any keyword. -->
-<!ENTITY searchWithHeader.label       "Search with:">
 <!-- LOCALIZATION NOTE (changeSearchSettings.button):
      This string won't wrap, so if the translated string is longer,
      consider translating it as if it said only "Search Settings". -->
 <!ENTITY changeSearchSettings.button  "Change Search Settings">
 <!ENTITY changeSearchSettings.tooltip "Change search settings">
 
 <!ENTITY searchInNewTab.label         "Search in New Tab">
 <!ENTITY searchInNewTab.accesskey     "T">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -310,17 +310,17 @@ notification[value="translation"] menuli
   --urlbar-popup-url-color: -moz-nativehyperlinktext;
   --urlbar-popup-action-color: -moz-nativehyperlinktext;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid ThreeDShadow;
 }
 
-#urlbarView-results {
+#urlbar-results {
   font-size: 1.05em;
 }
 
 /* Bookmarking panel */
 
 %include ../shared/places/editBookmarkPanel.inc.css
 
 /* Content area */
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -313,17 +313,17 @@
   --urlbar-popup-url-color: hsl(210, 77%, 47%);
   --urlbar-popup-action-color: hsl(178, 100%, 28%);
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid #C7C7C7;
 }
 
-#urlbarView-results {
+#urlbar-results {
   font-size: 14px;
 }
 
 #BMB_bookmarksPopup[side="top"],
 #BMB_bookmarksPopup[side="bottom"] {
   margin-left: -26px;
   margin-right: -26px;
 }
--- a/browser/themes/osx/searchbar.css
+++ b/browser/themes/osx/searchbar.css
@@ -1,19 +1,14 @@
 /* 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/. */
 
 %include ../shared/searchbar.inc.css
 
-.search-panel-header,
-.addengine-item {
-  font-size: 10px;
-}
-
 .searchbar-popup {
   margin-top: 4px;
   margin-inline-start: 3px;
 }
 
 #PopupSearchAutoComplete {
   border-radius: 4px;
 }
--- a/browser/themes/shared/searchbar.inc.css
+++ b/browser/themes/shared/searchbar.inc.css
@@ -7,16 +7,18 @@
   height: 16px;
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
 .search-one-offs {
   -moz-box-orient: vertical;
+  border-top: 1px solid var(--panel-separator-color);
+  width: 100%;
 }
 
 /**
  * The borders of the various elements are specified as follows.
  *
  * The current engine always has a bottom border.
  * The search results never have a border.
  *
@@ -44,92 +46,57 @@
 .search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-current-input,
 .search-panel-tree[collapsed=true] + .search-one-offs > .search-panel-one-offs,
 .search-panel-tree[collapsed=true] + .search-one-offs > vbox > .addengine-item:last-of-type {
   border-bottom: 1px solid var(--panel-separator-color);
 }
 
 .search-panel-header {
   font-weight: normal;
-  background-color: var(--arrowpanel-dimmed);
-  border-top: 1px solid var(--panel-separator-color);
   margin: 0;
-  padding: 3px 6px;
+  padding: 3px;
   color: var(--panel-disabled-color);
 }
 
-:root[lwt-popup-brighttext] .search-panel-header {
-  color: var(--autocomplete-popup-color);
-}
-
 .search-panel-header > label {
   margin-top: 2px !important;
   margin-bottom: 1px !important;
 }
 
-.search-panel-current-input > label {
-  margin: 2px 0 !important;
-}
-
 .search-panel-input-value {
   color: var(--autocomplete-popup-color);
 }
 
 .search-panel-one-offs {
   margin: 0 !important;
-  border-top: 1px solid var(--panel-separator-color);
-  background-color: var(--arrowpanel-dimmed);
   /* Bug 1108841: prevent font-size from affecting the layout */
   line-height: 0;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
 }
 
 .searchbar-engine-one-off-item {
   -moz-appearance: none;
   display: inline-block;
   min-width: 48px;
   height: 32px;
   margin: 0;
   padding: 0;
-  background: linear-gradient(transparent 15%, var(--panel-separator-color) 15%, var(--panel-separator-color) 85%, transparent 85%);
-  background-size: 1px auto;
-  background-repeat: no-repeat;
-  background-position: right center;
   color: var(--panel-disabled-color);
 }
 
-.searchbar-engine-one-off-item:-moz-locale-dir(rtl) {
-  background-position-x: left;
-}
-
-.searchbar-engine-one-off-item:not(.last-row) {
-  box-sizing: content-box;
-  border-bottom: 1px solid var(--panel-separator-color);
-}
-
-.search-setting-button-compact {
-  border-bottom: none !important;
-}
-
-.search-one-offs:not([compact=true]) .searchbar-engine-one-off-item.last-of-row,
-.search-one-offs[compact=true] .searchbar-engine-one-off-item.last-of-row:not(.dummy),
-.search-one-offs[compact=true] .searchbar-engine-one-off-item.dummy:not(.last-of-row),
-.search-one-offs[compact=true] .searchbar-engine-one-off-item.last-engine,
-.search-setting-button-compact {
-  background-image: none;
-}
-
-.searchbar-engine-one-off-item:not([selected]):not(.dummy):hover,
+.searchbar-engine-one-off-item:not([selected]):hover,
 .addengine-item:hover {
   background-color: var(--arrowpanel-dimmed-further);
   color: inherit;
 }
 
 .searchbar-engine-one-off-item[selected] {
   background-color: var(--autocomplete-popup-highlight-background);
-  background-image: none;
   color: var(--autocomplete-popup-highlight-color);
 }
 
 .searchbar-engine-one-off-item > .button-box {
   padding: 0;
 }
 
 .searchbar-engine-one-off-item > .button-box > .button-text {
@@ -138,20 +105,16 @@
 
 .searchbar-engine-one-off-item > .button-box > .button-icon {
   margin-inline-start: 0;
   margin-inline-end: 0;
   width: 16px;
   height: 16px;
 }
 
-.search-add-engines {
-  background-color: var(--arrowpanel-dimmed);
-}
-
 .addengine-item {
   -moz-appearance: none;
   color: inherit;
   height: 32px;
   margin: 0;
   padding: 0 10px;
 }
 
@@ -233,16 +196,23 @@
 .search-panel-tree > .autocomplete-richlistitem[originaltype="fromhistory"][selected] > .ac-type-icon {
   fill-opacity: 1;
 }
 
 .search-setting-button {
   -moz-appearance: none;
   margin: 0;
   min-height: 32px;
+  border-top: 1px solid var(--panel-separator-color);
+  background-color: var(--arrowpanel-dimmed);
+  color: var(--panel-disabled-color);
+}
+
+.search-setting-button-compact {
+  max-height: 32px;
 }
 
 .search-setting-button:hover,
 .search-setting-button[selected] {
   background-color: var(--arrowpanel-dimmed-further);
 }
 
 .search-setting-button-compact > .button-box > .button-icon {
--- a/browser/themes/shared/urlbar-autocomplete.inc.css
+++ b/browser/themes/shared/urlbar-autocomplete.inc.css
@@ -243,16 +243,43 @@
   margin-inline-start: .4em;
 }
 
 .urlbarView-row[selected] > .urlbarView-row-inner > .urlbarView-tags > .urlbarView-tag {
   background-color: var(--autocomplete-popup-highlight-color);
   color: var(--autocomplete-popup-highlight-background);
 }
 
+/* Search one-offs. */
+#urlbar-results > .search-one-offs {
+  -moz-box-orient: horizontal;
+  padding-inline-start: var(--item-padding-start, 5px);
+  padding-inline-end: var(--item-padding-end, 5px);
+  padding-top: 16px;
+  padding-bottom: 16px;
+}
+
+#urlbar-results .search-panel-one-offs {
+  padding-left: 12px;
+  padding-right: 12px;
+}
+
+#urlbar-results .search-panel-header {
+  padding: 0;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+#urlbar-results .searchbar-engine-one-off-item {
+  min-width: 32px;
+  height: 32px;
+  margin: 0 4px;
+}
+
 /* search bar popup */
 
 #PopupSearchAutoComplete {
   background: var(--autocomplete-popup-background);
   color: var(--autocomplete-popup-color);
   border-color: var(--arrowpanel-border-color);
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -604,17 +604,17 @@ menuitem.bookmark-item {
 
 %include ../shared/identity-block/identity-block.inc.css
 
 /* autocomplete */
 
 %include ../shared/autocomplete.inc.css
 %include ../shared/urlbar-autocomplete.inc.css
 
-#urlbarView-results {
+#urlbar-results {
   font-size: 1.15em;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype~="datalist-first"] {
   border-top: 1px solid ThreeDShadow;
 }
 
 @media (-moz-windows-default-theme) {