Bug 1534404 - Fold searchbar-textbox with searchbar CE r=mak
authorAlexander Surkov <surkov.alexander@gmail.com>
Fri, 07 Jun 2019 15:44:15 +0000
changeset 477861 a885db13bf151bf9d99f809f53b947b4c3e99a94
parent 477860 0fc603b37e654a5fb4205c8494c95d3102e604d9
child 477862 afaa6cacb3e4f07ddc534a663c36c38097ad6b08
push id87394
push userasurkov@mozilla.com
push dateFri, 07 Jun 2019 15:45:53 +0000
treeherderautoland@a885db13bf15 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmak
bugs1534404
milestone69.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 1534404 - Fold searchbar-textbox with searchbar CE r=mak Differential Revision: https://phabricator.services.mozilla.com/D34094
browser/base/content/browser.css
browser/components/search/content/search.xml
browser/components/search/content/searchbar.js
browser/components/search/jar.mn
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -50,20 +50,16 @@
   min-width: 300px;
 %endif
 }
 
 :root[customize-entered] {
   min-width: -moz-fit-content;
 }
 
-.searchbar-textbox {
-  -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
-}
-
 .search-one-offs[compact=true] .search-setting-button,
 .search-one-offs:not([compact=true]) .search-setting-button-compact {
   display: none;
 }
 
 /* Prevent shrinking the page content to 0 height and width */
 .browserStack > browser {
   min-height: 25px;
deleted file mode 100644
--- a/browser/components/search/content/search.xml
+++ /dev/null
@@ -1,369 +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/. -->
-
-<!-- XULCommandEvent is a specialised global. -->
-<!-- global XULCommandEvent -->
-
-<!DOCTYPE bindings [
-<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
-%browserDTD;
-]>
-
-<bindings id="SearchBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="searchbar-textbox"
-      extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
-    <implementation>
-      <constructor><![CDATA[
-        if (this.closest("searchbar").parentNode.parentNode.localName ==
-            "toolbarpaletteitem")
-          return;
-
-        if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll"))
-          this.setAttribute("clickSelectsAll", true);
-
-        let textBox = document.getAnonymousElementByAttribute(this,
-                                              "anonid", "moz-input-box");
-
-        // Force the Custom Element to upgrade until Bug 1470242 handles this:
-        customElements.upgrade(textBox);
-        let cxmenu = textBox.menupopup;
-        cxmenu.addEventListener("popupshowing",
-                                () => { this.initContextMenu(cxmenu); },
-                                {capture: true, once: true});
-
-        this.setAttribute("aria-owns", this.popup.id);
-        this.closest("searchbar")._textboxInitialized = true;
-      ]]></constructor>
-
-      <destructor><![CDATA[
-        // If the context menu has never been opened, there won't be anything
-        // to remove here.
-        // Also, XBL and the customize toolbar code sometimes interact poorly.
-        try {
-          this.closest("searchbar")._textboxInitialized = false;
-          this.controllers.removeController(this.searchbarController);
-        } catch (ex) { }
-      ]]></destructor>
-
-      // Add items to context menu and attach controller to handle them the
-      // first time the context menu is opened.
-      <method name="initContextMenu">
-        <parameter name="aMenu"/>
-        <body><![CDATA[
-          let stringBundle = this.closest("searchbar")._stringBundle;
-
-          let pasteAndSearch, suggestMenuItem;
-          let element, label, akey;
-
-          element = document.createXULElement("menuseparator");
-          aMenu.appendChild(element);
-
-          let insertLocation = aMenu.firstElementChild;
-          while (insertLocation.nextElementSibling &&
-                 insertLocation.getAttribute("cmd") != "cmd_paste")
-            insertLocation = insertLocation.nextElementSibling;
-          if (insertLocation) {
-            element = document.createXULElement("menuitem");
-            label = stringBundle.getString("cmd_pasteAndSearch");
-            element.setAttribute("label", label);
-            element.setAttribute("anonid", "paste-and-search");
-            element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
-            aMenu.insertBefore(element, insertLocation.nextElementSibling);
-            pasteAndSearch = element;
-          }
-
-          element = document.createXULElement("menuitem");
-          label = stringBundle.getString("cmd_clearHistory");
-          akey = stringBundle.getString("cmd_clearHistory_accesskey");
-          element.setAttribute("label", label);
-          element.setAttribute("accesskey", akey);
-          element.setAttribute("cmd", "cmd_clearhistory");
-          aMenu.appendChild(element);
-
-          element = document.createXULElement("menuitem");
-          label = stringBundle.getString("cmd_showSuggestions");
-          akey = stringBundle.getString("cmd_showSuggestions_accesskey");
-          element.setAttribute("anonid", "toggle-suggest-item");
-          element.setAttribute("label", label);
-          element.setAttribute("accesskey", akey);
-          element.setAttribute("cmd", "cmd_togglesuggest");
-          element.setAttribute("type", "checkbox");
-          element.setAttribute("autocheck", "false");
-          suggestMenuItem = element;
-          aMenu.appendChild(element);
-
-          if (AppConstants.platform == "macosx") {
-            this.addEventListener("keypress", aEvent => {
-              if (aEvent.keyCode == KeyEvent.DOM_VK_F4)
-                this.openSearch();
-            }, true);
-          }
-
-          this.controllers.appendController(this.searchbarController);
-
-          let onpopupshowing = function() {
-            BrowserSearch.searchBar._textbox.closePopup();
-            if (suggestMenuItem) {
-              let enabled =
-                Services.prefs.getBoolPref("browser.search.suggest.enabled");
-              suggestMenuItem.setAttribute("checked", enabled);
-            }
-
-            if (!pasteAndSearch)
-              return;
-            let controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
-            let enabled = controller.isCommandEnabled("cmd_paste");
-            if (enabled)
-              pasteAndSearch.removeAttribute("disabled");
-            else
-              pasteAndSearch.setAttribute("disabled", "true");
-          };
-          aMenu.addEventListener("popupshowing", onpopupshowing);
-          onpopupshowing();
-        ]]></body>
-      </method>
-
-      <!--
-        This overrides the searchParam property in autocomplete.xml.  We're
-        hijacking this property as a vehicle for delivering the privacy
-        information about the window into the guts of nsSearchSuggestions.
-
-        Note that the setter is the same as the parent.  We were not sure whether
-        we can override just the getter.  If that proves to be the case, the setter
-        can be removed.
-      -->
-      <property name="searchParam"
-                onget="return this.getAttribute('autocompletesearchparam') +
-                       (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');"
-                onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
-
-      <!-- This is implemented so that when textbox.value is set directly (e.g.,
-           by tests), the one-off query is updated. -->
-      <method name="onBeforeValueSet">
-        <parameter name="aValue"/>
-        <body><![CDATA[
-          this.popup.oneOffButtons.query = aValue;
-          return aValue;
-        ]]></body>
-      </method>
-
-      <!--
-        This method overrides the autocomplete binding's openPopup (essentially
-        duplicating the logic from the autocomplete popup binding's
-        openAutocompletePopup method), modifying it so that the popup is aligned with
-        the inner textbox, but sized to not extend beyond the search bar border.
-      -->
-      <method name="openPopup">
-        <body><![CDATA[
-          // Entering customization mode after the search bar had focus causes
-          // the popup to appear again, due to focus returning after the
-          // hamburger panel closes. Don't open in that spurious event.
-          if (document.documentElement.getAttribute("customizing") == "true") {
-            return;
-          }
-
-          let popup = this.popup;
-          if (!popup.mPopupOpen) {
-            // Initially the panel used for the searchbar (PopupSearchAutoComplete
-            // in browser.xhtml) is hidden to avoid impacting startup / new
-            // window performance. The base binding's openPopup would normally
-            // call the overriden openAutocompletePopup in
-            // browser-search-autocomplete-result-popup binding to unhide the popup,
-            // but since we're overriding openPopup we need to unhide the panel
-            // ourselves.
-            popup.hidden = false;
-
-            // Don't roll up on mouse click in the anchor for the search UI.
-            if (popup.id == "PopupSearchAutoComplete") {
-              popup.setAttribute("norolluponanchor", "true");
-            }
-
-            popup.mInput = this;
-            // clear any previous selection, see bugs 400671 and 488357
-            popup.selectedIndex = -1;
-
-            document.popupNode = null;
-
-            let outerRect = this.getBoundingClientRect();
-            let innerRect = this.inputField.getBoundingClientRect();
-            let width = RTL_UI ?
-                        innerRect.right - outerRect.left :
-                        outerRect.right - innerRect.left;
-            popup.setAttribute("width", width > 100 ? width : 100);
-
-            // invalidate() depends on the width attribute
-            popup._invalidate();
-
-            let yOffset = outerRect.bottom - innerRect.bottom;
-            popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false);
-          }
-        ]]></body>
-      </method>
-
-      <method name="openSearch">
-        <body>
-          <![CDATA[
-            if (!this.popupOpen) {
-              this.closest("searchbar").openSuggestionsPanel();
-              return false;
-            }
-            return true;
-          ]]>
-        </body>
-      </method>
-
-      <method name="handleEnter">
-        <parameter name="event"/>
-        <body><![CDATA[
-          // Toggle the open state of the add-engine menu button if it's
-          // selected.  We're using handleEnter for this instead of listening
-          // for the command event because a command event isn't fired.
-          if (this.selectedButton &&
-              this.selectedButton.getAttribute("anonid") ==
-                "addengine-menu-button") {
-            this.selectedButton.open = !this.selectedButton.open;
-            return true;
-          }
-          // Otherwise, "call super": do what the autocomplete binding's
-          // handleEnter implementation does.
-          return this.mController.handleEnter(false, event || null);
-        ]]></body>
-      </method>
-
-      <!-- override |onTextEntered| in autocomplete.xml -->
-      <method name="onTextEntered">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          let engine;
-          let oneOff = this.selectedButton;
-          if (oneOff) {
-            if (!oneOff.engine) {
-              oneOff.doCommand();
-              return;
-            }
-            engine = oneOff.engine;
-          }
-          if (this._selectionDetails) {
-            BrowserSearch.searchBar.telemetrySearchDetails = this._selectionDetails;
-            this._selectionDetails = null;
-          }
-          this.closest("searchbar").handleSearchCommand(aEvent, engine);
-        ]]></body>
-      </method>
-
-      <property name="selectedButton">
-        <getter><![CDATA[
-          return this.popup.oneOffButtons.selectedButton;
-        ]]></getter>
-        <setter><![CDATA[
-          return this.popup.oneOffButtons.selectedButton = val;
-        ]]></setter>
-      </property>
-
-      <method name="handleKeyboardNavigation">
-        <parameter name="aEvent"/>
-        <body><![CDATA[
-          let popup = this.popup;
-          if (!popup.popupOpen)
-            return;
-
-          // accel + up/down changes the default engine and shouldn't affect
-          // the selection on the one-off buttons.
-          if (aEvent.getModifierState("Accel"))
-            return;
-
-          let suggestionsHidden =
-            popup.richlistbox.getAttribute("collapsed") == "true";
-          let numItems = suggestionsHidden ? 0 : this.popup.matchCount;
-          this.popup.oneOffButtons.handleKeyPress(aEvent, numItems, true);
-        ]]></body>
-      </method>
-
-      <!-- nsIController -->
-      <field name="searchbarController" readonly="true"><![CDATA[({
-        _self: this,
-        supportsCommand(aCommand) {
-          return aCommand == "cmd_clearhistory" ||
-                 aCommand == "cmd_togglesuggest";
-        },
-
-        isCommandEnabled(aCommand) {
-          return true;
-        },
-
-        doCommand(aCommand) {
-          switch (aCommand) {
-            case "cmd_clearhistory":
-              let param = this._self.getAttribute("autocompletesearchparam");
-
-              BrowserSearch.searchBar.FormHistory.update({ op: "remove", fieldname: param }, null);
-              this._self.value = "";
-              break;
-            case "cmd_togglesuggest":
-              let enabled =
-                Services.prefs.getBoolPref("browser.search.suggest.enabled");
-              Services.prefs.setBoolPref("browser.search.suggest.enabled",
-                                         !enabled);
-              break;
-            default:
-              // do nothing with unrecognized command
-          }
-        },
-      })]]></field>
-    </implementation>
-
-    <handlers>
-      <handler event="input"><![CDATA[
-        this.popup.removeAttribute("showonlysettings");
-      ]]></handler>
-
-      <handler event="keypress" phase="capturing"
-               action="return this.handleKeyboardNavigation(event);"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="accel"
-               phase="capturing"
-               action='this.closest("searchbar").selectEngine(event, false);'/>
-
-      <handler event="keypress" keycode="VK_DOWN" modifiers="accel"
-               phase="capturing"
-               action='this.closest("searchbar").selectEngine(event, true);'/>
-
-      <handler event="keypress" keycode="VK_DOWN" modifiers="alt"
-               phase="capturing"
-               action="return this.openSearch();"/>
-
-      <handler event="keypress" keycode="VK_UP" modifiers="alt"
-               phase="capturing"
-               action="return this.openSearch();"/>
-
-      <handler event="dragover">
-      <![CDATA[
-        let types = event.dataTransfer.types;
-        if (types.includes("text/plain") || types.includes("text/x-moz-text-internal"))
-          event.preventDefault();
-      ]]>
-      </handler>
-
-      <handler event="drop">
-      <![CDATA[
-        let dataTransfer = event.dataTransfer;
-        let data = dataTransfer.getData("text/plain");
-        if (!data)
-          data = dataTransfer.getData("text/x-moz-text-internal");
-        if (data) {
-          event.preventDefault();
-          this.value = data;
-          this.closest("searchbar").openSuggestionsPanel();
-        }
-      ]]>
-      </handler>
-
-    </handlers>
-  </binding>
-</bindings>
--- a/browser/components/search/content/searchbar.js
+++ b/browser/components/search/content/searchbar.js
@@ -17,16 +17,17 @@ class MozSearchbar extends MozXULElement
     return {
       ".searchbar-textbox": "disabled,disableautocomplete,searchengine,src,newlines",
       ".searchbar-search-button": "addengines",
     };
   }
 
   constructor() {
     super();
+
     this.destroy = this.destroy.bind(this);
     this._setupEventListeners();
     let searchbar = this;
     this.observer = {
       observe(aEngine, aTopic, aVerb) {
         if (aTopic == "browser-search-engine-modified" ||
           (aTopic == "browser-search-service" && aVerb == "init-complete")) {
           // Make sure the engine list is refetched next time it's needed
@@ -34,16 +35,17 @@ class MozSearchbar extends MozXULElement
 
           // Update the popup header and update the display after any modification.
           searchbar._textbox.popup.updateHeader();
           searchbar.updateDisplay();
         }
       },
       QueryInterface: ChromeUtils.generateQI([Ci.nsIObserver]),
     };
+
     this.content = MozXULElement.parseXULToFragment(`
       <stringbundle src="chrome://browser/locale/search.properties"></stringbundle>
       <textbox class="searchbar-textbox" type="autocomplete" inputtype="search" placeholder="&searchInput.placeholder;" flex="1" autocompletepopup="PopupSearchAutoComplete" autocompletesearch="search-autocomplete" autocompletesearchparam="searchbar-history" maxrows="10" completeselectedindex="true" minresultsforpopup="0">
         <box>
           <hbox class="searchbar-search-button" tooltiptext="&searchIcon.tooltip;">
             <image class="searchbar-search-icon"></image>
             <image class="searchbar-search-icon-overlay"></image>
           </hbox>
@@ -58,33 +60,34 @@ class MozSearchbar extends MozXULElement
   connectedCallback() {
     // Don't initialize if this isn't going to be visible
     if (this.closest("#BrowserToolbarPalette")) {
       return;
     }
 
     this.appendChild(document.importNode(this.content, true));
     this.initializeAttributeInheritance();
+
+    this._stringBundle = this.querySelector("stringbundle");
+    this._textbox = this.querySelector(".searchbar-textbox");
+
+    this._setupTextboxEventListeners();
+    this._initTextbox();
+
     window.addEventListener("unload", this.destroy);
     this._ignoreFocus = false;
 
     this._clickClosedPopup = false;
-
-    this._stringBundle = this.querySelector("stringbundle");
-
-    this._textboxInitialized = false;
-
-    this._textbox = this.querySelector(".searchbar-textbox");
-
     this._engines = null;
 
     this.FormHistory = (ChromeUtils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
 
-    if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
+    if (this.parentNode.parentNode.localName == "toolbarpaletteitem") {
       return;
+    }
 
     Services.obs.addObserver(this.observer, "browser-search-engine-modified");
     Services.obs.addObserver(this.observer, "browser-search-service");
 
     this._initialized = true;
 
     (window.delayedStartupPromise || Promise.resolve()).then(() => {
       window.requestIdleCallback(() => {
@@ -157,18 +160,26 @@ class MozSearchbar extends MozXULElement
       Services.obs.removeObserver(this.observer, "browser-search-service");
     }
 
     // Make sure to break the cycle from _textbox to us. Otherwise we leak
     // the world. But make sure it's actually pointing to us.
     // Also make sure the textbox has ever been constructed, otherwise the
     // _textbox getter will cause the textbox constructor to run, add an
     // observer, and leak the world too.
-    if (this._textboxInitialized && this._textbox.mController.input == this)
+    if (this._textbox.mController && this._textbox.mController.input == this) {
       this._textbox.mController.input = null;
+    }
+
+    // If the context menu has never been opened, there won't be anything
+    // to remove here. Also, XBL and the customize toolbar code sometimes
+    // interact poorly.
+    try {
+      this.controllers.removeController(this.searchbarController);
+    } catch (ex) {}
   }
 
   focus() {
     this._textbox.focus();
   }
 
   select() {
     this._textbox.select();
@@ -453,12 +464,322 @@ class MozSearchbar extends MozXULElement
 
       // Open the suggestions whenever clicking on the search icon or if there
       // is text in the textbox.
       if (isIconClick || this._textbox.value) {
         this.openSuggestionsPanel(true);
       }
     });
   }
+
+  _setupTextboxEventListeners() {
+    this.textbox.addEventListener("input", (event) => {
+      this.textbox.popup.removeAttribute("showonlysettings");
+    });
+
+    this.textbox.addEventListener("keypress", (event) => {
+      // accel + up/down changes the default engine and shouldn't affect
+      // the selection on the one-off buttons.
+      let popup = this.textbox.popup;
+      if (!popup.popupOpen || event.getModifierState("Accel")) {
+        return;
+      }
+
+      let suggestionsHidden = popup.richlistbox.getAttribute("collapsed") == "true";
+      let numItems = suggestionsHidden ? 0 : popup.matchCount;
+      popup.oneOffButtons.handleKeyPress(event, numItems, true);
+    }, true);
+
+    this.textbox.addEventListener("keypress", (event) => {
+      if (event.keyCode == KeyEvent.DOM_VK_UP && event.getModifierState("Accel")) {
+        this.selectEngine(event, false);
+      }
+    }, true);
+
+    this.textbox.addEventListener("keypress", (event) => {
+      if (event.keyCode == KeyEvent.DOM_VK_DOWN && event.getModifierState("Accel")) {
+        this.selectEngine(event, true);
+      }
+    }, true);
+
+    this.textbox.addEventListener("keypress", (event) => {
+      if (event.getModifierState("Alt") &&
+          (event.keyCode == KeyEvent.DOM_VK_DOWN ||
+           event.keyCode == KeyEvent.DOM_VK_UP)) {
+        this.textbox.openSearch();
+      }
+    }, true);
+
+    this.textbox.addEventListener("dragover", (event) => {
+      let types = event.dataTransfer.types;
+      if (types.includes("text/plain") ||
+          types.includes("text/x-moz-text-internal")) {
+        event.preventDefault();
+      }
+    });
+
+    this.textbox.addEventListener("drop", (event) => {
+      let dataTransfer = event.dataTransfer;
+      let data = dataTransfer.getData("text/plain");
+      if (!data) {
+        data = dataTransfer.getData("text/x-moz-text-internal");
+      }
+      if (data) {
+        event.preventDefault();
+        this.textbox.value = data;
+        this.openSuggestionsPanel();
+      }
+    });
+  }
+
+  _initTextbox() {
+    // nsIController
+    this.searchbarController = {
+      textbox: this.textbox,
+      supportsCommand(command) {
+        return command == "cmd_clearhistory" ||
+               command == "cmd_togglesuggest";
+      },
+      isCommandEnabled(command) {
+        return true;
+      },
+      doCommand(command) {
+        switch (command) {
+          case "cmd_clearhistory":
+            let param = this.textbox.getAttribute("autocompletesearchparam");
+            BrowserSearch.searchBar.FormHistory.
+              update({ op: "remove", fieldname: param }, null);
+            this.textbox.value = "";
+            break;
+          case "cmd_togglesuggest":
+            let enabled =
+              Services.prefs.getBoolPref("browser.search.suggest.enabled");
+            Services.prefs.setBoolPref(
+              "browser.search.suggest.enabled", !enabled);
+            break;
+          default:
+            // do nothing with unrecognized command
+        }
+      },
+    };
+
+    if (this.parentNode.parentNode.localName == "toolbarpaletteitem") {
+      return;
+    }
+
+    if (Services.prefs.getBoolPref("browser.urlbar.clickSelectsAll")) {
+      this.textbox.setAttribute("clickSelectsAll", true);
+    }
+
+    let inputBox = document.getAnonymousElementByAttribute(this.textbox,
+      "anonid", "moz-input-box");
+
+    // Force the Custom Element to upgrade until Bug 1470242 handles this:
+    window.customElements.upgrade(inputBox);
+    let cxmenu = inputBox.menupopup;
+    cxmenu.addEventListener("popupshowing",
+      () => { this._initContextMenu(cxmenu); }, { capture: true, once: true });
+
+    this.textbox.setAttribute("aria-owns", this.textbox.popup.id);
+
+    // This overrides the searchParam property in autocomplete.xml. We're
+    // hijacking this property as a vehicle for delivering the privacy
+    // information about the window into the guts of nsSearchSuggestions.
+    // Note that the setter is the same as the parent. We were not sure whether
+    // we can override just the getter. If that proves to be the case, the setter
+    // can be removed.
+    Object.defineProperty(this.textbox, "searchParam", {
+      get() {
+        return this.getAttribute("autocompletesearchparam") +
+          (PrivateBrowsingUtils.isWindowPrivate(window) ? "|private" : "");
+      },
+      set(val) {
+        this.setAttribute("autocompletesearchparam", val);
+        return val;
+      },
+    });
+
+    Object.defineProperty(this.textbox, "selectedButton", {
+      get() {
+        return this.popup.oneOffButtons.selectedButton;
+      },
+      set(val) {
+        return this.popup.oneOffButtons.selectedButton = val;
+      },
+    });
+
+    // This is implemented so that when textbox.value is set directly (e.g.,
+    // by tests), the one-off query is updated.
+    this.textbox.onBeforeValueSet = (aValue) => {
+      this.textbox.popup.oneOffButtons.query = aValue;
+      return aValue;
+    };
+
+    // This method overrides the autocomplete binding's openPopup (essentially
+    // duplicating the logic from the autocomplete popup binding's
+    // openAutocompletePopup method), modifying it so that the popup is aligned with
+    // the inner textbox, but sized to not extend beyond the search bar border.
+    this.textbox.openPopup = () => {
+      // Entering customization mode after the search bar had focus causes
+      // the popup to appear again, due to focus returning after the
+      // hamburger panel closes. Don't open in that spurious event.
+      if (document.documentElement.getAttribute("customizing") == "true") {
+        return;
+      }
+
+      let popup = this.textbox.popup;
+      if (!popup.mPopupOpen) {
+        // Initially the panel used for the searchbar (PopupSearchAutoComplete
+        // in browser.xhtml) is hidden to avoid impacting startup / new
+        // window performance. The base binding's openPopup would normally
+        // call the overriden openAutocompletePopup in
+        // browser-search-autocomplete-result-popup binding to unhide the popup,
+        // but since we're overriding openPopup we need to unhide the panel
+        // ourselves.
+        popup.hidden = false;
+
+        // Don't roll up on mouse click in the anchor for the search UI.
+        if (popup.id == "PopupSearchAutoComplete") {
+          popup.setAttribute("norolluponanchor", "true");
+        }
+
+        popup.mInput = this.textbox;
+        // clear any previous selection, see bugs 400671 and 488357
+        popup.selectedIndex = -1;
+
+        document.popupNode = null;
+
+        let outerRect = this.textbox.getBoundingClientRect();
+        let innerRect = this.textbox.inputField.getBoundingClientRect();
+        let width = RTL_UI ?
+          innerRect.right - outerRect.left :
+          outerRect.right - innerRect.left;
+        popup.setAttribute("width", width > 100 ? width : 100);
+
+        // invalidate() depends on the width attribute
+        popup._invalidate();
+
+        let yOffset = outerRect.bottom - innerRect.bottom;
+        popup.openPopup(this.textbox.inputField, "after_start",
+                        0, yOffset, false, false);
+      }
+    };
+
+    this.textbox.openSearch = () => {
+      if (!this.textbox.popupOpen) {
+        this.openSuggestionsPanel();
+        return false;
+      }
+      return true;
+    };
+
+    this.textbox.handleEnter = (event) => {
+      // Toggle the open state of the add-engine menu button if it's
+      // selected.  We're using handleEnter for this instead of listening
+      // for the command event because a command event isn't fired.
+      if (this.textbox.selectedButton &&
+          this.textbox.selectedButton.getAttribute("anonid") ==
+            "addengine-menu-button") {
+        this.textbox.selectedButton.open = !this.textbox.selectedButton.open;
+        return true;
+      }
+      // Otherwise, "call super": do what the autocomplete binding's
+      // handleEnter implementation does.
+      return this.textbox.mController.handleEnter(false, event || null);
+    };
+
+    // override |onTextEntered| in autocomplete.xml
+    this.textbox.onTextEntered = (event) => {
+      let engine;
+      let oneOff = this.textbox.selectedButton;
+      if (oneOff) {
+        if (!oneOff.engine) {
+          oneOff.doCommand();
+          return;
+        }
+        engine = oneOff.engine;
+      }
+      if (this.textbox._selectionDetails) {
+        BrowserSearch.searchBar.telemetrySearchDetails = this.textbox._selectionDetails;
+        this.textbox._selectionDetails = null;
+      }
+      this.handleSearchCommand(event, engine);
+    };
+  }
+
+  _initContextMenu(aMenu) {
+    let stringBundle = this._stringBundle;
+
+    let pasteAndSearch, suggestMenuItem;
+    let element, label, akey;
+
+    element = document.createXULElement("menuseparator");
+    aMenu.appendChild(element);
+
+    let insertLocation = aMenu.firstElementChild;
+    while (insertLocation.nextElementSibling &&
+      insertLocation.getAttribute("cmd") != "cmd_paste")
+      insertLocation = insertLocation.nextElementSibling;
+    if (insertLocation) {
+      element = document.createXULElement("menuitem");
+      label = stringBundle.getString("cmd_pasteAndSearch");
+      element.setAttribute("label", label);
+      element.setAttribute("anonid", "paste-and-search");
+      element.setAttribute("oncommand", "BrowserSearch.pasteAndSearch(event)");
+      aMenu.insertBefore(element, insertLocation.nextElementSibling);
+      pasteAndSearch = element;
+    }
+
+    element = document.createXULElement("menuitem");
+    label = stringBundle.getString("cmd_clearHistory");
+    akey = stringBundle.getString("cmd_clearHistory_accesskey");
+    element.setAttribute("label", label);
+    element.setAttribute("accesskey", akey);
+    element.setAttribute("cmd", "cmd_clearhistory");
+    aMenu.appendChild(element);
+
+    element = document.createXULElement("menuitem");
+    label = stringBundle.getString("cmd_showSuggestions");
+    akey = stringBundle.getString("cmd_showSuggestions_accesskey");
+    element.setAttribute("anonid", "toggle-suggest-item");
+    element.setAttribute("label", label);
+    element.setAttribute("accesskey", akey);
+    element.setAttribute("cmd", "cmd_togglesuggest");
+    element.setAttribute("type", "checkbox");
+    element.setAttribute("autocheck", "false");
+    suggestMenuItem = element;
+    aMenu.appendChild(element);
+
+    if (AppConstants.platform == "macosx") {
+      this.textbox.addEventListener("keypress", (event) => {
+        if (event.keyCode == KeyEvent.DOM_VK_F4) {
+          this.textbox.openSearch();
+        }
+      }, true);
+    }
+
+    this.textbox.controllers.appendController(this.searchbarController);
+
+    let onpopupshowing = function() {
+      BrowserSearch.searchBar._textbox.closePopup();
+      if (suggestMenuItem) {
+        let enabled =
+          Services.prefs.getBoolPref("browser.search.suggest.enabled");
+        suggestMenuItem.setAttribute("checked", enabled);
+      }
+
+      if (!pasteAndSearch)
+        return;
+
+      let controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+      let enabled = controller.isCommandEnabled("cmd_paste");
+      if (enabled)
+        pasteAndSearch.removeAttribute("disabled");
+      else
+        pasteAndSearch.setAttribute("disabled", "true");
+    };
+    aMenu.addEventListener("popupshowing", onpopupshowing);
+    onpopupshowing();
+  }
 }
 
 customElements.define("searchbar", MozSearchbar);
 }
--- a/browser/components/search/jar.mn
+++ b/browser/components/search/jar.mn
@@ -1,14 +1,13 @@
 # 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/.
 
 browser.jar:
-        content/browser/search/search.xml                           (content/search.xml)
         content/browser/search/autocomplete-popup.js                (content/autocomplete-popup.js)
         content/browser/search/searchbar.js                         (content/searchbar.js)
         content/browser/search/search-one-offs.js                   (content/search-one-offs.js)
         search-extensions/                                          (extensions/**)
         searchplugins/                                              (searchplugins/**)
 
 % resource search-plugins %searchplugins/ contentaccessible=yes
 % resource search-extensions %search-extensions/ contentaccessible=yes