Bug 1005755 - Introduce pause/resume button; r=rickychien
☠☠ backed out by 8468f493cf23 ☠ ☠
authorJan Odvarko <odvarko@gmail.com>
Mon, 16 Oct 2017 12:08:10 +0200
changeset 386361 6f56f7ba748d86fda5e4d812358ab4dda3d6bf72
parent 386360 54bf60ede8e8797671c62c0195413a2faa43d3ae
child 386362 2bc7565e689063eb5308c3961c90b07a241096ab
push id32694
push userarchaeopteryx@coole-files.de
push dateTue, 17 Oct 2017 09:43:13 +0000
treeherdermozilla-central@3bd3448d9684 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrickychien
bugs1005755
milestone58.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 1005755 - Introduce pause/resume button; r=rickychien MozReview-Commit-ID: 3dRhufJweD3
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/src/actions/requests.js
devtools/client/netmonitor/src/assets/styles/netmonitor.css
devtools/client/netmonitor/src/components/toolbar.js
devtools/client/netmonitor/src/constants.js
devtools/client/netmonitor/src/middleware/moz.build
devtools/client/netmonitor/src/middleware/recording.js
devtools/client/netmonitor/src/reducers/requests.js
devtools/client/netmonitor/src/selectors/requests.js
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -643,16 +643,20 @@ netmonitor.toolbar.disableCache.label=Di
 # LOCALIZATION NOTE (netmonitor.toolbar.disableCache.tooltip): This is the tooltip
 # displayed for the checkbox for disabling browser cache.
 netmonitor.toolbar.disableCache.tooltip=Disable HTTP cache
 
 # 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.toolbar.toggleRecording): This is the label displayed
+# in the network toolbar for the toggle recording button.
+netmonitor.toolbar.toggleRecording=Pause/Resume recording network log
+
 # LOCALIZATION NOTE (netmonitor.toolbar.perf): This is the label displayed
 # in the network toolbar for the performance analysis button.
 netmonitor.toolbar.perf=Toggle performance analysis…
 
 # LOCALIZATION NOTE (netmonitor.toolbar.resetColumns): This is the label
 # displayed in the network table header context menu.
 netmonitor.toolbar.resetColumns=Reset Columns
 
--- a/devtools/client/netmonitor/src/actions/requests.js
+++ b/devtools/client/netmonitor/src/actions/requests.js
@@ -6,16 +6,17 @@
 
 const { sendHTTPRequest } = require("../connector/index");
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SEND_CUSTOM_REQUEST,
+  TOGGLE_RECORDING,
   UPDATE_REQUEST,
 } = require("../constants");
 const { getSelectedRequest } = require("../selectors/index");
 
 function addRequest(id, data, batch) {
   return {
     type: ADD_REQUEST,
     id,
@@ -87,16 +88,26 @@ function removeSelectedCustomRequest() {
 }
 
 function clearRequests() {
   return {
     type: CLEAR_REQUESTS
   };
 }
 
+/**
+ * Toggle monitoring
+ */
+function toggleRecording() {
+  return {
+    type: TOGGLE_RECORDING
+  };
+}
+
 module.exports = {
   addRequest,
   clearRequests,
   cloneSelectedRequest,
   removeSelectedCustomRequest,
   sendCustomRequest,
+  toggleRecording,
   updateRequest,
 };
--- a/devtools/client/netmonitor/src/assets/styles/netmonitor.css
+++ b/devtools/client/netmonitor/src/assets/styles/netmonitor.css
@@ -45,16 +45,22 @@
   --sort-descending-image: url(chrome://devtools/skin/images/sort-descending-arrow.svg);
 }
 
 :root.theme-firebug {
   --sort-ascending-image: url(chrome://devtools/skin/images/firebug/arrow-up.svg);
   --sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
 }
 
+/* Icons */
+:root {
+  --play-icon-url: url("chrome://devtools/skin/images/play.svg");
+  --pause-icon-url: url("chrome://devtools/skin/images/pause.svg");
+}
+
 /* General */
 
 * {
   box-sizing: border-box;
 }
 
 html,
 body,
@@ -92,16 +98,24 @@ body,
   align-items: center;
 }
 
 .requests-list-filter-buttons {
   display: flex;
   flex-wrap: wrap;
 }
 
+.devtools-button.devtools-pause-icon::before {
+  background-image: var(--pause-icon-url);
+}
+
+.devtools-button.devtools-play-icon::before {
+  background-image: var(--play-icon-url);
+}
+
 /* Learn more links */
 
 .learn-more-link::before {
   background-image: url(chrome://devtools/skin/images/help.svg);
 }
 
 .tree-container .treeTable tr .learn-more-link {
   position: absolute;
--- a/devtools/client/netmonitor/src/components/toolbar.js
+++ b/devtools/client/netmonitor/src/components/toolbar.js
@@ -11,93 +11,136 @@ const {
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
 const { FILTER_SEARCH_DELAY, FILTER_TAGS } = require("../constants");
 const {
   getDisplayedRequestsSummary,
+  getRecordingState,
   getRequestFilterTypes,
   getTypeFilteredRequests,
   isNetworkDetailsToggleButtonDisabled,
 } = require("../selectors/index");
 
 const { autocompleteProvider } = require("../utils/filter-autocomplete-provider");
 const { L10N } = require("../utils/l10n");
 
 // Components
 const SearchBox = createFactory(require("devtools/client/shared/components/SearchBox"));
 
 const { button, div, input, label, span } = DOM;
 
-const COLLPASE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
+// Localization
+const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
 const EXPAND_DETAILS_PANE = L10N.getStr("expandDetailsPane");
 const SEARCH_KEY_SHORTCUT = L10N.getStr("netmonitor.toolbar.filterFreetext.key");
 const SEARCH_PLACE_HOLDER = L10N.getStr("netmonitor.toolbar.filterFreetext.label");
 const TOOLBAR_CLEAR = L10N.getStr("netmonitor.toolbar.clear");
+const TOOLBAR_TOGGLE_RECORDING = L10N.getStr("netmonitor.toolbar.toggleRecording");
 
+// Preferences
 const DEVTOOLS_DISABLE_CACHE_PREF = "devtools.cache.disabled";
 const DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF = "devtools.netmonitor.persistlog";
 const TOOLBAR_FILTER_LABELS = FILTER_TAGS.concat("all").reduce((o, tag) =>
   Object.assign(o, { [tag]: L10N.getStr(`netmonitor.toolbar.filter.${tag}`) }), {});
 const ENABLE_PERSISTENT_LOGS_TOOLTIP =
   L10N.getStr("netmonitor.toolbar.enablePersistentLogs.tooltip");
 const ENABLE_PERSISTENT_LOGS_LABEL =
   L10N.getStr("netmonitor.toolbar.enablePersistentLogs.label");
 const DISABLE_CACHE_TOOLTIP = L10N.getStr("netmonitor.toolbar.disableCache.tooltip");
 const DISABLE_CACHE_LABEL = L10N.getStr("netmonitor.toolbar.disableCache.label");
 
-/*
- * Network monitor toolbar component
+/**
+ * Network monitor toolbar component.
+ *
  * Toolbar contains a set of useful tools to control network requests
+ * as well as set of filters for filtering the content.
  */
 const Toolbar = createClass({
   displayName: "Toolbar",
 
   propTypes: {
+    toggleRecording: PropTypes.func.isRequired,
+    recording: PropTypes.bool.isRequired,
     clearRequests: PropTypes.func.isRequired,
     requestFilterTypes: PropTypes.array.isRequired,
     setRequestFilterText: PropTypes.func.isRequired,
     networkDetailsToggleDisabled: PropTypes.bool.isRequired,
     networkDetailsOpen: PropTypes.bool.isRequired,
     toggleNetworkDetails: PropTypes.func.isRequired,
     enablePersistentLogs: PropTypes.func.isRequired,
     togglePersistentLogs: PropTypes.func.isRequired,
     persistentLogsEnabled: PropTypes.bool.isRequired,
     disableBrowserCache: PropTypes.func.isRequired,
     toggleBrowserCache: PropTypes.func.isRequired,
     browserCacheDisabled: PropTypes.bool.isRequired,
     toggleRequestFilterType: PropTypes.func.isRequired,
     filteredRequests: PropTypes.object.isRequired,
   },
 
+  componentDidMount() {
+    Services.prefs.addObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
+                               this.updatePersistentLogsEnabled);
+    Services.prefs.addObserver(DEVTOOLS_DISABLE_CACHE_PREF,
+                               this.updateBrowserCacheDisabled);
+  },
+
+  componentWillUnmount() {
+    Services.prefs.removeObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
+                                  this.updatePersistentLogsEnabled);
+    Services.prefs.removeObserver(DEVTOOLS_DISABLE_CACHE_PREF,
+                                  this.updateBrowserCacheDisabled);
+  },
+
+  toggleRequestFilterType(evt) {
+    if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
+      return;
+    }
+    this.props.toggleRequestFilterType(evt.target.dataset.key);
+  },
+
+  updatePersistentLogsEnabled() {
+    this.props.enablePersistentLogs(
+      Services.prefs.getBoolPref(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF));
+  },
+
+  updateBrowserCacheDisabled() {
+    this.props.disableBrowserCache(
+      Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
+  },
+
   render() {
     let {
+      toggleRecording,
       clearRequests,
       requestFilterTypes,
       setRequestFilterText,
       networkDetailsToggleDisabled,
       networkDetailsOpen,
       toggleNetworkDetails,
       togglePersistentLogs,
       persistentLogsEnabled,
       toggleBrowserCache,
       browserCacheDisabled,
       filteredRequests,
+      recording,
     } = this.props;
 
     let toggleButtonClassName = [
       "network-details-panel-toggle",
       "devtools-button",
     ];
+
     if (!networkDetailsOpen) {
       toggleButtonClassName.push("pane-collapsed");
     }
 
+    // Render list of filter-buttons.
     let buttons = requestFilterTypes.map(([type, checked]) => {
       let classList = ["devtools-button", `requests-list-filter-${type}-button`];
       checked && classList.push("checked");
 
       return (
         button({
           className: classList.join(" "),
           key: type,
@@ -106,20 +149,33 @@ const Toolbar = createClass({
           "aria-pressed": checked,
           "data-key": type,
         },
           TOOLBAR_FILTER_LABELS[type]
         )
       );
     });
 
+    // Calculate class-list for toggle recording button. The button
+    // has two states: pause/play.
+    let toggleButtonClassList = [
+      "devtools-button",
+      recording ? "devtools-pause-icon" : "devtools-play-icon",
+    ];
+
+    // Render the entire toolbar.
     return (
       span({ className: "devtools-toolbar devtools-toolbar-container" },
         span({ className: "devtools-toolbar-group" },
           button({
+            className: toggleButtonClassList.join(" "),
+            title: TOOLBAR_TOGGLE_RECORDING,
+            onClick: toggleRecording,
+          }),
+          button({
             className: "devtools-button devtools-clear-icon requests-list-clear-button",
             title: TOOLBAR_CLEAR,
             onClick: clearRequests,
           }),
           div({ className: "requests-list-filter-buttons" }, buttons),
           label(
             {
               className: "devtools-checkbox-label",
@@ -156,71 +212,42 @@ const Toolbar = createClass({
             placeholder: SEARCH_PLACE_HOLDER,
             type: "filter",
             onChange: setRequestFilterText,
             autocompleteProvider: filter =>
               autocompleteProvider(filter, filteredRequests),
           }),
           button({
             className: toggleButtonClassName.join(" "),
-            title: networkDetailsOpen ? COLLPASE_DETAILS_PANE : EXPAND_DETAILS_PANE,
+            title: networkDetailsOpen ? COLLAPSE_DETAILS_PANE : EXPAND_DETAILS_PANE,
             disabled: networkDetailsToggleDisabled,
             tabIndex: "0",
             onClick: toggleNetworkDetails,
           }),
         )
       )
     );
   },
-
-  componentDidMount() {
-    Services.prefs.addObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
-                               this.updatePersistentLogsEnabled);
-    Services.prefs.addObserver(DEVTOOLS_DISABLE_CACHE_PREF,
-                               this.updateBrowserCacheDisabled);
-  },
-
-  componentWillUnmount() {
-    Services.prefs.removeObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
-                                  this.updatePersistentLogsEnabled);
-    Services.prefs.removeObserver(DEVTOOLS_DISABLE_CACHE_PREF,
-                                  this.updateBrowserCacheDisabled);
-  },
-
-  toggleRequestFilterType(evt) {
-    if (evt.type === "keydown" && (evt.key !== "" || evt.key !== "Enter")) {
-      return;
-    }
-    this.props.toggleRequestFilterType(evt.target.dataset.key);
-  },
-
-  updatePersistentLogsEnabled() {
-    this.props.enablePersistentLogs(
-      Services.prefs.getBoolPref(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF));
-  },
-
-  updateBrowserCacheDisabled() {
-    this.props.disableBrowserCache(
-                        Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
-  }
 });
 
 module.exports = connect(
   (state) => ({
     networkDetailsToggleDisabled: isNetworkDetailsToggleButtonDisabled(state),
     networkDetailsOpen: state.ui.networkDetailsOpen,
     persistentLogsEnabled: state.ui.persistentLogsEnabled,
     browserCacheDisabled: state.ui.browserCacheDisabled,
+    recording: getRecordingState(state),
     requestFilterTypes: getRequestFilterTypes(state),
     filteredRequests: getTypeFilteredRequests(state),
     summary: getDisplayedRequestsSummary(state),
   }),
   (dispatch) => ({
     clearRequests: () => dispatch(Actions.clearRequests()),
-    setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
-    toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
-    toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
+    disableBrowserCache: (disabled) => dispatch(Actions.disableBrowserCache(disabled)),
     enablePersistentLogs: (enabled) => dispatch(Actions.enablePersistentLogs(enabled)),
+    setRequestFilterText: (text) => dispatch(Actions.setRequestFilterText(text)),
+    toggleBrowserCache: () => dispatch(Actions.toggleBrowserCache()),
+    toggleNetworkDetails: () => dispatch(Actions.toggleNetworkDetails()),
+    toggleRecording: () => dispatch(Actions.toggleRecording()),
     togglePersistentLogs: () => dispatch(Actions.togglePersistentLogs()),
-    disableBrowserCache: (disabled) => dispatch(Actions.disableBrowserCache(disabled)),
-    toggleBrowserCache: () => dispatch(Actions.toggleBrowserCache()),
+    toggleRequestFilterType: (type) => dispatch(Actions.toggleRequestFilterType(type)),
   }),
 )(Toolbar);
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -20,16 +20,17 @@ const actionTypes = {
   REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
   RESET_COLUMNS: "RESET_COLUMNS",
   SELECT_REQUEST: "SELECT_REQUEST",
   SELECT_DETAILS_PANEL_TAB: "SELECT_DETAILS_PANEL_TAB",
   SEND_CUSTOM_REQUEST: "SEND_CUSTOM_REQUEST",
   SET_REQUEST_FILTER_TEXT: "SET_REQUEST_FILTER_TEXT",
   SORT_BY: "SORT_BY",
   TOGGLE_COLUMN: "TOGGLE_COLUMN",
+  TOGGLE_RECORDING: "TOGGLE_RECORDING",
   TOGGLE_REQUEST_FILTER_TYPE: "TOGGLE_REQUEST_FILTER_TYPE",
   UPDATE_REQUEST: "UPDATE_REQUEST",
   WATERFALL_RESIZE: "WATERFALL_RESIZE",
 };
 
 // Descriptions for what this frontend is currently doing.
 const ACTIVITY_TYPE = {
   // Standing by and handling requests normally.
--- a/devtools/client/netmonitor/src/middleware/moz.build
+++ b/devtools/client/netmonitor/src/middleware/moz.build
@@ -1,9 +1,10 @@
 # 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(
     'batching.js',
     'prefs.js',
+    'recording.js',
     'thunk.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/middleware/recording.js
@@ -0,0 +1,24 @@
+/* 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 {
+  TOGGLE_RECORDING,
+} = require("../constants");
+
+/**
+ * Start/stop HTTP traffic recording.
+ */
+function recordingMiddleware(store) {
+  return next => action => {
+    const res = next(action);
+    if (action.type === TOGGLE_RECORDING) {
+      // TODO connect/disconnect the backend.
+    }
+    return res;
+  };
+}
+
+module.exports = recordingMiddleware;
--- a/devtools/client/netmonitor/src/reducers/requests.js
+++ b/devtools/client/netmonitor/src/reducers/requests.js
@@ -9,16 +9,17 @@ const { getUrlDetails } = require("../ut
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   OPEN_NETWORK_DETAILS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SELECT_REQUEST,
   SEND_CUSTOM_REQUEST,
+  TOGGLE_RECORDING,
   UPDATE_REQUEST,
   UPDATE_PROPS,
 } = require("../constants");
 
 const Request = I.Record({
   id: null,
   // Set to true in case of a request that's being edited as part of "edit and resend"
   isCustom: false,
@@ -63,16 +64,18 @@ const Requests = I.Record({
   // The collection of requests (keyed by id)
   requests: I.Map(),
   // Selection state
   selectedId: null,
   preselectedId: null,
   // Auxiliary fields to hold requests stats
   firstStartedMillis: +Infinity,
   lastEndedMillis: -Infinity,
+  // Recording state
+  recording: true,
 });
 
 /**
  * Remove the currently selected custom request.
  */
 function closeCustomRequest(state) {
   let { requests, selectedId } = state;
 
@@ -115,16 +118,74 @@ function requestsReducer(state = new Req
 
         // Select the request if it was preselected and there is no other selection
         if (st.preselectedId && st.preselectedId === action.id) {
           st.selectedId = st.selectedId || st.preselectedId;
           st.preselectedId = null;
         }
       });
     }
+    case CLEAR_REQUESTS: {
+      return new Requests({
+        recording: state.recording
+      });
+    }
+    case CLONE_SELECTED_REQUEST: {
+      let { requests, selectedId } = state;
+
+      if (!selectedId) {
+        return state;
+      }
+
+      let clonedRequest = requests.get(selectedId);
+      if (!clonedRequest) {
+        return state;
+      }
+
+      let newRequest = new Request({
+        id: clonedRequest.id + "-clone",
+        method: clonedRequest.method,
+        url: clonedRequest.url,
+        urlDetails: clonedRequest.urlDetails,
+        requestHeaders: clonedRequest.requestHeaders,
+        requestPostData: clonedRequest.requestPostData,
+        isCustom: true
+      });
+
+      return state.withMutations(st => {
+        st.requests = requests.set(newRequest.id, newRequest);
+        st.selectedId = newRequest.id;
+      });
+    }
+    case OPEN_NETWORK_DETAILS: {
+      if (!action.open) {
+        return state.set("selectedId", null);
+      }
+
+      if (!state.selectedId && !state.requests.isEmpty()) {
+        return state.set("selectedId", state.requests.first().id);
+      }
+
+      return state;
+    }
+    case REMOVE_SELECTED_CUSTOM_REQUEST: {
+      return closeCustomRequest(state);
+    }
+    case SELECT_REQUEST: {
+      return state.set("selectedId", action.id);
+    }
+    case SEND_CUSTOM_REQUEST: {
+      // When a new request with a given id is added in future, select it immediately.
+      // where we know in advance the ID of the request, at a time when it
+      // wasn't sent yet.
+      return closeCustomRequest(state.set("preselectedId", action.id));
+    }
+    case TOGGLE_RECORDING: {
+      return state.set("recording", !state.recording);
+    }
     case UPDATE_REQUEST: {
       let { requests, lastEndedMillis } = state;
 
       let updatedRequest = requests.get(action.id);
       if (!updatedRequest) {
         return state;
       }
 
@@ -155,69 +216,16 @@ function requestsReducer(state = new Req
         }
       });
 
       return state.withMutations(st => {
         st.requests = requests.set(updatedRequest.id, updatedRequest);
         st.lastEndedMillis = lastEndedMillis;
       });
     }
-    case CLEAR_REQUESTS: {
-      return new Requests();
-    }
-    case SELECT_REQUEST: {
-      return state.set("selectedId", action.id);
-    }
-    case CLONE_SELECTED_REQUEST: {
-      let { requests, selectedId } = state;
-
-      if (!selectedId) {
-        return state;
-      }
-
-      let clonedRequest = requests.get(selectedId);
-      if (!clonedRequest) {
-        return state;
-      }
-
-      let newRequest = new Request({
-        id: clonedRequest.id + "-clone",
-        method: clonedRequest.method,
-        url: clonedRequest.url,
-        urlDetails: clonedRequest.urlDetails,
-        requestHeaders: clonedRequest.requestHeaders,
-        requestPostData: clonedRequest.requestPostData,
-        isCustom: true
-      });
-
-      return state.withMutations(st => {
-        st.requests = requests.set(newRequest.id, newRequest);
-        st.selectedId = newRequest.id;
-      });
-    }
-    case REMOVE_SELECTED_CUSTOM_REQUEST: {
-      return closeCustomRequest(state);
-    }
-    case SEND_CUSTOM_REQUEST: {
-      // When a new request with a given id is added in future, select it immediately.
-      // where we know in advance the ID of the request, at a time when it
-      // wasn't sent yet.
-      return closeCustomRequest(state.set("preselectedId", action.id));
-    }
-    case OPEN_NETWORK_DETAILS: {
-      if (!action.open) {
-        return state.set("selectedId", null);
-      }
-
-      if (!state.selectedId && !state.requests.isEmpty()) {
-        return state.set("selectedId", state.requests.first().id);
-      }
-
-      return state;
-    }
 
     default:
       return state;
   }
 }
 
 module.exports = {
   Requests,
--- a/devtools/client/netmonitor/src/selectors/requests.js
+++ b/devtools/client/netmonitor/src/selectors/requests.js
@@ -122,17 +122,26 @@ const getSelectedRequest = createSelecto
 function getRequestById(state, id) {
   return state.requests.requests.get(id);
 }
 
 function getDisplayedRequestById(state, id) {
   return getDisplayedRequests(state).find(r => r.id === id);
 }
 
+/**
+ * Returns the current recording boolean state (HTTP traffic is
+ * monitored or not monitored)
+ */
+function getRecordingState(state) {
+  return state.requests.recording;
+}
+
 module.exports = {
   getDisplayedRequestById,
   getDisplayedRequests,
   getDisplayedRequestsSummary,
+  getRecordingState,
   getRequestById,
   getSelectedRequest,
   getSortedRequests,
   getTypeFilteredRequests,
 };