Bug 1287508 - Part 1: New console frontend: Add filter bar. r=bgrins
authorNicolas Chevobbe <chevobbe.nicolas@gmail.com>
Mon, 18 Jul 2016 19:02:19 +0200
changeset 395259 45170a42e54cddbd9b72d98953ff2d8225782bf7
parent 395258 23457f1d08dfa1fa19494da84c5ac7e7732a2536
child 395260 26c34b9a3fd3746e455efa51075b85120525ba31
push id24732
push userbmo:gps@mozilla.com
push dateMon, 01 Aug 2016 21:50:09 +0000
reviewersbgrins
bugs1287508
milestone50.0a1
Bug 1287508 - Part 1: New console frontend: Add filter bar. r=bgrins MozReview-Commit-ID: KoIEkQVPg2V
devtools/client/themes/toolbars.css
devtools/client/themes/webconsole.css
devtools/client/webconsole/new-console-output/actions/messages.js
devtools/client/webconsole/new-console-output/actions/moz.build
devtools/client/webconsole/new-console-output/actions/ui.js
devtools/client/webconsole/new-console-output/components/console-output.js
devtools/client/webconsole/new-console-output/components/filter-bar.js
devtools/client/webconsole/new-console-output/components/filter-toggle-button.js
devtools/client/webconsole/new-console-output/components/moz.build
devtools/client/webconsole/new-console-output/constants.js
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/new-console-output/reducers/filters.js
devtools/client/webconsole/new-console-output/reducers/index.js
devtools/client/webconsole/new-console-output/reducers/moz.build
devtools/client/webconsole/new-console-output/reducers/prefs.js
devtools/client/webconsole/new-console-output/reducers/ui.js
devtools/client/webconsole/new-console-output/selectors/filters.js
devtools/client/webconsole/new-console-output/selectors/messages.js
devtools/client/webconsole/new-console-output/selectors/moz.build
devtools/client/webconsole/new-console-output/selectors/ui.js
devtools/client/webconsole/new-console-output/store.js
devtools/client/webconsole/webconsole.js
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -243,16 +243,18 @@
 .devtools-button:disabled,
 .devtools-button[disabled],
 .devtools-toolbarbutton[disabled] {
   opacity: 0.5 !important;
 }
 
 .devtools-button[checked]:empty::before,
 .devtools-button[open]:empty::before,
+.devtools-button.active::before,
+.theme-light .devtools-button.active::before,
 .devtools-toolbarbutton:not([label])[checked=true] > image,
 .devtools-toolbarbutton:not([label])[open=true] > image {
   filter: var(--checked-icon-filter);
 }
 
 /* Icon-and-text buttons */
 .devtools-toolbarbutton.icon-and-text .toolbarbutton-text {
   margin-inline-start: .5em !important;
@@ -303,16 +305,20 @@
 :root {
   --clear-icon-url: url("chrome://devtools/skin/images/clear.svg");
 }
 
 .devtools-button.devtools-clear-icon::before {
   background-image: var(--clear-icon-url);
 }
 
+.devtools-button.devtools-filter-icon::before {
+  background-image: var(--filter-image);
+}
+
 .devtools-toolbarbutton.devtools-clear-icon {
   list-style-image: var(--clear-icon-url);
 }
 
 .devtools-option-toolbarbutton {
   list-style-image: var(--tool-options-image);
 }
 
@@ -644,8 +650,33 @@
 @keyframes throbber-spin {
   from {
     transform: none;
   }
   to {
     transform: rotate(360deg);
   }
 }
+
+/* Filter buttons */
+.menu-filter-button {
+  -moz-appearance: none;
+  background: rgba(128,128,128,0.1);
+  border: none;
+  border-radius: 2px;
+  min-width: 0;
+  padding: 0 5px;
+  margin: 2px;
+  color: var(--theme-body-color);
+}
+
+.menu-filter-button:hover {
+  background: rgba(128,128,128,0.2);
+}
+
+.menu-filter-button:hover:active {
+  background-color: var(--theme-selection-background-semitransparent);
+}
+
+.menu-filter-button:not(:active).active {
+  background-color: var(--theme-selection-background);
+  color: var(--theme-selection-color);
+}
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -146,16 +146,17 @@ a {
   display: block;
   flex: auto;
 }
 
 #output-wrapper {
   direction: ltr;
   overflow: auto;
   -moz-user-select: text;
+  position: relative;
 }
 
 /* The width on #output-container is set to a hardcoded px in webconsole.js
    since it's way faster than using 100% with -moz-box-flex (see Bug 1237368) */
 
 #output-container.hideTimestamps > .message {
   padding-inline-start: 0;
   margin-inline-start: 7px;
@@ -619,8 +620,46 @@ a.learn-more-link.webconsole-learn-more-
 
 .theme-firebug .consoletable .theme-body {
   width: 100%;
   border-top: 1px solid #D7D7D7;
   border-bottom: 2px solid #D7D7D7;
   border-left: 1px solid #D7D7D7;
   border-right: 1px solid #D7D7D7;
 }
+
+
+/* NEW CONSOLE STYLES */
+
+#output-wrapper > div {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  right: 0;
+}
+
+#output-container {
+  height: 100%;
+}
+
+.webconsole-output-wrapper {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+
+.webconsole-filteringbar-wrapper {
+  flex-grow: 0;
+}
+
+.webconsole-output {
+  flex: 1;
+  overflow: auto;
+}
+
+.webconsole-filteringbar-primary {
+  display: flex;
+}
+
+.webconsole-filteringbar-primary .devtools-searchinput {
+  flex: 1 1 100%;
+}
--- a/devtools/client/webconsole/new-console-output/actions/messages.js
+++ b/devtools/client/webconsole/new-console-output/actions/messages.js
@@ -8,26 +8,53 @@
 
 const {
   prepareMessage
 } = require("devtools/client/webconsole/new-console-output/utils/messages");
 
 const {
   MESSAGE_ADD,
   MESSAGES_CLEAR,
+  SEVERITY_FILTER,
+  MESSAGES_SEARCH,
+  FILTERS_CLEAR,
 } = require("../constants");
 
 function messageAdd(packet) {
   let message = prepareMessage(packet);
   return {
     type: MESSAGE_ADD,
     message
   };
 }
 
 function messagesClear() {
   return {
     type: MESSAGES_CLEAR
   };
 }
 
+function severityFilter(filter, toggled) {
+  return {
+    type: SEVERITY_FILTER,
+    filter,
+    toggled
+  };
+}
+
+function filtersClear() {
+  return {
+    type: FILTERS_CLEAR
+  };
+}
+
+function messagesSearch(searchText) {
+  return {
+    type: MESSAGES_SEARCH,
+    searchText
+  };
+}
+
 exports.messageAdd = messageAdd;
 exports.messagesClear = messagesClear;
+exports.severityFilter = severityFilter;
+exports.filtersClear = filtersClear;
+exports.messagesSearch = messagesSearch;
--- a/devtools/client/webconsole/new-console-output/actions/moz.build
+++ b/devtools/client/webconsole/new-console-output/actions/moz.build
@@ -1,8 +1,9 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'messages.js',
+    'ui.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/actions/ui.js
@@ -0,0 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 {
+  FILTERBAR_TOGGLE,
+} = require("../constants");
+
+function filterBarToggle(show) {
+  return {
+    type: FILTERBAR_TOGGLE
+  };
+}
+
+exports.filterBarToggle = filterBarToggle;
--- a/devtools/client/webconsole/new-console-output/components/console-output.js
+++ b/devtools/client/webconsole/new-console-output/components/console-output.js
@@ -43,17 +43,17 @@ const ConsoleOutput = createClass({
 
   render() {
     let messageNodes = this.props.messages.map(function (message) {
       return (
         MessageContainer({ message })
       );
     });
     return (
-      dom.div({}, messageNodes)
+      dom.div({className: "webconsole-output"}, messageNodes)
     );
   }
 });
 
 function isScrolledToBottom(outputNode, scrollNode) {
   let lastNodeHeight = outputNode.lastChild ?
                        outputNode.lastChild.clientHeight : 0;
   return scrollNode.scrollTop + scrollNode.clientHeight >=
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-bar.js
@@ -0,0 +1,133 @@
+/* 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 {
+  createFactory,
+  createClass,
+  DOM: dom,
+  PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
+const { getAllUi } = require("devtools/client/webconsole/new-console-output/selectors/ui");
+const messagesActions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const uiActions = require("devtools/client/webconsole/new-console-output/actions/ui");
+const { store } = require("devtools/client/webconsole/new-console-output/store");
+const {
+  SEVERITY_FILTER
+} = require("../constants");
+const FilterToggleButton = createFactory(require("devtools/client/webconsole/new-console-output/components/filter-toggle-button").FilterToggleButton);
+
+const FilterBar = createClass({
+
+  displayName: "FilterBar",
+
+  propTypes: {
+    filter: PropTypes.object.isRequired,
+    ui: PropTypes.object.isRequired
+  },
+
+  onClearOutputButtonClick: function () {
+    store.dispatch(messagesActions.messagesClear());
+  },
+
+  onToggleFilterConfigBarButtonClick: function () {
+    store.dispatch(uiActions.filterBarToggle());
+  },
+
+  onClearFiltersButtonClick: function () {
+    store.dispatch(messagesActions.filtersClear());
+  },
+
+  onSearchInput: function (e) {
+    store.dispatch(messagesActions.messagesSearch(e.target.value));
+  },
+
+  render() {
+    const {filter, ui} = this.props;
+    let configFilterBarVisible = ui.configFilterBarVisible;
+    let children = [];
+
+    children.push(dom.div({className: "devtools-toolbar webconsole-filteringbar-primary"},
+      dom.button({
+        className: "devtools-button devtools-clear-icon",
+        title: "Clear output",
+        onClick: this.onClearOutputButtonClick
+      }),
+      dom.button({
+        className: "devtools-button devtools-filter-icon" + (
+          configFilterBarVisible ? " active" : ""),
+        title: "Toggle filter bar",
+        onClick: this.onToggleFilterConfigBarButtonClick
+      }),
+      dom.input({
+        className: "devtools-searchinput",
+        type: "search",
+        value: filter.searchText,
+        placeholder: "Filter output",
+        onInput: this.onSearchInput
+      })
+    ));
+
+    if (configFilterBarVisible) {
+      children.push(
+        dom.div({className: "devtools-toolbar"},
+          FilterToggleButton({
+            active: filter.error,
+            label: "Errors",
+            filterType: SEVERITY_FILTER,
+            filterKey: "error"}),
+          FilterToggleButton({
+            active: filter.warn,
+            label: "Warnings",
+            filterType: SEVERITY_FILTER,
+            filterKey: "warn"}),
+          FilterToggleButton({
+            active: filter.log,
+            label: "Logs",
+            filterType: SEVERITY_FILTER,
+            filterKey: "log"}),
+          FilterToggleButton({
+            active: filter.info,
+            label: "Info",
+            filterType: SEVERITY_FILTER,
+            filterKey: "info"})
+        )
+      );
+    }
+
+    if (ui.filteredMessageVisible) {
+      children.push(
+        dom.div({className: "devtools-toolbar"},
+          dom.span({
+            className: "clear"},
+            "You have filters set that may hide some results. " +
+            "Learn more about our filtering syntax ",
+            dom.a({}, "here"),
+            "."),
+          dom.button({
+            className: "menu-filter-button",
+            onClick: this.onClearFiltersButtonClick
+          }, "Remove filters")
+        )
+      );
+    }
+
+    return (
+      dom.div({className: "webconsole-filteringbar-wrapper"},
+        children
+      )
+    );
+  }
+});
+
+function mapStateToProps(state) {
+  return {
+    filter: getAllFilters(state),
+    ui: getAllUi(state)
+  };
+}
+
+module.exports = connect(mapStateToProps)(FilterBar);
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/components/filter-toggle-button.js
@@ -0,0 +1,50 @@
+/* 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 {
+  createClass,
+  DOM: dom,
+  PropTypes
+} = require("devtools/client/shared/vendor/react");
+const { store } = require("devtools/client/webconsole/new-console-output/store");
+const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
+const {
+  SEVERITY_FILTER
+} = require("../constants");
+
+const FilterToggleButton = createClass({
+
+  displayName: "FilterToggleButton",
+
+  propTypes: {
+    label: PropTypes.string.isRequired,
+    filterType: PropTypes.string.isRequired,
+    filterKey: PropTypes.string.isRequired,
+    active: PropTypes.bool.isRequired,
+  },
+
+  onClick: function () {
+    if (this.props.filterType === SEVERITY_FILTER) {
+      store.dispatch(actions.severityFilter(
+        this.props.filterKey, !this.props.active));
+    }
+  },
+
+  render() {
+    const {label, active} = this.props;
+
+    let classList = ["menu-filter-button"];
+    if (active) {
+      classList.push("active");
+    }
+
+    return dom.button({
+      className: classList.join(" "),
+      onClick: this.onClick
+    }, label);
+  }
+});
+
+exports.FilterToggleButton = FilterToggleButton;
--- a/devtools/client/webconsole/new-console-output/components/moz.build
+++ b/devtools/client/webconsole/new-console-output/components/moz.build
@@ -4,14 +4,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'message-types'
 ]
 
 DevToolsModules(
     'console-output.js',
+    'filter-bar.js',
+    'filter-toggle-button.js',
     'grip-message-body.js',
     'message-container.js',
     'message-icon.js',
     'message-repeat.js',
     'variables-view-link.js'
 )
--- a/devtools/client/webconsole/new-console-output/constants.js
+++ b/devtools/client/webconsole/new-console-output/constants.js
@@ -3,16 +3,20 @@
 /* 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 actionTypes = {
   MESSAGE_ADD: "MESSAGE_ADD",
   MESSAGES_CLEAR: "MESSAGES_CLEAR",
+  SEVERITY_FILTER: "SEVERITY_FILTER",
+  MESSAGES_SEARCH: "MESSAGES_SEARCH",
+  FILTERS_CLEAR: "FILTERS_CLEAR",
+  FILTERBAR_TOGGLE: "FILTERBAR_TOGGLE",
 };
 
 const categories = {
   CATEGORY_NETWORK: "network",
   CATEGORY_CSS: "cssparser",
   CATEGORY_JS: "exception",
   CATEGORY_WEBDEV: "console",
   CATEGORY_INPUT: "input",
@@ -87,11 +91,15 @@ const chromeRDPEnums = {
     LOG: "log",
     ERROR: "error",
     WARN: "warn",
     DEBUG: "debug",
     INFO: "info"
   }
 };
 
+const filterTypes = {
+  SEVERITY_FILTER: "SEVERITY_FILTER"
+};
+
 // Combine into a single constants object
 module.exports = Object.assign({}, actionTypes, categories, severities, levels,
-  chromeRDPEnums);
+  chromeRDPEnums, filterTypes);
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -7,21 +7,29 @@
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
 const { store } = require("devtools/client/webconsole/new-console-output/store");
 
 const ConsoleOutput = React.createFactory(require("devtools/client/webconsole/new-console-output/components/console-output"));
+const FilterBar = React.createFactory(require("devtools/client/webconsole/new-console-output/components/filter-bar"));
 
 function NewConsoleOutputWrapper(parentNode, jsterm) {
   let childComponent = ConsoleOutput({ jsterm });
+  let filterBar = FilterBar({});
   let provider = React.createElement(
-    Provider, { store: store }, childComponent);
+    Provider,
+    { store: store },
+    React.DOM.div(
+      {className: "webconsole-output-wrapper"},
+      filterBar,
+      childComponent
+  ));
   this.body = ReactDOM.render(provider, parentNode);
 }
 
 NewConsoleOutputWrapper.prototype = {
   dispatchMessageAdd: (message) => {
     store.dispatch(actions.messageAdd(message));
   },
   dispatchMessagesClear: () => {
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/filters.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 constants = require("devtools/client/webconsole/new-console-output/constants");
+const {Filters} = require("devtools/client/webconsole/new-console-output/store");
+
+function filters(state = new Filters(), action) {
+  switch (action.type) {
+    case constants.SEVERITY_FILTER:
+      let {filter, toggled} = action;
+      return state.set(filter, toggled);
+    case constants.FILTERS_CLEAR:
+      return new Filters();
+    case constants.MESSAGES_SEARCH:
+      let {searchText} = action;
+      return state.set("searchText", searchText);
+  }
+
+  return state;
+}
+
+exports.filters = filters;
--- a/devtools/client/webconsole/new-console-output/reducers/index.js
+++ b/devtools/client/webconsole/new-console-output/reducers/index.js
@@ -1,14 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 { filters } = require("./filters");
 const { messages } = require("./messages");
 const { prefs } = require("./prefs");
+const { ui } = require("./ui");
 
 exports.reducers = {
+  filters,
   messages,
   prefs,
+  ui,
 };
--- a/devtools/client/webconsole/new-console-output/reducers/moz.build
+++ b/devtools/client/webconsole/new-console-output/reducers/moz.build
@@ -1,10 +1,12 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'filters.js',
     'index.js',
     'messages.js',
     'prefs.js',
+    'ui.js',
 )
--- a/devtools/client/webconsole/new-console-output/reducers/prefs.js
+++ b/devtools/client/webconsole/new-console-output/reducers/prefs.js
@@ -1,12 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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";
 
-function prefs(state = {}, action) {
+const {Prefs} = require("devtools/client/webconsole/new-console-output/store");
+
+function prefs(state = new Prefs(), action) {
   return state;
 }
 
 exports.prefs = prefs;
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/reducers/ui.js
@@ -0,0 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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 constants = require("devtools/client/webconsole/new-console-output/constants");
+const {Ui} = require("devtools/client/webconsole/new-console-output/store");
+
+function ui(state = new Ui(), action) {
+  switch (action.type) {
+    case constants.FILTERBAR_TOGGLE:
+      return state.set("configFilterBarVisible", !state.configFilterBarVisible);
+  }
+
+  return state;
+}
+
+exports.ui = ui;
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/filters.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+function getAllFilters(state) {
+  return state.filters;
+}
+
+exports.getAllFilters = getAllFilters;
--- a/devtools/client/webconsole/new-console-output/selectors/messages.js
+++ b/devtools/client/webconsole/new-console-output/selectors/messages.js
@@ -1,22 +1,55 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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 { getAllFilters } = require("devtools/client/webconsole/new-console-output/selectors/filters");
 const { getLogLimit } = require("devtools/client/webconsole/new-console-output/selectors/prefs");
 
 function getAllMessages(state) {
   let messages = state.messages;
+  let logLimit = getLogLimit(state);
+  let filters = getAllFilters(state);
+
+  return prune(
+    search(
+      filterSeverity(messages, filters),
+      filters.searchText
+    ),
+    logLimit
+  );
+}
+
+function filterSeverity(messages, filters) {
+  return messages.filter((message) => filters[message.severity] === true);
+}
+
+function search(messages, searchText = "") {
+  if (searchText === "") {
+    return messages;
+  }
+
+  return messages.filter(function (message) {
+    // @TODO: message.parameters can be a grip, see how we can handle that
+    if (!Array.isArray(message.parameters)) {
+      return true;
+    }
+    return message
+      .parameters.join("")
+      .toLocaleLowerCase()
+      .includes(searchText.toLocaleLowerCase());
+  });
+}
+
+function prune(messages, logLimit) {
   let messageCount = messages.count();
-  let logLimit = getLogLimit(state);
-
   if (messageCount > logLimit) {
     return messages.splice(0, messageCount - logLimit);
   }
 
   return messages;
 }
 
 exports.getAllMessages = getAllMessages;
--- a/devtools/client/webconsole/new-console-output/selectors/moz.build
+++ b/devtools/client/webconsole/new-console-output/selectors/moz.build
@@ -1,9 +1,11 @@
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
+    'filters.js',
     'messages.js',
     'prefs.js',
+    'ui.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/selectors/ui.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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";
+
+function getAllUi(state) {
+  return state.ui;
+}
+
+exports.getAllUi = getAllUi;
--- a/devtools/client/webconsole/new-console-output/store.js
+++ b/devtools/client/webconsole/new-console-output/store.js
@@ -1,24 +1,57 @@
 /* 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 Immutable = require("devtools/client/shared/vendor/immutable");
+const Services = require("Services");
+
+const Filters = Immutable.Record({
+  error: true,
+  warn: true,
+  info: true,
+  log: true,
+  searchText: ""
+});
+
+const Prefs = Immutable.Record({
+  logLimit: 1000
+});
+
+const Ui = Immutable.Record({
+  configFilterBarVisible: false,
+  filteredMessageVisible: false
+});
+
+module.exports.Prefs = Prefs;
+module.exports.Filters = Filters;
+module.exports.Ui = Ui;
+
 const { combineReducers, createStore } = require("devtools/client/shared/vendor/redux");
-const Immutable = require("devtools/client/shared/vendor/immutable");
 const { reducers } = require("./reducers/index");
-const Services = require("Services");
 
 function storeFactory() {
   const initialState = {
     messages: Immutable.List(),
-    prefs: {
-      logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1)
-    }
+    prefs: new Prefs({
+      logLimit: Math.max(Services.prefs.getIntPref("devtools.hud.loglimit"), 1),
+    }),
+    filters: new Filters({
+      error: Services.prefs.getBoolPref("devtools.webconsole.filter.error"),
+      warn: Services.prefs.getBoolPref("devtools.webconsole.filter.warn"),
+      info: Services.prefs.getBoolPref("devtools.webconsole.filter.info"),
+      log: Services.prefs.getBoolPref("devtools.webconsole.filter.log"),
+      searchText: ""
+    }),
+    ui: new Ui({
+      configFilterBarVisible: false,
+      filteredMessageVisible: false
+    })
   };
 
   return createStore(combineReducers(reducers), initialState);
 }
 
 // Provide the single store instance for app code.
 module.exports.store = storeFactory();
 // Provide the store factory for test code so that each test is working with
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -548,16 +548,19 @@ WebConsoleFrame.prototype = {
       // panel, but for now let's just hide it.
       this.experimentalOutputNode = this.outputNode.cloneNode();
       this.outputNode.hidden = true;
       this.outputNode.parentNode.appendChild(this.experimentalOutputNode);
       // @TODO Once the toolbox has been converted to React, see if passing
       // in JSTerm is still necessary.
       this.newConsoleOutput = new this.window.NewConsoleOutput(this.experimentalOutputNode, this.jsterm);
       console.log("Created newConsoleOutput", this.newConsoleOutput);
+
+      let filterToolbar = doc.querySelector(".hud-console-filter-toolbar");
+      filterToolbar.hidden = true;
     }
 
     this.resize();
     this.window.addEventListener("resize", this.resize, true);
     this.jsterm.on("sidebar-opened", this.resize);
     this.jsterm.on("sidebar-closed", this.resize);
 
     let toolbox = gDevTools.getToolbox(this.owner.target);
@@ -581,16 +584,23 @@ WebConsoleFrame.prototype = {
       }
 
       // Do not focus if a link was clicked
       if (event.target.nodeName.toLowerCase() === "a" ||
           event.target.parentNode.nodeName.toLowerCase() === "a") {
         return;
       }
 
+      // Do not focus if a search input was clicked on the new frontend
+      if (this.NEW_CONSOLE_OUTPUT_ENABLED &&
+          event.target.nodeName.toLowerCase() === "input" &&
+          event.target.getAttribute("type").toLowerCase() === "search") {
+        return;
+      }
+
       this.jsterm.focus();
     });
 
     // Toggle the timestamp on preference change
     gDevTools.on("pref-changed", this._onToolboxPrefChanged);
     this._onToolboxPrefChanged("pref-changed", {
       pref: PREF_MESSAGE_TIMESTAMP,
       newValue: Services.prefs.getBoolPref(PREF_MESSAGE_TIMESTAMP),