Bug 1533277 - Use SearchBox component in console filter bar. r=ntim.
authorAaditya Arora <b17071@students.iitmandi.ac.in>
Tue, 21 May 2019 12:20:50 +0000
changeset 474728 4039c6761d62239b4e55691f381cf2953969dcca
parent 474727 086a7a5fa9eb35bd1e341a91aa8cb934241b5b3f
child 474729 be01ec66f386d44d59fc56ab904a84c299521177
push id113168
push userrmaries@mozilla.com
push dateTue, 21 May 2019 16:39:23 +0000
treeherdermozilla-inbound@3c0f78074b72 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersntim
bugs1533277
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1533277 - Use SearchBox component in console filter bar. r=ntim. The code is adapted to work with the SearchBox, which means we can get rid of an event listener in webconsole-ui.js. Also, we take this as an opportunity to remove the element refs as they were only used in tests, where we can more easily use query selectors to get those elements. Some changes were needed in SearchBox's `onChange` function since `setState` is asynchronous. Differential Revision: https://phabricator.services.mozilla.com/D31762
devtools/client/shared/components/SearchBox.js
devtools/client/themes/webconsole.css
devtools/client/webconsole/components/App.js
devtools/client/webconsole/components/FilterBar.js
devtools/client/webconsole/test/components/filter-bar.test.js
devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_input.js
devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_regex_input.js
devtools/client/webconsole/test/mochitest/browser_webconsole_filter_scroll.js
devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
devtools/client/webconsole/test/mochitest/browser_webconsole_input_field_focus_on_panel_select.js
devtools/client/webconsole/test/mochitest/browser_webconsole_input_focus.js
devtools/client/webconsole/test/mochitest/browser_webconsole_keyboard_accessibility.js
devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
devtools/client/webconsole/test/mochitest/head.js
devtools/client/webconsole/webconsole-ui.js
devtools/client/webconsole/webconsole-wrapper.js
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -74,26 +74,26 @@ class SearchBox extends PureComponent {
     }
 
     // Clean up an existing timeout.
     if (this.searchTimeout) {
       clearTimeout(this.searchTimeout);
     }
   }
 
-  onChange() {
-    if (this.state.value !== this.inputRef.current.value) {
+  onChange(inputValue = "") {
+    if (this.state.value !== inputValue) {
       this.setState({
         focused: true,
-        value: this.inputRef.current.value,
+        value: inputValue,
       });
     }
 
     if (!this.props.delay) {
-      this.props.onChange(this.state.value);
+      this.props.onChange(inputValue);
       return;
     }
 
     // Clean up an existing timeout before creating a new one.
     if (this.searchTimeout) {
       clearTimeout(this.searchTimeout);
     }
 
@@ -101,18 +101,17 @@ class SearchBox extends PureComponent {
     // smoother if the user is typing quickly.
     this.searchTimeout = setTimeout(() => {
       this.searchTimeout = null;
       this.props.onChange(this.state.value);
     }, this.props.delay);
   }
 
   onClearButtonClick() {
-    this.setState({ value: "" });
-    this.onChange();
+    this.onChange("");
   }
 
   onFocus() {
     if (this.props.onFocus) {
       this.props.onFocus();
     }
 
     this.setState({ focused: true });
@@ -181,38 +180,36 @@ class SearchBox extends PureComponent {
 
     const inputClassList = [`devtools-${type}input`];
 
     return dom.div(
       { className: "devtools-searchbox" },
       dom.input({
         className: inputClassList.join(" "),
         onBlur: this.onBlur,
-        onChange: this.onChange,
+        onChange: e => this.onChange(e.target.value),
         onFocus: this.onFocus,
         onKeyDown: this.onKeyDown,
         placeholder,
         ref: this.inputRef,
         value,
+        type,
       }),
       showLearnMoreLink && MDNLink({
         title: learnMoreTitle,
         url: learnMoreUrl,
       }),
       dom.button({
         className: "devtools-searchinput-clear",
         hidden: value === "",
         onClick: this.onClearButtonClick,
       }),
       showAutocomplete && AutocompletePopup({
         autocompleteProvider,
         filter: value,
-        onItemSelected: (itemValue) => {
-          this.setState({ value: itemValue });
-          this.onChange();
-        },
+        onItemSelected: itemValue => this.onChange(itemValue),
         ref: this.autocompleteRef,
       })
     );
   }
 }
 
 module.exports = SearchBox;
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -91,17 +91,17 @@ a {
  *
  *   -----------------  <-- Grey
  *   Normal message
  *   -----------------  <-- Red
  *   Error message
  *   -----------------  <-- Red
  *
  * The exact stacking order is:
- * 
+ *
  *   - z-index: 3 = Navigation and Paused markers
  *   - z-index: 2 = Errors and warnings
  *   - z-index: 1 = Other (console.log, console.info, requests, etc.)
  */
 .message {
   position: relative;
   z-index: 1;
   display: flex;
@@ -679,16 +679,24 @@ a.learn-more-link.webconsole-learn-more-
   height: 100%;
   margin: 0;
 }
 
 #split-console-close-button::before {
   background-image: url(chrome://devtools/skin/images/close.svg);
 }
 
+/* set to prevent the filter height from devtools-searchbox */
+.webconsole-filterbar-primary .devtools-searchbox {
+  align-self: stretch;
+  height: unset;
+  flex: 1 1 100%;
+  margin-inline-start: 1px;
+}
+
 .webconsole-filterbar-primary .filter-checkbox {
   flex-shrink: 0;
   margin: 0 3px;
   display: flex;
   align-items: center;
   -moz-user-select: none;
 }
 
@@ -1114,16 +1122,8 @@ body {
 
 /** Utils **/
 .clipboard-only {
   position: absolute;
   left: -9999px;
   width: 0;
   height: 0;
 }
-
-/* set to prevent the filter height from devtools-searchbox */
-.webconsole-filterbar-primary .devtools-searchbox {
-  align-self: stretch;
-  height: unset;
-  flex: 1 1 100%;
-  margin-inline-start: 1px;
-}
--- a/devtools/client/webconsole/components/App.js
+++ b/devtools/client/webconsole/components/App.js
@@ -34,17 +34,16 @@ const { div } = dom;
 const isMacOS = Services.appinfo.OS === "Darwin";
 
 /**
  * Console root Application component.
  */
 class App extends Component {
   static get propTypes() {
     return {
-      attachRefToWebConsoleUI: PropTypes.func.isRequired,
       dispatch: PropTypes.func.isRequired,
       webConsoleUI: PropTypes.object.isRequired,
       notifications: PropTypes.object,
       onFirstMeaningfulPaint: PropTypes.func.isRequired,
       serviceContainer: PropTypes.object.isRequired,
       closeSplitConsole: PropTypes.func.isRequired,
       jstermCodeMirror: PropTypes.bool,
       autocomplete: PropTypes.bool,
@@ -190,17 +189,16 @@ class App extends Component {
 
     input.addEventListener("keyup", pasteKeyUpHandler);
   }
 
   // Rendering
 
   render() {
     const {
-      attachRefToWebConsoleUI,
       webConsoleUI,
       notifications,
       onFirstMeaningfulPaint,
       serviceContainer,
       closeSplitConsole,
       jstermCodeMirror,
       autocomplete,
       reverseSearchInitialValue,
@@ -231,17 +229,16 @@ class App extends Component {
         onClick: this.onClick,
         ref: node => {
           this.node = node;
         }},
         div({className: "webconsole-flex-wrapper"},
           FilterBar({
             hidePersistLogsCheckbox: webConsoleUI.isBrowserConsole,
             hideShowContentMessagesCheckbox,
-            attachRefToWebConsoleUI,
             closeSplitConsole,
           }),
           ConsoleOutput({
             serviceContainer,
             onFirstMeaningfulPaint,
           }),
           NotificationBox({
             id: "webconsole-notificationbox",
--- a/devtools/client/webconsole/components/FilterBar.js
+++ b/devtools/client/webconsole/components/FilterBar.js
@@ -1,29 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const { Component } = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { getAllFilters } = require("devtools/client/webconsole/selectors/filters");
 const { getFilteredMessagesCount } = require("devtools/client/webconsole/selectors/messages");
 const { getAllUi } = require("devtools/client/webconsole/selectors/ui");
 const actions = require("devtools/client/webconsole/actions/index");
 const { l10n } = require("devtools/client/webconsole/utils/messages");
 const { PluralForm } = require("devtools/shared/plural-form");
 const {
   DEFAULT_FILTERS,
   FILTERS,
 } = require("../constants");
 
 const FilterButton = require("devtools/client/webconsole/components/FilterButton");
 const FilterCheckbox = require("devtools/client/webconsole/components/FilterCheckbox");
+const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
 
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 
 class FilterBar extends Component {
   static get propTypes() {
     return {
       dispatch: PropTypes.func.isRequired,
       filter: PropTypes.object.isRequired,
@@ -44,35 +45,23 @@ class FilterBar extends Component {
       hideShowContentMessagesCheckbox: true,
     };
   }
 
   constructor(props) {
     super(props);
     this.onClickMessagesClear = this.onClickMessagesClear.bind(this);
     this.onClickRemoveAllFilters = this.onClickRemoveAllFilters.bind(this);
-    this.onClickRemoveTextFilter = this.onClickRemoveTextFilter.bind(this);
-    this.onSearchInput = this.onSearchInput.bind(this);
+    this.onSearchBoxChange = this.onSearchBoxChange.bind(this);
     this.onChangePersistToggle = this.onChangePersistToggle.bind(this);
     this.onChangeShowContent = this.onChangeShowContent.bind(this);
     this.renderFiltersConfigBar = this.renderFiltersConfigBar.bind(this);
     this.renderFilteredMessagesBar = this.renderFilteredMessagesBar.bind(this);
   }
 
-  componentDidMount() {
-    this.props.attachRefToWebConsoleUI(
-      "filterBox",
-      this.wrapperNode.querySelector(".text-filter")
-    );
-    this.props.attachRefToWebConsoleUI(
-      "clearButton",
-      this.wrapperNode.querySelector(".clear-button")
-    );
-  }
-
   shouldComponentUpdate(nextProps, nextState) {
     const {
       filter,
       persistLogs,
       showContentMessages,
       filteredMessagesCount,
       closeButtonVisible,
     } = this.props;
@@ -106,22 +95,18 @@ class FilterBar extends Component {
   onClickMessagesClear() {
     this.props.dispatch(actions.messagesClear());
   }
 
   onClickRemoveAllFilters() {
     this.props.dispatch(actions.defaultFiltersReset());
   }
 
-  onClickRemoveTextFilter() {
-    this.props.dispatch(actions.filterTextSet(""));
-  }
-
-  onSearchInput(e) {
-    this.props.dispatch(actions.filterTextSet(e.target.value));
+  onSearchBoxChange(text) {
+    this.props.dispatch(actions.filterTextSet(text));
   }
 
   onChangePersistToggle() {
     this.props.dispatch(actions.persistToggle());
   }
 
   onChangeShowContent() {
     this.props.dispatch(actions.contentMessagesToggle());
@@ -237,17 +222,16 @@ class FilterBar extends Component {
         className: "devtools-button reset-filters-button",
         onClick: this.onClickRemoveAllFilters,
       }, l10n.getStr("webconsole.resetFiltersButton.label"))
     );
   }
 
   render() {
     const {
-      filter,
       persistLogs,
       filteredMessagesCount,
       hidePersistLogsCheckbox,
       hideShowContentMessagesCheckbox,
       closeSplitConsole,
       showContentMessages,
     } = this.props;
 
@@ -259,31 +243,21 @@ class FilterBar extends Component {
         dom.button({
           className: "devtools-button devtools-clear-icon",
           title: l10n.getStr("webconsole.clearButton.tooltip"),
           onClick: this.onClickMessagesClear,
         }),
         dom.div({
           className: "devtools-separator",
         }),
-        dom.div(
-          { className: "devtools-searchbox" },
-          dom.input({
-            className: "devtools-filterinput text-filter",
-            type: "search",
-            value: filter.text,
-            placeholder: l10n.getStr("webconsole.filterInput.placeholder"),
-            onInput: this.onSearchInput,
-          }),
-          dom.button({
-            className: "devtools-searchinput-clear clear-button",
-            hidden: filter.text == "",
-            onClick: this.onClickRemoveTextFilter,
-          }),
-        ),
+        SearchBox({
+          placeholder: l10n.getStr("webconsole.filterInput.placeholder"),
+          keyShortcut: l10n.getStr("webconsole.find.key"),
+          onChange: this.onSearchBoxChange,
+        }),
         !hidePersistLogsCheckbox && FilterCheckbox({
           label: l10n.getStr("webconsole.enablePersistentLogs.label"),
           title: l10n.getStr("webconsole.enablePersistentLogs.tooltip"),
           onChange: this.onChangePersistToggle,
           checked: persistLogs,
         }),
         !hideShowContentMessagesCheckbox && FilterCheckbox({
           label: l10n.getStr("browserconsole.contentMessagesCheckbox.label"),
--- a/devtools/client/webconsole/test/components/filter-bar.test.js
+++ b/devtools/client/webconsole/test/components/filter-bar.test.js
@@ -53,25 +53,24 @@ describe("FilterBar component:", () => {
     expect(toolbar.children().eq(1).attr("class")).toBe("devtools-separator");
 
     // Text filter
     const textInput = toolbar.children().eq(2);
     expect(textInput.attr("class")).toBe("devtools-searchbox");
 
     // Text filter input
     const textFilter = textInput.children().eq(0);
-    expect(textFilter.attr("class")).toBe("devtools-filterinput text-filter");
+    expect(textFilter.attr("class")).toBe("devtools-searchinput");
     expect(textFilter.attr("placeholder")).toBe("Filter output");
     expect(textFilter.attr("type")).toBe("search");
     expect(textFilter.attr("value")).toBe("");
 
     // Text filter input clear button
     const textFilterClearButton = textInput.children().eq(1);
-    expect(textFilterClearButton.attr("class"))
-      .toBe("devtools-searchinput-clear clear-button");
+    expect(textFilterClearButton.attr("class")).toBe("devtools-searchinput-clear");
 
     // "Persist logs" checkbox
     expect(wrapper.find(".filter-checkbox input").length).toBe(1);
   });
 
   it("displays the number of hidden messages when there are one hidden message", () => {
     const store = setupStore([
       "console.log('foobar', 'test')",
@@ -247,17 +246,18 @@ describe("FilterBar component:", () => {
       type: MESSAGES_CLEAR,
     });
   });
 
   it("sets filter text when text is typed", () => {
     const store = setupStore();
 
     const wrapper = mount(Provider({store}, getFilterBar()));
-    wrapper.find(".devtools-filterinput").simulate("input", { target: { value: "a" } });
+    const input = wrapper.find(".devtools-searchinput");
+    input.simulate("change", { target: { value: "a" } });
     expect(store.getState().filters.text).toBe("a");
   });
 
   it("toggles persist logs when checkbox is clicked", () => {
     const store = setupStore();
 
     expect(getAllUi(store.getState()).persistLogs).toBe(false);
     expect(prefsService.getBoolPref(PREFS.UI.PERSIST), false);
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_hide_when_devtools_chrome_enabled_false.js
@@ -37,63 +37,51 @@ add_task(async function() {
 async function performTests() {
   let browserConsole, webConsole, objInspector;
 
   // We don't use `pushPref()` because we need to revert the same pref later
   // in the test.
   Services.prefs.setBoolPref("devtools.chrome.enabled", true);
 
   browserConsole = await HUDService.toggleBrowserConsole();
-  objInspector = await getObjectInspector(browserConsole);
+  objInspector = await logObject(browserConsole);
   testJSTermIsVisible(browserConsole);
   await testObjectInspectorPropertiesAreSet(objInspector);
 
   const browserTab = await addTab("data:text/html;charset=utf8,hello world");
   webConsole = await openConsole(browserTab);
-  objInspector = await getObjectInspector(webConsole);
+  objInspector = await logObject(webConsole);
   testJSTermIsVisible(webConsole);
   await testObjectInspectorPropertiesAreSet(objInspector);
   await closeConsole(browserTab);
 
   await HUDService.toggleBrowserConsole();
   Services.prefs.setBoolPref("devtools.chrome.enabled", false);
 
   browserConsole = await HUDService.toggleBrowserConsole();
-  objInspector = await getObjectInspector(browserConsole);
+  objInspector = await logObject(browserConsole);
   testJSTermIsNotVisible(browserConsole);
 
   webConsole = await openConsole(browserTab);
-  objInspector = await getObjectInspector(webConsole);
+  objInspector = await logObject(webConsole);
   testJSTermIsVisible(webConsole);
   await testObjectInspectorPropertiesAreSet(objInspector);
 
   info("Close webconsole and browser console");
   await closeConsole(browserTab);
   await HUDService.toggleBrowserConsole();
 }
 
-/**
- * Returns either the Variables View or Object Inspector depending on which is
- * currently in use.
- */
-async function getObjectInspector(hud) {
-  const { ui, jsterm } = hud;
-
-  // Filter out other messages to ensure ours stays visible.
-  ui.filterBox.value = "browser_console_hide_jsterm_test";
-
-  hud.ui.clearOutput();
-  jsterm.execute("new Object({ browser_console_hide_jsterm_test: true })");
-
-  const message = await waitFor(
-    () => findMessage(hud, "Object { browser_console_hide_jsterm_test: true }")
-  );
-
-  const objInspector = message.querySelector(".tree");
-  return objInspector;
+async function logObject(hud) {
+  const { jsterm } = hud;
+  const prop = "browser_console_hide_jsterm_test";
+  const onMessage = waitForMessage(hud, prop, ".result");
+  jsterm.execute(`new Object({ ${prop}: true })`);
+  const {node} = await onMessage;
+  return node.querySelector(".tree");
 }
 
 function testJSTermIsVisible(hud) {
   const inputContainer = hud.ui.window.document
                                     .querySelector(".jsterm-input-container");
   isnot(inputContainer.style.display, "none", "input is visible");
 }
 
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_input.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_input.js
@@ -82,95 +82,96 @@ add_task(async function() {
     checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
   }
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i + 1 + SEASONS.length], SEASONS[i].chinese,
                     JS_UNICODE_FILENAME);
   }
   // checking the visibility of clear button, it should be visible only when
   // there is text inside filter input box
-  clearFilterInput(hud);
-  is(hud.ui.clearButton.hidden, true, "Clear button is hidden");
-  setFilterInput(hud, JS_ASCII_FILENAME);
-  is(hud.ui.clearButton.hidden, false, "Clear button is visible");
+  await setFilterState(hud, {text: ""});
+  is(getClearButton(hud).hidden, true, "Clear button is hidden");
+  await setFilterState(hud, {text: JS_ASCII_FILENAME});
+  is(getClearButton(hud).hidden, false, "Clear button is visible");
 
   // All the logs outputted by the ASCII Javascript file are visible, the others
   // are hidden.
-  setFilterInput(hud, JS_ASCII_FILENAME);
+  await setFilterState(hud, {text: JS_ASCII_FILENAME});
   visibleLogs = getVisibleLogs(hud);
   is(visibleLogs.length, SEASONS.length,
        `the number of all the logs containing ${JS_ASCII_FILENAME}`);
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i], SEASONS[i].english, JS_ASCII_FILENAME);
   }
 
   // Every season name in English is outputted once.
   for (const curSeason of SEASONS) {
-    setFilterInput(hud, curSeason.english);
+    await setFilterState(hud, {text: curSeason.english});
     visibleLogs = getVisibleLogs(hud);
     is(visibleLogs.length, 1,
          `the number of all the logs containing ${curSeason.english}`);
     checkLogContent(visibleLogs[0], curSeason.english, JS_ASCII_FILENAME);
   }
 
   // All the logs outputted by the Unicode Javascript file are visible, the
   // others are hidden.
-  setFilterInput(hud, JS_UNICODE_FILENAME);
+  await setFilterState(hud, {text: JS_UNICODE_FILENAME});
   visibleLogs = getVisibleLogs(hud);
   is(visibleLogs.length, SEASONS.length,
        `the number of all the logs containing ${JS_UNICODE_FILENAME}`);
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i], SEASONS[i].chinese, JS_UNICODE_FILENAME);
   }
 
   // Every season name in Chinese is outputted once.
   for (const curSeason of SEASONS) {
-    setFilterInput(hud, curSeason.chinese);
+    await setFilterState(hud, {text: curSeason.chinese});
     visibleLogs = getVisibleLogs(hud);
     is(visibleLogs.length, 1,
          `the number of all the logs containing ${curSeason.chinese}`);
     checkLogContent(visibleLogs[0], curSeason.chinese, JS_UNICODE_FILENAME);
   }
 
   // The filename of the ASCII Javascript file contains the English word season,
   // so all the logs outputted by the file are visible, besides, the HTML
   // outputs one line containing the English word season, so it is also visible.
   // The other logs are hidden. So the number of all the visible logs is the
   // season number plus one.
-  setFilterInput(hud, SEASON.english);
+  await setFilterState(hud, {text: SEASON.english});
   visibleLogs = getVisibleLogs(hud);
   is(visibleLogs.length, SEASONS.length + 1,
        `the number of all the logs containing ${SEASON.english}`);
   checkLogContent(visibleLogs[0], HTML_CONSOLE_OUTPUT, HTML_FILENAME);
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
   }
 
   // The filename of the Unicode Javascript file contains the Chinese word
   // season, so all the logs outputted by the file are visible. The other logs
   // are hidden. So the number of all the visible logs is the season number.
-  setFilterInput(hud, SEASON.chinese);
+  await setFilterState(hud, {text: SEASON.chinese});
   visibleLogs = getVisibleLogs(hud);
   is(visibleLogs.length, SEASONS.length,
        `the number of all the logs containing ${SEASON.chinese}`);
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i], SEASONS[i].chinese, JS_UNICODE_FILENAME);
   }
 
   // After clearing the text in the filter input box, all the logs are visible
   // again.
-  clearFilterInput(hud);
+  await setFilterState(hud, {text: ""});
   checkAllMessagesAreVisible(hud);
 
   // clearing the text in the filter input box using clear button, so after which
   // all logs will be visible again
-  setFilterInput(hud, JS_ASCII_FILENAME);
+  await setFilterState(hud, {text: JS_ASCII_FILENAME});
 
   info("Click the input clear button");
   clickClearButton(hud);
+  await waitFor(() => getClearButton(hud).hidden === true);
   checkAllMessagesAreVisible(hud);
 });
 
 // Create an HTTP server to simulate a response for the a URL request and return
 // the URL.
 function createServerAndGetTestUrl() {
   const httpServer = createTestHTTPServer();
 
@@ -204,37 +205,30 @@ function createServerAndGetTestUrl() {
       }
       response.write(content);
     }
   );
   const port = httpServer.identity.primaryPort;
   return `http://localhost:${port}/${HTML_FILENAME}`;
 }
 
-function setFilterInput(hud, value) {
-  hud.ui.filterBox.focus();
-  hud.ui.filterBox.select();
-  EventUtils.sendString(value);
+function getClearButton(hud) {
+  return hud.ui.outputNode
+    .querySelector(".devtools-searchbox .devtools-searchinput-clear");
 }
 
-function clearFilterInput(hud) {
-  hud.ui.filterBox.focus();
-  hud.ui.filterBox.select();
-  EventUtils.synthesizeKey("KEY_Delete");
+function clickClearButton(hud) {
+  getClearButton(hud).click();
 }
 
 function getVisibleLogs(hud) {
   const outputNode = hud.outputNode;
   return outputNode.querySelectorAll(".message");
 }
 
-function clickClearButton(hud) {
-  hud.ui.clearButton.click();
-}
-
 function checkAllMessagesAreVisible(hud) {
   const visibleLogs = getVisibleLogs(hud);
   is(visibleLogs.length, SEASONS.length * 2 + 1,
        "the total number of all the logs after clearing filtering");
   checkLogContent(visibleLogs[0], HTML_CONSOLE_OUTPUT, HTML_FILENAME);
   for (let i = 0; i < SEASONS.length; i++) {
     checkLogContent(visibleLogs[i + 1], SEASONS[i].english, JS_ASCII_FILENAME);
   }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_regex_input.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_by_regex_input.js
@@ -62,19 +62,17 @@ add_task(async function() {
     MESSAGES[2],
     MESSAGES[3],
     MESSAGES[4],
     MESSAGES[5],
   ], 5);
 });
 
 async function setFilterInput(hud, value, lastMessage) {
-  hud.ui.filterBox.focus();
-  hud.ui.filterBox.select();
-  EventUtils.sendString(value);
+  await setFilterState(hud, {text: value});
   await waitFor(() => findMessage(hud, lastMessage), null, 200);
 }
 
 function checkFilteredMessages(filteredNodes, expectedMessages, expectedCount) {
   is(filteredNodes.length, expectedCount,
     `${expectedCount} messages should be displayed`);
 
   filteredNodes.forEach((node, id) => {
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_scroll.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_scroll.js
@@ -20,49 +20,41 @@ add_task(async function() {
   const outputContainer = ui.outputNode.querySelector(".webconsole-output");
 
   info("Console should be scrolled to bottom on initial load from page logs");
   await waitFor(() => findMessage(hud, "init-99"));
   ok(hasVerticalOverflow(outputContainer), "There is a vertical overflow");
   ok(isScrolledToBottom(outputContainer), "The console is scrolled to the bottom");
 
   info("Filter out some messages and check that the scroll position is not impacted");
-  const filterInput = hud.ui.outputNode.querySelector(".text-filter");
-
-  filterInput.value = "init-";
-  filterInput.focus();
   let onMessagesFiltered = waitFor(() => !findMessage(hud, "init-1"), null, 200);
-  EventUtils.sendString("9");
+  await setFilterState(hud, {text: "init-9"});
   await onMessagesFiltered;
   ok(isScrolledToBottom(outputContainer),
     "The console is still scrolled to the bottom after filtering");
 
   info("Clear the text filter and check that the scroll position is not impacted");
   let onMessagesUnFiltered = waitFor(() => findMessage(hud, "init-1"), null, 200);
-  filterInput.select();
-  EventUtils.synthesizeKey("KEY_Delete");
+  await setFilterState(hud, {text: ""});
   await onMessagesUnFiltered;
   ok(isScrolledToBottom(outputContainer),
     "The console is still scrolled to the bottom after clearing the filter");
 
   info("Scroll up");
   outputContainer.scrollTop = 0;
 
-  filterInput.value = "init-";
-  filterInput.focus();
+  await setFilterState(hud, {text: "init-9"});
   onMessagesFiltered = waitFor(() => !findMessage(hud, "init-1"), null, 200);
-  EventUtils.sendString("9");
   await onMessagesFiltered;
   is(outputContainer.scrollTop, 0,
     "The console is still scrolled to the top after filtering");
 
   info("Clear the text filter and check that the scroll position is not impacted");
   onMessagesUnFiltered = waitFor(() => findMessage(hud, "init-1"), null, 200);
-  filterInput.select();
-  EventUtils.synthesizeKey("KEY_Delete");
+  await setFilterState(hud, {text: ""});
   await onMessagesUnFiltered;
   is(outputContainer.scrollTop, 0,
     "The console is still scrolled to the top after clearing the filter");
 });
 
 function hasVerticalOverflow(container) {
   return container.scrollHeight > container.clientHeight;
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
@@ -67,19 +67,17 @@ async function performTests() {
     "One message is still visible in the output node");
   testLayout(appNode);
 
   const filterBarHeight = filterBarNode.clientHeight;
 
   info("Show the hidden messages label");
   const onHiddenMessagesLabelVisible = waitFor(() =>
     document.querySelector(".webconsole-filterbar-filtered-messages"));
-  ui.filterBox.focus();
-  ui.filterBox.select();
-  EventUtils.sendString("message-");
+  setFilterState(hud, {text: "message-"});
   await onHiddenMessagesLabelVisible;
 
   info("Shrink the window so the label is on its own line");
   const toolbox = hud.ui.wrapper.toolbox;
   const hostWindow = toolbox.win.parent;
   hostWindow.resizeTo(300, window.screen.availHeight);
 
   ok(filterBarNode.clientHeight > filterBarHeight, "The filter bar is taller");
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_input_field_focus_on_panel_select.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_input_field_focus_on_panel_select.js
@@ -8,21 +8,20 @@
 
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf8,<p>Test console input focus";
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
 
-  const filterInput = hud.ui.outputNode.querySelector(".text-filter");
-
   info("Focus after console is opened");
   ok(isInputFocused(hud), "input is focused after console is opened");
 
+  const filterInput = getFilterInput(hud);
   filterInput.focus();
   ok(hasFocus(filterInput), "filter input should be focused");
 
   is(isInputFocused(hud), false, "input node is not focused anymore");
 
   info("Go to the inspector panel");
   await openInspector();
 
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_input_focus.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_input_focus.js
@@ -47,11 +47,11 @@ function waitForBlurredInput(hud) {
   return new Promise(resolve => {
     const lostFocus = () => {
       ok(!isInputFocused(hud), "input node is not focused");
       resolve();
     };
     node.addEventListener("focusout", lostFocus, { once: true });
 
     // The 'focusout' event fires if we focus e.g. the filter box.
-    node.ownerDocument.querySelector("input.text-filter").focus();
+    getFilterInput(hud).focus();
   });
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_keyboard_accessibility.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_keyboard_accessibility.js
@@ -80,16 +80,16 @@ async function performTests() {
     await waitFor(() => findMessages(hud, "").length == 0);
     ok(isInputFocused(hud), "console was cleared as expected with alternative shortcut");
   }
 
   // Focus filter
   info("try ctrl-f to focus filter");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
   ok(!isInputFocused(hud), "input is not focused");
-  ok(hasFocus(hud.ui.filterBox), "filter input is focused");
+  ok(hasFocus(getFilterInput(hud)), "filter input is focused");
 
   info("try ctrl-f when filter is already focused");
   synthesizeKeyShortcut(WCUL10n.getStr("webconsole.find.key"));
   ok(!isInputFocused(hud), "input is not focused");
-  is(hud.ui.filterBox, outputScroller.ownerDocument.activeElement,
+  is(getFilterInput(hud), outputScroller.ownerDocument.activeElement,
     "filter input is focused");
 }
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
@@ -41,19 +41,17 @@ add_task(async function() {
 
   checkTelemetryEvent({
     trigger: "netxhr",
     active: "error,warn,info,debug,netxhr",
     inactive: "text,log,css,net",
   });
 
   info("Filter the output using the text filter input");
-  hud.ui.filterBox.focus();
-  hud.ui.filterBox.select();
-  EventUtils.sendString("no match");
+  await setFilterState(hud, {text: "no match"});
 
   checkTelemetryEvent({
     trigger: "text",
     active: "text,error,warn,info,debug,netxhr",
     inactive: "log,css,net",
   });
 
   info("Clear the filters using the 'Reset filters' button");
--- a/devtools/client/webconsole/test/mochitest/head.js
+++ b/devtools/client/webconsole/test/mochitest/head.js
@@ -933,16 +933,26 @@ async function getFilterState(hud) {
 
     result[category] = checked;
   }
 
   return result;
 }
 
 /**
+ * Return the filter input element.
+ *
+ * @param {Object} hud
+ * @return {HTMLInputElement}
+ */
+function getFilterInput(hud) {
+  return hud.ui.outputNode.querySelector(".devtools-searchbox input");
+}
+
+/**
  * Set the state of a console filter.
  *
  * @param {Object} hud
  * @param {Object} settings
  *        Category settings in the following format:
  *          {
  *            error: true,
  *            warn: true,
@@ -954,33 +964,46 @@ async function getFilterState(hud) {
  *            net: false
  *          }
  */
 async function setFilterState(hud, settings) {
   const {outputNode} = hud.ui;
   const filterBar = outputNode.querySelector(".webconsole-filterbar-secondary");
 
   for (const category in settings) {
-    const setActive = settings[category];
+    const value = settings[category];
     const button = filterBar.querySelector(`.${category}`);
 
+    if (category === "text") {
+      const filterInput = getFilterInput(hud);
+      filterInput.focus();
+      filterInput.select();
+      if (!value) {
+        EventUtils.synthesizeKey("KEY_Delete");
+      } else {
+        EventUtils.sendString(value);
+      }
+      await waitFor(() => filterInput.value === value);
+      continue;
+    }
+
     if (!button) {
       ok(false, `setFilterState() called with a category of ${category}, ` +
                 `which doesn't exist.`);
     }
 
-    info(`Setting the ${category} category to ${setActive ? "checked" : "disabled"}`);
+    info(`Setting the ${category} category to ${value ? "checked" : "disabled"}`);
 
     const isChecked = button.classList.contains("checked");
 
-    if (setActive !== isChecked) {
+    if (value !== isChecked) {
       button.click();
 
       await waitFor(() => {
-        return button.classList.contains("checked") === setActive;
+        return button.classList.contains("checked") === value;
       });
     }
   }
 }
 
 /**
  * Reset the filters at the end of a test that has changed them. This is
  * important when using the `--verify` test option as when it is used you need
--- a/devtools/client/webconsole/webconsole-ui.js
+++ b/devtools/client/webconsole/webconsole-ui.js
@@ -254,22 +254,16 @@ class WebConsoleUI {
     });
   }
 
   _initShortcuts() {
     const shortcuts = new KeyShortcuts({
       window: this.window,
     });
 
-    shortcuts.on(l10n.getStr("webconsole.find.key"),
-                 event => {
-                   this.filterBox.focus();
-                   event.preventDefault();
-                 });
-
     let clearShortcut;
     if (AppConstants.platform === "macosx") {
       const alternativaClearShortcut = l10n.getStr("webconsole.clear.alternativeKeyOSX");
       shortcuts.on(alternativaClearShortcut, event => this.clearOutput(true, event));
       clearShortcut = l10n.getStr("webconsole.clear.keyOSX");
     } else {
       clearShortcut = l10n.getStr("webconsole.clear.key");
     }
--- a/devtools/client/webconsole/webconsole-wrapper.js
+++ b/devtools/client/webconsole/webconsole-wrapper.js
@@ -362,17 +362,16 @@ class WebConsoleWrapper {
       });
 
       const {prefs} = store.getState();
       const jstermCodeMirror = prefs.jstermCodeMirror
         && !Services.appinfo.accessibilityEnabled;
       const autocomplete = prefs.autocomplete;
 
       const app = App({
-        attachRefToWebConsoleUI,
         serviceContainer,
         webConsoleUI,
         onFirstMeaningfulPaint: resolve,
         closeSplitConsole: this.closeSplitConsole.bind(this),
         jstermCodeMirror,
         autocomplete,
         hideShowContentMessagesCheckbox: !webConsoleUI.isBrowserConsole ||
           !prefs.filterContentMessages,