Bug 1005755 - Introduce pause/resume button; r=rickychien draft
authorJan Odvarko <odvarko@gmail.com>
Mon, 16 Oct 2017 14:42:13 +0200
changeset 680822 69bcf8ba90b96408bee68ddddb74c2b347110831
parent 680782 c6a2643362a67cdf7a87ac165454fce4b383debb
child 680823 f1392a9c909464d812e868dc3760e2f0d69fc8ae
push id84645
push userjodvarko@mozilla.com
push dateMon, 16 Oct 2017 12:45:59 +0000
reviewersrickychien
bugs1005755
milestone58.0a1
Bug 1005755 - Introduce pause/resume button; r=rickychien MozReview-Commit-ID: 3ZsW0y6LW7w
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,
 };