Bug 1309191 - Implement filter buttons for Net Panel Toolbar draft
authorFred Lin <gasolin@mozilla.com>
Fri, 21 Oct 2016 12:10:58 +0800
changeset 427949 c4be4446e569d13e53bb97eca99c3f4d73026112
parent 427890 1171f3ae792b91d7ca49ef0907b251e381f1df56
child 534611 92ac0a1e347c5c73b5a738ecaaf061f22c63c32e
push id33180
push userbmo:gasolin@mozilla.com
push dateFri, 21 Oct 2016 06:41:41 +0000
bugs1309191
milestone52.0a1
Bug 1309191 - Implement filter buttons for Net Panel Toolbar MozReview-Commit-ID: 56MUyFEK6Dq
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/actions/index.js
devtools/client/netmonitor/actions/moz.build
devtools/client/netmonitor/moz.build
devtools/client/netmonitor/netmonitor-controller.js
devtools/client/netmonitor/netmonitor-view.js
devtools/client/netmonitor/netmonitor.xul
devtools/client/netmonitor/reducers/index.js
devtools/client/netmonitor/reducers/moz.build
devtools/client/netmonitor/requests-menu-view.js
devtools/client/netmonitor/store.js
devtools/client/netmonitor/test/browser_net_icon-preview.js
devtools/client/netmonitor/test/browser_net_prefs-reload.js
devtools/client/netmonitor/test/head.js
devtools/client/themes/netmonitor.css
devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -427,75 +427,75 @@ netmonitor.tab.timings=Timings
 # LOCALIZATION NOTE (netmonitor.tab.preview): This is the label displayed
 # in the network details pane identifying the preview tab.
 netmonitor.tab.preview=Preview
 
 # LOCALIZATION NOTE (netmonitor.tab.security): This is the label displayed
 # in the network details pane identifying the security tab.
 netmonitor.tab.security=Security
 
-# LOCALIZATION NOTE (netmonitor.footer.filterAll): This is the label displayed
-# in the network details footer for the "All" filtering button.
-netmonitor.footer.filterAll=All
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.all): This is the label displayed
+# in the network toolbar for the "All" filtering button.
+netmonitor.toolbar.filter.all=All
 
-# LOCALIZATION NOTE (netmonitor.footer.filterHTML): This is the label displayed
-# in the network details footer for the "HTML" filtering button.
-netmonitor.footer.filterHTML=HTML
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.html): This is the label displayed
+# in the network toolbar for the "HTML" filtering button.
+netmonitor.toolbar.filter.html=HTML
 
-# LOCALIZATION NOTE (netmonitor.footer.filterCSS): This is the label displayed
-# in the network details footer for the "CSS" filtering button.
-netmonitor.footer.filterCSS=CSS
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.css): This is the label displayed
+# in the network toolbar for the "CSS" filtering button.
+netmonitor.toolbar.filter.css=CSS
 
-# LOCALIZATION NOTE (netmonitor.footer.filterJS): This is the label displayed
-# in the network details footer for the "JS" filtering button.
-netmonitor.footer.filterJS=JS
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.js): This is the label displayed
+# in the network toolbar for the "JS" filtering button.
+netmonitor.toolbar.filter.js=JS
 
-# LOCALIZATION NOTE (netmonitor.footer.filterXHR): This is the label displayed
-# in the network details footer for the "XHR" filtering button.
-netmonitor.footer.filterXHR=XHR
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.xhr): This is the label displayed
+# in the network toolbar for the "XHR" filtering button.
+netmonitor.toolbar.filter.xhr=XHR
 
-# LOCALIZATION NOTE (netmonitor.footer.filterFonts): This is the label displayed
-# in the network details footer for the "Fonts" filtering button.
-netmonitor.footer.filterFonts=Fonts
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.fonts): This is the label displayed
+# in the network toolbar for the "Fonts" filtering button.
+netmonitor.toolbar.filter.fonts=Fonts
 
-# LOCALIZATION NOTE (netmonitor.footer.filterImages): This is the label displayed
-# in the network details footer for the "Images" filtering button.
-netmonitor.footer.filterImages=Images
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.images): This is the label displayed
+# in the network toolbar for the "Images" filtering button.
+netmonitor.toolbar.filter.images=Images
 
-# LOCALIZATION NOTE (netmonitor.footer.filterMedia): This is the label displayed
-# in the network details footer for the "Media" filtering button.
-netmonitor.footer.filterMedia=Media
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.media): This is the label displayed
+# in the network toolbar for the "Media" filtering button.
+netmonitor.toolbar.filter.media=Media
 
-# LOCALIZATION NOTE (netmonitor.footer.filterFlash): This is the label displayed
-# in the network details footer for the "Flash" filtering button.
-netmonitor.footer.filterFlash=Flash
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.flash): This is the label displayed
+# in the network toolbar for the "Flash" filtering button.
+netmonitor.toolbar.filter.flash=Flash
 
-# LOCALIZATION NOTE (netmonitor.footer.filterWS): This is the label displayed
-# in the network details footer for the "WS" filtering button.
-netmonitor.footer.filterWS=WS
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.ws): This is the label displayed
+# in the network toolbar for the "WS" filtering button.
+netmonitor.toolbar.filter.ws=WS
 
-# LOCALIZATION NOTE (netmonitor.footer.filterOther): This is the label displayed
-# in the network details footer for the "Other" filtering button.
-netmonitor.footer.filterOther=Other
+# LOCALIZATION NOTE (netmonitor.toolbar.filter.other): This is the label displayed
+# in the network toolbar for the "Other" filtering button.
+netmonitor.toolbar.filter.other=Other
 
-# LOCALIZATION NOTE (netmonitor.footer.filterFreetext): This is the label displayed
-# in the network details footer for the url filtering textbox.
-netmonitor.footer.filterFreetext.label=Filter URLs
+# LOCALIZATION NOTE (netmonitor.toolbar.filterFreetext): This is the label displayed
+# in the network toolbar for the url filtering textbox.
+netmonitor.toolbar.filterFreetext.label=Filter URLs
 
 # LOCALIZATION NOTE (netmonitor.toolbar.filterFreetext.key): This is the
 # shortcut key to focus on the toolbar url filtering textbox
 netmonitor.toolbar.filterFreetext.key=CmdOrCtrl+F
 
-# LOCALIZATION NOTE (netmonitor.footer.clear): This is the label displayed
-# in the network details footer for the "Clear" button.
-netmonitor.footer.clear=Clear
+# LOCALIZATION NOTE (netmonitor.toolbar.clear): This is the label displayed
+# in the network toolbar for the "Clear" button.
+netmonitor.toolbar.clear=Clear
 
-# LOCALIZATION NOTE (netmonitor.footer.clear): This is the label displayed
-# in the network details footer for the performance analysis button.
-netmonitor.footer.perf=Toggle performance analysis…
+# LOCALIZATION NOTE (netmonitor.toolbar.clear): This is the label displayed
+# in the network toolbar for the performance analysis button.
+netmonitor.toolbar.perf=Toggle performance analysis…
 
 # LOCALIZATION NOTE (netmonitor.panesButton.tooltip): This is the tooltip for
 # the button that toggles the panes visible or hidden in the netmonitor UI.
 netmonitor.panesButton.tooltip=Toggle network info
 
 # LOCALIZATION NOTE (netmonitor.summary.url): This is the label displayed
 # in the network details headers tab identifying the URL.
 netmonitor.summary.url=Request URL:
--- a/devtools/client/netmonitor/actions/index.js
+++ b/devtools/client/netmonitor/actions/index.js
@@ -1,8 +1,8 @@
 /* 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";
 
-module.exports = {
-  // actions...
-};
+const filters = require("./filters");
+
+module.exports = Object.assign({}, filters);
--- a/devtools/client/netmonitor/actions/moz.build
+++ b/devtools/client/netmonitor/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(
+    'filters.js',
     'index.js'
 )
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -1,25 +1,28 @@
 # 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/.
 
 DIRS += [
     'actions',
+    'components',
     'har',
     'reducers',
     'selectors'
 ]
 
 DevToolsModules(
+    'constants.js',
     'events.js',
     'filter-predicates.js',
     'l10n.js',
     'panel.js',
     'prefs.js',
     'request-utils.js',
     'requests-menu-view.js',
     'sort-predicates.js',
-    'store.js'
+    'store.js',
+    'toolbar-view.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -1,14 +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/. */
-/* globals window, document, NetMonitorView */
+/* globals window, document, NetMonitorView, gStore, Actions */
 /* exported loader */
 "use strict";
 
 var { utils: Cu } = Components;
 
 // Descriptions for what this frontend is currently doing.
 const ACTIVITY_TYPE = {
   // Standing by and handling requests normally.
@@ -293,17 +293,17 @@ var NetMonitorController = {
     // the network monitor is still loading.
     let deferred = promise.defer();
     let request = null;
     let inspector = function () {
       let predicate = i => i.value === requestId;
       request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
       if (!request) {
         // Reset filters so that the request is visible.
-        NetMonitorView.RequestsMenu.filterOn("all");
+        gStore.dispatch(Actions.toggleFilter("all"));
         request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
       }
 
       // If the request was found, select it. Otherwise this function will be
       // called again once new requests arrive.
       if (request) {
         window.off(EVENTS.REQUEST_ADDED, inspector);
         NetMonitorView.RequestsMenu.selectedItem = request;
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -23,16 +23,22 @@ const {PluralForm} = require("devtools/s
 const {Filters} = require("./filter-predicates");
 const {getFormDataSections,
        formDataURI,
        writeHeaderText,
        getKeyWithEvent,
        getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
 const {RequestsMenuView} = require("./requests-menu-view");
+const {ToolbarView} = require("./toolbar-view");
+const {configureStore} = require("./store");
+const Actions = require("./actions/index");
+
+// Initialize the global redux variables
+var gStore = configureStore();
 
 // ms
 const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
 // be at least equal to the general mochitest timeout of 45 seconds so that this
 // never gets hit during testing.
@@ -83,18 +89,18 @@ const NETWORK_ANALYSIS_PIE_CHART_DIAMETE
  */
 var NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function () {
     this._initializePanes();
 
-    this.Toolbar.initialize();
-    this.RequestsMenu.initialize();
+    this.Toolbar.initialize(gStore);
+    this.RequestsMenu.initialize(gStore);
     this.NetworkDetails.initialize();
     this.CustomRequest.initialize();
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function () {
@@ -290,66 +296,16 @@ var NetMonitorView = {
   _detailsPane: null,
   _detailsPaneToggleButton: null,
   _collapsePaneString: "",
   _expandPaneString: "",
   _editorPromises: new Map()
 };
 
 /**
- * Functions handling the toolbar view: expand/collapse button etc.
- */
-function ToolbarView() {
-  dumpn("ToolbarView was instantiated");
-
-  this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
-}
-
-ToolbarView.prototype = {
-  /**
-   * Initialization function, called when the debugger is started.
-   */
-  initialize: function () {
-    dumpn("Initializing the ToolbarView");
-
-    this._detailsPaneToggleButton = $("#details-pane-toggle");
-    this._detailsPaneToggleButton.addEventListener("mousedown",
-      this._onTogglePanesPressed, false);
-  },
-
-  /**
-   * Destruction function, called when the debugger is closed.
-   */
-  destroy: function () {
-    dumpn("Destroying the ToolbarView");
-
-    this._detailsPaneToggleButton.removeEventListener("mousedown",
-      this._onTogglePanesPressed, false);
-  },
-
-  /**
-   * Listener handling the toggle button click event.
-   */
-  _onTogglePanesPressed: function () {
-    let requestsMenu = NetMonitorView.RequestsMenu;
-    let selectedIndex = requestsMenu.selectedIndex;
-
-    // Make sure there's a selection if the button is pressed, to avoid
-    // showing an empty network details pane.
-    if (selectedIndex == -1 && requestsMenu.itemCount) {
-      requestsMenu.selectedIndex = 0;
-    } else {
-      requestsMenu.selectedIndex = -1;
-    }
-  },
-
-  _detailsPaneToggleButton: null
-};
-
-/**
  * Functions handling the sidebar details view.
  */
 function SidebarView() {
   dumpn("SidebarView was instantiated");
 }
 
 SidebarView.prototype = {
   /**
@@ -1486,17 +1442,18 @@ PerformanceStatisticsView.prototype = {
       title: L10N.getStr(title),
       data: data,
       strings: strings,
       totals: totals,
       sorted: sorted
     });
 
     chart.on("click", (_, item) => {
-      NetMonitorView.RequestsMenu.filterOnlyOn(item.label);
+      // Reset FilterButtons and enable one filter exclusively
+      gStore.dispatch(Actions.enableFilterOnly(item.label));
       NetMonitorView.showNetworkInspectorView();
     });
 
     container.appendChild(chart.node);
   },
 
   /**
    * Sanitizes the data source used for creating charts, to follow the
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -59,85 +59,29 @@
   </popupset>
 
   <deck id="body" class="theme-sidebar" flex="1">
 
     <vbox id="network-inspector-view" flex="1">
       <hbox id="netmonitor-toolbar" class="devtools-toolbar">
         <toolbarbutton id="requests-menu-clear-button"
                        class="devtools-toolbarbutton devtools-clear-icon"
-                       data-localization="tooltiptext=netmonitor.footer.clear"/>
-        <hbox id="requests-menu-filter-buttons">
-          <button id="requests-menu-filter-all-button"
-                  class="requests-menu-filter-button"
-                  checked="true"
-                  data-key="all"
-                  data-localization="label=netmonitor.footer.filterAll">
-          </button>
-          <button id="requests-menu-filter-html-button"
-                  class="requests-menu-filter-button"
-                  data-key="html"
-                  data-localization="label=netmonitor.footer.filterHTML">
-          </button>
-          <button id="requests-menu-filter-css-button"
-                  class="requests-menu-filter-button"
-                  data-key="css"
-                  data-localization="label=netmonitor.footer.filterCSS">
-          </button>
-          <button id="requests-menu-filter-js-button"
-                  class="requests-menu-filter-button"
-                  data-key="js"
-                  data-localization="label=netmonitor.footer.filterJS">
-          </button>
-          <button id="requests-menu-filter-xhr-button"
-                  class="requests-menu-filter-button"
-                  data-key="xhr"
-                  data-localization="label=netmonitor.footer.filterXHR">
-          </button>
-          <button id="requests-menu-filter-fonts-button"
-                  class="requests-menu-filter-button"
-                  data-key="fonts"
-                  data-localization="label=netmonitor.footer.filterFonts">
-          </button>
-          <button id="requests-menu-filter-images-button"
-                  class="requests-menu-filter-button"
-                  data-key="images"
-                  data-localization="label=netmonitor.footer.filterImages">
-          </button>
-          <button id="requests-menu-filter-media-button"
-                  class="requests-menu-filter-button"
-                  data-key="media"
-                  data-localization="label=netmonitor.footer.filterMedia">
-          </button>
-          <button id="requests-menu-filter-flash-button"
-                  class="requests-menu-filter-button"
-                  data-key="flash"
-                  data-localization="label=netmonitor.footer.filterFlash">
-          </button>
-          <button id="requests-menu-filter-ws-button"
-                  class="requests-menu-filter-button"
-                  data-key="ws"
-                  data-localization="label=netmonitor.footer.filterWS">
-          </button>
-          <button id="requests-menu-filter-other-button"
-                  class="requests-menu-filter-button"
-                  data-key="other"
-                  data-localization="label=netmonitor.footer.filterOther">
-          </button>
-        </hbox>
+                       data-localization="tooltiptext=netmonitor.toolbar.clear"/>
+        <html:div xmlns="http://www.w3.org/1999/xhtml"
+                  id="react-filter-buttons-hook"/>
         <spacer id="requests-menu-spacer"
                 flex="1"/>
         <toolbarbutton id="requests-menu-network-summary-button"
                        class="devtools-toolbarbutton icon-and-text"
-                       data-localization="tooltiptext=netmonitor.footer.perf"/>
+                       data-localization="tooltiptext=netmonitor.toolbar.perf"/>
         <textbox id="requests-menu-filter-freetext-text"
                  class="devtools-filterinput"
                  type="search"
                  required="true"
-                 data-localization="placeholder=netmonitor.footer.filterFreetext.label"/>
+                 data-localization="placeholder=netmonitor.toolbar.filterFreetext.label"/>
         <toolbarbutton id="details-pane-toggle"
                        class="devtools-toolbarbutton"
                        data-localization="tooltiptext=netmonitor.panesButton.tooltip"
                        disabled="true"
                        tabindex="0"/>
       </hbox>
       <hbox id="network-table-and-sidebar"
             class="devtools-responsive-container"
--- a/devtools/client/netmonitor/reducers/index.js
+++ b/devtools/client/netmonitor/reducers/index.js
@@ -1,10 +1,11 @@
 /* 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 { combineReducers } = require("devtools/client/shared/vendor/redux");
+const filters = require("./filters");
 
 module.exports = combineReducers({
-  // reducers...
+  filters,
 });
--- a/devtools/client/netmonitor/reducers/moz.build
+++ b/devtools/client/netmonitor/reducers/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(
+    'filters.js',
     'index.js'
 )
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -23,16 +23,17 @@ const {getFormDataSections,
        formDataURI,
        writeHeaderText,
        getKeyWithEvent,
        getAbbreviatedMimeType,
        getUriNameWithQuery,
        getUriHostPort,
        getUriHost,
        loadCauseString} = require("./request-utils");
+const Actions = require("./actions/index");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
   "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
 
 loader.lazyRequireGetter(this, "HarExporter",
   "devtools/client/netmonitor/har/har-exporter", true);
 
 loader.lazyRequireGetter(this, "NetworkHelper",
@@ -82,16 +83,29 @@ const REQUEST_TIME_DECIMALS = 2;
 const CONTENT_SIZE_DECIMALS = 2;
 
 const CONTENT_MIME_TYPE_ABBREVIATIONS = {
   "ecmascript": "js",
   "javascript": "js",
   "x-javascript": "js"
 };
 
+// A smart store watcher to notify store changes as necessary
+function storeWatcher(initialValue, reduceValue, onChange) {
+  let currentValue = initialValue;
+
+  return () => {
+    const newValue = reduceValue(currentValue);
+    if (newValue !== currentValue) {
+      onChange(newValue, currentValue);
+      currentValue = newValue;
+    }
+  };
+}
+
 /**
  * Functions handling the requests menu (containing details about each request,
  * like status, method, file, domain, as well as a waterfall representing
  * timing imformation).
  */
 function RequestsMenuView() {
   dumpn("RequestsMenuView was instantiated");
 
@@ -103,17 +117,17 @@ function RequestsMenuView() {
   this._onScroll = this._onScroll.bind(this);
   this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
 }
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
-  initialize: function () {
+  initialize: function (store) {
     dumpn("Initializing the RequestsMenuView");
 
     let widgetParentEl = $("#requests-menu-contents");
     this.widget = new SideMenuWidget(widgetParentEl);
     this._splitter = $("#network-inspector-view-splitter");
     this._summary = $("#requests-menu-network-summary-button");
     this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
     this.userInputTimer = Cc["@mozilla.org/timer;1"]
@@ -122,32 +136,28 @@ RequestsMenuView.prototype = Heritage.ex
     // Create a tooltip for the newly appended network request item.
     this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
     this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true
     });
     $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
 
-    Prefs.filters.forEach(type => this.filterOn(type));
     this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
 
     this.allowFocusOnRightClick = true;
     this.maintainSelectionVisible = true;
 
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("swap", this._onSwap, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
 
     this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
     this.requestsMenuSortKeyboardEvent = getKeyWithEvent(this.sortBy.bind(this), true);
-    this.requestsMenuFilterEvent = getKeyWithEvent(this.filterOn.bind(this));
-    this.requestsMenuFilterKeyboardEvent = getKeyWithEvent(
-      this.filterOn.bind(this), true);
     this.reqeustsMenuClearEvent = this.clear.bind(this);
     this._onContextShowing = this._onContextShowing.bind(this);
     this._onContextNewTabCommand = this.openRequestInTab.bind(this);
     this._onContextCopyUrlCommand = this.copyUrl.bind(this);
     this._onContextCopyImageAsDataUriCommand =
       this.copyImageAsDataUri.bind(this);
     this._onContextCopyResponseCommand = this.copyResponse.bind(this);
     this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
@@ -171,35 +181,46 @@ RequestsMenuView.prototype = Heritage.ex
       this.requestsFreetextFilterEvent, false);
     this.freetextFilterBox.addEventListener("command",
       this.requestsFreetextFilterEvent, false);
 
     $("#toolbar-labels").addEventListener("click",
       this.requestsMenuSortEvent, false);
     $("#toolbar-labels").addEventListener("keydown",
       this.requestsMenuSortKeyboardEvent, false);
-    $("#requests-menu-filter-buttons").addEventListener("click",
-      this.requestsMenuFilterEvent, false);
-    $("#requests-menu-filter-buttons").addEventListener("keydown",
-      this.requestsMenuFilterKeyboardEvent, false);
     $("#requests-menu-clear-button").addEventListener("click",
       this.reqeustsMenuClearEvent, false);
     $("#network-request-popup").addEventListener("popupshowing",
       this._onContextShowing, false);
     $("#request-menu-context-newtab").addEventListener("command",
       this._onContextNewTabCommand, false);
     $("#request-menu-context-copy-url").addEventListener("command",
       this._onContextCopyUrlCommand, false);
     $("#request-menu-context-copy-response").addEventListener("command",
       this._onContextCopyResponseCommand, false);
     $("#request-menu-context-copy-image-as-data-uri").addEventListener(
       "command", this._onContextCopyImageAsDataUriCommand, false);
     $("#toggle-raw-headers").addEventListener("click",
       this.toggleRawHeadersEvent, false);
 
+    this.unsubscribeStore = store.subscribe(storeWatcher(
+      null,
+      () => store.getState().filters.types,
+      (newTypes) => {
+        this._activeFilters = newTypes
+          .toSeq()
+          .filter((checked, key) => checked)
+          .keySeq()
+          .toArray();
+        this.reFilterRequests();
+      }
+    ));
+
+    Prefs.filters.forEach(type => store.dispatch(Actions.toggleFilter(type)));
+
     window.once("connected", this._onConnect.bind(this));
   },
 
   _onConnect: function () {
     $("#requests-menu-reload-notice-button").addEventListener("command",
       this._onReloadCommand, false);
 
     if (NetMonitorController.supportsCustomRequest) {
@@ -255,20 +276,16 @@ RequestsMenuView.prototype = Heritage.ex
     this.widget.removeEventListener("swap", this._onSwap, false);
     this._splitter.removeEventListener("mousemove", this._onResize, false);
     window.removeEventListener("resize", this._onResize, false);
 
     $("#toolbar-labels").removeEventListener("click",
       this.requestsMenuSortEvent, false);
     $("#toolbar-labels").removeEventListener("keydown",
       this.requestsMenuSortKeyboardEvent, false);
-    $("#requests-menu-filter-buttons").removeEventListener("click",
-      this.requestsMenuFilterEvent, false);
-    $("#requests-menu-filter-buttons").removeEventListener("keydown",
-      this.requestsMenuFilterKeyboardEvent, false);
     $("#requests-menu-clear-button").removeEventListener("click",
       this.reqeustsMenuClearEvent, false);
     this.freetextFilterBox.removeEventListener("input",
       this.requestsFreetextFilterEvent, false);
     this.freetextFilterBox.removeEventListener("command",
       this.requestsFreetextFilterEvent, false);
 
     this.userInputTimer.cancel();
@@ -301,16 +318,18 @@ RequestsMenuView.prototype = Heritage.ex
     $("#custom-request-send-button").removeEventListener("click",
       this.sendCustomRequestEvent, false);
     $("#custom-request-close-button").removeEventListener("click",
       this.closeCustomRequestEvent, false);
     $("#headers-summary-resend").removeEventListener("click",
       this.cloneSelectedRequestEvent, false);
     $("#toggle-raw-headers").removeEventListener("click",
       this.toggleRawHeadersEvent, false);
+
+    this.unsubscribeStore();
   },
 
   /**
    * Resets this container (removes all the networking information).
    */
   reset: function () {
     this.empty();
     this._addQueue = [];
@@ -648,107 +667,16 @@ RequestsMenuView.prototype = Heritage.ex
    */
   reFilterRequests: function () {
     this.filterContents(this._filterPredicate);
     this.refreshSummary();
     this.refreshZebra();
   },
 
   /**
-   * Filters all network requests in this container by a specified type.
-   *
-   * @param string type
-   *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
-   *        "flash", "ws" or "other".
-   */
-  filterOn: function (type = "all") {
-    if (type === "all") {
-      // The filter "all" is special as it doesn't toggle.
-      // - If some filters are selected and 'all' is clicked, the previously
-      //   selected filters will be disabled and 'all' is the only active one.
-      // - If 'all' is already selected, do nothing.
-      if (this._activeFilters.indexOf("all") !== -1) {
-        return;
-      }
-
-      // Uncheck all other filters and select 'all'. Must create a copy as
-      // _disableFilter removes the filters from the list while it's being
-      // iterated. 'all' will be enabled automatically by _disableFilter once
-      // the last filter is disabled.
-      this._activeFilters.slice().forEach(this._disableFilter, this);
-    } else if (this._activeFilters.indexOf(type) === -1) {
-      this._enableFilter(type);
-    } else {
-      this._disableFilter(type);
-    }
-
-    this.reFilterRequests();
-  },
-
-  /**
-   * Same as `filterOn`, except that it only allows a single type exclusively.
-   *
-   * @param string type
-   *        @see RequestsMenuView.prototype.fitlerOn
-   */
-  filterOnlyOn: function (type = "all") {
-    this._activeFilters.slice().forEach(this._disableFilter, this);
-    this.filterOn(type);
-  },
-
-  /**
-   * Disables the given filter, its button and toggles 'all' on if the filter to
-   * be disabled is the last one active.
-   *
-   * @param string type
-   *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
-   *        "flash", "ws" or "other".
-   */
-  _disableFilter: function (type) {
-    // Remove the filter from list of active filters.
-    this._activeFilters.splice(this._activeFilters.indexOf(type), 1);
-
-    // Remove the checked status from the filter.
-    let target = $("#requests-menu-filter-" + type + "-button");
-    target.removeAttribute("checked");
-
-    // Check if the filter disabled was the last one. If so, toggle all on.
-    if (this._activeFilters.length === 0) {
-      this._enableFilter("all");
-    }
-  },
-
-  /**
-   * Enables the given filter, its button and toggles 'all' off if the filter to
-   * be enabled is the first one active.
-   *
-   * @param string type
-   *        Either "all", "html", "css", "js", "xhr", "fonts", "images", "media"
-   *        "flash", "ws" or "other".
-   */
-  _enableFilter: function (type) {
-    // Make sure this is a valid filter type.
-    if (!Object.keys(Filters).includes(type)) {
-      return;
-    }
-
-    // Add the filter to the list of active filters.
-    this._activeFilters.push(type);
-
-    // Add the checked status to the filter button.
-    let target = $("#requests-menu-filter-" + type + "-button");
-    target.setAttribute("checked", true);
-
-    // Check if 'all' was selected before. If so, disable it.
-    if (type !== "all" && this._activeFilters.indexOf("all") !== -1) {
-      this._disableFilter("all");
-    }
-  },
-
-  /**
    * Returns a predicate that can be used to test if a request matches any of
    * the active filters.
    */
   get _filterPredicate() {
     let currentFreetextFilter = this._currentFreetextFilter;
 
     return requestItem => {
       const { attachment } = requestItem;
--- a/devtools/client/netmonitor/store.js
+++ b/devtools/client/netmonitor/store.js
@@ -1,20 +1,13 @@
 /* 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 createStore = require("devtools/client/shared/redux/create-store");
-const reducers = require("devtools/client/netmonitor/reducers/index");
+const reducers = require("./reducers/index");
 
 function configureStore() {
-  const initialState = {
-      // All initial application states will be defined as store from here
-  };
-
-  return createStore()(
-    reducers,
-    initialState
-  );
+  return createStore()(reducers);
 }
 
 exports.configureStore = configureStore;
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -3,35 +3,37 @@
 
 "use strict";
 
 /**
  * Tests if image responses show a thumbnail in the requests menu.
  */
 
 add_task(function* () {
+  let Actions = require("devtools/client/netmonitor/actions/index");
+
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
-  let { $, $all, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
-    monitor.panelWin;
+  let { $, $all, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController,
+        gStore } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   let wait = waitForEvents();
   yield performRequests();
   yield wait;
 
   info("Checking the image thumbnail when all items are shown.");
   checkImageThumbnail();
 
   RequestsMenu.sortBy("size");
   info("Checking the image thumbnail when all items are sorted.");
   checkImageThumbnail();
 
-  RequestsMenu.filterOn("images");
+  gStore.dispatch(Actions.toggleFilter("images"));
   info("Checking the image thumbnail when only images are shown.");
   checkImageThumbnail();
 
   info("Reloading the debuggee and performing all requests again...");
   wait = waitForEvents();
   yield reloadAndPerformRequests();
   yield wait;
 
--- a/devtools/client/netmonitor/test/browser_net_prefs-reload.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -3,38 +3,41 @@
 
 "use strict";
 
 /**
  * Tests if the prefs that should survive across tool reloads work.
  */
 
 add_task(function* () {
+  let Actions = require("devtools/client/netmonitor/actions/index");
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   // This test reopens the network monitor a bunch of times, for different
   // hosts (bottom, side, window). This seems to be slow on debug builds.
   requestLongerTimeout(3);
 
   // Use these getters instead of caching instances inside the panel win,
   // since the tool is reopened a bunch of times during this test
   // and the instances will differ.
   let getView = () => monitor.panelWin.NetMonitorView;
+  let getStore = () => monitor.panelWin.gStore;
 
   let prefsToCheck = {
     filters: {
       // A custom new value to be used for the verified preference.
       newValue: ["html", "css"],
       // Getter used to retrieve the current value from the frontend, in order
       // to verify that the pref was applied properly.
       validateValue: ($) => getView().RequestsMenu._activeFilters,
       // Predicate used to modify the frontend when setting the new pref value,
       // before trying to validate the changes.
-      modifyFrontend: ($, value) => value.forEach(e => getView().RequestsMenu.filterOn(e))
+      modifyFrontend: ($, value) => value.forEach(e =>
+        getStore().dispatch(Actions.toggleFilter(e)))
     },
     networkDetailsWidth: {
       newValue: ~~(Math.random() * 200 + 100),
       validateValue: ($) => ~~$("#details-pane").getAttribute("width"),
       modifyFrontend: ($, value) => $("#details-pane").setAttribute("width", value)
     },
     networkDetailsHeight: {
       newValue: ~~(Math.random() * 300 + 100),
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -399,25 +399,25 @@ function testFilterButtons(aMonitor, aFi
  *
  * @param array aIsChecked
  *        An array specifying if a button at given index should have a
  *        'checked' attribute. For example, if the third item of the array
  *        evaluates to true, the third button should be checked.
  */
 function testFilterButtonsCustom(aMonitor, aIsChecked) {
   let doc = aMonitor.panelWin.document;
-  let buttons = doc.querySelectorAll(".requests-menu-filter-button");
+  let buttons = doc.querySelectorAll(".menu-filter-button");
   for (let i = 0; i < aIsChecked.length; i++) {
     let button = buttons[i];
     if (aIsChecked[i]) {
-      is(button.hasAttribute("checked"), true,
-        "The " + button.id + " button should have a 'checked' attribute.");
+      is(button.classList.contains("checked"), true,
+        "The " + button.id + " button should have a 'checked' class.");
     } else {
-      is(button.hasAttribute("checked"), false,
-        "The " + button.id + " button should not have a 'checked' attribute.");
+      is(button.classList.contains("checked"), false,
+        "The " + button.id + " button should not have a 'checked' class.");
     }
   }
 }
 
 /**
  * Loads shared/frame-script-utils.js in the specified tab.
  *
  * @param tab
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -736,41 +736,16 @@
 .custom-section {
   margin-top: 0.5em;
 }
 
 #custom-method-value {
   width: 4.5em;
 }
 
-/* Main toolbar */
-.requests-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);
-}
-
-.requests-menu-filter-button:hover {
-  background: rgba(128,128,128,0.2);
-}
-
-.requests-menu-filter-button:hover:active {
-  background-color: var(--theme-selection-background-semitransparent);
-}
-
-.requests-menu-filter-button:not(:active)[checked] {
-  background-color: var(--theme-selection-background);
-  color: var(--theme-selection-color);
-}
-
 /* Performance analysis buttons */
 
 #requests-menu-network-summary-button {
   background: none;
   box-shadow: none;
   border-color: transparent;
   list-style-image: url(images/profiler-stopwatch.svg);
   padding-inline-end: 0;
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
@@ -11,16 +11,18 @@
 const TEST_FILE_URI =
   "http://example.com/browser/devtools/client/webconsole/test/" +
   "test-network.html";
 const TEST_URI = "data:text/html;charset=utf8,<p>test file URI";
 
 var hud;
 
 add_task(function* () {
+  let Actions = require("devtools/client/netmonitor/actions/index");
+
   let requests = [];
   let { browser } = yield loadTab(TEST_URI);
 
   yield pushPrefEnv();
   hud = yield openConsole();
   hud.jsterm.clearOutput();
 
   HUDService.lastFinishedRequest.callback = request => requests.push(request);
@@ -40,17 +42,17 @@ add_task(function* () {
   let panel = toolbox.getCurrentPanel();
   let selected = panel.panelWin.NetMonitorView.RequestsMenu.selectedItem;
   is(selected.attachment.method, htmlRequest.request.method,
      "The correct request is selected");
   is(selected.attachment.url, htmlRequest.request.url,
      "The correct request is definitely selected");
 
   // Filter out the HTML request.
-  panel.panelWin.NetMonitorView.RequestsMenu.filterOn("js");
+  panel.panelWin.gStore.dispatch(Actions.toggleFilter("js"));
 
   yield toolbox.selectTool("webconsole");
   is(toolbox.currentToolId, "webconsole", "Web console was selected");
   yield hud.ui.openNetworkPanel(htmlRequest.actor);
 
   panel.panelWin.NetMonitorView.RequestsMenu.selectedItem;
   is(selected.attachment.method, htmlRequest.request.method,
      "The correct request is selected");