Bug 1523864 - Move filter bar layout change to the App level. r=Honza.
authorNicolas Chevobbe <nchevobbe@mozilla.com>
Fri, 07 Jun 2019 14:23:35 +0000
changeset 477855 89b7f73b270126eea62ee77f6873805976fd8abe
parent 477854 4c8aa3c1d1234d5c7c4ff5c8da26ff307e4e0a92
child 477856 dd17bc19ea6dd7fc6ac2e578104cc3321cac284d
push id36125
push userapavel@mozilla.com
push dateFri, 07 Jun 2019 22:00:07 +0000
treeherdermozilla-central@d820bbb356aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1523864
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 1523864 - Move filter bar layout change to the App level. r=Honza. This makes the console code more consistent, and adds the nice benefit of being able to check if the layout should be modified when performing non-window-resize events that might still impact the layout (sidebar toggle, sidebar resizing, ...). Differential Revision: https://phabricator.services.mozilla.com/D27668
devtools/client/locales/en-US/webconsole.properties
devtools/client/shared/components/SearchBox.js
devtools/client/themes/common.css
devtools/client/themes/webconsole.css
devtools/client/webconsole/actions/filters.js
devtools/client/webconsole/actions/ui.js
devtools/client/webconsole/components/App.js
devtools/client/webconsole/components/FilterBar.js
devtools/client/webconsole/components/SideBar.js
devtools/client/webconsole/constants.js
devtools/client/webconsole/reducers/filters.js
devtools/client/webconsole/reducers/ui.js
devtools/client/webconsole/test/components/filter-bar.test.js
devtools/client/webconsole/test/fixtures/L10n.js
devtools/client/webconsole/test/mocha-test-setup.js
devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
devtools/client/webconsole/test/mochitest/browser_webconsole_filter_buttons_overflow.js
devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
devtools/client/webconsole/test/store/filters.test.js
devtools/client/webconsole/test/store/hidden-messages.test.js
--- a/devtools/client/locales/en-US/webconsole.properties
+++ b/devtools/client/locales/en-US/webconsole.properties
@@ -275,23 +275,31 @@ webconsole.cssFilterButton.label=CSS
 webconsole.xhrFilterButton.label=XHR
 
 # LOCALIZATION NOTE (webconsole.requestsFilterButton.label)
 # Label used as the text of the "Requests" button in the additional filter toolbar.
 # It shows or hides messages displayed when the page makes a network call, for example
 # when an image or a scripts is requested.
 webconsole.requestsFilterButton.label=Requests
 
-# LOCALIZATION NOTE (webconsole.filteredMessages.label)
-# Text of the "filtered messages" bar, shown when console messages are hidden
-# because the user has set non-default filters in the filter bar.
+# LOCALIZATION NOTE (webconsole.filteredMessagesByText.label)
+# Text on the filter input displayed when some console messages are hidden because the
+# user has filled in the input.
 # This is a semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-# example: 345 items hidden by filters.
-webconsole.filteredMessages.label=#1 item hidden by filters;#1 items hidden by filters
+# example: 345 hidden.
+webconsole.filteredMessagesByText.label=#1 hidden;#1 hidden
+
+# LOCALIZATION NOTE (webconsole.filteredMessagesByText.tooltip)
+# Tooltip on the filter input "hidden" text, displayed when some console messages are
+# hidden because the user has filled in the input.
+# This is a semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# example: 345 items hidden by text filter.
+webconsole.filteredMessagesByText.tooltip=#1 item hidden by text filter;#1 items hidden by text filter
 
 # Label used as the text of the "Reset filters" button in the "filtered messages" bar.
 # It resets the default filters of the console to their original values.
 webconsole.resetFiltersButton.label=Reset filters
 
 # LOCALIZATION NOTE (webconsole.enablePersistentLogs.label)
 webconsole.enablePersistentLogs.label=Persist Logs
 # LOCALIZATION NOTE (webconsole.enablePersistentLogs.tooltip)
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -27,16 +27,18 @@ class SearchBox extends PureComponent {
       keyShortcut: PropTypes.string,
       learnMoreTitle: PropTypes.string,
       learnMoreUrl: PropTypes.string,
       onBlur: PropTypes.func,
       onChange: PropTypes.func.isRequired,
       onFocus: PropTypes.func,
       onKeyDown: PropTypes.func,
       placeholder: PropTypes.string.isRequired,
+      summary: PropTypes.string,
+      summaryTooltip: PropTypes.string,
       type: PropTypes.string,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
@@ -164,16 +166,18 @@ class SearchBox extends PureComponent {
         autocomplete.jumpToBottom();
         break;
     }
   }
 
   render() {
     const {
       autocompleteProvider,
+      summary,
+      summaryTooltip,
       learnMoreTitle,
       learnMoreUrl,
       placeholder,
       type = "search",
     } = this.props;
     const { value } = this.state;
     const showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
     const showLearnMoreLink = learnMoreUrl && value === "";
@@ -192,16 +196,22 @@ class SearchBox extends PureComponent {
         ref: this.inputRef,
         value,
         type: "search",
       }),
       showLearnMoreLink && MDNLink({
         title: learnMoreTitle,
         url: learnMoreUrl,
       }),
+      summary ?
+        dom.span({
+          className: "devtools-searchinput-summary",
+          title: summaryTooltip || "",
+        }, summary)
+        : null,
       dom.button({
         className: "devtools-searchinput-clear",
         hidden: value === "",
         onClick: this.onClearButtonClick,
       }),
       showAutocomplete && AutocompletePopup({
         autocompleteProvider,
         filter: value,
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -504,16 +504,18 @@ checkbox:-moz-focusring {
 }
 
 .devtools-searchbox > .devtools-searchinput,
 .devtools-searchbox > .devtools-filterinput {
   /* Let .devtools-searchbox handle the background and border */
   background-color: transparent;
   border: none;
   flex-grow: 1;
+  min-width: 0;
+  width: 100%;
 }
 
 .devtools-searchbox:focus-within,
 .devtools-textinput:focus,
 .devtools-searchinput:focus,
 .devtools-filterinput:focus {
   border-color: var(--blue-50);
   outline: none;
@@ -521,16 +523,17 @@ checkbox:-moz-focusring {
 
 .devtools-searchbox-no-match {
   background-color: var(--searchbox-no-match-background-color);
   border-color: var(--searchbox-no-match-stroke-color);
 }
 
 /* Clear icon within the searchbox */
 .devtools-searchinput-clear {
+  flex: 0 0 auto;
   align-self: center;
   margin: 0 4px;
   padding: 0;
   border: 0;
   width: 16px;
   height: 16px;
   background-color: transparent;
   background-image: url("chrome://devtools/skin/images/search-clear.svg");
@@ -551,16 +554,24 @@ checkbox:-moz-focusring {
 .devtools-searchbox .learn-more-link::before {
   opacity: 0.6;
 }
 
 .devtools-searchbox:not(:hover) .learn-more-link {
   visibility: hidden;
 }
 
+.devtools-searchbox .devtools-searchinput-summary {
+  flex-basis: auto;
+  flex-grow: 0;
+  flex-shrink: 0;
+  color: var(--theme-comment);
+  align-self: center;
+}
+
 /* Autocomplete popup within the searchbox */
 .devtools-searchbox .devtools-autocomplete-popup {
   position: absolute;
   left: 0;
   top: 100%;
   width: 100%;
   line-height: initial !important;
   /* See bug - 1414609. z-index is greater than 1000 so that searchbox's z-index
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -630,23 +630,24 @@ a.learn-more-link.webconsole-learn-more-
   ---------------------------------------------------------------------------------------------------
   |                                               X items hidden by filters  - Reset Filters button |
   ---------------------------------------------------------------------------------------------------
   | Error - Warning - Log - Info - Debug - CSS - Network - XHR                                      |
   ---------------------------------------------------------------------------------------------------
 */
 .webconsole-filteringbar-wrapper {
   display: grid;
-  grid-row: 1 / 2;
   grid-template-columns: 1fr max-content auto auto;
-  /* Wrap so the "Hidden messages" bar can go to its own row if needed */
-  flex-wrap: wrap;
   --primary-toolbar-height: 29px;
 }
 
+.webconsole-filteringbar-wrapper .devtools-toolbar {
+  background-color: var(--theme-body-background);
+}
+
 .webconsole-filterbar-primary {
   display: flex;
   /* We want the toolbar (which contain the text search input) to fit
   the content, we don't allow to shrink/overlap it */
   flex: 100 0 -moz-fit-content;
   align-items: center;
   min-height: var(--primary-toolbar-height);
 }
@@ -680,22 +681,25 @@ 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;
+  /* Prevent the filter height from devtools-searchbox */
   height: unset;
   flex: 1 1 100%;
   margin-inline-start: 1px;
+  /* It's important to keep this in px as it's used in JS to control the display mode
+   * of the filter toolbar. */
+  min-width: 150px;
 }
 
 .webconsole-filterbar-primary .filter-checkbox {
   flex-shrink: 0;
   margin: 0 3px;
   display: flex;
   align-items: center;
   -moz-user-select: none;
@@ -709,17 +713,17 @@ a.learn-more-link.webconsole-learn-more-
   text-align: end;
   align-items: center;
   min-height: var(--primary-toolbar-height);
   line-height: var(--primary-toolbar-height);
 }
 
 @media (max-width: 965px) {
   /* This media query will make filtered message element to be displayed in new
-    line. This width is based on greek localized size since it will longer than
+    line. This width is based on greek localized size since it is longer than
     other localized strings. */
   .webconsole-filterbar-filtered-messages {
     grid-row: 2 / 3;
     grid-column: 1 / -1;
     justify-self: stretch;
   }
 }
 
--- a/devtools/client/webconsole/actions/filters.js
+++ b/devtools/client/webconsole/actions/filters.js
@@ -7,20 +7,18 @@
 "use strict";
 
 const { getAllFilters } = require("devtools/client/webconsole/selectors/filters");
 
 const {
   FILTER_TEXT_SET,
   FILTER_TOGGLE,
   FILTERS_CLEAR,
-  DEFAULT_FILTERS_RESET,
   PREFS,
   FILTERS,
-  DEFAULT_FILTERS,
 } = require("devtools/client/webconsole/constants");
 
 function filterTextSet(text) {
   return {
     type: FILTER_TEXT_SET,
     text,
   };
 }
@@ -46,37 +44,13 @@ function filtersClear() {
     for (const filter in filterState) {
       if (filter !== FILTERS.TEXT) {
         prefsService.clearUserPref(PREFS.FILTER[filter.toUpperCase()]);
       }
     }
   };
 }
 
-/**
- * Set the default filters to their original values.
- * This is different than filtersClear where we reset
- * all the filters to their original values. Here we want
- * to keep non-default filters the user might have set.
- */
-function defaultFiltersReset() {
-  return ({dispatch, getState, prefsService}) => {
-    // Get the state before dispatching so the action does not alter prefs reset.
-    const filterState = getAllFilters(getState());
-
-    dispatch({
-      type: DEFAULT_FILTERS_RESET,
-    });
-
-    DEFAULT_FILTERS.forEach(filter => {
-      if (filterState[filter] === false) {
-        prefsService.clearUserPref(PREFS.FILTER[filter.toUpperCase()]);
-      }
-    });
-  };
-}
-
 module.exports = {
   filterTextSet,
   filterToggle,
   filtersClear,
-  defaultFiltersReset,
 };
--- a/devtools/client/webconsole/actions/ui.js
+++ b/devtools/client/webconsole/actions/ui.js
@@ -16,16 +16,17 @@ const {
   REVERSE_SEARCH_INPUT_TOGGLE,
   SELECT_NETWORK_MESSAGE_TAB,
   SHOW_CONTENT_MESSAGES_TOGGLE,
   SHOW_OBJECT_IN_SIDEBAR,
   SIDEBAR_CLOSE,
   SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE,
   TIMESTAMPS_TOGGLE,
   WARNING_GROUPS_TOGGLE,
+  FILTERBAR_DISPLAY_MODE_SET,
 } = require("devtools/client/webconsole/constants");
 
 function persistToggle() {
   return ({dispatch, getState, prefsService}) => {
     dispatch({
       type: PERSIST_TOGGLE,
     });
     const uiState = getAllUi(getState());
@@ -113,18 +114,26 @@ function showObjectInSidebar(grip) {
 
 function reverseSearchInputToggle({initialValue} = {}) {
   return {
     type: REVERSE_SEARCH_INPUT_TOGGLE,
     initialValue,
   };
 }
 
+function filterBarDisplayModeSet(displayMode) {
+  return {
+    type: FILTERBAR_DISPLAY_MODE_SET,
+    displayMode,
+  };
+}
+
 module.exports = {
   contentMessagesToggle,
+  filterBarDisplayModeSet,
   initialize,
   persistToggle,
   reverseSearchInputToggle,
   selectNetworkMessageTab,
   showMessageObjectInSidebar,
   showObjectInSidebar,
   sidebarClose,
   splitConsoleCloseButtonToggle,
--- a/devtools/client/webconsole/components/App.js
+++ b/devtools/client/webconsole/components/App.js
@@ -5,16 +5,19 @@
 
 const Services = require("Services");
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { connect } = require("devtools/client/shared/redux/visibility-handler-connect");
 
 const actions = require("devtools/client/webconsole/actions/index");
+const {
+  FILTERBAR_DISPLAY_MODES,
+} = require("devtools/client/webconsole/constants");
 const ConsoleOutput = createFactory(require("devtools/client/webconsole/components/ConsoleOutput"));
 const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
 const SideBar = createFactory(require("devtools/client/webconsole/components/SideBar"));
 const ReverseSearchInput = createFactory(require("devtools/client/webconsole/components/ReverseSearchInput"));
 const JSTerm = createFactory(require("devtools/client/webconsole/components/JSTerm"));
 const ConfirmDialog = createFactory(require("devtools/client/webconsole/components/ConfirmDialog"));
 const NotificationBox = createFactory(require("devtools/client/shared/components/NotificationBox").NotificationBox);
 
@@ -47,16 +50,19 @@ class App extends Component {
       closeSplitConsole: PropTypes.func.isRequired,
       jstermCodeMirror: PropTypes.bool,
       autocomplete: PropTypes.bool,
       currentReverseSearchEntry: PropTypes.string,
       reverseSearchInputVisible: PropTypes.bool,
       reverseSearchInitialValue: PropTypes.string,
       editorMode: PropTypes.bool,
       hideShowContentMessagesCheckbox: PropTypes.bool,
+      sidebarVisible: PropTypes.bool.isRequired,
+      filterBarDisplayMode:
+        PropTypes.oneOf([...Object.values(FILTERBAR_DISPLAY_MODES)]).isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.onClick = this.onClick.bind(this);
     this.onPaste = this.onPaste.bind(this);
@@ -198,16 +204,18 @@ class App extends Component {
       notifications,
       onFirstMeaningfulPaint,
       serviceContainer,
       closeSplitConsole,
       jstermCodeMirror,
       autocomplete,
       reverseSearchInitialValue,
       editorMode,
+      filterBarDisplayMode,
+      sidebarVisible,
       hideShowContentMessagesCheckbox,
     } = this.props;
 
     const classNames = ["webconsole-app"];
     if (jstermCodeMirror) {
       classNames.push("jsterm-cm");
     }
     if (editorMode) {
@@ -230,16 +238,17 @@ class App extends Component {
         ref: node => {
           this.node = node;
         }},
         div({className: "webconsole-flex-wrapper"},
           FilterBar({
             hidePersistLogsCheckbox: webConsoleUI.isBrowserConsole,
             hideShowContentMessagesCheckbox,
             closeSplitConsole,
+            displayMode: filterBarDisplayMode,
           }),
           ConsoleOutput({
             serviceContainer,
             onFirstMeaningfulPaint,
           }),
           NotificationBox({
             id: "webconsole-notificationbox",
             wrapping: true,
@@ -257,31 +266,34 @@ class App extends Component {
             setInputValue: serviceContainer.setInputValue,
             focusInput: serviceContainer.focusInput,
             evaluateInput: serviceContainer.evaluateInput,
             initialValue: reverseSearchInitialValue,
           })
         ),
         SideBar({
           serviceContainer,
+          visible: sidebarVisible,
         }),
         ConfirmDialog({
           webConsoleUI,
           serviceContainer,
           codeMirrorEnabled: jstermCodeMirror,
         }),
       )
     );
   }
 }
 
 const mapStateToProps = state => ({
   notifications: getAllNotifications(state),
   reverseSearchInputVisible: state.ui.reverseSearchInputVisible,
   reverseSearchInitialValue: state.ui.reverseSearchInitialValue,
   editorMode: state.ui.editor,
+  sidebarVisible: state.ui.sidebarVisible,
+  filterBarDisplayMode: state.ui.filterBarDisplayMode,
 });
 
 const mapDispatchToProps = dispatch => ({
   dispatch,
 });
 
 module.exports = connect(mapStateToProps, mapDispatchToProps)(App);
--- a/devtools/client/webconsole/components/FilterBar.js
+++ b/devtools/client/webconsole/components/FilterBar.js
@@ -8,18 +8,18 @@ const dom = require("devtools/client/sha
 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,
+  FILTERBAR_DISPLAY_MODES,
 } = 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");
 
@@ -30,65 +30,65 @@ class FilterBar extends Component {
       filter: PropTypes.object.isRequired,
       persistLogs: PropTypes.bool.isRequired,
       hidePersistLogsCheckbox: PropTypes.bool.isRequired,
       showContentMessages: PropTypes.bool.isRequired,
       hideShowContentMessagesCheckbox: PropTypes.bool.isRequired,
       filteredMessagesCount: PropTypes.object.isRequired,
       closeButtonVisible: PropTypes.bool,
       closeSplitConsole: PropTypes.func,
+      displayMode:
+        PropTypes.oneOf([...Object.values(FILTERBAR_DISPLAY_MODES)]).isRequired,
     };
   }
 
   static get defaultProps() {
     return {
       hidePersistLogsCheckbox: false,
       hideShowContentMessagesCheckbox: true,
     };
   }
 
   constructor(props) {
     super(props);
 
-    this.state = {
-      overflowFilterButtons: false,
-    };
-
-    // Store the width of the filter button container to compare against the
-    // available space beside the filter input. If there is no room for the
-    // filter buttons beside the input, the buttons will overflow to a new row.
-    this._cachedFilterButtonWidth = 0;
-
-    this._resizeTimerId = null;
-    this.resizeHandler = this.resizeHandler.bind(this);
-
-    this.updateCachedFilterButtonsWidth = this.updateCachedFilterButtonsWidth.bind(this);
-    this.updateFilterButtonsOverflow = this.updateFilterButtonsOverflow.bind(this);
     this.onClickMessagesClear = this.onClickMessagesClear.bind(this);
-    this.onClickRemoveAllFilters = this.onClickRemoveAllFilters.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);
+
+    this.maybeUpdateLayout = this.maybeUpdateLayout.bind(this);
+    this.resizeObserver = new ResizeObserver(this.maybeUpdateLayout);
   }
 
   componentDidMount() {
-    window.addEventListener("resize", this.resizeHandler);
-    this.updateCachedFilterButtonsWidth();
-    this.updateFilterButtonsOverflow();
+    this.filterInputMinWidth = 150;
+    try {
+      const filterInput = this.wrapperNode.querySelector(".devtools-searchbox");
+      this.filterInputMinWidth =
+        Number(window.getComputedStyle(filterInput)["min-width"].replace("px", ""));
+    } catch (e) {
+      // If the min-width of the filter input isn't set, or is set in a different unit
+      // than px.
+      console.error("min-width of the filter input couldn't be retrieved.", e);
+    }
+
+    this.maybeUpdateLayout();
+    this.resizeObserver.observe(this.wrapperNode);
   }
 
   shouldComponentUpdate(nextProps, nextState) {
     const {
       filter,
       persistLogs,
       showContentMessages,
       filteredMessagesCount,
       closeButtonVisible,
+      displayMode,
     } = this.props;
 
     if (nextProps.filter !== filter) {
       return true;
     }
 
     if (nextProps.persistLogs !== persistLogs) {
       return true;
@@ -104,75 +104,70 @@ class FilterBar extends Component {
     ) {
       return true;
     }
 
     if (nextProps.closeButtonVisible != closeButtonVisible) {
       return true;
     }
 
-    if (nextState.overflowFilterButtons != this.state.overflowFilterButtons) {
+    if (nextProps.displayMode != displayMode) {
       return true;
     }
 
     return false;
   }
 
   componentWillUnmount() {
-    window.removeEventListener("resize", this.resizeHandler);
-    window.cancelIdleCallback(this._resizeTimerId);
-  }
-
-  /**
-   * Update the width of the filter buttons container.
-   */
-  updateCachedFilterButtonsWidth() {
-    const filterBar = this.wrapperNode.querySelector(".webconsole-filterbar-secondary");
-    this._cachedFilterButtonWidth = parseInt(getComputedStyle(filterBar).width, 10);
+    this.resizeObserver.disconnect();
   }
 
   /**
    * Update the boolean state that informs where the filter buttons should be rendered.
    * If the filter buttons are rendered inline with the filter input and the filter
    * input width is reduced below a threshold, the filter buttons are rendered on a new
    * row. When the filter buttons are on a separate row and the filter input grows
    * wide enough to display the filter buttons without dropping below the threshold,
    * the filter buttons are rendered inline.
    */
-  updateFilterButtonsOverflow() {
+  maybeUpdateLayout() {
     const {
-      overflowFilterButtons,
-    } = this.state;
+      dispatch,
+      displayMode,
+    } = this.props;
 
     const filterInput = this.wrapperNode.querySelector(".devtools-searchbox");
-    const filterInputWidth = parseInt(getComputedStyle(filterInput).width, 10);
-    const overflowSize = 250;
-    const inlineSize = overflowSize + this._cachedFilterButtonWidth;
+    const {width: filterInputWidth} = filterInput.getBoundingClientRect();
 
-    if (!overflowFilterButtons && filterInputWidth < overflowSize ||
-      overflowFilterButtons && filterInputWidth > inlineSize) {
-      this.setState({ overflowFilterButtons: !this.state.overflowFilterButtons });
+    if (displayMode === FILTERBAR_DISPLAY_MODES.WIDE) {
+      if (filterInputWidth <= this.filterInputMinWidth) {
+        dispatch(actions.filterBarDisplayModeSet(FILTERBAR_DISPLAY_MODES.NARROW));
+      }
+
+      return;
     }
-  }
+
+    if (displayMode === FILTERBAR_DISPLAY_MODES.NARROW) {
+      const filterButtonsToolbar =
+        this.wrapperNode.querySelector(".webconsole-filterbar-secondary");
 
-  resizeHandler(evt) {
-    window.cancelIdleCallback(this._resizeTimerId);
-    this._resizeTimerId = window.requestIdleCallback(() => {
-      this.updateFilterButtonsOverflow();
-    }, { timeout: 100 });
+      const buttonMargin = 5;
+      const filterButtonsToolbarWidth = Array.from(filterButtonsToolbar.children).reduce(
+        (width, el) => width + el.getBoundingClientRect().width + buttonMargin, 0);
+
+      if (filterInputWidth - this.filterInputMinWidth > filterButtonsToolbarWidth) {
+        dispatch(actions.filterBarDisplayModeSet(FILTERBAR_DISPLAY_MODES.WIDE));
+      }
+    }
   }
 
   onClickMessagesClear() {
     this.props.dispatch(actions.messagesClear());
   }
 
-  onClickRemoveAllFilters() {
-    this.props.dispatch(actions.defaultFiltersReset());
-  }
-
   onSearchBoxChange(text) {
     this.props.dispatch(actions.filterTextSet(text));
   }
 
   onChangePersistToggle() {
     this.props.dispatch(actions.persistToggle());
   }
 
@@ -254,63 +249,42 @@ class FilterBar extends Component {
         active: filter[FILTERS.NET],
         label: l10n.getStr("webconsole.requestsFilterButton.label"),
         filterKey: FILTERS.NET,
         dispatch,
       }),
     );
   }
 
-  renderFilteredMessagesBar() {
-    const {
-      filteredMessagesCount,
-    } = this.props;
-    const {
-      global,
-    } = filteredMessagesCount;
-
-    let label = l10n.getStr("webconsole.filteredMessages.label");
-    label = PluralForm.get(global, label).replace("#1", global);
-
-    // Include all default filters that are hiding messages.
-    const title = DEFAULT_FILTERS.reduce((res, filter) => {
-      if (filteredMessagesCount[filter] > 0) {
-        return res.concat(`${filter}: ${filteredMessagesCount[filter]}`);
-      }
-      return res;
-    }, []).join(", ");
-
-    return dom.div({
-      className: "devtools-toolbar webconsole-filterbar-filtered-messages",
-      key: "filtered-messages-bar",
-    },
-      dom.span({
-        className: "filter-message-text",
-        title,
-      }, label),
-      dom.button({
-        className: "devtools-button reset-filters-button",
-        onClick: this.onClickRemoveAllFilters,
-      }, l10n.getStr("webconsole.resetFiltersButton.label"))
-    );
-  }
-
   render() {
     const {
       persistLogs,
-      filteredMessagesCount,
       hidePersistLogsCheckbox,
       hideShowContentMessagesCheckbox,
       closeSplitConsole,
+      displayMode,
       showContentMessages,
+      filteredMessagesCount,
     } = this.props;
 
-    const {
-      overflowFilterButtons,
-    } = this.state;
+    const isNarrow = displayMode === FILTERBAR_DISPLAY_MODES.NARROW;
+    const isWide = displayMode === FILTERBAR_DISPLAY_MODES.WIDE;
+
+    let searchBoxSummary;
+    let searchBoxSummaryTooltip;
+    if (filteredMessagesCount.text > 0) {
+      searchBoxSummary = l10n.getStr("webconsole.filteredMessagesByText.label");
+      searchBoxSummary = PluralForm.get(filteredMessagesCount.text, searchBoxSummary)
+        .replace("#1", filteredMessagesCount.text);
+
+      searchBoxSummaryTooltip = l10n.getStr("webconsole.filteredMessagesByText.tooltip");
+      searchBoxSummaryTooltip =
+        PluralForm.get(filteredMessagesCount.text, searchBoxSummaryTooltip)
+          .replace("#1", filteredMessagesCount.text);
+    }
 
     const children = [
       dom.div({
         className: "devtools-toolbar devtools-input-toolbar webconsole-filterbar-primary",
         key: "primary-bar",
       },
         dom.button({
           className: "devtools-button devtools-clear-icon",
@@ -320,21 +294,23 @@ class FilterBar extends Component {
         dom.div({
           className: "devtools-separator",
         }),
         SearchBox({
           type: "filter",
           placeholder: l10n.getStr("webconsole.filterInput.placeholder"),
           keyShortcut: l10n.getStr("webconsole.find.key"),
           onChange: this.onSearchBoxChange,
+          summary: searchBoxSummary,
+          summaryTooltip: searchBoxSummaryTooltip,
         }),
-        !overflowFilterButtons && dom.div({
+        isWide && dom.div({
           className: "devtools-separator",
         }),
-        !overflowFilterButtons && this.renderFiltersConfigBar(),
+        isWide && this.renderFiltersConfigBar(),
         !hidePersistLogsCheckbox && dom.div({
           className: "devtools-separator",
         }),
         !hidePersistLogsCheckbox && FilterCheckbox({
           label: l10n.getStr("webconsole.enablePersistentLogs.label"),
           title: l10n.getStr("webconsole.enablePersistentLogs.tooltip"),
           onChange: this.onChangePersistToggle,
           checked: persistLogs,
@@ -343,47 +319,44 @@ class FilterBar extends Component {
           label: l10n.getStr("browserconsole.contentMessagesCheckbox.label"),
           title: l10n.getStr("browserconsole.contentMessagesCheckbox.tooltip"),
           onChange: this.onChangeShowContent,
           checked: showContentMessages,
         }),
       ),
     ];
 
-    if (filteredMessagesCount.global > 0) {
-      children.push(this.renderFilteredMessagesBar());
-    }
-
     if (this.props.closeButtonVisible) {
       children.push(dom.div(
         {
           className: "devtools-toolbar split-console-close-button-wrapper",
         },
         dom.button({
           id: "split-console-close-button",
           className: "devtools-button",
           title: l10n.getStr("webconsole.closeSplitConsoleButton.tooltip"),
           onClick: () => {
             closeSplitConsole();
           },
         })
       ));
     }
 
-    overflowFilterButtons && children.push(this.renderFiltersConfigBar());
+    if (isNarrow) {
+      children.push(this.renderFiltersConfigBar());
+    }
 
     return (
       dom.div({
-        className: "webconsole-filteringbar-wrapper",
+        className: `webconsole-filteringbar-wrapper ${displayMode}`,
         "aria-live": "off",
         ref: node => {
           this.wrapperNode = node;
         },
-      }, ...children
-      )
+      }, children)
     );
   }
 }
 
 function mapStateToProps(state) {
   const uiState = getAllUi(state);
   return {
     filter: getAllFilters(state),
--- a/devtools/client/webconsole/components/SideBar.js
+++ b/devtools/client/webconsole/components/SideBar.js
@@ -14,48 +14,49 @@ loader.lazyRequireGetter(this, "SplitBox
 loader.lazyRequireGetter(this, "reps", "devtools/client/shared/components/reps/reps");
 loader.lazyRequireGetter(this, "l10n", "devtools/client/webconsole/utils/messages", true);
 
 class SideBar extends Component {
   static get propTypes() {
     return {
       serviceContainer: PropTypes.object,
       dispatch: PropTypes.func.isRequired,
-      sidebarVisible: PropTypes.bool,
+      visible: PropTypes.bool,
       grip: PropTypes.object,
+      onResized: PropTypes.func,
     };
   }
 
   constructor(props) {
     super(props);
     this.onClickSidebarClose = this.onClickSidebarClose.bind(this);
   }
 
   shouldComponentUpdate(nextProps) {
     const {
       grip,
-      sidebarVisible,
+      visible,
     } = nextProps;
 
-    return sidebarVisible !== this.props.sidebarVisible
-      || grip !== this.props.grip;
+    return visible !== this.props.visible || grip !== this.props.grip;
   }
 
   onClickSidebarClose() {
     this.props.dispatch(actions.sidebarClose());
   }
 
   render() {
-    if (!this.props.sidebarVisible) {
+    if (!this.props.visible) {
       return null;
     }
 
     const {
       grip,
       serviceContainer,
+      onResized,
     } = this.props;
 
     const objectInspector = getObjectInspector(grip, serviceContainer, {
       autoExpandDepth: 1,
       mode: reps.MODE.SHORT,
       autoFocusRoot: true,
     });
 
@@ -78,20 +79,20 @@ class SideBar extends Component {
 
     return createElement(SplitBox, {
       className: "sidebar",
       endPanel,
       endPanelControl: true,
       initialSize: "200px",
       minSize: "100px",
       vert: true,
+      onControlledPanelResized: onResized,
     });
   }
 }
 
 function mapStateToProps(state, props) {
   return {
-    sidebarVisible: state.ui.sidebarVisible,
     grip: state.ui.gripInSidebar,
   };
 }
 
 module.exports = connect(mapStateToProps)(SideBar);
--- a/devtools/client/webconsole/constants.js
+++ b/devtools/client/webconsole/constants.js
@@ -9,20 +9,20 @@ const actionTypes = {
   APPEND_NOTIFICATION: "APPEND_NOTIFICATION",
   APPEND_TO_HISTORY: "APPEND_TO_HISTORY",
   AUTOCOMPLETE_CLEAR: "AUTOCOMPLETE_CLEAR",
   AUTOCOMPLETE_DATA_RECEIVE: "AUTOCOMPLETE_DATA_RECEIVE",
   AUTOCOMPLETE_PENDING_REQUEST: "AUTOCOMPLETE_PENDING_REQUEST",
   AUTOCOMPLETE_RETRIEVE_FROM_CACHE: "AUTOCOMPLETE_RETRIEVE_FROM_CACHE",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   CLEAR_HISTORY: "CLEAR_HISTORY",
-  DEFAULT_FILTERS_RESET: "DEFAULT_FILTERS_RESET",
   FILTER_TEXT_SET: "FILTER_TEXT_SET",
   FILTER_TOGGLE: "FILTER_TOGGLE",
   FILTERS_CLEAR: "FILTERS_CLEAR",
+  FILTERBAR_DISPLAY_MODE_SET: "FILTERBAR_DISPLAY_MODE_SET",
   HISTORY_LOADED: "HISTORY_LOADED",
   INITIALIZE: "INITIALIZE",
   MESSAGE_CLOSE: "MESSAGE_CLOSE",
   MESSAGE_OPEN: "MESSAGE_OPEN",
   MESSAGE_UPDATE_PAYLOAD: "MESSAGE_UPDATE_PAYLOAD",
   MESSAGES_ADD: "MESSAGES_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
   MESSAGES_CLEAR_LOGPOINT: "MESSAGES_CLEAR_LOGPOINT",
@@ -175,15 +175,19 @@ const historyCommands = {
   HISTORY_FORWARD: 1,
 };
 
 // Combine into a single constants object
 module.exports = Object.assign({
   FILTERS,
   DEFAULT_FILTERS,
   DEFAULT_FILTERS_VALUES,
+  FILTERBAR_DISPLAY_MODES: {
+    NARROW: "narrow",
+    WIDE: "wide",
+  },
 },
   actionTypes,
   chromeRDPEnums,
   jstermCommands,
   prefs,
   historyCommands,
 );
--- a/devtools/client/webconsole/reducers/filters.js
+++ b/devtools/client/webconsole/reducers/filters.js
@@ -14,22 +14,16 @@ const FilterState = (overrides) => Objec
 function filters(state = FilterState(), action) {
   switch (action.type) {
     case constants.FILTER_TOGGLE:
       const {filter} = action;
       const active = !state[filter];
       return cloneState(state, {[filter]: active});
     case constants.FILTERS_CLEAR:
       return FilterState();
-    case constants.DEFAULT_FILTERS_RESET:
-      const newState = cloneState(state);
-      constants.DEFAULT_FILTERS.forEach(filterName => {
-        newState[filterName] = constants.DEFAULT_FILTERS_VALUES[filterName];
-      });
-      return newState;
     case constants.FILTER_TEXT_SET:
       const {text} = action;
       return cloneState(state, {[constants.FILTERS.TEXT]: text});
   }
 
   return state;
 }
 
--- a/devtools/client/webconsole/reducers/ui.js
+++ b/devtools/client/webconsole/reducers/ui.js
@@ -11,16 +11,18 @@ const {
   PERSIST_TOGGLE,
   REVERSE_SEARCH_INPUT_TOGGLE,
   SELECT_NETWORK_MESSAGE_TAB,
   SHOW_CONTENT_MESSAGES_TOGGLE,
   SHOW_OBJECT_IN_SIDEBAR,
   SIDEBAR_CLOSE,
   SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE,
   TIMESTAMPS_TOGGLE,
+  FILTERBAR_DISPLAY_MODE_SET,
+  FILTERBAR_DISPLAY_MODES,
 } = require("devtools/client/webconsole/constants");
 
 const {
   PANELS,
 } = require("devtools/client/netmonitor/src/constants");
 
 const UiState = (overrides) => Object.freeze(Object.assign({
   initialized: false,
@@ -29,16 +31,17 @@ const UiState = (overrides) => Object.fr
   showContentMessages: false,
   sidebarVisible: false,
   timestampsVisible: true,
   gripInSidebar: null,
   closeButtonVisible: false,
   reverseSearchInputVisible: false,
   reverseSearchInitialValue: "",
   editor: false,
+  filterBarDisplayMode: FILTERBAR_DISPLAY_MODES.WIDE,
 }, overrides));
 
 function ui(state = UiState(), action) {
   switch (action.type) {
     case PERSIST_TOGGLE:
       return {...state, persistLogs: !state.persistLogs};
     case SHOW_CONTENT_MESSAGES_TOGGLE:
       return {...state, showContentMessages: !state.showContentMessages};
@@ -64,16 +67,21 @@ function ui(state = UiState(), action) {
     case SPLIT_CONSOLE_CLOSE_BUTTON_TOGGLE:
       return {...state, closeButtonVisible: action.shouldDisplayButton};
     case REVERSE_SEARCH_INPUT_TOGGLE:
       return {
         ...state,
         reverseSearchInputVisible: !state.reverseSearchInputVisible,
         reverseSearchInitialValue: action.initialValue || "",
       };
+    case FILTERBAR_DISPLAY_MODE_SET:
+      return {
+        ...state,
+        filterBarDisplayMode: action.displayMode,
+      };
   }
 
   return state;
 }
 
 module.exports = {
   UiState,
   ui,
--- a/devtools/client/webconsole/test/components/filter-bar.test.js
+++ b/devtools/client/webconsole/test/components/filter-bar.test.js
@@ -9,17 +9,17 @@ const { render, mount, shallow } = requi
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const Provider = createFactory(require("react-redux").Provider);
 
 const actions = require("devtools/client/webconsole/actions/index");
 const FilterButton = require("devtools/client/webconsole/components/FilterButton");
 const FilterBar = createFactory(require("devtools/client/webconsole/components/FilterBar"));
 const { getAllUi } = require("devtools/client/webconsole/selectors/ui");
-const { getAllFilters } = require("devtools/client/webconsole/selectors/filters");
+const { FILTERBAR_DISPLAY_MODES } = require("devtools/client/webconsole/constants");
 const {
   MESSAGES_CLEAR,
   FILTERS,
   PREFS,
 } = require("devtools/client/webconsole/constants");
 
 const { setupStore, prefsService, clearPrefs } = require("devtools/client/webconsole/test/helpers");
 const serviceContainer = require("devtools/client/webconsole/test/fixtures/serviceContainer");
@@ -66,149 +66,81 @@ describe("FilterBar component:", () => {
     // Text filter input clear button
     const textFilterClearButton = textInput.children().eq(1);
     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')",
-    ]);
-    // Filter-out LOG messages
-    store.dispatch(actions.filterToggle(FILTERS.LOG));
-
-    const wrapper = mount(Provider({store}, getFilterBar()));
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
-    expect(toolbar.exists()).toBeTruthy();
-
-    const message = toolbar.find(".filter-message-text");
-    expect(message.text()).toBe("1 item hidden by filters");
-    expect(message.prop("title")).toBe("log: 1");
-  });
-
-  it("Reset filters when the Reset filters button is clicked.", () => {
-    const store = setupStore([
-      "console.log('foobar', 'test')",
-    ]);
-    // Filter-out LOG messages
-    store.dispatch(actions.filterToggle(FILTERS.LOG));
-    const wrapper = mount(Provider({store}, getFilterBar()));
-
-    const resetFiltersButton = wrapper.find(
-      ".webconsole-filterbar-filtered-messages .devtools-button");
-    resetFiltersButton.simulate("click");
-
-    // Toolbar is now hidden
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
-    expect(toolbar.exists()).toBeFalsy();
-    expect(getAllFilters(store.getState())[FILTERS.LOG]).toBeTruthy();
-  });
-
   it("displays the number of hidden messages when a search hide messages", () => {
     const store = setupStore([
       "console.log('foobar', 'test')",
       "console.info('info message');",
       "console.warn('danger, will robinson!')",
       "console.debug('debug message');",
       "console.error('error message');",
     ]);
     store.dispatch(actions.filterTextSet("qwerty"));
 
     const wrapper = mount(Provider({store}, getFilterBar()));
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
-    expect(toolbar.exists()).toBeTruthy();
 
-    const message = toolbar.find(".filter-message-text");
-    expect(message.text()).toBe("5 items hidden by filters");
-    expect(message.prop("title")).toBe("text: 5");
+    const message = wrapper.find(".devtools-searchinput-summary");
+    expect(message.text()).toBe("5 hidden");
+    expect(message.prop("title")).toBe("5 items hidden by text filter");
   });
 
-  it("displays the number of hidden messages when there are multiple ones", () => {
+  it("displays the number of hidden messages when a search hide 1 message", () => {
     const store = setupStore([
       "console.log('foobar', 'test')",
       "console.info('info message');",
-      "console.warn('danger, will robinson!')",
-      "console.debug('debug message');",
-      "console.error('error message');",
-      "console.log('foobar', 'test')",
-      "console.info('info message');",
-      "console.warn('danger, will robinson!')",
-      "console.debug('debug message');",
-      "console.error('error message');",
     ]);
-
-    store.dispatch(actions.filterTextSet("qwerty"));
+    store.dispatch(actions.filterTextSet("foobar"));
 
     const wrapper = mount(Provider({store}, getFilterBar()));
-    const message = wrapper.find(".filter-message-text");
 
-    expect(message.prop("title")).toBe("text: 10");
+    const message = wrapper.find(".devtools-searchinput-summary");
+    expect(message.text()).toBe("1 hidden");
+    expect(message.prop("title")).toBe("1 item hidden by text filter");
   });
 
-  it("displays expected tooltip when there is text & level hidden-messages", () => {
+  it("displays the expected number of hidden messages when multiple filters", () => {
     const store = setupStore([
       "console.log('foobar', 'test')",
       "console.info('info message');",
       "console.warn('danger, will robinson!')",
       "console.debug('debug message');",
       "console.error('error message');",
-      "console.log('foobar', 'test')",
-      "console.info('info message');",
-      "console.warn('danger, will robinson!')",
-      "console.debug('debug message');",
-      "console.error('error message');",
     ]);
-
+    store.dispatch(actions.filterTextSet("qwerty"));
     store.dispatch(actions.filterToggle(FILTERS.ERROR));
-    store.dispatch(actions.filterToggle(FILTERS.WARN));
-    store.dispatch(actions.filterToggle(FILTERS.LOG));
     store.dispatch(actions.filterToggle(FILTERS.INFO));
-    store.dispatch(actions.filterToggle(FILTERS.DEBUG));
 
     const wrapper = mount(Provider({store}, getFilterBar()));
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
-    expect(toolbar.exists()).toBeTruthy();
 
-    const message = toolbar.find(".filter-message-text");
-    expect(message.text()).toBe("10 items hidden by filters");
-    expect(message.prop("title")).toBe("error: 2, warn: 2, log: 2, info: 2, debug: 2");
+    const message = wrapper.find(".devtools-searchinput-summary");
+    expect(message.text()).toBe("3 hidden");
+    expect(message.prop("title")).toBe("3 items hidden by text filter");
   });
 
   it("does not display the number of hidden messages when there are no messages", () => {
     const store = setupStore();
+    store.dispatch(actions.filterTextSet("qwerty"));
     const wrapper = mount(Provider({store}, getFilterBar()));
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
+
+    const toolbar = wrapper.find(".devtools-searchinput-summary");
     expect(toolbar.exists()).toBeFalsy();
   });
 
-  it("does not display the number of hidden non-default filters (CSS, Network,…)", () => {
-    const store = setupStore([
-      "Unknown property ‘such-unknown-property’.  Declaration dropped.",
-      "GET request",
-      "XHR GET request",
-    ]);
-    const wrapper = mount(Provider({store}, getFilterBar()));
-
-    // Let's make sure those non-default filters are off.
-    const filters = getAllFilters(store.getState());
-    expect(filters[FILTERS.CSS]).toBe(false);
-    expect(filters[FILTERS.NET]).toBe(false);
-    expect(filters[FILTERS.NETXHR]).toBe(false);
-
-    const toolbar = wrapper.find(".webconsole-filterbar-filtered-messages");
-    expect(toolbar.exists()).toBeFalsy();
-  });
-
-  it("Always displays filter bar", () => {
+  it("Displays a filter buttons bar on its own element in narrow displayMode", () => {
     const store = setupStore();
 
-    const wrapper = mount(Provider({store}, getFilterBar()));
+    const wrapper = mount(Provider({store}, getFilterBar({
+      displayMode: FILTERBAR_DISPLAY_MODES.NARROW,
+    })));
 
     const secondaryBar = wrapper.find(".webconsole-filterbar-secondary");
     expect(secondaryBar.length).toBe(1);
 
     // Buttons are displayed
     const filterBtn = props => FilterButton(
       Object.assign({}, {
         active: true,
--- a/devtools/client/webconsole/test/fixtures/L10n.js
+++ b/devtools/client/webconsole/test/fixtures/L10n.js
@@ -34,18 +34,20 @@ class L10n {
       case "webconsole.cssFilterButton.label":
         return "CSS";
       case "webconsole.xhrFilterButton.label":
         return "XHR";
       case "webconsole.requestsFilterButton.label":
         return "Requests";
       case "messageRepeats.tooltip2":
         return "#1 repeat;#1 repeats";
-      case "webconsole.filteredMessages.label":
-        return "#1 item hidden by filters;#1 items hidden by filters";
+      case "webconsole.filteredMessagesByText.label":
+        return "#1 hidden;#1 hidden";
+      case "webconsole.filteredMessagesByText.tooltip":
+        return "#1 item hidden by text filter;#1 items hidden by text filter";
       case "webconsole.group.contentBlocked":
         return "Content blocked messages";
       default:
         return str;
     }
   }
 
   getFormatStr(str) {
--- a/devtools/client/webconsole/test/mocha-test-setup.js
+++ b/devtools/client/webconsole/test/mocha-test-setup.js
@@ -62,16 +62,24 @@ global.isWorker = false;
 global.indexedDB = {open: () => ({})};
 
 // URLSearchParams was added to the global object in Node 10.0.0. To not cause any issue
 // with prior versions, we add it to the global object if it is not defined there.
 if (!global.URLSearchParams) {
   global.URLSearchParams = require("url").URLSearchParams;
 }
 
+if (!global.ResizeObserver) {
+  global.ResizeObserver = class ResizeObserver {
+    observe() {}
+    unobserve() {}
+    disconnect() {}
+  };
+}
+
 // Mock ChromeUtils.
 global.ChromeUtils = {
   import: () => {},
   defineModuleGetter: () => {},
 };
 
 // Point to vendored-in files and mocks when needed.
 const requireHacker = require("require-hacker");
--- a/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
+++ b/devtools/client/webconsole/test/mochitest/browser_jsterm_no_input_and_tab_key_pressed.js
@@ -24,23 +24,23 @@ async function performTests(codeMirror) 
 
   info("Check that hitting Tab when input is empty insert blur the input");
   jsterm.focus();
   setInputValue(hud, "");
   EventUtils.synthesizeKey("KEY_Tab");
   is(getInputValue(hud), "", "inputnode is empty - matched");
   ok(!isInputFocused(hud), "input isn't focused anymore");
 
-  info("Check that hitting Shift+Tab when input is not empty insert a tab");
+  info("Check that hitting Shift+Tab when input is empty blur the input");
   jsterm.focus();
   EventUtils.synthesizeKey("KEY_Tab", {shiftKey: true});
   is(getInputValue(hud), "", "inputnode is empty - matched");
   ok(!isInputFocused(hud), "input isn't focused anymore");
-  ok(hasFocus(hud.ui.outputNode.querySelector(".devtools-button.net")),
-    `The "Requests" filter button is now focused`);
+  ok(hasFocus(hud.ui.outputNode.querySelector(".filter-checkbox input")),
+    `The "Persist Logs" checkbox is now focused`);
 
   info("Check that hitting Tab when input is not empty insert a tab");
   jsterm.focus();
 
   const testString = "window.Bug583816";
   await setInputValueForAutocompletion(hud, testString, 0);
   checkInputValueAndCursorPosition(hud, `|${testString}`,
     "cursor is at the start of the input");
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_buttons_overflow.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_filter_buttons_overflow.js
@@ -10,74 +10,56 @@ const TEST_URI = "http://example.com/bro
 
 add_task(async function() {
   const hud = await openNewTabAndConsole(TEST_URI);
   const win = hud.browserWindow;
   const initialWindowWidth = win.outerWidth;
 
   info("Check filter buttons are inline with filter input when window is large.");
   resizeWindow(1500, win);
-  let primaryFilterBar = await getPrimaryFilterBar(hud);
-  let filterButtons = await getSecondaryFilterBar(hud);
-  let filterInput = await getFilterInput(hud);
-  checkFilterInputWidth(filterInput);
-  is(filterButtons.parentNode, primaryFilterBar,
-    "The filter buttons div should be a child of the primary filter bar.");
-  is(filterButtons.previousSibling.previousSibling, filterInput,
-    "The filter buttons div should appear after a divider after the filter input.");
+  await waitForFilterBarLayout(hud, ".wide");
+  ok(true, "The filter bar has the wide layout");
 
   info("Check filter buttons overflow when window is small.");
   resizeWindow(400, win);
-  primaryFilterBar = await getPrimaryFilterBar(hud);
-  filterButtons = await getSecondaryFilterBar(hud);
-  filterInput = await getFilterInput(hud);
-  checkFilterInputWidth(filterInput);
-  is(filterButtons.parentNode, primaryFilterBar.parentNode,
-    "The filter buttons div should share the same parent as the primary filter bar.");
-  for (const sibling of primaryFilterBar.childNodes) {
-    isnot(filterButtons, sibling,
-      "The filter buttons should not be in the same div as the filter input");
-  }
+  await waitForFilterBarLayout(hud, ".narrow");
+  ok(true, "The filter bar has the narrow layout");
+
+  info("Check that the filter bar layout changes when opening the sidebar");
+  resizeWindow(800, win);
+  await waitForFilterBarLayout(hud, ".wide");
+  const onMessage = waitForMessage(hud, "world");
+  ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
+    content.console.log({hello: "world"});
+  });
+  const {node} = await onMessage;
+  const object = node.querySelector(".object-inspector .objectBox-object");
+  info("Ctrl+click on an object to put it in the sidebar");
+  const onSidebarShown = waitFor(() => hud.ui.document.querySelector(".sidebar"));
+  EventUtils.sendMouseEvent({
+    type: "click",
+    [Services.appinfo.OS === "Darwin" ? "metaKey" : "ctrlKey"]: true,
+  }, object, hud.ui.window);
+  const sidebar = await onSidebarShown;
+  await waitForFilterBarLayout(hud, ".narrow");
+  ok(true, "FilterBar layout changed when opening the sidebar");
+
+  info("Check that filter bar layout changes when closing the sidebar");
+  sidebar.querySelector(".sidebar-close-button").click();
+  await waitForFilterBarLayout(hud, ".wide");
 
   info("Restore the original window size");
   await resizeWindow(initialWindowWidth, win);
 
   await closeTabAndToolbox();
 });
 
-async function resizeWindow(width, win) {
+function resizeWindow(width, win) {
   const onResize = once(win, "resize");
   win.resizeTo(width, win.outerHeight);
   info("Wait for window resize event");
-  await onResize;
-}
-
-async function getElementBySelector(hud, query) {
-  const outputNode = hud.ui.outputNode;
-
-  const element = await waitFor(() => {
-    return outputNode.querySelector(query);
-  });
-
-  return element;
+  return onResize;
 }
 
-async function getPrimaryFilterBar(hud) {
-  info("Wait for console filterbar to appear.");
-  return getElementBySelector(hud, ".webconsole-filterbar-primary");
-}
-
-async function getSecondaryFilterBar(hud) {
-  info("Wait for filter buttons to appear.");
-  return getElementBySelector(hud, ".webconsole-filterbar-secondary");
+function waitForFilterBarLayout(hud, query) {
+  return waitFor(() =>
+    hud.ui.outputNode.querySelector(`.webconsole-filteringbar-wrapper${query}`));
 }
-
-async function getFilterInput(hud) {
-  info("Wait for fitler input to appear.");
-  return getElementBySelector(hud, ".devtools-searchbox");
-}
-
-async function checkFilterInputWidth(input) {
-  const minInputWidth = 250;
-  const inputWidth = parseInt(getComputedStyle(input).width, 10);
-
-  ok(inputWidth >= minInputWidth, "Filter input should be greater than 250.");
-}
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_in_line_layout.js
@@ -64,32 +64,28 @@ async function performTests() {
   info("Make sure setting a tall value in the input does not break the layout");
   setInputValue(hud, "multiline\n".repeat(200));
   is(outputNode.clientHeight, MINIMUM_MESSAGE_HEIGHT,
     "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"));
-  setFilterState(hud, {text: "message-"});
-  await onHiddenMessagesLabelVisible;
-
-  info("Shrink the window so the label is on its own line");
+  info("Shrink the window so the filter buttons are put in a new line");
   const toolbox = hud.ui.wrapper.toolbox;
   const hostWindow = toolbox.win.parent;
   hostWindow.resizeTo(300, window.screen.availHeight);
+  await waitFor(() => document.querySelector(".webconsole-filteringbar-wrapper.narrow"));
 
   ok(filterBarNode.clientHeight > filterBarHeight, "The filter bar is taller");
   testLayout(appNode);
 
-  info("Expand the window so hidden label isn't on its own line anymore");
+  info("Expand the window so filter buttons aren't on their own line anymore");
   hostWindow.resizeTo(window.screen.availWidth, window.screen.availHeight);
+  await waitFor(() => document.querySelector(".webconsole-filteringbar-wrapper.wide"));
   testLayout(appNode);
 
   setInputValue(hud, "");
   testLayout(appNode);
 
   ui.clearOutput();
   testLayout(appNode);
   is(outputNode.offsetHeight, 0, "output node has no height");
--- a/devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
+++ b/devtools/client/webconsole/test/mochitest/browser_webconsole_telemetry_filters_changed.js
@@ -48,30 +48,16 @@ add_task(async function() {
   info("Filter the output using the text filter input");
   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");
-  const resetButton = await waitFor(() =>
-    hud.ui.window.document.querySelector(".reset-filters-button"));
-  const onResetButtonHidden = waitFor(() =>
-    !hud.ui.window.document.querySelector(".reset-filters-button"));
-  resetButton.click();
-  await onResetButtonHidden;
-
-  checkTelemetryEvent({
-    trigger: "reset",
-    active: "error,warn,log,info,debug,netxhr",
-    inactive: "text,css,net",
-  });
 });
 
 function checkTelemetryEvent(expectedEvent) {
   const events = getFiltersChangedEventsExtra();
   is(events.length, 1, "There was only 1 event logged");
   const [event] = events;
   ok(event.session_id > 0, "There is a valid session_id in the logged event");
   const f = e => JSON.stringify(e, null, 2);
--- a/devtools/client/webconsole/test/store/filters.test.js
+++ b/devtools/client/webconsole/test/store/filters.test.js
@@ -289,82 +289,16 @@ describe("Clear filters", () => {
       [PREFS.FILTER.LOG]: true,
       [PREFS.FILTER.NET]: false,
       [PREFS.FILTER.NETXHR]: false,
       [PREFS.FILTER.WARN]: true,
     });
   });
 });
 
-describe("Resets filters", () => {
-  it("resets default filters value to their original one.", () => {
-    const store = setupStore();
-
-    // Setup test case
-    store.dispatch(actions.filterToggle(FILTERS.ERROR));
-    store.dispatch(actions.filterToggle(FILTERS.LOG));
-    store.dispatch(actions.filterToggle(FILTERS.CSS));
-    store.dispatch(actions.filterToggle(FILTERS.NET));
-    store.dispatch(actions.filterToggle(FILTERS.NETXHR));
-    store.dispatch(actions.filterTextSet("foobar"));
-
-    expect(getAllFilters(store.getState())).toEqual({
-      // default
-      [FILTERS.WARN]: true,
-      [FILTERS.INFO]: true,
-      [FILTERS.DEBUG]: true,
-      // changed
-      [FILTERS.ERROR]: false,
-      [FILTERS.LOG]: false,
-      [FILTERS.CSS]: true,
-      [FILTERS.NET]: true,
-      [FILTERS.NETXHR]: true,
-      [FILTERS.TEXT]: "foobar",
-    });
-
-    expect(getFiltersPrefs()).toEqual({
-      [PREFS.FILTER.WARN]: true,
-      [PREFS.FILTER.INFO]: true,
-      [PREFS.FILTER.DEBUG]: true,
-      [PREFS.FILTER.ERROR]: false,
-      [PREFS.FILTER.LOG]: false,
-      [PREFS.FILTER.CSS]: true,
-      [PREFS.FILTER.NET]: true,
-      [PREFS.FILTER.NETXHR]: true,
-    });
-
-    store.dispatch(actions.defaultFiltersReset());
-
-    expect(getAllFilters(store.getState())).toEqual({
-      // default
-      [FILTERS.ERROR]: true,
-      [FILTERS.WARN]: true,
-      [FILTERS.LOG]: true,
-      [FILTERS.INFO]: true,
-      [FILTERS.DEBUG]: true,
-      [FILTERS.TEXT]: "",
-      // non-default filters weren't changed
-      [FILTERS.CSS]: true,
-      [FILTERS.NET]: true,
-      [FILTERS.NETXHR]: true,
-    });
-
-    expect(getFiltersPrefs()).toEqual({
-      [PREFS.FILTER.ERROR]: true,
-      [PREFS.FILTER.WARN]: true,
-      [PREFS.FILTER.LOG]: true,
-      [PREFS.FILTER.INFO]: true,
-      [PREFS.FILTER.DEBUG]: true,
-      [PREFS.FILTER.CSS]: true,
-      [PREFS.FILTER.NET]: true,
-      [PREFS.FILTER.NETXHR]: true,
-    });
-  });
-});
-
 function prepareBaseStore() {
   const store = setupStore([
     // Console API
     "console.log('foobar', 'test')",
     "console.warn('danger, will robinson!')",
     "console.log(undefined)",
     "console.count('bar')",
     "console.log('鼬')",
--- a/devtools/client/webconsole/test/store/hidden-messages.test.js
+++ b/devtools/client/webconsole/test/store/hidden-messages.test.js
@@ -131,32 +131,16 @@ describe("Filtering - Hidden messages", 
       [FILTERS.LOG]: 0,
       [FILTERS.INFO]: 0,
       [FILTERS.DEBUG]: 0,
       [FILTERS.TEXT]: 0,
       global: 0,
     });
   });
 
-  it("has the expected numbers after filter reset", () => {
-    // Add a text search to make sure it is handled as well.
-    store.dispatch(actions.filterTextSet("danger, will robinson!"));
-    store.dispatch(actions.defaultFiltersReset());
-    const counter = getFilteredMessagesCount(store.getState());
-    expect(counter).toEqual({
-      [FILTERS.ERROR]: 0,
-      [FILTERS.WARN]: 0,
-      [FILTERS.LOG]: 0,
-      [FILTERS.INFO]: 0,
-      [FILTERS.DEBUG]: 0,
-      [FILTERS.TEXT]: 0,
-      global: 0,
-    });
-  });
-
   it("has the expected numbers after filter clear", () => {
     // Add a text search to make sure it is handled as well.
     store.dispatch(actions.filterTextSet("danger, will robinson!"));
     store.dispatch(actions.filtersClear());
     const counter = getFilteredMessagesCount(store.getState());
     expect(counter).toEqual({
       [FILTERS.ERROR]: 0,
       [FILTERS.WARN]: 0,