Bug 1384830 - Show search result as the user types in Preferences search, r=jaws draft
authorTimothy Guan-tin Chien <timdream@gmail.com>
Thu, 03 Aug 2017 14:32:45 +0800
changeset 645722 2f1b6cd9f50d79c24dcddc22c61c50d347ddf3f0
parent 645674 3bfcbdf5c6c381d5a8febb5c209e27a69fb89f9b
child 725989 2228581d088d7a088797bf502a12bb40720350fc
push id73850
push usertimdream@gmail.com
push dateMon, 14 Aug 2017 03:48:11 +0000
reviewersjaws
bugs1384830
milestone57.0a1
Bug 1384830 - Show search result as the user types in Preferences search, r=jaws This patch changes the event to listen on the search textbox from "command" to "input", which means the code will run with every keystroke, instead of once a few keystrokes after a timeout. It will make the search very responsive. However, with that change, each keystroke will be blocked by the search function, which go through the entire DOM of the page. This patch fixes that by making the search function async. The search will now check the execution time with Performance API, and give way when it blocks the page for more than 1/60 sec. Further care was taken to make sure page won't flash unchecked content while the loop go through the entire DOM. The elements to be checked can't be hidden because of the JS bindings, so CSS rules are set to ensure they are kept visually hidden. Unfortunately, CSS cannot change XUL layout so they still occupies page space. Additional check was made to make sure the search function skips hidden elements, if the previous query is a substring of the search query. This is done so that search function does not unhide and hide these elements, and causes the search result to flash as the user types. MozReview-Commit-ID: BwBoJmTperw
browser/components/preferences/in-content-new/findInPage.js
browser/components/preferences/in-content-new/preferences.js
browser/components/preferences/in-content-new/tests/browser.ini
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js
browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js
browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js
browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js
browser/components/preferences/in-content-new/tests/browser_search_within_preferences_command.js
browser/components/preferences/in-content-new/tests/head.js
browser/themes/shared/incontentprefs/preferences.inc.css
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -13,16 +13,17 @@ var gSearchResultsPane = {
   init() {
     if (this.inited) {
       return;
     }
     this.inited = true;
     this.searchInput = document.getElementById("searchInput");
     this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search");
     if (!this.searchInput.hidden) {
+      this.searchInput.addEventListener("input", this);
       this.searchInput.addEventListener("command", this);
       window.addEventListener("DOMContentLoaded", () => {
         this.searchInput.focus();
       });
       // Initialize other panes in an idle callback.
       window.requestIdleCallback(() => this.initializeCategories());
     }
     let strings = this.strings;
@@ -140,17 +141,18 @@ var gSearchResultsPane = {
     // Looping through each spot the searchPhrase is found in the concatenated string
     for (let startValue of indices) {
       let endValue = startValue + searchPhrase.length;
       let startNode = null;
       let endNode = null;
       let nodeStartIndex = null;
 
       // Determining the start and end node to highlight from
-      nodeSizes.forEach(function(lengthNodes, index) {
+      for (let index = 0; index < nodeSizes.length; index++) {
+        let lengthNodes = nodeSizes[index];
         // Determining the start node
         if (!startNode && lengthNodes >= startValue) {
           startNode = textNodes[index];
           nodeStartIndex = index;
           // Calculating the offset when found query is not in the first node
           if (index > 0) {
             startValue -= nodeSizes[index - 1];
           }
@@ -159,17 +161,17 @@ var gSearchResultsPane = {
         if (!endNode && lengthNodes >= endValue) {
           endNode = textNodes[index];
           // Calculating the offset when endNode is different from startNode
           // or when endNode is not the first node
           if (index != nodeStartIndex || index > 0 ) {
             endValue -= nodeSizes[index - 1];
           }
         }
-      });
+      }
       let range = document.createRange();
       range.setStart(startNode, startValue);
       range.setEnd(endNode, endValue);
       this.getFindSelection(startNode.ownerGlobal).addRange(range);
     }
 
     return indices.length > 0;
   },
@@ -202,18 +204,25 @@ var gSearchResultsPane = {
   },
 
   /**
    * Shows or hides content according to search input
    *
    * @param String event
    *    to search for filted query in
    */
-  searchFunction(event) {
-    this.query = event.target.value.trim().toLowerCase();
+  async searchFunction(event) {
+    let query = event.target.value.trim().toLowerCase();
+    if (this.query == query) {
+      return;
+    }
+
+    let subQuery = this.query && query.indexOf(this.query) !== -1;
+    this.query = query;
+
     this.getFindSelection(window).removeAllRanges();
     this.removeAllSearchTooltips();
     this.removeAllSearchMenuitemIndicators();
 
     // Clear telemetry request if user types very frequently.
     if (this.telemetryTimer) {
       clearTimeout(this.telemetryTimer);
     }
@@ -225,35 +234,63 @@ var gSearchResultsPane = {
       gotoPref("paneSearchResults");
 
       let resultsFound = false;
 
       // Building the range for highlighted areas
       let rootPreferencesChildren = document
         .querySelectorAll("#mainPrefPane > *:not([data-hidden-from-search])");
 
-      // Showing all the children to bind JS, Access Keys, etc
+      if (subQuery) {
+        // Since the previous query is a subset of the current query,
+        // there is no need to check elements that is hidden already.
+        rootPreferencesChildren =
+          Array.prototype.filter.call(rootPreferencesChildren, el => !el.hidden);
+      }
+
+      // Mark all the children to check be visible to bind JS, Access Keys, etc,
+      // but don't really show them by setting their visibility to hidden in CSS.
       for (let i = 0; i < rootPreferencesChildren.length; i++) {
         rootPreferencesChildren[i].hidden = false;
+        rootPreferencesChildren[i].classList.add("visually-hidden");
       }
 
+      let ts = performance.now();
+      let FRAME_THRESHOLD = 1000 / 60;
+
       // Showing or Hiding specific section depending on if words in query are found
       for (let i = 0; i < rootPreferencesChildren.length; i++) {
+        if (performance.now() - ts > FRAME_THRESHOLD) {
+          // Creating tooltips for all the instances found
+          for (let anchorNode of this.listSearchTooltips) {
+            this.createSearchTooltip(anchorNode, this.query);
+          }
+          // It hides Search Results header so turning it on
+          srHeader.hidden = false;
+          srHeader.classList.remove("visually-hidden");
+          ts = await new Promise(resolve => window.requestAnimationFrame(resolve));
+          if (query !== this.query) {
+            return;
+          }
+        }
+
+        rootPreferencesChildren[i].classList.remove("visually-hidden");
         if (!rootPreferencesChildren[i].classList.contains("header") &&
             !rootPreferencesChildren[i].classList.contains("subcategory") &&
             !rootPreferencesChildren[i].classList.contains("no-results-message") &&
             this.searchWithinNode(rootPreferencesChildren[i], this.query)) {
           rootPreferencesChildren[i].hidden = false;
           resultsFound = true;
         } else {
           rootPreferencesChildren[i].hidden = true;
         }
       }
       // It hides Search Results header so turning it on
       srHeader.hidden = false;
+      srHeader.classList.remove("visually-hidden");
 
       if (!resultsFound) {
         let noResultsEl = document.querySelector(".no-results-message");
         noResultsEl.hidden = false;
 
         let strings = this.strings;
 
         document.getElementById("sorry-message").textContent = AppConstants.platform == "win" ?
@@ -261,30 +298,34 @@ var gSearchResultsPane = {
           strings.getFormattedString("searchResults.sorryMessageUnix", [this.query]);
         let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences";
         let brandName = document.getElementById("bundleBrand").getString("brandShortName");
         // eslint-disable-next-line no-unsanitized/property
         document.getElementById("need-help").innerHTML =
           strings.getFormattedString("searchResults.needHelp2", [helpUrl, brandName]);
       } else {
         // Creating tooltips for all the instances found
-        this.listSearchTooltips.forEach((anchorNode) => this.createSearchTooltip(anchorNode, this.query));
+        for (let anchorNode of this.listSearchTooltips) {
+          this.createSearchTooltip(anchorNode, this.query);
+        }
 
         // Implant search telemetry probe after user stops typing for a while
         if (this.query.length >= 2) {
           this.telemetryTimer = setTimeout(() => {
             Services.telemetry.keyedScalarAdd("preferences.search_query", this.query, 1);
           }, 1000);
         }
       }
     } else {
       document.getElementById("sorry-message").textContent = "";
       // Going back to General when cleared
       gotoPref("paneGeneral");
     }
+
+    window.dispatchEvent(new CustomEvent("PreferencesSearchCompleted", { detail: query }));
   },
 
   /**
    * Finding leaf nodes and checking their content for words to search,
    * It is a recursive function
    *
    * @param Node nodeObject
    *    DOM Element
@@ -404,16 +445,19 @@ var gSearchResultsPane = {
    * Then calculation the offsets to position the tooltip in the correct place.
    *
    * @param Node anchorNode
    *    DOM Element
    * @param String query
    *    Word or words that are being searched for
    */
   createSearchTooltip(anchorNode, query) {
+    if (anchorNode.tooltipNode) {
+      return;
+    }
     let searchTooltip = anchorNode.ownerDocument.createElement("span");
     searchTooltip.setAttribute("class", "search-tooltip");
     searchTooltip.textContent = query;
 
     // Set tooltipNode property to track corresponded tooltip node.
     anchorNode.tooltipNode = searchTooltip;
     anchorNode.parentElement.classList.add("search-tooltip-parent");
     anchorNode.parentElement.appendChild(searchTooltip);
@@ -434,20 +478,25 @@ var gSearchResultsPane = {
    * Remove all search tooltips that were created.
    */
   removeAllSearchTooltips() {
     let searchTooltips = Array.from(document.querySelectorAll(".search-tooltip"));
     for (let searchTooltip of searchTooltips) {
       searchTooltip.parentElement.classList.remove("search-tooltip-parent");
       searchTooltip.remove();
     }
-    this.listSearchTooltips.forEach((anchorNode) => anchorNode.tooltipNode.remove());
+    for (let anchorNode of this.listSearchTooltips) {
+      anchorNode.tooltipNode.remove();
+      anchorNode.tooltipNode = null;
+    }
     this.listSearchTooltips.clear();
   },
 
   /**
    * Remove all indicators on menuitem.
    */
   removeAllSearchMenuitemIndicators() {
-    this.listSearchMenuitemIndicators.forEach((node) => node.removeAttribute("indicator"));
+    for (let node of this.listSearchMenuitemIndicators) {
+      node.removeAttribute("indicator");
+    }
     this.listSearchMenuitemIndicators.clear();
   }
 }
--- a/browser/components/preferences/in-content-new/preferences.js
+++ b/browser/components/preferences/in-content-new/preferences.js
@@ -235,16 +235,17 @@ function search(aQuery, aAttribute, aSub
           element.hidden = subAttributeValue != aSubquery;
         } else {
           element.hidden = false;
         }
       } else {
         element.hidden = true;
       }
     }
+    element.classList.remove("visually-hidden");
   }
 
   let keysets = mainPrefPane.getElementsByTagName("keyset");
   for (let element of keysets) {
     let attributeValue = element.getAttribute(aAttribute);
     if (attributeValue == aQuery)
       element.removeAttribute("disabled");
     else
--- a/browser/components/preferences/in-content-new/tests/browser.ini
+++ b/browser/components/preferences/in-content-new/tests/browser.ini
@@ -10,20 +10,25 @@ support-files =
 [browser_advanced_update.js]
 skip-if = !updater
 [browser_basic_rebuild_fonts_test.js]
 [browser_bug410900.js]
 [browser_bug705422.js]
 [browser_bug731866.js]
 [browser_search_within_preferences_1.js]
 [browser_search_within_preferences_2.js]
+[browser_search_within_preferences_command.js]
 [browser_search_subdialogs_within_preferences_1.js]
 [browser_search_subdialogs_within_preferences_2.js]
 [browser_search_subdialogs_within_preferences_3.js]
 [browser_search_subdialogs_within_preferences_4.js]
+[browser_search_subdialogs_within_preferences_5.js]
+[browser_search_subdialogs_within_preferences_6.js]
+[browser_search_subdialogs_within_preferences_7.js]
+[browser_search_subdialogs_within_preferences_8.js]
 [browser_bug795764_cachedisabled.js]
 [browser_bug1018066_resetScrollPosition.js]
 [browser_bug1020245_openPreferences_to_paneContent.js]
 [browser_bug1184989_prevent_scrolling_when_preferences_flipped.js]
 [browser_engines.js]
 support-files =
   browser_bug1184989_prevent_scrolling_when_preferences_flipped.xul
 [browser_change_app_handler.js]
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js
@@ -7,47 +7,20 @@ add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
  * Test for searching for the "Set Home Page" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Set Home Page", "startupGroup");
+  await evaluateSearchResults("Set Home Page", "startupGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Languages" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Choose languages", "languagesGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Fonts" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Text Encoding", "fontsGroup");
+  await evaluateSearchResults("Choose languages", "languagesGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
-
-/**
- * Test for searching for the "Colors" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Link Colors", "fontsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Exceptions - Saved Logins" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("sites will not be saved", "passwordsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js
@@ -10,38 +10,20 @@ add_task(async function() {
   ]});
 });
 
 /**
  * Test for searching for the "Saved Logins" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("sites are stored", "passwordsGroup");
+  await evaluateSearchResults("sites are stored", "passwordsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Exceptions - Tracking Protection" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("disabled Tracking Protection", "trackingGroup");
+  await evaluateSearchResults("disabled Tracking Protection", "trackingGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
-
-/**
- * Test for searching for the "Block Lists" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("block Web elements", "trackingGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Allowed Sites - Pop-ups" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("open pop-up windows", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js
@@ -10,47 +10,20 @@ add_task(async function() {
   ]});
 });
 
 /**
  * Test for searching for the "Allowed Sites - Add-ons Installation" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("allowed to install add-ons", "permissionsGroup");
+  await evaluateSearchResults("allowed to install add-ons", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Certificate Manager" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("identify these certificate authorities", "certSelection");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Device Manager" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Security Modules and Devices", "certSelection");
+  await evaluateSearchResults("identify these certificate authorities", "certSelection");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
-
-/**
- * Test for searching for the "Connection Settings" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Use system proxy settings", "connectionGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Settings - Site Data" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("store site data on your computer", "siteDataGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js
@@ -7,47 +7,20 @@ add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
  * Test for searching for the "Update History" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("updates have been installed", "updateApp");
+  await evaluateSearchResults("updates have been installed", "updateApp");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Location Permissions" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set location permissions", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Camera Permissions" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set camera permissions", "permissionsGroup");
+  await evaluateSearchResults("set location permissions", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
-
-/**
- * Test for searching for the "Microphone Permissions" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set microphone permissions", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Notification Permissions" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set notifications permissions", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
copy from browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js
copy to browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_1.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_5.js
@@ -3,51 +3,33 @@
 */
 
 // Enabling Searching functionatily. Will display search bar form this testcase forward.
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
- * Test for searching for the "Set Home Page" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Set Home Page", "startupGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Languages" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Choose languages", "languagesGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
  * Test for searching for the "Fonts" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Text Encoding", "fontsGroup");
+  await evaluateSearchResults("Text Encoding", "fontsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Colors" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Link Colors", "fontsGroup");
+  await evaluateSearchResults("Link Colors", "fontsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Exceptions - Saved Logins" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("sites will not be saved", "passwordsGroup");
+  await evaluateSearchResults("sites will not be saved", "passwordsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
copy from browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js
copy to browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_2.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_6.js
@@ -6,42 +6,24 @@
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [
     ["browser.preferences.search", true],
     ["privacy.trackingprotection.ui.enabled", true]
   ]});
 });
 
 /**
- * Test for searching for the "Saved Logins" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("sites are stored", "passwordsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Exceptions - Tracking Protection" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("disabled Tracking Protection", "trackingGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
  * Test for searching for the "Block Lists" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("block Web elements", "trackingGroup");
+  await evaluateSearchResults("block Web elements", "trackingGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Allowed Sites - Pop-ups" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("open pop-up windows", "permissionsGroup");
+  await evaluateSearchResults("open pop-up windows", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
copy from browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js
copy to browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_3.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_7.js
@@ -6,51 +6,33 @@
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [
     ["browser.preferences.search", true],
     ["browser.storageManager.enabled", true]
   ]});
 });
 
 /**
- * Test for searching for the "Allowed Sites - Add-ons Installation" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("allowed to install add-ons", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Certificate Manager" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("identify these certificate authorities", "certSelection");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
  * Test for searching for the "Device Manager" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Security Modules and Devices", "certSelection");
+  await evaluateSearchResults("Security Modules and Devices", "certSelection");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Connection Settings" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("Use system proxy settings", "connectionGroup");
+  await evaluateSearchResults("Use system proxy settings", "connectionGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Settings - Site Data" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("store site data on your computer", "siteDataGroup");
+  await evaluateSearchResults("store site data on your computer", "siteDataGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
copy from browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js
copy to browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js
--- a/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_4.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_subdialogs_within_preferences_8.js
@@ -3,51 +3,33 @@
 */
 
 // Enabling Searching functionatily. Will display search bar form this testcase forward.
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", true]]});
 });
 
 /**
- * Test for searching for the "Update History" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("updates have been installed", "updateApp");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
- * Test for searching for the "Location Permissions" subdialog.
- */
-add_task(async function() {
-  await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set location permissions", "permissionsGroup");
-  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
-});
-
-/**
  * Test for searching for the "Camera Permissions" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set camera permissions", "permissionsGroup");
+  await evaluateSearchResults("set camera permissions", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Microphone Permissions" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set microphone permissions", "permissionsGroup");
+  await evaluateSearchResults("set microphone permissions", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for searching for the "Notification Permissions" subdialog.
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
-  evaluateSearchResults("set notifications permissions", "permissionsGroup");
+  await evaluateSearchResults("set notifications permissions", "permissionsGroup");
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_1.js
@@ -1,14 +1,14 @@
 "use strict";
 /**
  * This file contains tests for the Preferences search bar.
  */
 
-requestLongerTimeout(2);
+requestLongerTimeout(6);
 
 /**
  * Tests to see if search bar is being hidden when pref is turned off
  */
 add_task(async function() {
   await SpecialPowers.pushPrefEnv({"set": [["browser.preferences.search", false]]});
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   let searchInput = gBrowser.contentDocument.querySelectorAll("#searchInput");
@@ -44,28 +44,36 @@ add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "password";
-  searchInput.doCommand();
+  let query = "password";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let categoriesList = gBrowser.contentDocument.getElementById("categories");
 
   for (let i = 0; i < categoriesList.childElementCount; i++) {
     let child = categoriesList.children[i]
     is(child.selected, false, "No other panel should be selected");
   }
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand();
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  let count = query.length;
+  while (count--) {
+    EventUtils.sendKey("BACK_SPACE");
+  }
+  await searchCompletedPromise;
 
   // Checks if back to generalPane
   for (let i = 0; i < categoriesList.childElementCount; i++) {
     let child = categoriesList.children[i]
     if (child.id == "category-general") {
       is(child.selected, true, "General panel should be selected");
     } else if (child.id) {
       is(child.selected, false, "No other panel should be selected");
@@ -82,18 +90,21 @@ add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "password";
-  searchInput.doCommand();
+  let query = "password";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
 
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i]
     if (child.id == "passwordsGroup" ||
         child.id == "weavePrefsDeck" ||
         child.id == "header-searchResults" ||
@@ -101,18 +112,23 @@ add_task(async function() {
         child.id == "connectionGroup") {
       is_element_visible(child, "Should be in search results");
     } else if (child.id) {
       is_element_hidden(child, "Should not be in search results");
     }
   }
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand();
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  let count = query.length;
+  while (count--) {
+    EventUtils.sendKey("BACK_SPACE");
+  }
+  await searchCompletedPromise;
 
   // Checks if back to generalPane
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i]
     if (child.id == "paneGeneral"
     || child.id == "startupGroup"
     || child.id == "languagesGroup"
     || child.id == "fontsGroup"
@@ -150,24 +166,32 @@ add_task(async function() {
   is_element_hidden(noResultsEl, "Should not be in search results yet");
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "coach";
-  searchInput.doCommand();
+  let query = "coach";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   is_element_visible(noResultsEl, "Should be in search results");
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand();
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  let count = query.length;
+  while (count--) {
+    EventUtils.sendKey("BACK_SPACE");
+  }
+  await searchCompletedPromise;
 
   is_element_hidden(noResultsEl, "Should not be in search results");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
  * Test for if we go back to general tab after search case
@@ -179,22 +203,30 @@ add_task(async function() {
   is_element_hidden(generalPane, "Should not be in general");
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "password";
-  searchInput.doCommand();
+  let query = "password";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand();
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  let count = query.length;
+  while (count--) {
+    EventUtils.sendKey("BACK_SPACE");
+  }
+  await searchCompletedPromise;
 
   // Checks if back to normal
   is_element_visible(generalPane, "Should be in generalPane");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
@@ -209,27 +241,35 @@ add_task(async function() {
   is_element_hidden(generalPane, "Should not be in general");
 
   // Performs search
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "site data";
-  searchInput.doCommand();
+  let query = "site data";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
 
   let child = mainPrefTag.querySelector("#siteDataGroup");
   is_element_hidden(child, "Should be hidden in search results");
 
   // Takes search off
-  searchInput.value = "";
-  searchInput.doCommand();
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  let count = query.length;
+  while (count--) {
+    EventUtils.sendKey("BACK_SPACE");
+  }
+  await searchCompletedPromise;
 
   // Checks if back to normal
   is_element_visible(generalPane, "Should be in generalPane");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
 /**
@@ -237,18 +277,21 @@ add_task(async function() {
  */
 add_task(async function() {
   await openPreferencesViaOpenPreferencesAPI("paneGeneral", {leaveOpen: true});
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "password";
-  searchInput.doCommand();
+  let query = "password";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let privacyCategory = gBrowser.contentDocument.getElementById("category-privacy");
   privacyCategory.click();
   is(searchInput.value, "", "search input should be empty");
   let categoriesList = gBrowser.contentDocument.getElementById("categories");
   for (let i = 0; i < categoriesList.childElementCount; i++) {
     let child = categoriesList.children[i]
     if (child.id == "category-privacy") {
--- a/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js
+++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_2.js
@@ -26,18 +26,21 @@ add_task(async function() {
   is(noFxaSignUp.label, "Create Account", "The Create Account button should exist");
 
   // Performs search.
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
 
   is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
     "Search input should be focused when visiting preferences");
 
-  searchInput.value = "Create Account";
-  searchInput.doCommand();
+  let query = "Create Account";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i];
     if (child.id == "header-searchResults" ||
         child.id == "weavePrefsDeck") {
       is_element_visible(child, "Should be in search results");
     } else if (child.id) {
@@ -46,16 +49,19 @@ add_task(async function() {
   }
 
   // Ensure the "Forget this Email" button exists in the hidden child of the <xul:deck>.
   let unlinkFxaAccount = weavePrefsDeck.childNodes[1].querySelector("#unverifiedUnlinkFxaAccount");
   is(unlinkFxaAccount.label, "Forget this Email", "The Forget this Email button should exist");
 
   // Performs search.
   searchInput.focus();
-  searchInput.value = "Forget this Email";
-  searchInput.doCommand();
+  query = "Forget this Email";
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
 
   let noResultsEl = gBrowser.contentDocument.querySelector(".no-results-message");
   is_element_visible(noResultsEl, "Should be reporting no results");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/in-content-new/tests/browser_search_within_preferences_command.js
@@ -0,0 +1,37 @@
+"use strict";
+
+/**
+ * Test for "command" event on search input (when user clicks the x button)
+ */
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({"set": [["browser.storageManager.enabled", false]]});
+  await openPreferencesViaOpenPreferencesAPI("privacy", {leaveOpen: true});
+  let generalPane = gBrowser.contentDocument.getElementById("generalCategory");
+
+  is_element_hidden(generalPane, "Should not be in general");
+
+  // Performs search
+  let searchInput = gBrowser.contentDocument.getElementById("searchInput");
+  is(searchInput, gBrowser.contentDocument.activeElement.closest("#searchInput"),
+    "Search input should be focused when visiting preferences");
+
+  let query = "x";
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == query);
+  EventUtils.sendString(query);
+  await searchCompletedPromise;
+
+  is_element_hidden(generalPane, "Should not be in generalPane");
+
+  // Takes search off with "command"
+  searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == "");
+  searchInput.value = "";
+  searchInput.doCommand();
+  await searchCompletedPromise;
+
+  // Checks if back to normal
+  is_element_visible(generalPane, "Should be in generalPane");
+
+  await BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
--- a/browser/components/preferences/in-content-new/tests/head.js
+++ b/browser/components/preferences/in-content-new/tests/head.js
@@ -222,24 +222,26 @@ function assertSitesListed(doc, hosts) {
   hosts.forEach(host => {
     let site = sitesList.querySelector(`richlistitem[host="${host}"]`);
     ok(site, `Should list the site of ${host}`);
   });
   is(removeBtn.disabled, false, "Should enable the removeSelected button");
   is(removeAllBtn.disabled, false, "Should enable the removeAllBtn button");
 }
 
-function evaluateSearchResults(keyword, searchReults) {
+async function evaluateSearchResults(keyword, searchReults) {
   searchReults = Array.isArray(searchReults) ? searchReults : [searchReults];
   searchReults.push("header-searchResults");
 
   let searchInput = gBrowser.contentDocument.getElementById("searchInput");
   searchInput.focus();
-  searchInput.value = keyword;
-  searchInput.doCommand();
+  let searchCompletedPromise = BrowserTestUtils.waitForEvent(
+      gBrowser.contentWindow, "PreferencesSearchCompleted", evt => evt.detail == keyword);
+  EventUtils.sendString(keyword);
+  await searchCompletedPromise;
 
   let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
   for (let i = 0; i < mainPrefTag.childElementCount; i++) {
     let child = mainPrefTag.children[i];
     if (searchReults.includes(child.id)) {
       is_element_visible(child, "Should be in search results");
     } else if (child.id) {
       is_element_hidden(child, "Should not be in search results");
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -716,16 +716,20 @@ groupbox {
   top: 100%;
   offset-inline-start: calc(50% - 6px);
 }
 
 .search-tooltip-parent {
   position: relative;
 }
 
+.visually-hidden {
+  visibility: hidden;
+}
+
 menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left {
   display: -moz-box;
   min-width: auto; /* Override the min-width defined in menu.css */
   margin-inline-end: 6px;
 }
 
 menulist[indicator=true] > menupopup menuitem:not([image]) > .menu-iconic-left > .menu-iconic-icon {
   width: 8px;