Bug 1521280 - Convert search-textbox to a custom element. r=dao,bgrins
authorTim Nguyen <ntim.bugs@gmail.com>
Fri, 10 May 2019 11:46:49 +0000
changeset 532197 c5798de806e28251aeda7d23123ab2b05eb7e8fb
parent 532196 73254a69497b209b372ed209ce9d004080ea8052
child 532198 d3340f866c7df1f283153c1dec37d5b83933bf7f
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdao, bgrins
bugs1521280
milestone68.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 1521280 - Convert search-textbox to a custom element. r=dao,bgrins Differential Revision: https://phabricator.services.mozilla.com/D23045
accessible/tests/mochitest/states/test_textbox.xul
accessible/tests/mochitest/tree/test_txtctrl.xul
browser/base/content/test/forms/browser_selectpopup.js
browser/base/content/test/forms/browser_selectpopup_searchfocus.js
browser/components/places/content/bookmarksSidebar.xul
browser/components/places/content/historySidebar.xul
browser/components/places/content/places.xul
browser/components/preferences/in-content/main.xul
browser/components/preferences/in-content/preferences.xul
browser/components/preferences/siteDataSettings.xul
browser/components/preferences/sitePermissions.xul
editor/reftests/xul/emptytextbox-4.xul
layout/reftests/native-theme/searchfield-mirrored-when-rtl-ref.xul
layout/reftests/native-theme/searchfield-mirrored-when-rtl.xul
toolkit/components/passwordmgr/content/passwordManager.xul
toolkit/components/viewconfig/content/config.xul
toolkit/content/customElements.js
toolkit/content/editMenuOverlay.js
toolkit/content/jar.mn
toolkit/content/tests/chrome/file_edit_contextmenu.xul
toolkit/content/tests/chrome/test_edit_contextmenu.html
toolkit/content/tests/chrome/test_textbox_search.xul
toolkit/content/widgets/search-textbox.js
toolkit/content/widgets/textbox.xml
toolkit/content/xul.css
toolkit/modules/SelectParentHelper.jsm
toolkit/mozapps/extensions/content/extensions.xul
toolkit/mozapps/extensions/test/browser/browser_bug570760.js
toolkit/themes/linux/global/textbox.css
toolkit/themes/osx/global/in-content/common.css
toolkit/themes/osx/global/textbox.css
toolkit/themes/windows/global/textbox.css
--- a/accessible/tests/mochitest/states/test_textbox.xul
+++ b/accessible/tests/mochitest/states/test_textbox.xul
@@ -107,14 +107,14 @@
 
   <vbox flex="1">
     <textbox id="textbox"/>
     <textbox id="password" type="password"/>
 
     <textbox id="readonly_textbox" readonly="true"/>
     <textbox id="disabled_textbox" disabled="true"/>
 
-    <textbox id="searchbox" flex="1" type="search" results="historyTree"/>
+    <textbox id="searchbox" flex="1" is="search-textbox" results="historyTree"/>
     <textbox id="searchfield" placeholder="Search all add-ons"
-             type="search" searchbutton="true"/>
+             is="search-textbox" searchbutton="true"/>
   </vbox>
   </hbox>
 </window>
--- a/accessible/tests/mochitest/tree/test_txtctrl.xul
+++ b/accessible/tests/mochitest/tree/test_txtctrl.xul
@@ -37,35 +37,32 @@
       // default textbox
       testAccessibleTree("txc", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // search textbox
       accTree =
         { SECTION: [
           { ENTRY: [ { TEXT_LEAF: [] } ] },
-          { MENUPOPUP: [] }
         ] };
       testAccessibleTree("txc_search", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // search textbox with search button
 
       if (MAC) {
         accTree =
           { SECTION: [
             { ENTRY: [ { TEXT_LEAF: [] } ] },
-            { MENUPOPUP: [] }
           ] };
       } else {
         accTree =
           { SECTION: [
             { ENTRY: [ { TEXT_LEAF: [] } ] },
             { PUSHBUTTON: [] },
-            { MENUPOPUP: [] }
           ] };
       }
 
       testAccessibleTree("txc_search_searchbutton", accTree);
 
       //////////////////////////////////////////////////////////////////////////
       // number textbox
 
@@ -151,17 +148,17 @@
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>
 
     <vbox flex="1">
       <textbox id="txc" value="hello"/>
-      <textbox id="txc_search" type="search" value="hello"/>
-      <textbox id="txc_search_searchbutton" searchbutton="true" type="search" value="hello"/>
+      <textbox id="txc_search" is="search-textbox" value="hello"/>
+      <textbox id="txc_search_searchbutton" searchbutton="true" is="search-textbox" value="hello"/>
       <textbox id="txc_number" type="number" value="44"/>
       <textbox id="txc_password" type="password" value="hello"/>
       <textbox id="txc_autocomplete" type="autocomplete" value="hello"/>
     </vbox>
   </hbox>
 
 </window>
--- a/browser/base/content/test/forms/browser_selectpopup.js
+++ b/browser/base/content/test/forms/browser_selectpopup.js
@@ -635,17 +635,17 @@ async function performSelectSearchTests(
 
     select.options[1].selected = true;
     select.focus();
   });
 
   let selectPopup = win.document.getElementById("ContentSelectDropdown").menupopup;
   await openSelectPopup(selectPopup, false, "select", win);
 
-  let searchElement = selectPopup.querySelector("textbox");
+  let searchElement = selectPopup.querySelector(".contentSelectDropdown-searchbox");
   searchElement.focus();
 
   EventUtils.synthesizeKey("O", {}, win);
   is(selectPopup.children[2].hidden, false, "First option should be visible");
   is(selectPopup.children[3].hidden, false, "Second option should be visible");
 
   EventUtils.synthesizeKey("3", {}, win);
   is(selectPopup.children[2].hidden, true, "First option should be hidden");
--- a/browser/base/content/test/forms/browser_selectpopup_searchfocus.js
+++ b/browser/base/content/test/forms/browser_selectpopup_searchfocus.js
@@ -23,20 +23,19 @@ add_task(async function test_focus_on_se
 
   let menulist = document.getElementById("ContentSelectDropdown");
   let selectPopup = menulist.menupopup;
 
   let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
   await BrowserTestUtils.synthesizeMouseAtCenter("#one", { type: "mousedown" }, gBrowser.selectedBrowser);
   await popupShownPromise;
 
-  let searchInput = selectPopup.querySelector("textbox[type='search']");
+  let searchInput = selectPopup.querySelector(".contentSelectDropdown-searchbox");
   searchInput.scrollIntoView();
-  let searchFocused = BrowserTestUtils.waitForEvent(searchInput, "focus");
+  let searchFocused = BrowserTestUtils.waitForEvent(searchInput, "focus", true);
   await EventUtils.synthesizeMouseAtCenter(searchInput, {}, window);
   await searchFocused;
 
   is(selectPopup.state, "open", "select popup should still be open after clicking on the search field");
 
   await hideSelectPopup(selectPopup, "escape");
   BrowserTestUtils.removeTab(tab);
 });
-
--- a/browser/components/places/content/bookmarksSidebar.xul
+++ b/browser/components/places/content/bookmarksSidebar.xul
@@ -32,17 +32,17 @@
   <script src="chrome://browser/content/places/places-tree.js"/>
   <script src="chrome://global/content/editMenuOverlay.js"/>
 
 #include placesCommands.inc.xul
 #include placesContextMenu.inc.xul
 #include bookmarksHistoryTooltip.inc.xul
 
   <hbox id="sidebar-search-container" align="center">
-    <textbox id="search-box" flex="1" type="search"
+    <textbox id="search-box" flex="1" is="search-textbox"
              placeholder="&bookmarksSearch.placeholder;"
              aria-controls="bookmarks-view"
              oncommand="searchBookmarks(this.value);"/>
   </hbox>
 
   <tree id="bookmarks-view"
         class="sidebar-placesTree"
         is="places-tree"
--- a/browser/components/places/content/historySidebar.xul
+++ b/browser/components/places/content/historySidebar.xul
@@ -40,17 +40,17 @@
     <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
   </keyset>
 #endif
 
 #include placesContextMenu.inc.xul
 #include bookmarksHistoryTooltip.inc.xul
 
   <hbox id="sidebar-search-container">
-    <textbox id="search-box" flex="1" type="search"
+    <textbox id="search-box" flex="1" is="search-textbox"
              placeholder="&historySearch.placeholder;"
              aria-controls="historyTree"
              oncommand="searchHistory(this.value);"/>
     <button id="viewButton" style="min-width:0px !important;" type="menu"
             label="&view.label;" accesskey="&view.accesskey;" selectedsort="day"
             persist="selectedsort">
       <menupopup>
         <menuitem id="bydayandsite" label="&byDayAndSite.label;"
--- a/browser/components/places/content/places.xul
+++ b/browser/components/places/content/places.xul
@@ -328,17 +328,17 @@
         </menu>
       </menubar>
 #endif
 
       <spacer id="libraryToolbarSpacer" flex="1"/>
 
       <textbox id="searchFilter"
                flex="1"
-               type="search"
+               is="search-textbox"
                aria-controls="placeContent"
                oncommand="PlacesSearchBox.search(this.value);"
                collection="bookmarks">
       </textbox>
       <toolbarbutton id="clearDownloadsButton"
 #ifdef XP_MACOSX
                      class="tabbable"
 #endif
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -378,17 +378,17 @@
            data-l10n-id="download-always-ask-where"/>
   </radiogroup>
 </groupbox>
 
 <groupbox id="applicationsGroup" data-category="paneGeneral" hidden="true">
   <label><html:h2 data-l10n-id="applications-header"/></label>
   <description data-l10n-id="applications-description"/>
   <textbox id="filter" flex="1"
-           type="search"
+           is="search-textbox"
            data-l10n-id="applications-filter"
            aria-controls="handlersView"/>
 
   <listheader equalsize="always">
     <treecol id="typeColumn" data-l10n-id="applications-type-column" value="type"
              persist="sortDirection"
              flex="1" sortDirection="ascending"/>
     <treecol id="actionColumn" data-l10n-id="applications-action-column" value="action"
--- a/browser/components/preferences/in-content/preferences.xul
+++ b/browser/components/preferences/in-content/preferences.xul
@@ -168,17 +168,17 @@
             <hbox align="top">
               <image class="info-icon"></image>
             </hbox>
             <hbox align="center" flex="1">
               <label class="policies-label" flex="1" data-l10n-id="policies-notice"></label>
             </hbox>
           </hbox>
           <textbox
-            type="search" id="searchInput"
+            is="search-textbox" id="searchInput"
             data-l10n-id="search-input-box"
             data-l10n-attrs="style"
             hidden="true" clickSelectsAll="true"/>
         </hbox>
         <vbox id="mainPrefPane">
 #include searchResults.xul
 #include main.xul
 #include home.xul
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -26,17 +26,17 @@
 
   <script src="chrome://browser/content/preferences/siteDataSettings.js"/>
 
   <vbox flex="1" class="contentPane">
     <description id="settingsDescription" data-l10n-id="site-data-settings-description"/>
     <separator class="thin"/>
 
     <hbox id="searchBoxContainer">
-      <textbox id="searchBox" type="search" flex="1"
+      <textbox id="searchBox" is="search-textbox" flex="1"
         data-l10n-id="site-data-search-textbox"/>
     </hbox>
     <separator class="thin"/>
 
     <listheader>
       <treecol flex="4" width="50" data-l10n-id="site-data-column-host" id="hostCol"/>
       <treecol flex="1" width="50" data-l10n-id="site-data-column-cookies" id="cookiesCol"/>
       <!-- Sorted by usage so the user can quickly see which sites use the most data. -->
--- a/browser/components/preferences/sitePermissions.xul
+++ b/browser/components/preferences/sitePermissions.xul
@@ -31,17 +31,17 @@
     <key data-l10n-id="permissions-close-key" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
   <vbox class="contentPane">
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
     <hbox align="start">
       <textbox id="searchBox" flex="1" data-l10n-id="permissions-searchbox"
-               type="search" oncommand="gSitePermissionsManager.buildPermissionsList();"/>
+               is="search-textbox" oncommand="gSitePermissionsManager.buildPermissionsList();"/>
     </hbox>
     <separator class="thin"/>
     <listheader>
       <treecol id="siteCol" data-l10n-id="permissions-site-name" flex="3" width="50"
                onclick="gSitePermissionsManager.buildPermissionsList(event.target)"/>
       <treecol id="statusCol" data-l10n-id="permissions-status" flex="1" width="50"
                data-isCurrentSortCol="true"
                onclick="gSitePermissionsManager.buildPermissionsList(event.target);"/>
@@ -61,17 +61,17 @@
             icon="clear"
             oncommand="gSitePermissionsManager.onAllPermissionsDelete();"/>
   </hbox>
 
   <spacer flex="1"/>
   <checkbox id="permissionsDisableCheckbox"/>
   <description id="permissionsDisableDescription"/>
   <spacer flex="1"/>
-  <hbox id="browserNotificationsPermissionExtensionContent" 
+  <hbox id="browserNotificationsPermissionExtensionContent"
         class="extension-controlled" align="center" hidden="true">
     <description control="disableNotificationsPermissionExtension" flex="1"/>
     <button id="disableNotificationsPermissionExtension"
             class="extension-controlled-button accessory-button"
             data-l10n-id="disable-extension"/>
   </hbox>
   <hbox class="actionButtons" align="right" flex="1">
     <button oncommand="close();" icon="close" id="cancel"
--- a/editor/reftests/xul/emptytextbox-4.xul
+++ b/editor/reftests/xul/emptytextbox-4.xul
@@ -2,11 +2,11 @@
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
         title="Textbox tests">
 
   <script type="text/javascript" src="platform.js"/>
 
-  <textbox type="search"/>
-      
+  <textbox is="search-textbox"/>
+
 </window>
--- a/layout/reftests/native-theme/searchfield-mirrored-when-rtl-ref.xul
+++ b/layout/reftests/native-theme/searchfield-mirrored-when-rtl-ref.xul
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <window title="mirrored searchfield"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <hbox>
-    <textbox type="search" width="200" id="searchbox" style="-moz-transform: scaleX(-1);"/>
+    <textbox is="search-textbox" width="200" id="searchbox" style="-moz-transform: scaleX(-1);"/>
     <spacer flex="1"/>
   </hbox>
 </window>
--- a/layout/reftests/native-theme/searchfield-mirrored-when-rtl.xul
+++ b/layout/reftests/native-theme/searchfield-mirrored-when-rtl.xul
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
 <?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 
 <window title="RTL searchfield"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <hbox>
-    <textbox type="search" width="200" id="searchbox" style="direction: rtl;"/>
+    <textbox is="search-textbox" width="200" id="searchbox" style="direction: rtl;"/>
     <spacer flex="1"/>
   </hbox>
 </window>
--- a/toolkit/components/passwordmgr/content/passwordManager.xul
+++ b/toolkit/components/passwordmgr/content/passwordManager.xul
@@ -56,17 +56,17 @@
                 oncommand="EditCellInSelectedRow('password')"/>
     </menupopup>
   </popupset>
 
   <!-- saved signons -->
   <vbox id="savedsignons" class="contentPane" flex="1">
     <!-- filter -->
     <hbox align="center">
-      <textbox id="filter" flex="1" type="search"
+      <textbox id="filter" flex="1" is="search-textbox"
                aria-controls="signonsTree"
                oncommand="FilterPasswords();"
                data-l10n-id="search-filter"/>
     </hbox>
 
     <label control="signonsTree" id="signonsIntro"/>
     <separator class="thin"/>
     <tree id="signonsTree" flex="1"
--- a/toolkit/components/viewconfig/content/config.xul
+++ b/toolkit/components/viewconfig/content/config.xul
@@ -65,17 +65,17 @@
         </hbox>
       </vbox>
     </vbox>
     <spacer flex="2"/>
   </vbox>
   <vbox flex="1">
     <hbox id="filterRow" align="center">
       <label data-l10n-id="config-search-prefs" control="textbox"/>
-      <textbox id="textbox" flex="1" type="search"
+      <textbox id="textbox" flex="1" is="search-textbox"
                aria-controls="configTree"
                oncommand="FilterPrefs();"/>
     </hbox>
 
     <tree id="configTree" flex="1" seltype="single"
           onselect="updateCommands('select');"
           enableColumnDrag="true" context="configContext">
       <treecols>
--- a/toolkit/content/customElements.js
+++ b/toolkit/content/customElements.js
@@ -663,16 +663,17 @@ if (!isDummyDocument) {
     "chrome://global/content/elements/wizard.js",
   ]) {
     Services.scriptloader.loadSubScript(script, window);
   }
 
   for (let [tag, script] of [
     ["findbar", "chrome://global/content/elements/findbar.js"],
     ["menulist", "chrome://global/content/elements/menulist.js"],
+    ["search-textbox", "chrome://global/content/elements/search-textbox.js"],
     ["stringbundle", "chrome://global/content/elements/stringbundle.js"],
     ["printpreview-toolbar", "chrome://global/content/printPreviewToolbar.js"],
     ["editor", "chrome://global/content/elements/editor.js"],
   ]) {
     customElements.setElementCreationCallback(tag, () => {
       Services.scriptloader.loadSubScript(script, window);
     });
   }
--- a/toolkit/content/editMenuOverlay.js
+++ b/toolkit/content/editMenuOverlay.js
@@ -55,22 +55,23 @@ window.addEventListener("DOMContentLoade
       <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
       <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/>
     </commandset>
   `));
 }, { once: true });
 
 // Support context menus on html textareas in the parent process:
 window.addEventListener("contextmenu", (e) => {
+  const HTML_NS = "http://www.w3.org/1999/xhtml";
   // Note that there's not a risk of e.target being XBL anonymous content for <textbox> (which manages
   // its own context menu), because e.target will be the XBL binding parent in that case.
-  let needsContextMenu = e.target.ownerDocument == document &&
-                         !e.defaultPrevented &&
-                         e.target.localName == "textarea" &&
-                         e.target.namespaceURI == "http://www.w3.org/1999/xhtml";
+  let needsContextMenu = e.target.ownerDocument == document && !e.defaultPrevented && (
+    (e.target.localName == "textarea" && e.target.namespaceURI == HTML_NS)
+    || e.target.closest("textbox[is='search-textbox']")
+  );
 
   if (!needsContextMenu) {
     return;
   }
 
   let popup = document.getElementById("textbox-contextmenu");
   if (!popup) {
     MozXULElement.insertFTLIfNeeded("toolkit/main-window/editmenu.ftl");
--- a/toolkit/content/jar.mn
+++ b/toolkit/content/jar.mn
@@ -93,16 +93,17 @@ toolkit.jar:
    content/global/elements/notificationbox.js  (widgets/notificationbox.js)
    content/global/elements/pluginProblem.js    (widgets/pluginProblem.js)
    content/global/elements/radio.js            (widgets/radio.js)
    content/global/elements/richlistbox.js      (widgets/richlistbox.js)
    content/global/elements/marquee.css         (widgets/marquee.css)
    content/global/elements/marquee.js          (widgets/marquee.js)
    content/global/elements/menulist.js         (widgets/menulist.js)
    content/global/elements/popupnotification.js  (widgets/popupnotification.js)
+   content/global/elements/search-textbox.js     (widgets/search-textbox.js)
    content/global/elements/stringbundle.js     (widgets/stringbundle.js)
    content/global/elements/tabbox.js           (widgets/tabbox.js)
    content/global/elements/text.js             (widgets/text.js)
    content/global/elements/textbox.js          (widgets/textbox.js)
    content/global/elements/videocontrols.js    (widgets/videocontrols.js)
    content/global/elements/tree.js             (widgets/tree.js)
    content/global/elements/wizard.js           (widgets/wizard.js)
 #ifdef XP_MACOSX
--- a/toolkit/content/tests/chrome/file_edit_contextmenu.xul
+++ b/toolkit/content/tests/chrome/file_edit_contextmenu.xul
@@ -20,9 +20,11 @@
   <command id="cmd_paste" oncommand="goDoCommand('cmd_paste')"/>
   <command id="cmd_delete" oncommand="goDoCommand('cmd_delete')"/>
   <command id="cmd_selectAll" oncommand="goDoCommand('cmd_selectAll')"/>
   <command id="cmd_switchTextDirection" oncommand="goDoCommand('cmd_switchTextDirection');"/>
 </commandset>
 
 <html:textarea />
 
+<textbox is="search-textbox"/>
+
 </window>
--- a/toolkit/content/tests/chrome/test_edit_contextmenu.html
+++ b/toolkit/content/tests/chrome/test_edit_contextmenu.html
@@ -14,54 +14,67 @@ https://bugzilla.mozilla.org/show_bug.cg
     const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
     SimpleTest.waitForExplicitFinish();
 
     async function runTest() {
       let win = window.open("file_edit_contextmenu.xul", "context-menu", "chrome,width=600,height=600");
       await new Promise(r => win.addEventListener("load", r, { once: true}));
       await SimpleTest.promiseFocus(win);
 
-      let textarea = win.document.querySelector("textarea");
-      ok(textarea, "textarea exists");
+      const elements = [
+        win.document.querySelector("textarea"),
+        win.document.querySelector("textbox[is='search-textbox']"),
+      ];
+      for (const element of elements) {
+        await testElement(element, win);
+      }
+      SimpleTest.finish();
+    }
+
+    async function testElement(element, win) {
+      ok(element, "element exists");
 
       info("Synthesizing a key so 'Undo' will be enabled");
-      textarea.focus();
+      element.focus();
       synthesizeKey("x", {}, win);
-      is(textarea.value, "x", "initial value");
+      is(element.value, "x", "initial value");
 
-      textarea.select();
+      element.select();
       synthesizeKey("c", { accelKey: true }, win); // copy to clipboard
       synthesizeKey("KEY_ArrowRight", {}, win); // drop selection to disable cut and copy context menu items
 
       win.document.addEventListener("contextmenu", (e) => {
         info("Calling prevent default on the first contextmenu event");
         e.preventDefault();
       }, { once: true });
-      synthesizeMouseAtCenter(textarea, {type: "contextmenu"}, win);
+      synthesizeMouseAtCenter(element, {type: "contextmenu"}, win);
       ok(!win.document.getElementById("textbox-contextmenu"), "contextmenu with preventDefault() doesn't run");
 
       let popupshown = new Promise(r => win.addEventListener("popupshown", r, { once: true }));
-      synthesizeMouseAtCenter(textarea, {type: "contextmenu"}, win);
+      synthesizeMouseAtCenter(element, {type: "contextmenu"}, win);
       let contextmenu = win.document.getElementById("textbox-contextmenu");
       ok(contextmenu, "context menu exists after right click");
       await popupshown;
 
       ok(!contextmenu.querySelector("[command=cmd_undo]").hasAttribute("disabled"), "undo enabled");
       ok(contextmenu.querySelector("[command=cmd_cut]").hasAttribute("disabled"), "cut disabled");
       ok(contextmenu.querySelector("[command=cmd_copy]").hasAttribute("disabled"), "copy disabled");
       ok(!contextmenu.querySelector("[command=cmd_paste]").hasAttribute("disabled"), "paste enabled");
       ok(contextmenu.querySelector("[command=cmd_delete]").hasAttribute("disabled"), "delete disabled");
       ok(!contextmenu.querySelector("[command=cmd_selectAll]").hasAttribute("disabled"), "select all enabled");
 
       contextmenu.querySelector("[command=cmd_undo]").click();
-      is(textarea.value, "", "undo worked");
+      is(element.value, "", "undo worked");
 
-      SimpleTest.finish();
+      // Close the context menu to avoid affecting next test
+      let popuphidden = new Promise(r => win.addEventListener("popuphidden", r, { once: true }));
+      contextmenu.hidePopup();
+      await popuphidden;
+      contextmenu.remove();
     }
-
   </script>
 </head>
 <body onload="runTest()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1513343">Mozilla Bug 1513343</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/toolkit/content/tests/chrome/test_textbox_search.xul
+++ b/toolkit/content/tests/chrome/test_textbox_search.xul
@@ -6,17 +6,17 @@
   -->
 <window title="Search textbox test" width="500" height="600"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
   <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
 
   <hbox>
     <textbox id="searchbox"
-             type="search"
+             is="search-textbox"
              oncommand="doSearch(this.value);"
              placeholder="random placeholder"
              timeout="1"/>
   </hbox>
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
 
@@ -25,19 +25,19 @@
 
 SimpleTest.waitForExplicitFinish();
 
 var gExpectedValue;
 var gLastTest;
 
 function doTests() {
   var textbox = $("searchbox");
-  var icons = document.getAnonymousElementByAttribute(textbox, "anonid", "search-icons");
-  var searchIcon = document.getAnonymousElementByAttribute(textbox, "class", "textbox-search-icon");
-  var clearIcon = document.getAnonymousElementByAttribute(textbox, "class", "textbox-search-clear");
+  var icons = textbox.querySelector(".textbox-search-icons");
+  var searchIcon = icons.querySelector(".textbox-search-icon");
+  var clearIcon = icons.querySelector(".textbox-search-clear");
 
   ok(icons, "icon deck found");
   ok(searchIcon, "search icon found");
   ok(clearIcon, "clear icon found");
   is(icons.selectedPanel, searchIcon, "search icon is displayed");
 
   is(textbox.placeholder, "random placeholder", "search textbox supports placeholder");
   is(textbox.value, "", "placeholder doesn't interfere with the real value");
copy from toolkit/content/widgets/textbox.xml
copy to toolkit/content/widgets/search-textbox.js
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/search-textbox.js
@@ -1,398 +1,225 @@
-<?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/. -->
-
-<!-- This files relies on these specific Chrome/XBL globals -->
-<!-- globals ChromeWindow -->
-
-
-<!DOCTYPE bindings [
-  <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
-  %textcontextDTD;
-]>
-
-<bindings id="textboxBindings"
-   xmlns="http://www.mozilla.org/xbl"
-   xmlns:html="http://www.w3.org/1999/xhtml"
-   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-   xmlns:xbl="http://www.mozilla.org/xbl">
-
-  <binding id="textbox">
-    <content>
-      <children/>
-      <xul:moz-input-box anonid="moz-input-box" flex="1" xbl:inherits="context,spellcheck">
-        <html:input class="textbox-input" anonid="input"
-                    xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,noinitialfocus,mozactionhint,spellcheck"/>
-      </xul:moz-input-box>
-    </content>
-
-    <implementation>
-      <!-- nsIDOMXULLabeledControlElement -->
-      <field name="crop">""</field>
-      <field name="image">""</field>
-      <field name="command">""</field>
-      <field name="accessKey">""</field>
-
-      <field name="mInputField">null</field>
-      <field name="mIgnoreClick">false</field>
-      <field name="mIgnoreFocus">false</field>
-      <field name="mEditor">null</field>
+/* 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/. */
 
-      <property name="inputField" readonly="true">
-        <getter><![CDATA[
-          if (!this.mInputField)
-            this.mInputField = document.getAnonymousElementByAttribute(this, "anonid", "input");
-          return this.mInputField;
-        ]]></getter>
-      </property>
+"use strict";
 
-      <property name="value"      onset="this.inputField.value = val; return val;"
-                                  onget="return this.inputField.value;"/>
-      <property name="defaultValue" onset="this.inputField.defaultValue = val; return val;"
-                                  onget="return this.inputField.defaultValue;"/>
-      <property name="label"      onset="this.setAttribute('label', val); return val;"
-                                  onget="return this.getAttribute('label') || this.placeholder;" />
-      <property name="placeholder" onset="this.inputField.placeholder = val; return val;"
-                                  onget="return this.inputField.placeholder;"/>
-      <property name="emptyText"  onset="this.placeholder = val; return val;"
-                                  onget="return this.placeholder;"/>
-      <property name="type"       onset="if (val) this.setAttribute('type', val);
-                                         else this.removeAttribute('type'); return val;"
-                                  onget="return this.getAttribute('type');"/>
-      <property name="maxLength"  onset="this.inputField.maxLength = val; return val;"
-                                  onget="return this.inputField.maxLength;"/>
-      <property name="disabled"   onset="this.inputField.disabled = val;
-                                         if (val) this.setAttribute('disabled', 'true');
-                                         else this.removeAttribute('disabled'); return val;"
-                                  onget="return this.inputField.disabled;"/>
-      <property name="tabIndex"   onget="return parseInt(this.getAttribute('tabindex'));"
-                                  onset="this.inputField.tabIndex = val;
-                                         if (val) this.setAttribute('tabindex', val);
-                                         else this.removeAttribute('tabindex'); return val;"/>
-      <property name="size"       onset="this.inputField.size = val; return val;"
-                                  onget="return this.inputField.size;"/>
-      <property name="readOnly"   onset="this.inputField.readOnly = val;
-                                         if (val) this.setAttribute('readonly', 'true');
-                                         else this.removeAttribute('readonly'); return val;"
-                                  onget="return this.inputField.readOnly;"/>
-      <property name="clickSelectsAll"
-                onget="return this.getAttribute('clickSelectsAll') == 'true';"
-                onset="if (val) this.setAttribute('clickSelectsAll', 'true');
-                       else this.removeAttribute('clickSelectsAll'); return val;" />
-
-      <property name="editor" readonly="true">
-        <getter><![CDATA[
-          if (!this.mEditor) {
-            this.mEditor = this.inputField.editor;
-          }
-          return this.mEditor;
-        ]]></getter>
-      </property>
+// This is loaded into all XUL windows. Wrap in a block to prevent
+// leaking to window scope.
+{
+const HTML_NS = "http://www.w3.org/1999/xhtml";
 
-      <method name="reset">
-        <body><![CDATA[
-          this.value = this.defaultValue;
-          try {
-            this.editor.transactionManager.clear();
-            return true;
-          } catch (e) {}
-          return false;
-        ]]></body>
-      </method>
-
-      <method name="select">
-        <body>
-          this.inputField.select();
-        </body>
-      </method>
-
-      <method name="setUserInput">
-        <parameter name="value"/>
-        <body><![CDATA[
-          this.inputField.setUserInput(value);
-        ]]></body>
-      </method>
-
-      <property name="controllers"    readonly="true" onget="return this.inputField.controllers"/>
-      <property name="textLength"     readonly="true"
-                                      onget="return this.inputField.textLength;"/>
-      <property name="selectionStart" onset="this.inputField.selectionStart = val; return val;"
-                                      onget="return this.inputField.selectionStart;"/>
-      <property name="selectionEnd"   onset="this.inputField.selectionEnd = val; return val;"
-                                      onget="return this.inputField.selectionEnd;"/>
-
-      <method name="setSelectionRange">
-        <parameter name="aSelectionStart"/>
-        <parameter name="aSelectionEnd"/>
-        <body>
-          // According to https://html.spec.whatwg.org/#do-not-apply,
-          // setSelectionRange() is only available on a limited set of input types.
-          if (this.inputField.type == "text") {
-            this.inputField.setSelectionRange( aSelectionStart, aSelectionEnd );
-          }
-        </body>
-      </method>
+class MozSearchTextbox extends MozXULElement {
+  constructor() {
+    super();
 
-      <method name="_setNewlineHandling">
-        <body><![CDATA[
-          var str = this.getAttribute("newlines");
-          if (str && this.editor) {
-            const nsIPlaintextEditor = Ci.nsIPlaintextEditor;
-            for (var x in nsIPlaintextEditor) {
-              if (/^eNewlines/.test(x)) {
-                if (str == RegExp.rightContext.toLowerCase()) {
-                  this.editor.QueryInterface(nsIPlaintextEditor)
-                      .newlineHandling = nsIPlaintextEditor[x];
-                  break;
-                }
-              }
-            }
-          }
-        ]]></body>
-      </method>
+    this.inputField = document.createElementNS(HTML_NS, "input");
 
-      <method name="_maybeSelectAll">
-        <body><![CDATA[
-          if (!this.mIgnoreClick && this.clickSelectsAll &&
-              document.activeElement == this.inputField &&
-              this.inputField.selectionStart == this.inputField.selectionEnd)
-            this.editor.selectAll();
-        ]]></body>
-      </method>
-
-      <constructor><![CDATA[
-        var str = this._cachedInputFieldValue;
-        if (str) {
-          this.inputField.value = str;
-          delete this._cachedInputFieldValue;
-        }
-
-        this._setNewlineHandling();
-
-        if (this.hasAttribute("emptytext"))
-          this.placeholder = this.getAttribute("emptytext");
-      ]]></constructor>
-
-      <destructor>
-        <![CDATA[
-          var field = this.inputField;
-          if (field && field.value) {
-            this._cachedInputFieldValue = field.value;
-          }
-
-          this.mInputField = null;
-        ]]>
-      </destructor>
-
-    </implementation>
+    const METHODS = ["focus", "blur", "select", "setUserInput", "setSelectionRange"];
+    for (const method of METHODS) {
+      this[method] = (...args) => this.inputField[method](...args);
+    }
 
-    <handlers>
-      <handler event="focus" phase="capturing">
-        <![CDATA[
-          if (this.hasAttribute("focused"))
-            return;
-
-          switch (event.originalTarget) {
-            case this:
-              // Forward focus to actual HTML input
-              this.inputField.focus();
-              this.setAttribute("focused", "true");
-              break;
-            case this.inputField:
-              if (this.mIgnoreFocus) {
-                this.mIgnoreFocus = false;
-              } else if (this.clickSelectsAll) {
-                try {
-                  if (!this.editor || !this.editor.composing)
-                    this.editor.selectAll();
-                } catch (e) {}
-              }
-              this.setAttribute("focused", "true");
-              break;
-            default:
-              // Otherwise, allow other children (e.g. URL bar buttons) to get focus
-              break;
-          }
-        ]]>
-      </handler>
-
-      <handler event="blur" phase="capturing">
-        <![CDATA[
-          this.removeAttribute("focused");
-
-          // don't trigger clickSelectsAll when switching application windows
-          if (window == window.top &&
-              window.isChromeWindow &&
-              document.activeElement == this.inputField)
-            this.mIgnoreFocus = true;
-        ]]>
-      </handler>
-
-      <handler event="mousedown">
-        <![CDATA[
-          this.mIgnoreClick = this.hasAttribute("focused");
-
-          if (!this.mIgnoreClick) {
-            this.mIgnoreFocus = true;
-            this.setSelectionRange(0, 0);
-            if (event.originalTarget == this ||
-                event.originalTarget == this.inputField.parentNode)
-              this.inputField.focus();
-          }
-        ]]>
-      </handler>
-
-      <handler event="click" action="this._maybeSelectAll();"/>
-
-#ifndef XP_WIN
-      <handler event="contextmenu">
-        // Only care about context clicks on the textbox itself.
-        if (event.target != this)
-          return;
-
-        if (!event.button) // context menu opened via keyboard shortcut
-          return;
-        this._maybeSelectAll();
-        // see bug 576135 comment 4
-        let box = this.inputField.parentNode;
-        box._doPopupItemEnabling(box.menupopup);
-      </handler>
-#endif
-    </handlers>
-  </binding>
+    const READ_WRITE_PROPERTIES = ["defaultValue", "placeholder", "readOnly",
+      "size", "selectionStart", "selectionEnd"];
+    for (const property of READ_WRITE_PROPERTIES) {
+      Object.defineProperty(this, property, {
+        enumerable: true,
+        get() {
+          return this.inputField[property];
+        },
+        set(val) {
+          return this.inputField[property] = val;
+        },
+      });
+    }
 
-  <binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
-    <content>
-      <children/>
-      <xul:moz-input-box anonid="moz-input-box" flex="1" xbl:inherits="context,spellcheck" align="center">
-        <xul:image class="textbox-search-sign"/>
-        <html:input class="textbox-input" anonid="input" mozactionhint="search"
-                    xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck"/>
-        <xul:deck class="textbox-search-icons" anonid="search-icons">
-          <xul:image class="textbox-search-icon" anonid="searchbutton-icon"
-                     xbl:inherits="src=image,label=searchbuttonlabel,searchbutton,disabled"/>
-          <xul:image class="textbox-search-clear"
-                     onclick="document.getBindingParent(this)._clearSearch();"
-                     label="&searchTextBox.clear.label;"
-                     xbl:inherits="disabled"/>
-        </xul:deck>
-      </xul:moz-input-box>
-    </content>
-    <implementation>
-      <field name="_timer">null</field>
-      <field name="_searchIcons">
-        document.getAnonymousElementByAttribute(this, "anonid", "search-icons");
-      </field>
-      <field name="_searchButtonIcon">
-        document.getAnonymousElementByAttribute(this, "anonid", "searchbutton-icon");
-      </field>
-      <property name="timeout"
-                onset="this.setAttribute('timeout', val); return val;"
-                onget="return parseInt(this.getAttribute('timeout')) || 500;"/>
-      <property name="searchButton"
-                onget="return this.getAttribute('searchbutton') == 'true';">
-        <setter><![CDATA[
-          if (val) {
-            this.setAttribute("searchbutton", "true");
-            this.removeAttribute("aria-autocomplete");
-            // Hack for the button to get the right accessible:
-            this._searchButtonIcon.setAttribute("onclick", "true");
-          } else {
-            this.removeAttribute("searchbutton");
-            this._searchButtonIcon.removeAttribute("onclick");
-            this.setAttribute("aria-autocomplete", "list");
-          }
-          return val;
-        ]]></setter>
-      </property>
-      <property name="value"
-                onget="return this.inputField.value;">
-        <setter><![CDATA[
-          this.inputField.value = val;
-
-          if (val)
-            this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
-          else
-            this._searchIcons.selectedIndex = 0;
-
-          if (this._timer)
-            clearTimeout(this._timer);
+    this.addEventListener("input", (event) => {
+      if (this.searchButton) {
+        this._searchIcons.selectedIndex = 0;
+        return;
+      }
+      if (this._timer) {
+        clearTimeout(this._timer);
+      }
+      this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
+      this._searchIcons.selectedIndex = this.value ? 1 : 0;
+    });
 
-          return val;
-        ]]></setter>
-      </property>
-      <constructor><![CDATA[
-        // Ensure the button state is up to date:
-        this.searchButton = this.searchButton;
-        this._searchButtonIcon.addEventListener("click", (e) => this._iconClick(e));
-      ]]></constructor>
-      <method name="_fireCommand">
-        <parameter name="me"/>
-        <body><![CDATA[
-          if (me._timer)
-            clearTimeout(me._timer);
-          me._timer = null;
-          me.doCommand();
-        ]]></body>
-      </method>
-      <method name="_iconClick">
-        <body><![CDATA[
-          if (this.searchButton)
-            this._enterSearch();
-          else
-            this.focus();
-        ]]></body>
-      </method>
-      <method name="_enterSearch">
-        <body><![CDATA[
-          if (this.disabled)
-            return;
-          if (this.searchButton && this.value && !this.readOnly)
-            this._searchIcons.selectedIndex = 1;
-          this._fireCommand(this);
-        ]]></body>
-      </method>
-      <method name="_clearSearch">
-        <body><![CDATA[
-          if (!this.disabled && !this.readOnly && this.value) {
-            this.value = "";
-            this._fireCommand(this);
-            this._searchIcons.selectedIndex = 0;
-            return true;
-          }
-          return false;
-        ]]></body>
-      </method>
-    </implementation>
-    <handlers>
-      <handler event="input">
-        <![CDATA[
-          if (this.searchButton) {
-            this._searchIcons.selectedIndex = 0;
-            return;
-          }
-          if (this._timer)
-            clearTimeout(this._timer);
-          this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
-          this._searchIcons.selectedIndex = this.value ? 1 : 0;
-        ]]>
-      </handler>
-      <handler event="keypress" keycode="VK_ESCAPE">
-        <![CDATA[
+    this.addEventListener("keypress", (event) => {
+      switch (event.keyCode) {
+        case KeyEvent.DOM_VK_ESCAPE:
           if (this._clearSearch()) {
             event.preventDefault();
             event.stopPropagation();
           }
-        ]]>
-      </handler>
-      <handler event="keypress" keycode="VK_RETURN">
-        <![CDATA[
+          break;
+        case KeyEvent.DOM_VK_RETURN:
           this._enterSearch();
           event.preventDefault();
           event.stopPropagation();
-        ]]>
-      </handler>
-    </handlers>
-  </binding>
-</bindings>
+          break;
+      }
+    });
+  }
+
+  static get inheritedAttributes() {
+    return {
+      ".textbox-input": "value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck",
+      ".textbox-search-icon": "src=image,label=searchbuttonlabel,searchbutton,disabled",
+      ".textbox-search-clear": "disabled",
+    };
+  }
+
+  connectedCallback() {
+    if (this.delayConnectedCallback()) {
+      return;
+    }
+    this.textContent = "";
+
+    const textboxSign = document.createXULElement("image");
+    textboxSign.className = "textbox-search-sign";
+
+    const input = this.inputField;
+    input.className = "textbox-input";
+    input.setAttribute("mozactionhint", "search");
+    input.addEventListener("focus", () => this.setAttribute("focused", "true"));
+    input.addEventListener("blur", () => this.removeAttribute("focused"));
+
+    const searchBtn = this._searchButtonIcon = document.createXULElement("image");
+    searchBtn.className = "textbox-search-icon";
+    searchBtn.addEventListener("click", (e) => this._iconClick(e));
+
+    // TODO: Bug 1534799 - Convert string to Fluent and use manual DOM construction
+    let clearBtn = MozXULElement.parseXULToFragment(`
+      <image class="textbox-search-clear" label="&searchTextBox.clear.label;"/>
+    `, ["chrome://global/locale/textcontext.dtd"]);
+    clearBtn = this._searchClearIcon = clearBtn.querySelector(".textbox-search-clear");
+    clearBtn.addEventListener("click", () => this._clearSearch());
+
+    const deck = this._searchIcons = document.createXULElement("deck");
+    deck.className = "textbox-search-icons";
+    deck.append(searchBtn, clearBtn);
+    this.append(textboxSign, input, deck);
+
+    this._timer = null;
+
+    // Ensure the button state is up to date:
+    this.searchButton = this.searchButton;
+
+    // Set is attribute for styling
+    this.setAttribute("is", "search-textbox");
+
+    this.initializeAttributeInheritance();
+  }
+
+  set timeout(val) {
+    this.setAttribute("timeout", val);
+    return val;
+  }
+
+  get timeout() {
+    return parseInt(this.getAttribute("timeout")) || 500;
+  }
+
+  set searchButton(val) {
+    if (val) {
+      this.setAttribute("searchbutton", "true");
+      this.removeAttribute("aria-autocomplete");
+      // Hack for the button to get the right accessible:
+      this._searchButtonIcon.setAttribute("onclick", "true");
+    } else {
+      this.removeAttribute("searchbutton");
+      this._searchButtonIcon.removeAttribute("onclick");
+      this.setAttribute("aria-autocomplete", "list");
+    }
+    return val;
+  }
+
+  get searchButton() {
+    return this.getAttribute("searchbutton") == "true";
+  }
+
+  set value(val) {
+    this.inputField.value = val;
+
+    if (val) {
+      this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
+    } else {
+      this._searchIcons.selectedIndex = 0;
+    }
+
+    if (this._timer) {
+      clearTimeout(this._timer);
+    }
+    return val;
+  }
+
+  get value() {
+    return this.inputField.value;
+  }
+
+  get editor() {
+    return this.inputField.editor;
+  }
+
+  set disabled(val) {
+    this.inputField.disabled = val;
+    if (val) this.setAttribute("disabled", "true");
+    else this.removeAttribute("disabled");
+    return val;
+  }
+
+  get disabled() {
+    return this.inputField.disabled;
+  }
+
+  reset() {
+    this.value = this.defaultValue;
+    // XXX: Is this still needed ?
+    try {
+      this.editor.transactionManager.clear();
+      return true;
+    } catch (e) {}
+    return false;
+  }
+
+  _fireCommand(me) {
+    if (me._timer) {
+      clearTimeout(me._timer);
+    }
+    me._timer = null;
+    me.doCommand();
+  }
+
+  _iconClick() {
+    if (this.searchButton) {
+      this._enterSearch();
+    } else {
+      this.focus();
+    }
+  }
+
+  _enterSearch() {
+    if (this.disabled) {
+      return;
+    }
+    if (this.searchButton && this.value && !this.readOnly) {
+      this._searchIcons.selectedIndex = 1;
+    }
+    this._fireCommand(this);
+  }
+
+  _clearSearch() {
+    if (!this.disabled && !this.readOnly && this.value) {
+      this.value = "";
+      this._fireCommand(this);
+      this._searchIcons.selectedIndex = 0;
+      return true;
+    }
+    return false;
+  }
+}
+
+customElements.define("search-textbox", MozSearchTextbox, { extends: "textbox" });
+}
--- a/toolkit/content/widgets/textbox.xml
+++ b/toolkit/content/widgets/textbox.xml
@@ -256,143 +256,9 @@
         this._maybeSelectAll();
         // see bug 576135 comment 4
         let box = this.inputField.parentNode;
         box._doPopupItemEnabling(box.menupopup);
       </handler>
 #endif
     </handlers>
   </binding>
-
-  <binding id="search-textbox" extends="chrome://global/content/bindings/textbox.xml#textbox">
-    <content>
-      <children/>
-      <xul:moz-input-box anonid="moz-input-box" flex="1" xbl:inherits="context,spellcheck" align="center">
-        <xul:image class="textbox-search-sign"/>
-        <html:input class="textbox-input" anonid="input" mozactionhint="search"
-                    xbl:inherits="value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck"/>
-        <xul:deck class="textbox-search-icons" anonid="search-icons">
-          <xul:image class="textbox-search-icon" anonid="searchbutton-icon"
-                     xbl:inherits="src=image,label=searchbuttonlabel,searchbutton,disabled"/>
-          <xul:image class="textbox-search-clear"
-                     onclick="document.getBindingParent(this)._clearSearch();"
-                     label="&searchTextBox.clear.label;"
-                     xbl:inherits="disabled"/>
-        </xul:deck>
-      </xul:moz-input-box>
-    </content>
-    <implementation>
-      <field name="_timer">null</field>
-      <field name="_searchIcons">
-        document.getAnonymousElementByAttribute(this, "anonid", "search-icons");
-      </field>
-      <field name="_searchButtonIcon">
-        document.getAnonymousElementByAttribute(this, "anonid", "searchbutton-icon");
-      </field>
-      <property name="timeout"
-                onset="this.setAttribute('timeout', val); return val;"
-                onget="return parseInt(this.getAttribute('timeout')) || 500;"/>
-      <property name="searchButton"
-                onget="return this.getAttribute('searchbutton') == 'true';">
-        <setter><![CDATA[
-          if (val) {
-            this.setAttribute("searchbutton", "true");
-            this.removeAttribute("aria-autocomplete");
-            // Hack for the button to get the right accessible:
-            this._searchButtonIcon.setAttribute("onclick", "true");
-          } else {
-            this.removeAttribute("searchbutton");
-            this._searchButtonIcon.removeAttribute("onclick");
-            this.setAttribute("aria-autocomplete", "list");
-          }
-          return val;
-        ]]></setter>
-      </property>
-      <property name="value"
-                onget="return this.inputField.value;">
-        <setter><![CDATA[
-          this.inputField.value = val;
-
-          if (val)
-            this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
-          else
-            this._searchIcons.selectedIndex = 0;
-
-          if (this._timer)
-            clearTimeout(this._timer);
-
-          return val;
-        ]]></setter>
-      </property>
-      <constructor><![CDATA[
-        // Ensure the button state is up to date:
-        this.searchButton = this.searchButton;
-        this._searchButtonIcon.addEventListener("click", (e) => this._iconClick(e));
-      ]]></constructor>
-      <method name="_fireCommand">
-        <parameter name="me"/>
-        <body><![CDATA[
-          if (me._timer)
-            clearTimeout(me._timer);
-          me._timer = null;
-          me.doCommand();
-        ]]></body>
-      </method>
-      <method name="_iconClick">
-        <body><![CDATA[
-          if (this.searchButton)
-            this._enterSearch();
-          else
-            this.focus();
-        ]]></body>
-      </method>
-      <method name="_enterSearch">
-        <body><![CDATA[
-          if (this.disabled)
-            return;
-          if (this.searchButton && this.value && !this.readOnly)
-            this._searchIcons.selectedIndex = 1;
-          this._fireCommand(this);
-        ]]></body>
-      </method>
-      <method name="_clearSearch">
-        <body><![CDATA[
-          if (!this.disabled && !this.readOnly && this.value) {
-            this.value = "";
-            this._fireCommand(this);
-            this._searchIcons.selectedIndex = 0;
-            return true;
-          }
-          return false;
-        ]]></body>
-      </method>
-    </implementation>
-    <handlers>
-      <handler event="input">
-        <![CDATA[
-          if (this.searchButton) {
-            this._searchIcons.selectedIndex = 0;
-            return;
-          }
-          if (this._timer)
-            clearTimeout(this._timer);
-          this._timer = this.timeout && setTimeout(this._fireCommand, this.timeout, this);
-          this._searchIcons.selectedIndex = this.value ? 1 : 0;
-        ]]>
-      </handler>
-      <handler event="keypress" keycode="VK_ESCAPE">
-        <![CDATA[
-          if (this._clearSearch()) {
-            event.preventDefault();
-            event.stopPropagation();
-          }
-        ]]>
-      </handler>
-      <handler event="keypress" keycode="VK_RETURN">
-        <![CDATA[
-          this._enterSearch();
-          event.preventDefault();
-          event.stopPropagation();
-        ]]>
-      </handler>
-    </handlers>
-  </binding>
 </bindings>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -453,18 +453,18 @@ tooltip[titletip="true"] {
 /********** textbox **********/
 
 textbox {
   -moz-binding: url("chrome://global/content/bindings/textbox.xml#textbox");
   -moz-user-select: text;
   text-shadow: none;
 }
 
-textbox[type="search"] {
-  -moz-binding: url("chrome://global/content/bindings/textbox.xml#search-textbox");
+textbox[is="search-textbox"] {
+  -moz-binding: none;
 }
 
 /* Prefix with (xul|*):root to workaround HTML tests loading xul.css */
 :root html|textarea:not([resizable="true"]) {
   resize: none;
 }
 
 @supports -moz-bool-pref("layout.css.emulate-moz-box-with-flex") {
--- a/toolkit/modules/SelectParentHelper.jsm
+++ b/toolkit/modules/SelectParentHelper.jsm
@@ -431,21 +431,23 @@ function populateChildren(menulist, opti
     }
   }
 
   // Check if search pref is enabled, if this is the first time iterating through
   // the dropdown, and if the list is long enough for a search element to be added.
   if (Services.prefs.getBoolPref("dom.forms.selectSearch") && addSearch
       && element.childElementCount > SEARCH_MINIMUM_ELEMENTS) {
     // Add a search text field as the first element of the dropdown
-    let searchbox = element.ownerDocument.createXULElement("textbox");
-    searchbox.setAttribute("type", "search");
+    let searchbox = element.ownerDocument.createXULElement("textbox", {
+      is: "search-textbox",
+    });
+    searchbox.className = "contentSelectDropdown-searchbox";
     searchbox.addEventListener("input", onSearchInput);
-    searchbox.addEventListener("focus", onSearchFocus);
-    searchbox.addEventListener("blur", onSearchBlur);
+    searchbox.inputField.addEventListener("focus", onSearchFocus);
+    searchbox.inputField.addEventListener("blur", onSearchBlur);
     searchbox.addEventListener("command", onSearchInput);
 
     // Handle special keys for exiting search
     searchbox.addEventListener("keydown", function(event) {
       if (event.defaultPrevented) {
         return;
       }
       switch (event.key) {
@@ -537,19 +539,19 @@ function onSearchInput() {
         prevCaption.hidden = allHidden;
       }
     }
   }
 }
 
 function onSearchFocus() {
   let searchObj = this;
-  let menupopup = searchObj.parentElement;
+  let menupopup = searchObj.closest("menupopup");
   menupopup.parentElement.activeChild = null;
   menupopup.setAttribute("ignorekeys", "true");
   currentBrowser.messageManager.sendAsyncMessage("Forms:SearchFocused", {});
 }
 
 function onSearchBlur() {
   let searchObj = this;
-  let menupopup = searchObj.parentElement;
+  let menupopup = searchObj.closest("menupopup");
   menupopup.setAttribute("ignorekeys", "false");
 }
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -221,17 +221,17 @@
               <button id="show-all-extensions" hidden="true"
                       data-l10n-id="show-all-extensions-button"
                       command="cmd_showAllExtensions"/>
               <spacer flex="1"/>
               <button id="show-disabled-unsigned-extensions" hidden="true"
                       class="warning" data-l10n-id="show-unsigned-extensions-button"
                       command="cmd_showUnsignedExtensions"/>
               <label id="search-label" control="header-search"/>
-              <textbox id="header-search" type="search" searchbutton="true"
+              <textbox id="header-search" is="search-textbox" searchbutton="true"
                        data-l10n-id="search-header"
                        data-l10n-attrs="searchbuttonlabel" maxlength="100"/>
             </hbox>
           </hbox>
 
           <hbox id="heading">
             <hbox class="heading-inner" align="stretch">
               <hbox id="go-back-wrapper">
--- a/toolkit/mozapps/extensions/test/browser/browser_bug570760.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug570760.js
@@ -5,28 +5,30 @@
 // Bug 570760 - Make ctrl-f and / focus the search box in the add-ons manager
 
 var gManagerWindow;
 var focusCount = 0;
 
 async function test() {
   waitForExplicitFinish();
 
-  let aWindow = await open_manager(null);
+  // The discovery pane does not display the about:addons searchbox,
+  // open the extensions pane instead.
+  let aWindow = await open_manager("addons://list/extension");
   gManagerWindow = aWindow;
 
   var searchBox = gManagerWindow.document.getElementById("header-search");
   function focusHandler() {
     searchBox.blur();
     focusCount++;
   }
-  searchBox.addEventListener("focus", focusHandler);
+  searchBox.inputField.addEventListener("focus", focusHandler);
   f_key_test();
   slash_key_test();
-  searchBox.removeEventListener("focus", focusHandler);
+  searchBox.inputField.removeEventListener("focus", focusHandler);
   end_test();
 }
 
 function end_test() {
   close_manager(gManagerWindow, finish);
 }
 
 function f_key_test() {
--- a/toolkit/themes/linux/global/textbox.css
+++ b/toolkit/themes/linux/global/textbox.css
@@ -65,17 +65,17 @@ textbox.plain {
 }
 
 textbox.plain html|*.textbox-input {
   padding: 0px !important;
 }
 
 /* ::::: search textbox ::::: */
 
-textbox:not([searchbutton]) > moz-input-box > .textbox-search-sign {
+textbox:not([searchbutton]) > .textbox-search-sign {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
   margin-inline-end: 5px;
 }
 
 .textbox-search-icon[searchbutton] {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
 }
 
--- a/toolkit/themes/osx/global/in-content/common.css
+++ b/toolkit/themes/osx/global/in-content/common.css
@@ -63,23 +63,23 @@ xul|tab:-moz-focusring > .tab-middle > .
   outline-offset: 1px;
   -moz-outline-radius: 2px;
 }
 
 xul|radio[focused="true"] > .radio-check {
   -moz-outline-radius: 100%;
 }
 
-textbox[type="search"] {
+textbox[is="search-textbox"] {
   -moz-appearance: none;
   padding-inline-start: 8px;
   padding-inline-end: 8px;
 }
 
-xul|textbox[type="search"] > moz-input-box > .textbox-search-sign {
+xul|textbox[is="search-textbox"] > .textbox-search-sign {
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   fill-opacity: 0.8;
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
   margin-inline-end: 5px;
 }
 
 html|button {
--- a/toolkit/themes/osx/global/textbox.css
+++ b/toolkit/themes/osx/global/textbox.css
@@ -55,17 +55,17 @@ textbox.plain {
 }
 
 textbox.plain html|*.textbox-input {
   padding: 0px !important;
 }
 
 /* ::::: search box ::::: */
 
-textbox[type="search"] {
+textbox[is="search-textbox"] {
   -moz-appearance: searchfield;
   padding: 1px;
   font-size: 12px;
 }
 
 .textbox-search-clear {
   list-style-image: url(chrome://global/skin/icons/searchfield-cancel.svg);
   -moz-image-region: rect(0, 14px, 14px, 0);
--- a/toolkit/themes/windows/global/textbox.css
+++ b/toolkit/themes/windows/global/textbox.css
@@ -71,17 +71,17 @@ textbox.plain {
 }
 
 textbox.plain html|*.textbox-input {
   padding: 0px !important;
 }
 
 /* ::::: search textbox ::::: */
 
-textbox:not([searchbutton]) > moz-input-box > .textbox-search-sign {
+textbox:not([searchbutton]) > .textbox-search-sign {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
   margin-inline-end: 5px;
 }
 
 .textbox-search-icon[searchbutton] {
   list-style-image: url(chrome://global/skin/icons/search-textbox.svg);
 }