Bug 1336383 - Implement RequestsList component r?honza draft
authorRicky Chien <rchien@mozilla.com>
Tue, 31 Jan 2017 12:19:31 +0800
changeset 482169 85dc8b42bd01db69313666145db10047ceee0c55
parent 481821 b9c6246f13ead0159d8a262959fae0efcbbca407
child 545371 31723ad855bba6d217f4e8dea75a57e343f50e42
push id45014
push userbmo:rchien@mozilla.com
push dateSat, 11 Feb 2017 01:47:46 +0000
reviewershonza
bugs1336383
milestone54.0a1
Bug 1336383 - Implement RequestsList component r?honza MozReview-Commit-ID: 5ktx8fncDl0
devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
devtools/client/netmonitor/actions/selection.js
devtools/client/netmonitor/actions/ui.js
devtools/client/netmonitor/components/moz.build
devtools/client/netmonitor/components/request-list-content.js
devtools/client/netmonitor/components/request-list-context-menu.js
devtools/client/netmonitor/components/request-list-empty.js
devtools/client/netmonitor/components/request-list-item.js
devtools/client/netmonitor/components/request-list-tooltip.js
devtools/client/netmonitor/components/request-list.js
devtools/client/netmonitor/components/toolbar.js
devtools/client/netmonitor/har/har-builder.js
devtools/client/netmonitor/har/har-exporter.js
devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
devtools/client/netmonitor/har/test/browser_net_har_post_data.js
devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
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/request-list-context-menu.js
devtools/client/netmonitor/requests-menu-view.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_accessibility-01.js
devtools/client/netmonitor/test/browser_net_accessibility-02.js
devtools/client/netmonitor/test/browser_net_api-calls.js
devtools/client/netmonitor/test/browser_net_autoscroll.js
devtools/client/netmonitor/test/browser_net_brotli.js
devtools/client/netmonitor/test/browser_net_cached-status.js
devtools/client/netmonitor/test/browser_net_cause.js
devtools/client/netmonitor/test/browser_net_cause_redirect.js
devtools/client/netmonitor/test/browser_net_clear.js
devtools/client/netmonitor/test/browser_net_complex-params.js
devtools/client/netmonitor/test/browser_net_content-type.js
devtools/client/netmonitor/test/browser_net_copy_as_curl.js
devtools/client/netmonitor/test/browser_net_copy_headers.js
devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
devtools/client/netmonitor/test/browser_net_copy_params.js
devtools/client/netmonitor/test/browser_net_copy_response.js
devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
devtools/client/netmonitor/test/browser_net_copy_url.js
devtools/client/netmonitor/test/browser_net_cors_requests.js
devtools/client/netmonitor/test/browser_net_curl-utils.js
devtools/client/netmonitor/test/browser_net_cyrillic-01.js
devtools/client/netmonitor/test/browser_net_cyrillic-02.js
devtools/client/netmonitor/test/browser_net_filter-01.js
devtools/client/netmonitor/test/browser_net_filter-02.js
devtools/client/netmonitor/test/browser_net_filter-03.js
devtools/client/netmonitor/test/browser_net_filter-04.js
devtools/client/netmonitor/test/browser_net_footer-summary.js
devtools/client/netmonitor/test/browser_net_frame.js
devtools/client/netmonitor/test/browser_net_header-docs.js
devtools/client/netmonitor/test/browser_net_html-preview.js
devtools/client/netmonitor/test/browser_net_icon-preview.js
devtools/client/netmonitor/test/browser_net_image-tooltip.js
devtools/client/netmonitor/test/browser_net_json-b64.js
devtools/client/netmonitor/test/browser_net_json-long.js
devtools/client/netmonitor/test/browser_net_json-malformed.js
devtools/client/netmonitor/test/browser_net_json-null.js
devtools/client/netmonitor/test/browser_net_json_custom_mime.js
devtools/client/netmonitor/test/browser_net_json_text_mime.js
devtools/client/netmonitor/test/browser_net_jsonp.js
devtools/client/netmonitor/test/browser_net_large-response.js
devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
devtools/client/netmonitor/test/browser_net_page-nav.js
devtools/client/netmonitor/test/browser_net_pane-collapse.js
devtools/client/netmonitor/test/browser_net_pane-toggle.js
devtools/client/netmonitor/test/browser_net_persistent_logs.js
devtools/client/netmonitor/test/browser_net_post-data-01.js
devtools/client/netmonitor/test/browser_net_post-data-02.js
devtools/client/netmonitor/test/browser_net_post-data-03.js
devtools/client/netmonitor/test/browser_net_post-data-04.js
devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
devtools/client/netmonitor/test/browser_net_raw_headers.js
devtools/client/netmonitor/test/browser_net_reload-button.js
devtools/client/netmonitor/test/browser_net_reload-markers.js
devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
devtools/client/netmonitor/test/browser_net_resend.js
devtools/client/netmonitor/test/browser_net_resend_cors.js
devtools/client/netmonitor/test/browser_net_resend_headers.js
devtools/client/netmonitor/test/browser_net_security-details.js
devtools/client/netmonitor/test/browser_net_security-error.js
devtools/client/netmonitor/test/browser_net_security-icon-click.js
devtools/client/netmonitor/test/browser_net_security-redirect.js
devtools/client/netmonitor/test/browser_net_security-state.js
devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
devtools/client/netmonitor/test/browser_net_security-warnings.js
devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
devtools/client/netmonitor/test/browser_net_send-beacon.js
devtools/client/netmonitor/test/browser_net_service-worker-status.js
devtools/client/netmonitor/test/browser_net_simple-request-data.js
devtools/client/netmonitor/test/browser_net_simple-request-details.js
devtools/client/netmonitor/test/browser_net_simple-request.js
devtools/client/netmonitor/test/browser_net_sort-01.js
devtools/client/netmonitor/test/browser_net_sort-02.js
devtools/client/netmonitor/test/browser_net_sort-03.js
devtools/client/netmonitor/test/browser_net_status-codes.js
devtools/client/netmonitor/test/browser_net_streaming-response.js
devtools/client/netmonitor/test/browser_net_throttle.js
devtools/client/netmonitor/test/browser_net_timing-division.js
devtools/client/netmonitor/test/browser_net_truncate.js
devtools/client/netmonitor/test/head.js
devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
devtools/client/themes/netmonitor.css
devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js
devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
devtools/client/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
--- a/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
+++ b/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
@@ -18,16 +18,18 @@ add_task(function* () {
   let tab = yield addTab(URL_ROOT + "doc_viewsource.html");
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
   let panel = toolbox.getPanel("styleeditor");
 
   is(panel.UI.editors.length, 1, "correct number of editors opened");
 
   let monitor = yield toolbox.selectTool("netmonitor");
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  is(RequestsMenu.itemCount, 0, "No network requests appear in the network panel");
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+
+  is(gStore.getState().requests.requests.size, 0, "No network requests appear in the network panel");
 
   yield gDevTools.closeToolbox(target);
   tab = target = toolbox = panel = null;
   gBrowser.removeCurrentTab();
   flags.testing = isTesting;
 });
--- a/devtools/client/netmonitor/actions/selection.js
+++ b/devtools/client/netmonitor/actions/selection.js
@@ -1,28 +1,45 @@
 /* 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 { getDisplayedRequests } = require("../selectors/index");
 const { SELECT_REQUEST } = require("../constants");
+const {
+  getDisplayedRequests,
+  getSortedRequests,
+} = require("../selectors/index");
+
+const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
 
 /**
  * Select request with a given id.
  */
 function selectRequest(id) {
   return {
     type: SELECT_REQUEST,
-    id
+    id,
   };
 }
 
-const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
+/**
+ * Select request with a given index (sorted order)
+ */
+function selectRequestByIndex(index) {
+  return (dispatch, getState) => {
+    const requests = getSortedRequests(getState());
+    let itemId;
+    if (index >= 0 && index < requests.size) {
+      itemId = requests.get(index).id;
+    }
+    dispatch(selectRequest(itemId));
+  };
+}
 
 /**
  * Move the selection up to down according to the "delta" parameter. Possible values:
  * - Number: positive or negative, move up or down by specified distance
  * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down
  * - +Infinity | -Infinity: move to the start or end of the list
  */
 function selectDelta(delta) {
@@ -45,10 +62,11 @@ function selectDelta(delta) {
     const newIndex = Math.min(Math.max(0, selIndex + delta), requests.size - 1);
     const newItem = requests.get(newIndex);
     dispatch(selectRequest(newItem.id));
   };
 }
 
 module.exports = {
   selectRequest,
+  selectRequestByIndex,
   selectDelta,
 };
--- a/devtools/client/netmonitor/actions/ui.js
+++ b/devtools/client/netmonitor/actions/ui.js
@@ -19,19 +19,19 @@ const {
 function openNetworkDetails(open) {
   return {
     type: OPEN_NETWORK_DETAILS,
     open,
   };
 }
 
 /**
- * Change performance statistics view open state.
+ * Change performance statistics panel open state.
  *
- * @param {boolean} visible - expected performance statistics open state
+ * @param {boolean} visible - expected performance statistics panel open state
  */
 function openStatistics(open) {
   return {
     type: OPEN_STATISTICS,
     open,
   };
 }
 
@@ -61,20 +61,21 @@ function selectDetailsPanelTab(id) {
  * Toggle network details panel.
  */
 function toggleNetworkDetails() {
   return (dispatch, getState) =>
     dispatch(openNetworkDetails(!getState().ui.networkDetailsOpen));
 }
 
 /**
- * Toggle to show/hide performance statistics view.
+ * Toggle performance statistics panel.
  */
 function toggleStatistics() {
-  return (dispatch, getState) => dispatch(openStatistics(!getState().ui.statisticsOpen));
+  return (dispatch, getState) =>
+    dispatch(openStatistics(!getState().ui.statisticsOpen));
 }
 
 module.exports = {
   openNetworkDetails,
   openStatistics,
   resizeWaterfall,
   selectDetailsPanelTab,
   toggleNetworkDetails,
--- a/devtools/client/netmonitor/components/moz.build
+++ b/devtools/client/netmonitor/components/moz.build
@@ -1,14 +1,15 @@
 # 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(
     'request-list-content.js',
+    'request-list-context-menu.js',
     'request-list-empty.js',
     'request-list-header.js',
     'request-list-item.js',
     'request-list-tooltip.js',
     'request-list.js',
     'statistics-panel.js',
     'toolbar.js',
 )
--- a/devtools/client/netmonitor/components/request-list-content.js
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -1,58 +1,83 @@
 /* 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 NetMonitorView */
+
+/* globals NetMonitorController */
 
 "use strict";
 
-const { Task } = require("devtools/shared/task");
-const { createClass, createFactory, DOM, PropTypes } = require("devtools/client/shared/vendor/react");
-const { div } = DOM;
-const Actions = require("../actions/index");
-const RequestListItem = createFactory(require("./request-list-item"));
+const { KeyCodes } = require("devtools/client/shared/keycodes");
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
-const { setTooltipImageContent,
-        setTooltipStackTraceContent } = require("./request-list-tooltip");
-const { getDisplayedRequests,
-        getWaterfallScale } = require("../selectors/index");
-const { KeyCodes } = require("devtools/client/shared/keycodes");
+const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+const { Task } = require("devtools/shared/task");
+const Actions = require("../actions/index");
+const {
+  setTooltipImageContent,
+  setTooltipStackTraceContent,
+} = require("./request-list-tooltip");
+const {
+  getDisplayedRequests,
+  getWaterfallScale,
+} = require("../selectors/index");
+
+// Components
+const RequestListItem = createFactory(require("./request-list-item"));
+const RequestListContextMenu = require("./request-list-context-menu");
+
+const { div } = DOM;
 
 // tooltip show/hide delay in ms
 const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
 
 /**
  * Renders the actual contents of the request list.
  */
 const RequestListContent = createClass({
   displayName: "RequestListContent",
 
   propTypes: {
+    contextMenu: PropTypes.object.isRequired,
+    dispatch: PropTypes.func.isRequired,
     displayedRequests: PropTypes.object.isRequired,
     firstRequestStartedMillis: PropTypes.number.isRequired,
-    onItemContextMenu: PropTypes.func.isRequired,
     onItemMouseDown: PropTypes.func.isRequired,
     onSecurityIconClick: PropTypes.func.isRequired,
     onSelectDelta: PropTypes.func.isRequired,
     scale: PropTypes.number,
     selectedRequestId: PropTypes.string,
     tooltip: PropTypes.shape({
       hide: PropTypes.func.isRequired,
       startTogglingOnHover: PropTypes.func.isRequired,
       stopTogglingOnHover: PropTypes.func.isRequired,
     }).isRequired
   },
 
+  componentWillMount() {
+    const { dispatch } = this.props;
+    this.contextMenu = new RequestListContextMenu({
+      cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+      openStatistics: (open) => dispatch(Actions.openStatistics(open)),
+    });
+    this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
+  },
+
   componentDidMount() {
     // Set the CSS variables for waterfall scaling
     this.setScalingStyles();
 
     // Install event handler for displaying a tooltip
-    this.props.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
+    this.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
       toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
       interactive: true
     });
 
     // Install event handler to hide the tooltip on scroll
     this.refs.contentEl.addEventListener("scroll", this.onScroll, true);
   },
 
@@ -73,17 +98,17 @@ const RequestListContent = createClass({
       node.scrollTop = node.scrollHeight;
     }
   },
 
   componentWillUnmount() {
     this.refs.contentEl.removeEventListener("scroll", this.onScroll, true);
 
     // Uninstall the tooltip event handler
-    this.props.tooltip.stopTogglingOnHover();
+    this.tooltip.stopTogglingOnHover();
   },
 
   /**
    * Set the CSS variables for waterfall scaling. If React supported setting CSS
    * variables as part of the "style" property of a DOM element, we would use that.
    *
    * However, React doesn't support this, so we need to use a hack and update the
    * DOM element directly: https://github.com/facebook/react/issues/6411
@@ -147,17 +172,17 @@ const RequestListContent = createClass({
 
     return false;
   }),
 
   /**
    * Scroll listener for the requests menu view.
    */
   onScroll() {
-    this.props.tooltip.hide();
+    this.tooltip.hide();
   },
 
   /**
    * Handler for keyboard events. For arrow up/down, page up/down, home/end,
    * move the selection up or down.
    */
   onKeyDown(e) {
     let delta;
@@ -188,16 +213,21 @@ const RequestListContent = createClass({
     if (delta) {
       // Prevent scrolling when pressing navigation keys.
       e.preventDefault();
       e.stopPropagation();
       this.props.onSelectDelta(delta);
     }
   },
 
+  onContextMenu(evt) {
+    evt.preventDefault();
+    this.contextMenu.open(evt);
+  },
+
   /**
    * If selection has just changed (by keyboard navigation), don't keep the list
    * scrolled to bottom, but allow scrolling up with the selection.
    */
   onFocusedNodeChange() {
     this.shouldScrollBottom = false;
   },
 
@@ -206,66 +236,62 @@ const RequestListContent = createClass({
    */
   onFocusedNodeUnmount() {
     if (this.refs.contentEl) {
       this.refs.contentEl.focus();
     }
   },
 
   render() {
-    const { selectedRequestId,
-            displayedRequests,
-            firstRequestStartedMillis,
-            onItemMouseDown,
-            onItemContextMenu,
-            onSecurityIconClick } = this.props;
+    const {
+      displayedRequests,
+      firstRequestStartedMillis,
+      selectedRequestId,
+      onItemMouseDown,
+      onSecurityIconClick,
+    } = this.props;
 
-    return div(
-      {
+    return (
+      div({
         ref: "contentEl",
         className: "requests-menu-contents",
         tabIndex: 0,
         onKeyDown: this.onKeyDown,
       },
-      displayedRequests.map((item, index) => RequestListItem({
-        key: item.id,
-        item,
-        index,
-        isSelected: item.id === selectedRequestId,
-        firstRequestStartedMillis,
-        onMouseDown: e => onItemMouseDown(e, item.id),
-        onContextMenu: e => onItemContextMenu(e, item.id),
-        onSecurityIconClick: e => onSecurityIconClick(e, item),
-        onFocusedNodeChange: this.onFocusedNodeChange,
-        onFocusedNodeUnmount: this.onFocusedNodeUnmount,
-      }))
+        displayedRequests.map((item, index) => RequestListItem({
+          key: item.id,
+          item,
+          index,
+          isSelected: item.id === selectedRequestId,
+          firstRequestStartedMillis,
+          onMouseDown: () => onItemMouseDown(item.id),
+          onContextMenu: this.onContextMenu,
+          onFocusedNodeChange: this.onFocusedNodeChange,
+          onFocusedNodeUnmount: this.onFocusedNodeUnmount,
+          onSecurityIconClick: () => onSecurityIconClick(item.securityState),
+        }))
+      )
     );
   },
 });
 
 module.exports = connect(
-  state => ({
+  (state) => ({
     displayedRequests: getDisplayedRequests(state),
+    firstRequestStartedMillis: state.requests.firstStartedMillis,
     selectedRequestId: state.requests.selectedId,
     scale: getWaterfallScale(state),
-    firstRequestStartedMillis: state.requests.firstStartedMillis,
-    tooltip: NetMonitorView.RequestsMenu.tooltip,
   }),
-  dispatch => ({
-    onItemMouseDown: (e, item) => dispatch(Actions.selectRequest(item)),
-    onItemContextMenu: (e, item) => {
-      e.preventDefault();
-      NetMonitorView.RequestsMenu.contextMenu.open(e);
-    },
-    onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
+  (dispatch) => ({
+    dispatch,
+    onItemMouseDown: (id) => dispatch(Actions.selectRequest(id)),
     /**
      * A handler that opens the security tab in the details view if secure or
      * broken security indicator is clicked.
      */
-    onSecurityIconClick: (e, item) => {
-      const { securityState } = item;
-      // Choose the security tab.
+    onSecurityIconClick: (securityState) => {
       if (securityState && securityState !== "insecure") {
         dispatch(Actions.selectDetailsPanelTab("security"));
       }
     },
-  })
+    onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
+  }),
 )(RequestListContent);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-context-menu.js
@@ -0,0 +1,360 @@
+/* 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 NetMonitorController, gNetwork, gStore */
+
+"use strict";
+
+const Services = require("Services");
+const { Task } = require("devtools/shared/task");
+const { Curl } = require("devtools/client/shared/curl");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const Menu = require("devtools/client/framework/menu");
+const MenuItem = require("devtools/client/framework/menu-item");
+const { L10N } = require("../l10n");
+const {
+  formDataURI,
+  getFormDataSections,
+  getUrlQuery,
+  parseQueryString,
+} = require("../request-utils");
+const {
+  getSelectedRequest,
+  getSortedRequests,
+} = require("../selectors/index");
+
+loader.lazyRequireGetter(this, "HarExporter",
+  "devtools/client/netmonitor/har/har-exporter", true);
+
+loader.lazyServiceGetter(this, "clipboardHelper",
+  "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
+
+function RequestListContextMenu({
+  cloneSelectedRequest,
+  openStatistics,
+}) {
+  this.cloneSelectedRequest = cloneSelectedRequest;
+  this.openStatistics = openStatistics;
+}
+
+RequestListContextMenu.prototype = {
+  get selectedRequest() {
+    return getSelectedRequest(gStore.getState());
+  },
+
+  get sortedRequests() {
+    return getSortedRequests(gStore.getState());
+  },
+
+  /**
+   * Handle the context menu opening. Hide items if no request is selected.
+   * Since visible attribute only accept boolean value but the method call may
+   * return undefined, we use !! to force convert any object to boolean
+   */
+  open({ screenX = 0, screenY = 0 } = {}) {
+    let selectedRequest = this.selectedRequest;
+
+    let menu = new Menu();
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-url",
+      label: L10N.getStr("netmonitor.context.copyUrl"),
+      accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
+      visible: !!selectedRequest,
+      click: () => this.copyUrl(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-url-params",
+      label: L10N.getStr("netmonitor.context.copyUrlParams"),
+      accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
+      visible: !!(selectedRequest && getUrlQuery(selectedRequest.url)),
+      click: () => this.copyUrlParams(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-post-data",
+      label: L10N.getStr("netmonitor.context.copyPostData"),
+      accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
+      visible: !!(selectedRequest && selectedRequest.requestPostData),
+      click: () => this.copyPostData(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-as-curl",
+      label: L10N.getStr("netmonitor.context.copyAsCurl"),
+      accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
+      visible: !!selectedRequest,
+      click: () => this.copyAsCurl(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedRequest,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-request-headers",
+      label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
+      accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
+      visible: !!(selectedRequest && selectedRequest.requestHeaders),
+      click: () => this.copyRequestHeaders(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "response-menu-context-copy-response-headers",
+      label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
+      accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
+      visible: !!(selectedRequest && selectedRequest.responseHeaders),
+      click: () => this.copyResponseHeaders(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-response",
+      label: L10N.getStr("netmonitor.context.copyResponse"),
+      accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
+      visible: !!(selectedRequest &&
+               selectedRequest.responseContent &&
+               selectedRequest.responseContent.content.text &&
+               selectedRequest.responseContent.content.text.length !== 0),
+      click: () => this.copyResponse(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-image-as-data-uri",
+      label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
+      accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
+      visible: !!(selectedRequest &&
+               selectedRequest.responseContent &&
+               selectedRequest.responseContent.content.mimeType.includes("image/")),
+      click: () => this.copyImageAsDataUri(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedRequest,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-all-as-har",
+      label: L10N.getStr("netmonitor.context.copyAllAsHar"),
+      accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
+      visible: this.sortedRequests.size > 0,
+      click: () => this.copyAllAsHar(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-save-all-as-har",
+      label: L10N.getStr("netmonitor.context.saveAllAsHar"),
+      accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
+      visible: this.sortedRequests.size > 0,
+      click: () => this.saveAllAsHar(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedRequest,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-resend",
+      label: L10N.getStr("netmonitor.context.editAndResend"),
+      accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
+      visible: !!(NetMonitorController.supportsCustomRequest &&
+               selectedRequest && !selectedRequest.isCustom),
+      click: this.cloneSelectedRequest,
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedRequest,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-newtab",
+      label: L10N.getStr("netmonitor.context.newTab"),
+      accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
+      visible: !!selectedRequest,
+      click: () => this.openRequestInTab()
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-perf",
+      label: L10N.getStr("netmonitor.context.perfTools"),
+      accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
+      visible: !!NetMonitorController.supportsPerfStats,
+      click: () => this.openStatistics(true)
+    }));
+
+    menu.popup(screenX, screenY, NetMonitorController._toolbox);
+    return menu;
+  },
+
+  /**
+   * Opens selected item in a new tab.
+   */
+  openRequestInTab() {
+    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    win.openUILinkIn(this.selectedRequest.url, "tab", { relatedToCurrent: true });
+  },
+
+  /**
+   * Copy the request url from the currently selected item.
+   */
+  copyUrl() {
+    clipboardHelper.copyString(this.selectedRequest.url);
+  },
+
+  /**
+   * Copy the request url query string parameters from the currently
+   * selected item.
+   */
+  copyUrlParams() {
+    let { url } = this.selectedRequest;
+    let params = getUrlQuery(url).split("&");
+    let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+    clipboardHelper.copyString(string);
+  },
+
+  /**
+   * Copy the request form data parameters (or raw payload) from
+   * the currently selected item.
+   */
+  copyPostData: Task.async(function* () {
+    let selected = this.selectedRequest;
+
+    // Try to extract any form data parameters.
+    let formDataSections = yield getFormDataSections(
+      selected.requestHeaders,
+      selected.requestHeadersFromUploadStream,
+      selected.requestPostData,
+      gNetwork.getString.bind(gNetwork));
+
+    let params = [];
+    formDataSections.forEach(section => {
+      let paramsArray = parseQueryString(section);
+      if (paramsArray) {
+        params = [...params, ...paramsArray];
+      }
+    });
+
+    let string = params
+      .map(param => param.name + (param.value ? "=" + param.value : ""))
+      .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+
+    // Fall back to raw payload.
+    if (!string) {
+      let postData = selected.requestPostData.postData.text;
+      string = yield gNetwork.getString(postData);
+      if (Services.appinfo.OS !== "WINNT") {
+        string = string.replace(/\r/g, "");
+      }
+    }
+
+    clipboardHelper.copyString(string);
+  }),
+
+  /**
+   * Copy a cURL command from the currently selected item.
+   */
+  copyAsCurl: Task.async(function* () {
+    let selected = this.selectedRequest;
+
+    // Create a sanitized object for the Curl command generator.
+    let data = {
+      url: selected.url,
+      method: selected.method,
+      headers: [],
+      httpVersion: selected.httpVersion,
+      postDataText: null
+    };
+
+    // Fetch header values.
+    for (let { name, value } of selected.requestHeaders.headers) {
+      let text = yield gNetwork.getString(value);
+      data.headers.push({ name: name, value: text });
+    }
+
+    // Fetch the request payload.
+    if (selected.requestPostData) {
+      let postData = selected.requestPostData.postData.text;
+      data.postDataText = yield gNetwork.getString(postData);
+    }
+
+    clipboardHelper.copyString(Curl.generateCommand(data));
+  }),
+
+  /**
+   * Copy the raw request headers from the currently selected item.
+   */
+  copyRequestHeaders() {
+    let rawHeaders = this.selectedRequest.requestHeaders.rawHeaders.trim();
+    if (Services.appinfo.OS !== "WINNT") {
+      rawHeaders = rawHeaders.replace(/\r/g, "");
+    }
+    clipboardHelper.copyString(rawHeaders);
+  },
+
+  /**
+   * Copy the raw response headers from the currently selected item.
+   */
+  copyResponseHeaders() {
+    let rawHeaders = this.selectedRequest.responseHeaders.rawHeaders.trim();
+    if (Services.appinfo.OS !== "WINNT") {
+      rawHeaders = rawHeaders.replace(/\r/g, "");
+    }
+    clipboardHelper.copyString(rawHeaders);
+  },
+
+  /**
+   * Copy image as data uri.
+   */
+  copyImageAsDataUri() {
+    const { mimeType, text, encoding } = this.selectedRequest.responseContent.content;
+
+    gNetwork.getString(text).then(string => {
+      let data = formDataURI(mimeType, encoding, string);
+      clipboardHelper.copyString(data);
+    });
+  },
+
+  /**
+   * Copy response data as a string.
+   */
+  copyResponse() {
+    const { text } = this.selectedRequest.responseContent.content;
+
+    gNetwork.getString(text).then(string => {
+      clipboardHelper.copyString(string);
+    });
+  },
+
+  /**
+   * Copy HAR from the network panel content to the clipboard.
+   */
+  copyAllAsHar() {
+    let options = this.getDefaultHarOptions();
+    return HarExporter.copy(options);
+  },
+
+  /**
+   * Save HAR from the network panel content to a file.
+   */
+  saveAllAsHar() {
+    let options = this.getDefaultHarOptions();
+    return HarExporter.save(options);
+  },
+
+  getDefaultHarOptions() {
+    let form = NetMonitorController._target.form;
+    let title = form.title || form.url;
+
+    return {
+      getString: gNetwork.getString.bind(gNetwork),
+      items: this.sortedRequests,
+      title: title
+    };
+  }
+};
+
+module.exports = RequestListContextMenu;
--- a/devtools/client/netmonitor/components/request-list-empty.js
+++ b/devtools/client/netmonitor/components/request-list-empty.js
@@ -62,13 +62,13 @@ const RequestListEmptyNotice = createCla
       )
     );
   }
 });
 
 module.exports = connect(
   undefined,
   dispatch => ({
-    onPerfClick: e => dispatch(Actions.openStatistics(true)),
-    onReloadClick: e =>
+    onPerfClick: () => dispatch(Actions.openStatistics(true)),
+    onReloadClick: () =>
       NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
   })
 )(RequestListEmptyNotice);
--- a/devtools/client/netmonitor/components/request-list-item.js
+++ b/devtools/client/netmonitor/components/request-list-item.js
@@ -1,21 +1,27 @@
 /* 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/. */
 
 /* eslint-disable react/prop-types */
 
 "use strict";
 
-const { createClass, createFactory, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
-const { div, span, img } = DOM;
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../l10n");
+const { getAbbreviatedMimeType } = require("../request-utils");
 const { getFormattedSize } = require("../utils/format-utils");
-const { getAbbreviatedMimeType } = require("../request-utils");
+
+const { div, img, span } = DOM;
 
 /**
  * Compare two objects on a subset of their properties
  */
 function propertiesEqual(props, item1, item2) {
   return item1 === item2 || props.every(p => item1[p] === item2[p]);
 }
 
@@ -42,17 +48,17 @@ const UPDATED_REQ_ITEM_PROPS = [
   "transferredSize",
   "startedMillis",
   "totalTime",
 ];
 
 const UPDATED_REQ_PROPS = [
   "index",
   "isSelected",
-  "firstRequestStartedMillis"
+  "firstRequestStartedMillis",
 ];
 
 /**
  * Render one row in the request list.
  */
 const RequestListItem = createClass({
   displayName: "RequestListItem",
 
@@ -71,17 +77,17 @@ const RequestListItem = createClass({
   componentDidMount() {
     if (this.props.isSelected) {
       this.refs.el.focus();
     }
   },
 
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_REQ_ITEM_PROPS, this.props.item, nextProps.item) ||
-           !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps);
+      !propertiesEqual(UPDATED_REQ_PROPS, this.props, nextProps);
   },
 
   componentDidUpdate(prevProps) {
     if (!prevProps.isSelected && this.props.isSelected) {
       this.refs.el.focus();
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
@@ -114,46 +120,49 @@ const RequestListItem = createClass({
     } = this.props;
 
     let classList = [ "request-list-item" ];
     if (isSelected) {
       classList.push("selected");
     }
     classList.push(index % 2 ? "odd" : "even");
 
-    return div(
-      {
+    return (
+      div({
         ref: "el",
         className: classList.join(" "),
         "data-id": item.id,
         tabIndex: 0,
         onContextMenu,
         onMouseDown,
       },
-      StatusColumn({ item }),
-      MethodColumn({ item }),
-      FileColumn({ item }),
-      DomainColumn({ item, onSecurityIconClick }),
-      CauseColumn({ item }),
-      TypeColumn({ item }),
-      TransferredSizeColumn({ item }),
-      ContentSizeColumn({ item }),
-      WaterfallColumn({ item, firstRequestStartedMillis })
+        StatusColumn({ item }),
+        MethodColumn({ item }),
+        FileColumn({ item }),
+        DomainColumn({ item, onSecurityIconClick }),
+        CauseColumn({ item }),
+        TypeColumn({ item }),
+        TransferredSizeColumn({ item }),
+        ContentSizeColumn({ item }),
+        WaterfallColumn({ item, firstRequestStartedMillis }),
+      )
     );
   }
 });
 
 const UPDATED_STATUS_PROPS = [
   "status",
   "statusText",
   "fromCache",
   "fromServiceWorker",
 ];
 
 const StatusColumn = createFactory(createClass({
+  displayName: "StatusColumn",
+
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_STATUS_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
     const { status, statusText, fromCache, fromServiceWorker } = this.props.item;
 
     let code, title;
@@ -173,74 +182,85 @@ const StatusColumn = createFactory(creat
           title += " (cached)";
         }
         if (fromServiceWorker) {
           title += " (service worker)";
         }
       }
     }
 
-    return div({ className: "requests-menu-subitem requests-menu-status", title },
-      div({ className: "requests-menu-status-icon", "data-code": code }),
-      span({ className: "subitem-label requests-menu-status-code" }, status)
+    return (
+      div({ className: "requests-menu-subitem requests-menu-status", title },
+        div({ className: "requests-menu-status-icon", "data-code": code }),
+        span({ className: "subitem-label requests-menu-status-code" }, status),
+      )
     );
   }
 }));
 
 const MethodColumn = createFactory(createClass({
+  displayName: "MethodColumn",
+
   shouldComponentUpdate(nextProps) {
     return this.props.item.method !== nextProps.item.method;
   },
 
   render() {
     const { method } = this.props.item;
-    return div({ className: "requests-menu-subitem requests-menu-method-box" },
-      span({ className: "subitem-label requests-menu-method" }, method)
+    return (
+      div({ className: "requests-menu-subitem requests-menu-method-box" },
+        span({ className: "subitem-label requests-menu-method" }, method)
+      )
     );
   }
 }));
 
 const UPDATED_FILE_PROPS = [
   "urlDetails",
   "responseContentDataUri",
 ];
 
 const FileColumn = createFactory(createClass({
+  displayName: "FileColumn",
+
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_FILE_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
     const { urlDetails, responseContentDataUri } = this.props.item;
 
-    return div({ className: "requests-menu-subitem requests-menu-icon-and-file" },
-      img({
-        className: "requests-menu-icon",
-        src: responseContentDataUri,
-        hidden: !responseContentDataUri,
-        "data-type": responseContentDataUri ? "thumbnail" : undefined
-      }),
-      div(
-        {
+    return (
+      div({ className: "requests-menu-subitem requests-menu-icon-and-file" },
+        img({
+          className: "requests-menu-icon",
+          src: responseContentDataUri,
+          hidden: !responseContentDataUri,
+          "data-type": responseContentDataUri ? "thumbnail" : undefined,
+        }),
+        div({
           className: "subitem-label requests-menu-file",
-          title: urlDetails.unicodeUrl
+          title: urlDetails.unicodeUrl,
         },
-        urlDetails.baseNameWithQuery
+          urlDetails.baseNameWithQuery,
+        ),
       )
     );
   }
 }));
 
 const UPDATED_DOMAIN_PROPS = [
   "urlDetails",
   "remoteAddress",
   "securityState",
 ];
 
 const DomainColumn = createFactory(createClass({
+  displayName: "DomainColumn",
+
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_DOMAIN_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
     const { item, onSecurityIconClick } = this.props;
     const { urlDetails, remoteAddress, securityState } = item;
 
@@ -251,29 +271,32 @@ const DomainColumn = createFactory(creat
       iconTitle = L10N.getStr("netmonitor.security.state.secure");
     } else if (securityState) {
       iconClassList.push(`security-state-${securityState}`);
       iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
     }
 
     let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
 
-    return div(
-      { className: "requests-menu-subitem requests-menu-security-and-domain" },
-      div({
-        className: iconClassList.join(" "),
-        title: iconTitle,
-        onClick: onSecurityIconClick,
-      }),
-      span({ className: "subitem-label requests-menu-domain", title }, urlDetails.host)
+    return (
+      div({ className: "requests-menu-subitem requests-menu-security-and-domain" },
+        div({
+          className: iconClassList.join(" "),
+          title: iconTitle,
+          onClick: onSecurityIconClick,
+        }),
+        span({ className: "subitem-label requests-menu-domain", title }, urlDetails.host),
+      )
     );
   }
 }));
 
 const CauseColumn = createFactory(createClass({
+  displayName: "CauseColumn",
+
   shouldComponentUpdate(nextProps) {
     return this.props.item.cause !== nextProps.item.cause;
   },
 
   render() {
     const { cause } = this.props.item;
 
     let causeType = "";
@@ -282,57 +305,72 @@ const CauseColumn = createFactory(create
 
     if (cause) {
       // Legacy server might send a numeric value. Display it as "unknown"
       causeType = typeof cause.type === "string" ? cause.type : "unknown";
       causeUri = cause.loadingDocumentUri;
       causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
     }
 
-    return div(
-      { className: "requests-menu-subitem requests-menu-cause", title: causeUri },
-      span({ className: "requests-menu-cause-stack", hidden: !causeHasStack }, "JS"),
-      span({ className: "subitem-label" }, causeType)
+    return (
+      div({
+        className: "requests-menu-subitem requests-menu-cause",
+        title: causeUri,
+      },
+        span({
+          className: "requests-menu-cause-stack",
+          hidden: !causeHasStack,
+        }, "JS"),
+        span({ className: "subitem-label" }, causeType),
+      )
     );
   }
 }));
 
 const CONTENT_MIME_TYPE_ABBREVIATIONS = {
   "ecmascript": "js",
   "javascript": "js",
   "x-javascript": "js"
 };
 
 const TypeColumn = createFactory(createClass({
+  displayName: "TypeColumn",
+
   shouldComponentUpdate(nextProps) {
     return this.props.item.mimeType !== nextProps.item.mimeType;
   },
 
   render() {
     const { mimeType } = this.props.item;
     let abbrevType;
     if (mimeType) {
       abbrevType = getAbbreviatedMimeType(mimeType);
       abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
     }
 
-    return div(
-      { className: "requests-menu-subitem requests-menu-type", title: mimeType },
-      span({ className: "subitem-label" }, abbrevType)
+    return (
+      div({
+        className: "requests-menu-subitem requests-menu-type",
+        title: mimeType,
+      },
+        span({ className: "subitem-label" }, abbrevType),
+      )
     );
   }
 }));
 
 const UPDATED_TRANSFERRED_PROPS = [
   "transferredSize",
   "fromCache",
   "fromServiceWorker",
 ];
 
 const TransferredSizeColumn = createFactory(createClass({
+  displayName: "TransferredSizeColumn",
+
   shouldComponentUpdate(nextProps) {
     return !propertiesEqual(UPDATED_TRANSFERRED_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
     const { transferredSize, fromCache, fromServiceWorker } = this.props.item;
 
     let text;
@@ -344,68 +382,81 @@ const TransferredSizeColumn = createFact
       text = L10N.getStr("networkMenu.sizeServiceWorker");
       className += " theme-comment";
     } else if (typeof transferredSize == "number") {
       text = getFormattedSize(transferredSize);
     } else if (transferredSize === null) {
       text = L10N.getStr("networkMenu.sizeUnavailable");
     }
 
-    return div(
-      { className: "requests-menu-subitem requests-menu-transferred", title: text },
-      span({ className }, text)
+    return (
+      div({
+        className: "requests-menu-subitem requests-menu-transferred",
+        title: text,
+      },
+        span({ className }, text),
+      )
     );
   }
 }));
 
 const ContentSizeColumn = createFactory(createClass({
+  displayName: "ContentSizeColumn",
+
   shouldComponentUpdate(nextProps) {
     return this.props.item.contentSize !== nextProps.item.contentSize;
   },
 
   render() {
     const { contentSize } = this.props.item;
 
     let text;
     if (typeof contentSize == "number") {
       text = getFormattedSize(contentSize);
     }
 
-    return div(
-      {
+    return (
+      div({
         className: "requests-menu-subitem subitem-label requests-menu-size",
-        title: text
+        title: text,
       },
-      span({ className: "subitem-label" }, text)
+        span({ className: "subitem-label" }, text),
+      )
     );
   }
 }));
 
 const UPDATED_WATERFALL_PROPS = [
   "eventTimings",
   "totalTime",
   "fromCache",
   "fromServiceWorker",
 ];
 
 const WaterfallColumn = createFactory(createClass({
+  displayName: "WaterfallColumn",
+
   shouldComponentUpdate(nextProps) {
     return this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis ||
-           !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
+      !propertiesEqual(UPDATED_WATERFALL_PROPS, this.props.item, nextProps.item);
   },
 
   render() {
     const { item, firstRequestStartedMillis } = this.props;
-    const startedDeltaMillis = item.startedMillis - firstRequestStartedMillis;
-    const paddingInlineStart = `${startedDeltaMillis}px`;
 
-    return div({ className: "requests-menu-subitem requests-menu-waterfall" },
-      div(
-        { className: "requests-menu-timings", style: { paddingInlineStart } },
-        timingBoxes(item)
+    return (
+      div({ className: "requests-menu-subitem requests-menu-waterfall" },
+        div({
+          className: "requests-menu-timings",
+          style: {
+            paddingInlineStart: `${item.startedMillis - firstRequestStartedMillis}px`,
+          },
+        },
+          timingBoxes(item),
+        )
       )
     );
   }
 }));
 
 // List of properties of the timing info we want to create boxes for
 const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
 
--- a/devtools/client/netmonitor/components/request-list-tooltip.js
+++ b/devtools/client/netmonitor/components/request-list-tooltip.js
@@ -2,20 +2,22 @@
  * 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 gNetwork, NetMonitorController */
 
 "use strict";
 
 const { Task } = require("devtools/shared/task");
-const { formDataURI } = require("../request-utils");
+const {
+  setImageTooltip,
+  getImageDimensions,
+} = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const { WEBCONSOLE_L10N } = require("../l10n");
-const { setImageTooltip,
-        getImageDimensions } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
+const { formDataURI } = require("../request-utils");
 
 // px
 const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
 // px
 const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
--- a/devtools/client/netmonitor/components/request-list.js
+++ b/devtools/client/netmonitor/components/request-list.js
@@ -1,34 +1,127 @@
 /* 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/. */
 
+/* eslint-env browser */
+/* globals gNetwork */
+
 "use strict";
 
-const { createFactory, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
-const { div } = DOM;
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
-const RequestListHeader = createFactory(require("./request-list-header"));
+const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
+const Actions = require("../actions/index");
+const { Prefs } = require("../prefs");
+const { getFormDataSections } = require("../request-utils");
+const {
+  getActiveFilters,
+  getSelectedRequest,
+} = require("../selectors/index");
+
+// Components
+const RequestListContent = createFactory(require("./request-list-content"));
 const RequestListEmptyNotice = createFactory(require("./request-list-empty"));
-const RequestListContent = createFactory(require("./request-list-content"));
+const RequestListHeader = createFactory(require("./request-list-header"));
+
+const { div } = DOM;
 
 /**
- * Renders the request list - header, empty text, the actual content with rows
+ * Request panel component
  */
-const RequestList = function ({ isEmpty }) {
-  return div({ className: "request-list-container" },
-    RequestListHeader(),
-    isEmpty ? RequestListEmptyNotice() : RequestListContent()
-  );
-};
+const RequestList = createClass({
+  displayName: "RequestList",
+
+  propTypes: {
+    activeFilters: PropTypes.array,
+    dispatch: PropTypes.func,
+    isEmpty: PropTypes.bool.isRequired,
+    request: PropTypes.object,
+    networkDetailsOpen: PropTypes.bool,
+  },
+
+  componentDidMount() {
+    const { dispatch } = this.props;
+
+    Prefs.filters.forEach((type) => dispatch(Actions.toggleRequestFilterType(type)));
+    this.splitter = document.querySelector("#network-inspector-view-splitter");
+    this.splitter.addEventListener("mouseup", this.resize);
+    window.addEventListener("resize", this.resize);
+  },
+
+  componentWillReceiveProps(nextProps) {
+    const { dispatch, request = {}, networkDetailsOpen } = this.props;
+
+    if (nextProps.request && nextProps.request !== request) {
+      dispatch(Actions.openNetworkDetails(true));
+    }
+
+    if (nextProps.networkDetailsOpen !== networkDetailsOpen) {
+      this.resize();
+    }
+
+    const {
+      formDataSections,
+      requestHeaders,
+      requestHeadersFromUploadStream,
+      requestPostData,
+    } = nextProps.request || {};
 
-RequestList.displayName = "RequestList";
+    if (!formDataSections && requestHeaders &&
+        requestHeadersFromUploadStream && requestPostData) {
+      getFormDataSections(
+        requestHeaders,
+        requestHeadersFromUploadStream,
+        requestPostData,
+        gNetwork.getString.bind(gNetwork),
+      ).then((newFormDataSections) => {
+        dispatch(Actions.updateRequest(
+          request.id,
+          { formDataSections: newFormDataSections },
+          true,
+        ));
+      });
+    }
+  },
+
+  componentWillUnmount() {
+    Prefs.filters = this.props.activeFilters;
+    this.splitter.removeEventListener("mouseup", this.resize);
+    window.removeEventListener("resize", this.resize);
+  },
 
-RequestList.propTypes = {
-  isEmpty: PropTypes.bool.isRequired,
-};
+  resize() {
+    const { dispatch } = this.props;
+    // Allow requests to settle down first.
+    setNamedTimeout("resize-events", 50, () => {
+      const waterfallHeaderEl =
+        document.querySelector("#requests-menu-waterfall-header-box");
+      if (waterfallHeaderEl) {
+        const { width } = waterfallHeaderEl.getBoundingClientRect();
+        dispatch(Actions.resizeWaterfall(width));
+      }
+    });
+  },
+
+  render() {
+    return (
+      div({ className: "request-list-container" },
+        RequestListHeader(),
+        this.props.isEmpty ? RequestListEmptyNotice() : RequestListContent(),
+      )
+    );
+  }
+});
 
 module.exports = connect(
-  state => ({
-    isEmpty: state.requests.requests.isEmpty()
+  (state) => ({
+    activeFilters: getActiveFilters(state),
+    isEmpty: state.requests.requests.isEmpty(),
+    request: getSelectedRequest(state),
+    networkDetailsOpen: state.ui.networkDetailsOpen,
   })
 )(RequestList);
--- a/devtools/client/netmonitor/components/toolbar.js
+++ b/devtools/client/netmonitor/components/toolbar.js
@@ -111,17 +111,17 @@ function Toolbar({
           type: "filter",
           onChange: setRequestFilterText,
         }),
         button({
           className: toggleButtonClassName.join(" "),
           title: networkDetailsOpen ? COLLPASE_DETAILS_PANE : EXPAND_DETAILS_PANE,
           disabled: networkDetailsToggleDisabled,
           tabIndex: "0",
-          onMouseDown: toggleNetworkDetails,
+          onClick: toggleNetworkDetails,
         }),
       )
     )
   );
 }
 
 Toolbar.displayName = "Toolbar";
 
--- a/devtools/client/netmonitor/har/har-builder.js
+++ b/devtools/client/netmonitor/har/har-builder.js
@@ -25,18 +25,17 @@ const HAR_VERSION = "1.1";
  * This object is responsible for building HAR file. See HAR spec:
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html
  * http://www.softwareishard.com/blog/har-12-spec/
  *
  * @param {Object} options configuration object
  *
  * The following options are supported:
  *
- * - items {Array}: List of Network requests to be exported. It is possible
- *   to use directly: NetMonitorView.RequestsMenu.items
+ * - items {Array}: List of Network requests to be exported.
  *
  * - id {String}: ID of the exported page.
  *
  * - title {String}: Title of the exported page.
  *
  * - includeResponseBodies {Boolean}: Set to true to include HTTP response
  *   bodies in the result data structure.
  */
--- a/devtools/client/netmonitor/har/har-exporter.js
+++ b/devtools/client/netmonitor/har/har-exporter.js
@@ -41,18 +41,17 @@ const HarExporter = {
    *        Configuration object
    *
    * The following options are supported:
    *
    * - includeResponseBodies {Boolean}: If set to true, HTTP response bodies
    *   are also included in the HAR file (can produce significantly bigger
    *   amount of data).
    *
-   * - items {Array}: List of Network requests to be exported. It is possible
-   *   to use directly: NetMonitorView.RequestsMenu.items
+   * - items {Array}: List of Network requests to be exported.
    *
    * - jsonp {Boolean}: If set to true the export format is HARP (support
    *   for JSONP syntax).
    *
    * - jsonpCallback {String}: Default name of JSONP callback (used for
    *   HARP format).
    *
    * - compress {Boolean}: If set to true the final HAR file is zipped.
--- a/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
@@ -6,26 +6,29 @@
 /**
  * Basic tests for exporting Network panel content into HAR format.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
 
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let RequestListContextMenu = windowRequire(
+    "devtools/client/netmonitor/components/request-list-context-menu");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
-  yield RequestsMenu.contextMenu.copyAllAsHar();
+  let contextMenu = new RequestListContextMenu({});
+  yield contextMenu.copyAllAsHar();
 
   let jsonString = SpecialPowers.getClipboardData("text/unicode");
   let har = JSON.parse(jsonString);
 
   // Check out HAR log
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.creator.name, "Firefox", "The creator field must be set");
   is(har.log.browser.name, "Firefox", "The browser field must be set");
--- a/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
@@ -7,30 +7,33 @@
  * Tests for exporting POST data into HAR format.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(
     HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
 
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let RequestListContextMenu = windowRequire(
+    "devtools/client/netmonitor/components/request-list-context-menu");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   // Execute one POST request on the page and wait till its done.
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.executeTest();
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
-  let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
+  let contextMenu = new RequestListContextMenu({});
+  let jsonString = yield contextMenu.copyAllAsHar();
   let har = JSON.parse(jsonString);
 
   // Check out the HAR log.
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.pages.length, 1, "There must be one page");
   is(har.log.entries.length, 1, "There must be one request");
 
   let entry = har.log.entries[0];
--- a/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
@@ -11,18 +11,22 @@ add_task(function* () {
 });
 
 function* throttleUploadTest(actuallyThrottle) {
   let { tab, monitor } = yield initNetMonitor(
     HAR_EXAMPLE_URL + "html_har_post-data-test-page.html");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
-  let { NetMonitorController, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let RequestListContextMenu = windowRequire(
+    "devtools/client/netmonitor/components/request-list-context-menu");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   const size = 4096;
   const uploadSize = actuallyThrottle ? size / 3 : 0;
 
   const request = {
     "NetworkMonitor.throttleData": {
       latencyMean: 0,
       latencyMax: 0,
@@ -30,33 +34,32 @@ function* throttleUploadTest(actuallyThr
       downloadBPSMax: 200000,
       uploadBPSMean: uploadSize,
       uploadBPSMax: uploadSize,
     },
   };
   let client = NetMonitorController.webConsoleClient;
 
   info("sending throttle request");
-  let deferred = promise.defer();
-  client.setPreferences(request, response => {
-    deferred.resolve(response);
+  yield new Promise((resolve) => {
+    client.setPreferences(request, response => {
+      resolve(response);
+    });
   });
-  yield deferred.promise;
-
-  RequestsMenu.lazyUpdate = false;
 
   // Execute one POST request on the page and wait till its done.
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) {
     content.wrappedJSObject.executeTest2(args.size);
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
-  let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
+  let contextMenu = new RequestListContextMenu({});
+  let jsonString = yield contextMenu.copyAllAsHar();
   let har = JSON.parse(jsonString);
 
   // Check out the HAR log.
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.pages.length, 1, "There must be one page");
   is(har.log.entries.length, 1, "There must be one request");
 
   let entry = har.log.entries[0];
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -17,19 +17,17 @@ DevToolsModules(
     'constants.js',
     'events.js',
     'filter-predicates.js',
     'l10n.js',
     'netmonitor-controller.js',
     'netmonitor-view.js',
     'panel.js',
     'prefs.js',
-    'request-list-context-menu.js',
     'request-utils.js',
-    'requests-menu-view.js',
     'sort-predicates.js',
     'store.js',
     'waterfall-background.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 with Files('**'):
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -1,35 +1,35 @@
 /* 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/. */
 
 /* eslint-disable mozilla/reject-some-requires */
-/* globals window, NetMonitorView, gStore, dumpn */
+/* globals window, NetMonitorView, gStore, gNetwork, dumpn */
 
 "use strict";
 
 const promise = require("promise");
 const Services = require("Services");
-const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
-const Editor = require("devtools/client/sourceeditor/editor");
-const {TimelineFront} = require("devtools/shared/fronts/timeline");
-const {Task} = require("devtools/shared/task");
+const { TimelineFront } = require("devtools/shared/fronts/timeline");
+const { CurlUtils } = require("devtools/client/shared/curl");
+const { Task } = require("devtools/shared/task");
 const { ACTIVITY_TYPE } = require("./constants");
 const { EVENTS } = require("./events");
 const { configureStore } = require("./store");
 const Actions = require("./actions/index");
-const { getDisplayedRequestById } = require("./selectors/index");
-const { Prefs } = require("./prefs");
-
-XPCOMUtils.defineConstant(window, "EVENTS", EVENTS);
-XPCOMUtils.defineConstant(window, "ACTIVITY_TYPE", ACTIVITY_TYPE);
-XPCOMUtils.defineConstant(window, "Editor", Editor);
-XPCOMUtils.defineConstant(window, "Prefs", Prefs);
+const {
+  fetchHeaders,
+  formDataURI,
+} = require("./request-utils");
+const {
+  getRequestById,
+  getDisplayedRequestById,
+} = require("./selectors/index");
 
 // Initialize the global Redux store
 window.gStore = configureStore();
 
 /**
  * Object defining the network monitor controller components.
  */
 var NetMonitorController = {
@@ -413,17 +413,18 @@ TargetEventsHandler.prototype = {
    * @param object packet
    *        Packet received from the server.
    */
   _onTabNavigated: function (type, packet) {
     switch (type) {
       case "will-navigate": {
         // Reset UI.
         if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
-          NetMonitorView.RequestsMenu.reset();
+          gStore.dispatch(Actions.batchReset());
+          gStore.dispatch(Actions.clearRequests());
         } else {
           // If the log is persistent, just clear all accumulated timing markers.
           gStore.dispatch(Actions.clearTimingMarkers());
         }
 
         window.emit(EVENTS.TARGET_WILL_NAVIGATE);
         break;
       }
@@ -441,25 +442,28 @@ TargetEventsHandler.prototype = {
     NetMonitorController.shutdownNetMonitor();
   }
 };
 
 /**
  * Functions handling target network events.
  */
 function NetworkEventsHandler() {
+  this.addRequest = this.addRequest.bind(this);
+  this.updateRequest = this.updateRequest.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this);
   this._onRequestHeaders = this._onRequestHeaders.bind(this);
   this._onRequestCookies = this._onRequestCookies.bind(this);
   this._onRequestPostData = this._onRequestPostData.bind(this);
   this._onResponseHeaders = this._onResponseHeaders.bind(this);
   this._onResponseCookies = this._onResponseCookies.bind(this);
   this._onResponseContent = this._onResponseContent.bind(this);
+  this._onSecurityInfo = this._onSecurityInfo.bind(this);
   this._onEventTimings = this._onEventTimings.bind(this);
 }
 
 NetworkEventsHandler.prototype = {
   get client() {
     return NetMonitorController._target.client;
   },
 
@@ -543,203 +547,343 @@ NetworkEventsHandler.prototype = {
       startedDateTime,
       request: { method, url },
       isXHR,
       cause,
       fromCache,
       fromServiceWorker
     } = networkInfo;
 
-    NetMonitorView.RequestsMenu.addRequest(
+    this.addRequest(
       actor, {startedDateTime, method, url, isXHR, cause, fromCache, fromServiceWorker}
     );
     window.emit(EVENTS.NETWORK_EVENT, actor);
   },
 
+  addRequest(id, data) {
+    let { method, url, isXHR, cause, startedDateTime, fromCache,
+          fromServiceWorker } = data;
+
+    gStore.dispatch(Actions.addRequest(
+      id,
+      {
+        // Convert the received date/time string to a unix timestamp.
+        startedMillis: Date.parse(startedDateTime),
+        method,
+        url,
+        isXHR,
+        cause,
+        fromCache,
+        fromServiceWorker,
+      },
+      true
+    ))
+    .then(() => window.emit(EVENTS.REQUEST_ADDED, id));
+  },
+
+  updateRequest: Task.async(function* (id, data) {
+    const action = Actions.updateRequest(id, data, true);
+    yield gStore.dispatch(action);
+    let {
+      responseContent,
+      responseCookies,
+      responseHeaders,
+      requestCookies,
+      requestHeaders,
+      requestPostData,
+    } = action.data;
+    let request = getRequestById(gStore.getState(), action.id);
+
+    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
+      let headers = yield fetchHeaders(
+        requestHeaders, gNetwork.getString.bind(gNetwork));
+      if (headers) {
+        yield gStore.dispatch(Actions.updateRequest(
+          action.id,
+          { requestHeaders: headers },
+          true,
+        ));
+      }
+    }
+
+    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
+      let headers = yield fetchHeaders(
+        responseHeaders, gNetwork.getString.bind(gNetwork));
+      if (headers) {
+        yield gStore.dispatch(Actions.updateRequest(
+          action.id,
+          { responseHeaders: headers },
+          true,
+        ));
+      }
+    }
+
+    if (request && responseContent && responseContent.content) {
+      let { mimeType } = request;
+      let { text, encoding } = responseContent.content;
+      let response = yield gNetwork.getString(text);
+      let payload = {};
+
+      if (mimeType.includes("image/")) {
+        payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
+      }
+
+      responseContent.content.text = response;
+      payload.responseContent = responseContent;
+
+      yield gStore.dispatch(Actions.updateRequest(action.id, payload, true));
+
+      if (mimeType.includes("image/")) {
+        window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+      }
+    }
+
+    // Search the POST data upload stream for request headers and add
+    // them as a separate property, different from the classic headers.
+    if (requestPostData && requestPostData.postData) {
+      let { text } = requestPostData.postData;
+      let postData = yield gNetwork.getString(text);
+      const headers = CurlUtils.getHeadersFromMultipartText(postData);
+      const headersSize = headers.reduce((acc, { name, value }) => {
+        return acc + name.length + value.length + 2;
+      }, 0);
+      let payload = {};
+      requestPostData.postData.text = postData;
+      payload.requestPostData = Object.assign({}, requestPostData);
+      payload.requestHeadersFromUploadStream = { headers, headersSize };
+
+      yield gStore.dispatch(Actions.updateRequest(action.id, payload, true));
+    }
+
+    // Fetch request and response cookies long value.
+    // Actor does not provide full sized cookie value when the value is too long
+    // To display values correctly, we need fetch them in each request.
+    if (requestCookies) {
+      let reqCookies = [];
+      // request store cookies in requestCookies or requestCookies.cookies
+      let cookies = requestCookies.cookies ?
+        requestCookies.cookies : requestCookies;
+      // make sure cookies is iterable
+      if (typeof cookies[Symbol.iterator] === "function") {
+        for (let cookie of cookies) {
+          reqCookies.push(Object.assign({}, cookie, {
+            value: yield gNetwork.getString(cookie.value),
+          }));
+        }
+        if (reqCookies.length) {
+          yield gStore.dispatch(Actions.updateRequest(
+            action.id,
+            { requestCookies: reqCookies },
+            true));
+        }
+      }
+    }
+
+    if (responseCookies) {
+      let resCookies = [];
+      // response store cookies in responseCookies or responseCookies.cookies
+      let cookies = responseCookies.cookies ?
+        responseCookies.cookies : responseCookies;
+      // make sure cookies is iterable
+      if (typeof cookies[Symbol.iterator] === "function") {
+        for (let cookie of cookies) {
+          resCookies.push(Object.assign({}, cookie, {
+            value: yield gNetwork.getString(cookie.value),
+          }));
+        }
+        if (resCookies.length) {
+          yield gStore.dispatch(Actions.updateRequest(
+            action.id,
+            { responseCookies: resCookies },
+            true));
+        }
+      }
+    }
+  }),
+
   /**
    * The "networkEventUpdate" message type handler.
    *
    * @param string type
    *        Message type.
    * @param object packet
    *        The message received from the server.
    * @param object networkInfo
    *        The network request information.
    */
   _onNetworkEventUpdate: function (type, { packet, networkInfo }) {
     let { actor } = networkInfo;
-
     switch (packet.updateType) {
       case "requestHeaders":
         this.webConsoleClient.getRequestHeaders(actor, this._onRequestHeaders);
         window.emit(EVENTS.UPDATING_REQUEST_HEADERS, actor);
         break;
       case "requestCookies":
         this.webConsoleClient.getRequestCookies(actor, this._onRequestCookies);
         window.emit(EVENTS.UPDATING_REQUEST_COOKIES, actor);
         break;
       case "requestPostData":
         this.webConsoleClient.getRequestPostData(actor,
           this._onRequestPostData);
         window.emit(EVENTS.UPDATING_REQUEST_POST_DATA, actor);
         break;
       case "securityInfo":
-        NetMonitorView.RequestsMenu.updateRequest(actor, {
+        this.updateRequest(actor, {
           securityState: networkInfo.securityInfo,
         });
         this.webConsoleClient.getSecurityInfo(actor, this._onSecurityInfo);
         window.emit(EVENTS.UPDATING_SECURITY_INFO, actor);
         break;
       case "responseHeaders":
         this.webConsoleClient.getResponseHeaders(actor,
           this._onResponseHeaders);
         window.emit(EVENTS.UPDATING_RESPONSE_HEADERS, actor);
         break;
       case "responseCookies":
         this.webConsoleClient.getResponseCookies(actor,
           this._onResponseCookies);
         window.emit(EVENTS.UPDATING_RESPONSE_COOKIES, actor);
         break;
       case "responseStart":
-        NetMonitorView.RequestsMenu.updateRequest(actor, {
+        this.updateRequest(actor, {
           httpVersion: networkInfo.response.httpVersion,
           remoteAddress: networkInfo.response.remoteAddress,
           remotePort: networkInfo.response.remotePort,
           status: networkInfo.response.status,
           statusText: networkInfo.response.statusText,
           headersSize: networkInfo.response.headersSize
         });
         window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
         break;
       case "responseContent":
-        NetMonitorView.RequestsMenu.updateRequest(actor, {
+        this.updateRequest(actor, {
           contentSize: networkInfo.response.bodySize,
           transferredSize: networkInfo.response.transferredSize,
           mimeType: networkInfo.response.content.mimeType
         });
         this.webConsoleClient.getResponseContent(actor,
           this._onResponseContent);
         window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
         break;
       case "eventTimings":
-        NetMonitorView.RequestsMenu.updateRequest(actor, {
+        this.updateRequest(actor, {
           totalTime: networkInfo.totalTime
         });
         this.webConsoleClient.getEventTimings(actor, this._onEventTimings);
         window.emit(EVENTS.UPDATING_EVENT_TIMINGS, actor);
         break;
     }
   },
 
   /**
    * Handles additional information received for a "requestHeaders" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onRequestHeaders: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       requestHeaders: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "requestCookies" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onRequestCookies: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       requestCookies: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "requestPostData" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onRequestPostData: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       requestPostData: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "securityInfo" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onSecurityInfo: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       securityInfo: response.securityInfo
     }).then(() => {
       window.emit(EVENTS.RECEIVED_SECURITY_INFO, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "responseHeaders" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onResponseHeaders: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       responseHeaders: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "responseCookies" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onResponseCookies: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       responseCookies: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "responseContent" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onResponseContent: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       responseContent: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
     });
   },
 
   /**
    * Handles additional information received for a "eventTimings" packet.
    *
    * @param object response
    *        The message received from the server.
    */
   _onEventTimings: function (response) {
-    NetMonitorView.RequestsMenu.updateRequest(response.from, {
+    this.updateRequest(response.from, {
       eventTimings: response
     }).then(() => {
       window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
     });
   },
 
   /**
    * Fetches the full text of a LongString.
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -1,102 +1,97 @@
 /* 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/. */
 
-/* eslint-disable mozilla/reject-some-requires */
-/* globals $, gStore, NetMonitorController */
+/* eslint-env browser */
+/* globals gStore, NetMonitorController */
 
 "use strict";
 
-const { RequestsMenuView } = require("./requests-menu-view");
 const { ACTIVITY_TYPE } = require("./constants");
 const { createFactory } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
 
 // Components
 const NetworkDetailsPanel = createFactory(require("./shared/components/network-details-panel"));
+const RequestList = createFactory(require("./components/request-list"));
 const StatisticsPanel = createFactory(require("./components/statistics-panel"));
 const Toolbar = createFactory(require("./components/toolbar"));
 
 /**
  * Object defining the network monitor view components.
  */
-var NetMonitorView = {
+exports.NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function () {
-    this._body = $("#body");
+    this._body = document.querySelector("#body");
 
-    this.networkDetailsPanel = $("#react-network-details-panel-hook");
-
+    this.networkDetailsPanel = document.querySelector(
+      "#react-network-details-panel-hook");
     ReactDOM.render(Provider(
       { store: gStore },
       NetworkDetailsPanel({ toolbox: NetMonitorController._toolbox }),
     ), this.networkDetailsPanel);
 
-    this.statisticsPanel = $("#react-statistics-panel-hook");
+    this.requestList = document.querySelector("#react-request-list-hook");
+    ReactDOM.render(Provider(
+      { store: gStore },
+      RequestList({ toolbox: NetMonitorController._toolbox })
+    ), this.requestList);
 
+    this.statisticsPanel = document.querySelector("#react-statistics-panel-hook");
     ReactDOM.render(Provider(
       { store: gStore },
       StatisticsPanel(),
     ), this.statisticsPanel);
 
-    this.toolbar = $("#react-toolbar-hook");
-
+    this.toolbar = document.querySelector("#react-toolbar-hook");
     ReactDOM.render(Provider(
       { store: gStore },
       Toolbar(),
     ), this.toolbar);
 
-    this.RequestsMenu.initialize(gStore);
-
     // Store watcher here is for observing the statisticsOpen state change.
     // It should be removed once we migrate to react and apply react/redex binding.
     this.unsubscribeStore = gStore.subscribe(storeWatcher(
       false,
       () => gStore.getState().ui.statisticsOpen,
       this.toggleFrontendMode.bind(this)
     ));
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function () {
-    this.RequestsMenu.destroy();
     ReactDOM.unmountComponentAtNode(this.networkDetailsPanel);
+    ReactDOM.unmountComponentAtNode(this.requestList);
     ReactDOM.unmountComponentAtNode(this.statisticsPanel);
     ReactDOM.unmountComponentAtNode(this.toolbar);
     this.unsubscribeStore();
   },
 
   toggleFrontendMode: function () {
     if (gStore.getState().ui.statisticsOpen) {
-      this._body.selectedPanel = $("#react-statistics-panel-hook");
+      this._body.selectedPanel = document.querySelector("#react-statistics-panel-hook");
       NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
     } else {
-      this._body.selectedPanel = $("#inspector-panel");
+      this._body.selectedPanel = document.querySelector("#inspector-panel");
     }
   },
 };
 
 // A smart store watcher to notify store changes as necessary
 function storeWatcher(initialValue, reduceValue, onChange) {
   let currentValue = initialValue;
 
   return () => {
     const newValue = reduceValue();
     if (newValue !== currentValue) {
       onChange();
       currentValue = newValue;
     }
   };
 }
-
-/**
- * Preliminary setup for the NetMonitorView object.
- */
-NetMonitorView.RequestsMenu = new RequestsMenuView();
-
-exports.NetMonitorView = NetMonitorView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -21,17 +21,17 @@
 
     <vbox id="inspector-panel" flex="1">
       <html:div xmlns="http://www.w3.org/1999/xhtml"
                 id="react-toolbar-hook"/>
       <hbox id="network-table-and-sidebar"
             class="devtools-responsive-container"
             flex="1">
         <html:div xmlns="http://www.w3.org/1999/xhtml"
-                  id="network-table"
+                  id="react-request-list-hook"
                   class="devtools-main-content">
         </html:div>
 
         <splitter id="network-inspector-view-splitter"
                   class="devtools-side-splitter"/>
 
         <box id="splitter-adjustable-box"
              hidden="true">
deleted file mode 100644
--- a/devtools/client/netmonitor/request-list-context-menu.js
+++ /dev/null
@@ -1,358 +0,0 @@
-/* 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 NetMonitorController, NetMonitorView, gNetwork */
-
-"use strict";
-
-const Services = require("Services");
-const { Task } = require("devtools/shared/task");
-const { Curl } = require("devtools/client/shared/curl");
-const { gDevTools } = require("devtools/client/framework/devtools");
-const Menu = require("devtools/client/framework/menu");
-const MenuItem = require("devtools/client/framework/menu-item");
-const { L10N } = require("./l10n");
-const {
-  formDataURI,
-  getFormDataSections,
-  getUrlQuery,
-  parseQueryString,
-} = require("./request-utils");
-const Actions = require("./actions/index");
-
-loader.lazyRequireGetter(this, "HarExporter",
-  "devtools/client/netmonitor/har/har-exporter", true);
-
-loader.lazyServiceGetter(this, "clipboardHelper",
-  "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
-
-function RequestListContextMenu() {}
-
-RequestListContextMenu.prototype = {
-  get selectedItem() {
-    return NetMonitorView.RequestsMenu.selectedItem;
-  },
-
-  get items() {
-    return NetMonitorView.RequestsMenu.items;
-  },
-
-  /**
-   * Initialization function, called when the RequestsMenu is initialized.
-   */
-  initialize: function (store) {
-    this.store = store;
-  },
-
-  /**
-   * Handle the context menu opening. Hide items if no request is selected.
-   * Since visible attribute only accept boolean value but the method call may
-   * return undefined, we use !! to force convert any object to boolean
-   */
-  open({ screenX = 0, screenY = 0 } = {}) {
-    let selectedItem = this.selectedItem;
-
-    let menu = new Menu();
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-url",
-      label: L10N.getStr("netmonitor.context.copyUrl"),
-      accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
-      visible: !!selectedItem,
-      click: () => this.copyUrl(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-url-params",
-      label: L10N.getStr("netmonitor.context.copyUrlParams"),
-      accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
-      visible: !!(selectedItem && getUrlQuery(selectedItem.url)),
-      click: () => this.copyUrlParams(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-post-data",
-      label: L10N.getStr("netmonitor.context.copyPostData"),
-      accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
-      visible: !!(selectedItem && selectedItem.requestPostData),
-      click: () => this.copyPostData(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-as-curl",
-      label: L10N.getStr("netmonitor.context.copyAsCurl"),
-      accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
-      visible: !!selectedItem,
-      click: () => this.copyAsCurl(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-request-headers",
-      label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
-      accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
-      visible: !!(selectedItem && selectedItem.requestHeaders),
-      click: () => this.copyRequestHeaders(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "response-menu-context-copy-response-headers",
-      label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
-      accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
-      visible: !!(selectedItem && selectedItem.responseHeaders),
-      click: () => this.copyResponseHeaders(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-response",
-      label: L10N.getStr("netmonitor.context.copyResponse"),
-      accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
-      visible: !!(selectedItem &&
-               selectedItem.responseContent &&
-               selectedItem.responseContent.content.text &&
-               selectedItem.responseContent.content.text.length !== 0),
-      click: () => this.copyResponse(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-image-as-data-uri",
-      label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
-      accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
-      visible: !!(selectedItem &&
-               selectedItem.responseContent &&
-               selectedItem.responseContent.content.mimeType.includes("image/")),
-      click: () => this.copyImageAsDataUri(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-all-as-har",
-      label: L10N.getStr("netmonitor.context.copyAllAsHar"),
-      accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
-      visible: this.items.size > 0,
-      click: () => this.copyAllAsHar(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-save-all-as-har",
-      label: L10N.getStr("netmonitor.context.saveAllAsHar"),
-      accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
-      visible: this.items.size > 0,
-      click: () => this.saveAllAsHar(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-resend",
-      label: L10N.getStr("netmonitor.context.editAndResend"),
-      accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
-      visible: !!(NetMonitorController.supportsCustomRequest &&
-               selectedItem && !selectedItem.isCustom),
-      click: () => NetMonitorView.RequestsMenu.cloneSelectedRequest(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-newtab",
-      label: L10N.getStr("netmonitor.context.newTab"),
-      accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
-      visible: !!selectedItem,
-      click: () => this.openRequestInTab()
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-perf",
-      label: L10N.getStr("netmonitor.context.perfTools"),
-      accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
-      visible: !!NetMonitorController.supportsPerfStats,
-      click: () => this.store.dispatch(Actions.openStatistics(true))
-    }));
-
-    menu.popup(screenX, screenY, NetMonitorController._toolbox);
-    return menu;
-  },
-
-  /**
-   * Opens selected item in a new tab.
-   */
-  openRequestInTab() {
-    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
-    win.openUILinkIn(this.selectedItem.url, "tab", { relatedToCurrent: true });
-  },
-
-  /**
-   * Copy the request url from the currently selected item.
-   */
-  copyUrl() {
-    clipboardHelper.copyString(this.selectedItem.url);
-  },
-
-  /**
-   * Copy the request url query string parameters from the currently
-   * selected item.
-   */
-  copyUrlParams() {
-    let { url } = this.selectedItem;
-    let params = getUrlQuery(url).split("&");
-    let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
-    clipboardHelper.copyString(string);
-  },
-
-  /**
-   * Copy the request form data parameters (or raw payload) from
-   * the currently selected item.
-   */
-  copyPostData: Task.async(function* () {
-    let selected = this.selectedItem;
-
-    // Try to extract any form data parameters.
-    let formDataSections = yield getFormDataSections(
-      selected.requestHeaders,
-      selected.requestHeadersFromUploadStream,
-      selected.requestPostData,
-      gNetwork.getString.bind(gNetwork));
-
-    let params = [];
-    formDataSections.forEach(section => {
-      let paramsArray = parseQueryString(section);
-      if (paramsArray) {
-        params = [...params, ...paramsArray];
-      }
-    });
-
-    let string = params
-      .map(param => param.name + (param.value ? "=" + param.value : ""))
-      .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
-
-    // Fall back to raw payload.
-    if (!string) {
-      let postData = selected.requestPostData.postData.text;
-      string = yield gNetwork.getString(postData);
-      if (Services.appinfo.OS !== "WINNT") {
-        string = string.replace(/\r/g, "");
-      }
-    }
-
-    clipboardHelper.copyString(string);
-  }),
-
-  /**
-   * Copy a cURL command from the currently selected item.
-   */
-  copyAsCurl: Task.async(function* () {
-    let selected = this.selectedItem;
-
-    // Create a sanitized object for the Curl command generator.
-    let data = {
-      url: selected.url,
-      method: selected.method,
-      headers: [],
-      httpVersion: selected.httpVersion,
-      postDataText: null
-    };
-
-    // Fetch header values.
-    for (let { name, value } of selected.requestHeaders.headers) {
-      let text = yield gNetwork.getString(value);
-      data.headers.push({ name: name, value: text });
-    }
-
-    // Fetch the request payload.
-    if (selected.requestPostData) {
-      let postData = selected.requestPostData.postData.text;
-      data.postDataText = yield gNetwork.getString(postData);
-    }
-
-    clipboardHelper.copyString(Curl.generateCommand(data));
-  }),
-
-  /**
-   * Copy the raw request headers from the currently selected item.
-   */
-  copyRequestHeaders() {
-    let rawHeaders = this.selectedItem.requestHeaders.rawHeaders.trim();
-    if (Services.appinfo.OS !== "WINNT") {
-      rawHeaders = rawHeaders.replace(/\r/g, "");
-    }
-    clipboardHelper.copyString(rawHeaders);
-  },
-
-  /**
-   * Copy the raw response headers from the currently selected item.
-   */
-  copyResponseHeaders() {
-    let rawHeaders = this.selectedItem.responseHeaders.rawHeaders.trim();
-    if (Services.appinfo.OS !== "WINNT") {
-      rawHeaders = rawHeaders.replace(/\r/g, "");
-    }
-    clipboardHelper.copyString(rawHeaders);
-  },
-
-  /**
-   * Copy image as data uri.
-   */
-  copyImageAsDataUri() {
-    const { mimeType, text, encoding } = this.selectedItem.responseContent.content;
-
-    gNetwork.getString(text).then(string => {
-      let data = formDataURI(mimeType, encoding, string);
-      clipboardHelper.copyString(data);
-    });
-  },
-
-  /**
-   * Copy response data as a string.
-   */
-  copyResponse() {
-    const { text } = this.selectedItem.responseContent.content;
-
-    gNetwork.getString(text).then(string => {
-      clipboardHelper.copyString(string);
-    });
-  },
-
-  /**
-   * Copy HAR from the network panel content to the clipboard.
-   */
-  copyAllAsHar() {
-    let options = this.getDefaultHarOptions();
-    return HarExporter.copy(options);
-  },
-
-  /**
-   * Save HAR from the network panel content to a file.
-   */
-  saveAllAsHar() {
-    let options = this.getDefaultHarOptions();
-    return HarExporter.save(options);
-  },
-
-  getDefaultHarOptions() {
-    let form = NetMonitorController._target.form;
-    let title = form.title || form.url;
-
-    return {
-      getString: gNetwork.getString.bind(gNetwork),
-      items: this.items,
-      title: title
-    };
-  }
-};
-
-module.exports = RequestListContextMenu;
deleted file mode 100644
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ /dev/null
@@ -1,410 +0,0 @@
-/* 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, dumpn, $, gNetwork, NetMonitorController */
-
-"use strict";
-
-const { Task } = require("devtools/shared/task");
-const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
-const { CurlUtils } = require("devtools/client/shared/curl");
-const { L10N } = require("./l10n");
-const { EVENTS } = require("./events");
-const { createElement, createFactory } = 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 RequestList = createFactory(require("./components/request-list"));
-const RequestListContextMenu = require("./request-list-context-menu");
-const Actions = require("./actions/index");
-const { Prefs } = require("./prefs");
-
-const {
-  fetchHeaders,
-  formDataURI,
-  getFormDataSections,
-} = require("./request-utils");
-
-const {
-  getActiveFilters,
-  getDisplayedRequests,
-  getRequestById,
-  getSelectedRequest,
-  getSortedRequests,
-} = require("./selectors/index");
-
-// ms
-const RESIZE_REFRESH_RATE = 50;
-
-// A smart store watcher to notify store changes as necessary
-function storeWatcher(initialValue, reduceValue, onChange) {
-  let currentValue = initialValue;
-
-  return () => {
-    const oldValue = currentValue;
-    const newValue = reduceValue(currentValue);
-    if (newValue !== oldValue) {
-      currentValue = newValue;
-      onChange(newValue, oldValue);
-    }
-  };
-}
-
-/**
- * Functions handling the requests menu (containing details about each request,
- * like status, method, file, domain, as well as a waterfall representing
- * timing information).
- */
-function RequestsMenuView() {
-  dumpn("RequestsMenuView was instantiated");
-}
-
-RequestsMenuView.prototype = {
-  /**
-   * Initialization function, called when the network monitor is started.
-   */
-  initialize: function (store) {
-    dumpn("Initializing the RequestsMenuView");
-
-    this.store = store;
-
-    this.contextMenu = new RequestListContextMenu();
-    this.contextMenu.initialize(store);
-
-    Prefs.filters.forEach(type => store.dispatch(Actions.toggleRequestFilterType(type)));
-
-    // Watch selection changes
-    this.store.subscribe(storeWatcher(
-      null,
-      () => getSelectedRequest(this.store.getState()),
-      (newSelected, oldSelected) => this.onSelectionUpdate(newSelected, oldSelected)
-    ));
-
-    // Watch the network details panel toggle and resize the waterfall column on change
-    this.store.subscribe(storeWatcher(
-      false,
-      () => this.store.getState().ui.networkDetailsOpen,
-      () => this.onResize()
-    ));
-
-    // Watch the requestHeaders, requestHeadersFromUploadStream and requestPostData
-    // in order to update formDataSections for composing form data
-    this.store.subscribe(storeWatcher(
-      false,
-      (currentRequest) => {
-        const request = getSelectedRequest(this.store.getState());
-        if (!request) {
-          return {};
-        }
-
-        const isChanged = request.requestHeaders !== currentRequest.requestHeaders ||
-        request.requestHeadersFromUploadStream !==
-        currentRequest.requestHeadersFromUploadStream ||
-        request.requestPostData !== currentRequest.requestPostData;
-
-        if (isChanged) {
-          return {
-            id: request.id,
-            requestHeaders: request.requestHeaders,
-            requestHeadersFromUploadStream: request.requestHeadersFromUploadStream,
-            requestPostData: request.requestPostData,
-          };
-        }
-
-        return currentRequest;
-      },
-      (newRequest) => {
-        const {
-          id,
-          requestHeaders,
-          requestHeadersFromUploadStream,
-          requestPostData,
-        } = newRequest;
-
-        if (requestHeaders && requestHeadersFromUploadStream && requestPostData) {
-          getFormDataSections(
-            requestHeaders,
-            requestHeadersFromUploadStream,
-            requestPostData,
-            gNetwork.getString.bind(gNetwork),
-          ).then((formDataSections) => {
-            this.store.dispatch(Actions.updateRequest(
-              id,
-              { formDataSections },
-              true,
-            ));
-          });
-        }
-      },
-    ));
-
-    this._summary = $("#requests-menu-network-summary-button");
-    this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
-
-    this.onResize = this.onResize.bind(this);
-    this._splitter = $("#network-inspector-view-splitter");
-    this._splitter.addEventListener("mouseup", this.onResize);
-    window.addEventListener("resize", this.onResize);
-
-    this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
-
-    this.mountPoint = $("#network-table");
-    ReactDOM.render(createElement(Provider,
-      { store: this.store },
-      RequestList()
-    ), this.mountPoint);
-  },
-
-  /**
-   * Destruction function, called when the network monitor is closed.
-   */
-  destroy() {
-    dumpn("Destroying the RequestsMenuView");
-
-    Prefs.filters = getActiveFilters(this.store.getState());
-
-    this._splitter.removeEventListener("mouseup", this.onResize);
-    window.removeEventListener("resize", this.onResize);
-
-    this.tooltip.destroy();
-
-    ReactDOM.unmountComponentAtNode(this.mountPoint);
-  },
-
-  /**
-   * Resets this container (removes all the networking information).
-   */
-  reset() {
-    this.store.dispatch(Actions.batchReset());
-    this.store.dispatch(Actions.clearRequests());
-  },
-
-  /**
-   * Removes all network requests and closes the network details panel if open.
-   */
-  clear() {
-    this.store.dispatch(Actions.clearRequests());
-  },
-
-  addRequest(id, data) {
-    let { method, url, isXHR, cause, startedDateTime, fromCache,
-          fromServiceWorker } = data;
-
-    // Convert the received date/time string to a unix timestamp.
-    let startedMillis = Date.parse(startedDateTime);
-
-    const action = Actions.addRequest(
-      id,
-      {
-        startedMillis,
-        method,
-        url,
-        isXHR,
-        cause,
-        fromCache,
-        fromServiceWorker,
-      },
-      true
-    );
-
-    this.store.dispatch(action).then(() => window.emit(EVENTS.REQUEST_ADDED, action.id));
-  },
-
-  updateRequest: Task.async(function* (id, data) {
-    const action = Actions.updateRequest(id, data, true);
-    yield this.store.dispatch(action);
-    let {
-      responseContent,
-      responseCookies,
-      responseHeaders,
-      requestCookies,
-      requestHeaders,
-      requestPostData,
-    } = action.data;
-    let request = getRequestById(this.store.getState(), action.id);
-
-    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
-      let headers = yield fetchHeaders(
-        requestHeaders, gNetwork.getString.bind(gNetwork));
-      if (headers) {
-        yield this.store.dispatch(Actions.updateRequest(
-          action.id,
-          { requestHeaders: headers },
-          true,
-        ));
-      }
-    }
-
-    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
-      let headers = yield fetchHeaders(
-        responseHeaders, gNetwork.getString.bind(gNetwork));
-      if (headers) {
-        yield this.store.dispatch(Actions.updateRequest(
-          action.id,
-          { responseHeaders: headers },
-          true,
-        ));
-      }
-    }
-
-    if (request && responseContent && responseContent.content) {
-      let { mimeType } = request;
-      let { text, encoding } = responseContent.content;
-      let response = yield gNetwork.getString(text);
-      let payload = {};
-
-      if (mimeType.includes("image/")) {
-        payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
-      }
-
-      responseContent.content.text = response;
-      payload.responseContent = responseContent;
-
-      yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
-
-      if (mimeType.includes("image/")) {
-        window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
-      }
-    }
-
-    // Search the POST data upload stream for request headers and add
-    // them as a separate property, different from the classic headers.
-    if (requestPostData && requestPostData.postData) {
-      let { text } = requestPostData.postData;
-      let postData = yield gNetwork.getString(text);
-      const headers = CurlUtils.getHeadersFromMultipartText(postData);
-      const headersSize = headers.reduce((acc, { name, value }) => {
-        return acc + name.length + value.length + 2;
-      }, 0);
-      let payload = {};
-      requestPostData.postData.text = postData;
-      payload.requestPostData = Object.assign({}, requestPostData);
-      payload.requestHeadersFromUploadStream = { headers, headersSize };
-
-      yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
-    }
-
-    // Fetch request and response cookies long value.
-    // Actor does not provide full sized cookie value when the value is too long
-    // To display values correctly, we need fetch them in each request.
-    if (requestCookies) {
-      let reqCookies = [];
-      // request store cookies in requestCookies or requestCookies.cookies
-      let cookies = requestCookies.cookies ?
-        requestCookies.cookies : requestCookies;
-      // make sure cookies is iterable
-      if (typeof cookies[Symbol.iterator] === "function") {
-        for (let cookie of cookies) {
-          reqCookies.push(Object.assign({}, cookie, {
-            value: yield gNetwork.getString(cookie.value),
-          }));
-        }
-        if (reqCookies.length) {
-          yield this.store.dispatch(Actions.updateRequest(
-            action.id,
-            { requestCookies: reqCookies },
-            true));
-        }
-      }
-    }
-
-    if (responseCookies) {
-      let resCookies = [];
-      // response store cookies in responseCookies or responseCookies.cookies
-      let cookies = responseCookies.cookies ?
-        responseCookies.cookies : responseCookies;
-      // make sure cookies is iterable
-      if (typeof cookies[Symbol.iterator] === "function") {
-        for (let cookie of cookies) {
-          resCookies.push(Object.assign({}, cookie, {
-            value: yield gNetwork.getString(cookie.value),
-          }));
-        }
-        if (resCookies.length) {
-          yield this.store.dispatch(Actions.updateRequest(
-            action.id,
-            { responseCookies: resCookies },
-            true));
-        }
-      }
-    }
-  }),
-
-  /**
-   * Disable batched updates. Used by tests.
-   */
-  set lazyUpdate(value) {
-    this.store.dispatch(Actions.batchEnable(value));
-  },
-
-  get items() {
-    return getSortedRequests(this.store.getState());
-  },
-
-  get visibleItems() {
-    return getDisplayedRequests(this.store.getState());
-  },
-
-  get itemCount() {
-    return this.store.getState().requests.requests.size;
-  },
-
-  getItemAtIndex(index) {
-    return getSortedRequests(this.store.getState()).get(index);
-  },
-
-  get selectedIndex() {
-    const state = this.store.getState();
-    if (!state.requests.selectedId) {
-      return -1;
-    }
-    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
-  },
-
-  set selectedIndex(index) {
-    const requests = getSortedRequests(this.store.getState());
-    let itemId = null;
-    if (index >= 0 && index < requests.size) {
-      itemId = requests.get(index).id;
-    }
-    this.store.dispatch(Actions.selectRequest(itemId));
-  },
-
-  get selectedItem() {
-    return getSelectedRequest(this.store.getState());
-  },
-
-  set selectedItem(item) {
-    this.store.dispatch(Actions.selectRequest(item ? item.id : null));
-  },
-
-  /**
-   * Updates the network details panel state when something about the selection changes
-   */
-  onSelectionUpdate(newSelected, oldSelected) {
-    if (newSelected) {
-      // Another item just got selected
-      this.store.dispatch(Actions.openNetworkDetails(true));
-    } else {
-      // Selection just got empty
-      this.store.dispatch(Actions.openNetworkDetails(false));
-    }
-  },
-
-  /**
-   * The resize listener for this container's window.
-   */
-  onResize() {
-    // Allow requests to settle down first.
-    setNamedTimeout("resize-events", RESIZE_REFRESH_RATE, () => {
-      const waterfallHeaderEl = $("#requests-menu-waterfall-header-box");
-      if (waterfallHeaderEl) {
-        const { width } = waterfallHeaderEl.getBoundingClientRect();
-        this.store.dispatch(Actions.resizeWaterfall(width));
-      }
-    });
-  }
-};
-
-exports.RequestsMenuView = RequestsMenuView;
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -143,24 +143,21 @@ skip-if = true # bug 1309183, it should 
 [browser_net_security-state.js]
 [browser_net_security-tab-deselect.js]
 [browser_net_security-tab-visibility.js]
 [browser_net_security-warnings.js]
 [browser_net_send-beacon.js]
 [browser_net_send-beacon-other-tab.js]
 [browser_net_simple-init.js]
 [browser_net_simple-request-data.js]
-skip-if = true # Bug 1258809
 [browser_net_simple-request-details.js]
 skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
-skip-if = true # Redundant for React/Redux version
 [browser_net_sort-02.js]
-[browser_net_sort-03.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
 [browser_net_status-codes.js]
 [browser_net_streaming-response.js]
 [browser_net_throttle.js]
 [browser_net_timeline_ticks.js]
 skip-if = true # TODO: fix the test
 [browser_net_timing-division.js]
--- a/devtools/client/netmonitor/test/browser_net_accessibility-01.js
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-01.js
@@ -9,28 +9,27 @@
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on Ubuntu builds running on ec2.
   requestLongerTimeout(2);
 
-  let { document, NetMonitorView, gStore, windowRequire } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
-
-  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  gStore.dispatch(Actions.batchEnable(false));
 
   let count = 0;
   function check(selectedIndex, panelVisibility) {
     info("Performing check " + (count++) + ".");
 
-    is(RequestsMenu.selectedIndex, selectedIndex,
+    let requestItems = Array.from(document.querySelectorAll(".request-list-item"));
+    is(requestItems.findIndex((item) => item.matches(".selected")), selectedIndex,
       "The selected item in the requests menu was incorrect.");
     is(!!document.querySelector(".network-details-panel"), panelVisibility,
       "The network details panel should render correctly.");
   }
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(2);
--- a/devtools/client/netmonitor/test/browser_net_accessibility-02.js
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
@@ -9,26 +9,27 @@
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on Ubuntu builds running on ec2.
   requestLongerTimeout(2);
 
-  let { window, document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { window, document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let count = 0;
   function check(selectedIndex, panelVisibility) {
     info("Performing check " + (count++) + ".");
 
-    is(RequestsMenu.selectedIndex, selectedIndex,
+    let requestItems = Array.from(document.querySelectorAll(".request-list-item"));
+    is(requestItems.findIndex((item) => item.matches(".selected")), selectedIndex,
       "The selected item in the requests menu was incorrect.");
     is(!!document.querySelector(".network-details-panel"), panelVisibility,
       "The network details panel should render correctly.");
   }
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(2);
--- a/devtools/client/netmonitor/test/browser_net_api-calls.js
+++ b/devtools/client/netmonitor/test/browser_net_api-calls.js
@@ -7,33 +7,43 @@
  * Tests whether API call URLs (without a filename) are correctly displayed
  * (including Unicode)
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(API_CALLS_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   const REQUEST_URIS = [
     "http://example.com/api/fileName.xml",
     "http://example.com/api/file%E2%98%A2.xml",
     "http://example.com/api/ascii/get/",
     "http://example.com/api/unicode/%E2%98%A2/",
     "http://example.com/api/search/?q=search%E2%98%A2"
   ];
 
   let wait = waitForNetworkEvents(monitor, 5);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   REQUEST_URIS.forEach(function (uri, index) {
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(index), "GET", uri);
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(index),
+      "GET",
+      uri
+     );
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -5,22 +5,23 @@
 
 /**
  * Bug 863102 - Automatically scroll down upon new network requests.
  */
 add_task(function* () {
   requestLongerTimeout(2);
 
   let { monitor } = yield initNetMonitor(INFINITE_GET_URL);
-  let { $ } = monitor.panelWin;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
   // Wait until the first request makes the empty notice disappear
   yield waitForRequestListToAppear();
 
-  let requestsContainer = $(".requests-menu-contents");
+  let requestsContainer = document.querySelector(".requests-menu-contents");
   ok(requestsContainer, "Container element exists as expected.");
 
   // (1) Check that the scroll position is maintained at the bottom
   // when the requests overflow the vertical size of the container.
   yield waitForRequestsToOverflowContainer();
   yield waitForScroll();
   ok(true, "Scrolled to bottom on overflow.");
 
@@ -41,34 +42,36 @@ add_task(function* () {
   requestsContainer.scrollTop = requestsContainer.scrollHeight;
   ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
   yield waitForNetworkEvents(monitor, 8);
   yield waitForScroll();
   ok(true, "Still scrolled to bottom.");
 
   // (4) Now select an item in the list and check that additional requests
   // do not change the scroll position.
-  monitor.panelWin.NetMonitorView.RequestsMenu.selectedIndex = 0;
+  gStore.dispatch(Actions.selectRequestByIndex(0));
   yield waitForNetworkEvents(monitor, 8);
   yield waitSomeTime();
   is(requestsContainer.scrollTop, 0, "Did not scroll.");
 
   // Done: clean up.
   return teardown(monitor);
 
   function waitForRequestListToAppear() {
     info("Waiting until the empty notice disappears and is replaced with the list");
-    return waitUntil(() => !!$(".requests-menu-contents"));
+    return waitUntil(() => !!document.querySelector(".requests-menu-contents"));
   }
 
   function* waitForRequestsToOverflowContainer() {
     info("Waiting for enough requests to overflow the container");
     while (true) {
       info("Waiting for one network request");
       yield waitForNetworkEvents(monitor, 1);
+      console.log(requestsContainer.scrollHeight);
+      console.log(requestsContainer.clientHeight)
       if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
         info("The list is long enough, returning");
         return;
       }
     }
   }
 
   function scrolledToBottom(element) {
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -11,42 +11,50 @@ const BROTLI_REQUESTS = 1;
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(BROTLI_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, BROTLI_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
     "GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", {
       status: 200,
       statusText: "Connected",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 10),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 64),
       time: true
     });
 
   wait = waitForDOM(document, "#response-panel .editor-mount iframe");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   let [editorFrame] = yield wait;
 
   yield once(editorFrame, "DOMContentLoaded");
   yield waitForDOM(editorFrame.contentDocument, ".CodeMirror-code");
   yield testResponse("br");
 
   yield teardown(monitor);
 
--- a/devtools/client/netmonitor/test/browser_net_cached-status.js
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -6,20 +6,24 @@
 /**
  * Tests if cached requests have the correct status code
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL, null, true);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu, NetworkDetails } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   const REQUEST_DATA = [
     {
       method: "GET",
       uri: STATUS_CODES_SJS + "?sts=ok&cached",
       details: {
         status: 200,
         statusText: "OK",
@@ -84,21 +88,25 @@ add_task(function* () {
   info("Performing requests #1...");
   yield performRequestsAndWait();
 
   info("Performing requests #2...");
   yield performRequestsAndWait();
 
   let index = 0;
   for (let request of REQUEST_DATA) {
-    let item = RequestsMenu.getItemAtIndex(index);
-
     info("Verifying request #" + index);
-    yield verifyRequestItemTarget(RequestsMenu, item,
-      request.method, request.uri, request.details);
+    yield verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(index),
+      request.method,
+      request.uri,
+      request.details
+    );
 
     index++;
   }
 
   yield teardown(monitor);
 
   function* performRequestsAndWait() {
     let wait = waitForNetworkEvents(monitor, 3);
--- a/devtools/client/netmonitor/test/browser_net_cause.js
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -83,33 +83,43 @@ add_task(function* () {
 
   // the initNetMonitor function clears the network request list after the
   // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
   // and only then load the real thing from CAUSE_URL - we want to catch
   // all the requests the page is making, not only the XHRs.
   // We can't use about:blank here, because initNetMonitor checks that the
   // page has actually made at least one request.
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
-  let { $, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
 
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
   let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
   tab.linkedBrowser.loadURI(CAUSE_URL);
   yield wait;
 
-  is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length,
+  is(gStore.getState().requests.requests.size, EXPECTED_REQUESTS.length,
     "All the page events should be recorded.");
 
   EXPECTED_REQUESTS.forEach((spec, i) => {
     let { method, url, causeType, causeUri, stack } = spec;
 
-    let requestItem = RequestsMenu.getItemAtIndex(i);
-    verifyRequestItemTarget(RequestsMenu, requestItem,
-      method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
+    let requestItem = getSortedRequests(gStore.getState()).get(i);
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      requestItem,
+      method,
+      url,
+      { cause: { type: causeType, loadingDocumentUri: causeUri } }
     );
 
     let { stacktrace } = requestItem.cause;
     let stackLen = stacktrace ? stacktrace.length : 0;
 
     if (stack) {
       ok(stacktrace, `Request #${i} has a stacktrace`);
       ok(stackLen > 0,
@@ -129,17 +139,18 @@ add_task(function* () {
         });
       }
     } else {
       is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
     }
   });
 
   // Sort the requests by cause and check the order
-  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button"));
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-cause-button"));
   let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort();
   expectedOrder.forEach((expectedCause, i) => {
-    const cause = RequestsMenu.getItemAtIndex(i).cause.type;
+    const cause = getSortedRequests(gStore.getState()).get(i).cause.type;
     is(cause, expectedCause, `The request #${i} has the expected cause after sorting`);
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_cause_redirect.js
+++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js
@@ -14,25 +14,28 @@ add_task(function* () {
     { status: 302, hasStack: true },
     // Serves HTTPS, sets the Strict-Transport-Security header, no stack
     { status: 200, hasStack: false },
     // Second request to HTTP redirects to HTTPS internally
     { status: 200, hasStack: true },
   ];
 
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
   yield performRequests(2, HSTS_SJS);
   yield wait;
 
   EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => {
-    let item = RequestsMenu.getItemAtIndex(i);
+    let item = getSortedRequests(gStore.getState()).get(i);
 
     is(item.status, status, `Request #${i} has the expected status`);
 
     let { stacktrace } = item.cause;
     let stackLen = stacktrace ? stacktrace.length : 0;
 
     if (hasStack) {
       ok(stacktrace, `Request #${i} has a stacktrace`);
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -6,72 +6,74 @@
 /**
  * Tests if the clear button empties the request menu.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let detailsPane = document.querySelector("#details-pane");
   let detailsPanelToggleButton = document.querySelector(".network-details-panel-toggle");
   let clearButton = document.querySelector("#requests-menu-clear-button");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   // Make sure we start in a sane state
-  assertNoRequestState(RequestsMenu, detailsPanelToggleButton);
+  assertNoRequestState();
 
   // Load one request and assert it shows up in the list
-  let networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
+  let networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
   tab.linkedBrowser.reload();
   yield networkEvent;
 
   assertSingleRequestState();
 
   // Click clear and make sure the requests are gone
   EventUtils.sendMouseEvent({ type: "click" }, clearButton);
   assertNoRequestState();
 
   // Load a second request and make sure they still show up
-  networkEvent = monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT);
+  networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
   tab.linkedBrowser.reload();
   yield networkEvent;
 
   assertSingleRequestState();
 
   // Make sure we can now open the network details panel
-  EventUtils.sendMouseEvent({ type: "mousedown" }, detailsPanelToggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, detailsPanelToggleButton);
 
   ok(document.querySelector(".network-details-panel") &&
     !detailsPanelToggleButton.classList.contains("pane-collapsed"),
     "The details pane should be visible after clicking the toggle button.");
 
   // Click clear and make sure the details pane closes
   EventUtils.sendMouseEvent({ type: "click" }, clearButton);
+
   assertNoRequestState();
-  ok(!document.querySelector(".network-details-panel") &&
-    detailsPanelToggleButton.classList.contains("pane-collapsed"),
+  ok(!document.querySelector(".network-details-panel"),
     "The details pane should not be visible clicking 'clear'.");
 
   return teardown(monitor);
 
   /**
    * Asserts the state of the network monitor when one request has loaded
    */
   function assertSingleRequestState() {
-    is(RequestsMenu.itemCount, 1,
+    is(gStore.getState().requests.requests.size, 1,
       "The request menu should have one item at this point.");
     is(detailsPanelToggleButton.hasAttribute("disabled"), false,
       "The pane toggle button should be enabled after a request is made.");
   }
 
   /**
    * Asserts the state of the network monitor when no requests have loaded
    */
   function assertNoRequestState() {
-    is(RequestsMenu.itemCount, 0,
+    is(gStore.getState().requests.requests.size, 0,
       "The request menu should be empty at this point.");
     is(detailsPanelToggleButton.hasAttribute("disabled"), true,
       "The pane toggle button should be disabled when the request menu is cleared.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -4,69 +4,68 @@
 "use strict";
 
 /**
  * Tests whether complex request params and payload sent via POST are
  * displayed correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
-
   let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { L10N } = windowRequire("devtools/client/netmonitor/l10n");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#params-tab").click();
+  gStore.dispatch(Actions.selectRequestByIndex(0));
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
   yield wait;
   testParamsTab1("a", '""', '{ "foo": "bar" }', '""');
 
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
-  RequestsMenu.selectedIndex = 1;
+  gStore.dispatch(Actions.selectRequestByIndex(1));
   yield wait;
   testParamsTab1("a", '"b"', '{ "foo": "bar" }', '""');
 
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
-  RequestsMenu.selectedIndex = 2;
+  gStore.dispatch(Actions.selectRequestByIndex(2));
   yield wait;
   testParamsTab1("a", '"b"', "foo", '"bar"');
 
   wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
-  RequestsMenu.selectedIndex = 3;
+  gStore.dispatch(Actions.selectRequestByIndex(3));
   yield wait;
   testParamsTab2("a", '""', '{ "foo": "bar" }', "js");
 
   wait = waitForDOM(document, "#params-panel tr:not(.tree-section).treeRow", 2);
-  RequestsMenu.selectedIndex = 4;
+  gStore.dispatch(Actions.selectRequestByIndex(4));
   yield wait;
   testParamsTab2("a", '"b"', '{ "foo": "bar" }', "js");
 
   // Wait for all tree sections and editor updated by react
   let waitSections = waitForDOM(document, "#params-panel .tree-section", 2);
   let waitEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
-  RequestsMenu.selectedIndex = 5;
+  gStore.dispatch(Actions.selectRequestByIndex(5));
   let [, editorFrames] = yield Promise.all([waitSections, waitEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   testParamsTab2("a", '"b"', "?foo=bar", "text");
 
-  RequestsMenu.selectedIndex = 6;
+  gStore.dispatch(Actions.selectRequestByIndex(6));
   testParamsTab3();
 
   yield teardown(monitor);
 
   function testParamsTab1(queryStringParamName, queryStringParamValue,
                           formDataParamName, formDataParamValue) {
     let tabpanel = document.querySelector("#params-panel");
 
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -8,84 +8,120 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=xml", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=xml",
+    {
       status: 200,
       statusText: "OK",
       type: "xml",
       fullMimeType: "text/xml; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
-    "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(1),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=css",
+    {
       status: 200,
       statusText: "OK",
       type: "css",
       fullMimeType: "text/css; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(2),
-    "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(2),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=js",
+    {
       status: 200,
       statusText: "OK",
       type: "js",
       fullMimeType: "application/javascript; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(3),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(3),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=json",
+    {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "application/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(4),
-    "GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(4),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=bogus", {
       status: 404,
       statusText: "Not Found",
       type: "html",
       fullMimeType: "text/html; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(5),
-    "GET", TEST_IMAGE, {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(5),
+    "GET",
+    TEST_IMAGE, {
       fuzzyUrl: true,
       status: 200,
       statusText: "OK",
       type: "png",
       fullMimeType: "image/png",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(6),
-    "GET", CONTENT_TYPE_SJS + "?fmt=gzip", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(6),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=gzip", {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
     });
@@ -222,34 +258,34 @@ add_task(function* () {
       }
     }
   }
 
   function* selectIndexAndWaitForEditor(index) {
     let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
       let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
-      RequestsMenu.selectedIndex = index;
+      gStore.dispatch(Actions.selectRequestByIndex(index));
       document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
-      RequestsMenu.selectedIndex = index;
+      gStore.dispatch(Actions.selectRequestByIndex(index));
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function* selectIndexAndWaitForJSONView(index) {
     let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".treeTable");
-    RequestsMenu.selectedIndex = index;
+    gStore.dispatch(Actions.selectRequestByIndex(index));
     yield waitDOM;
   }
 
   function* selectIndexAndWaitForImageView(index) {
     let tabpanel = document.querySelector("#response-panel");
     let waitDOM = waitForDOM(tabpanel, ".response-image");
-    RequestsMenu.selectedIndex = index;
+    gStore.dispatch(Actions.selectRequestByIndex(index));
     let [imageNode] = yield waitDOM;
     yield once(imageNode, "load");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -36,31 +36,35 @@ add_task(function* () {
     header("X-Custom-Header-2: 8.8.8.8"),
     header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
     header("Referer: " + CURL_URL),
     header("Connection: keep-alive"),
     header("Pragma: no-cache"),
     header("Cache-Control: no-cache")
   ];
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
     content.wrappedJSObject.performRequest(url);
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyAsCurl();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-as-curl").click();
   }, function validate(result) {
     if (typeof result !== "string") {
       return false;
     }
 
     // Different setups may produce the same command, but with the
     // parameters in a different order in the commandline (which is fine).
     // Here we confirm that the commands are the same even in that case.
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -6,45 +6,50 @@
 /**
  * Tests if copying a request's request/response headers works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
 
+  let requestItem = getSortedRequests(gStore.getState()).get(0);
   let { method, httpVersion, status, statusText } = requestItem;
 
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
+
   const EXPECTED_REQUEST_HEADERS = [
     `${method} ${SIMPLE_URL} ${httpVersion}`,
     "Host: example.com",
     "User-Agent: " + navigator.userAgent + "",
     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
     "Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
     "Accept-Encoding: gzip, deflate",
     "Connection: keep-alive",
     "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache"
   ].join("\n");
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyRequestHeaders();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-request-headers").click();
   }, function validate(result) {
     // Sometimes, a "Cookie" header is left over from other tests. Remove it:
     result = String(result).replace(/Cookie: [^\n]+\n/, "");
     return result === EXPECTED_REQUEST_HEADERS;
   });
   info("Clipboard contains the currently selected item's request headers.");
 
   const EXPECTED_RESPONSE_HEADERS = [
@@ -52,18 +57,24 @@ add_task(function* () {
     "Last-Modified: Sun, 3 May 2015 11:11:11 GMT",
     "Content-Type: text/html",
     "Content-Length: 465",
     "Connection: close",
     "Server: httpd.js",
     "Date: Sun, 3 May 2015 11:11:11 GMT"
   ].join("\n");
 
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
+
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyResponseHeaders();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#response-menu-context-copy-response-headers").click();
   }, function validate(result) {
     // Fake the "Last-Modified" and "Date" headers because they will vary:
     result = String(result)
       .replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
       .replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
     return result === EXPECTED_RESPONSE_HEADERS;
   });
   info("Clipboard contains the currently selected item's response headers.");
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -6,30 +6,32 @@
 /**
  * Tests if copying an image as data uri works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(5);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[5]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[5]);
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyImageAsDataUri();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-image-as-data-uri").click();
   }, TEST_IMAGE_DATA_URI);
 
   ok(true, "Clipboard contains the currently selected image as data uri.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_params.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_params.js
@@ -6,93 +6,102 @@
 /**
  * Tests whether copying a request item's parameters works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(0);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("{ \"foo\": \"bar\" }");
+  yield testCopyUrlParamsHidden(0, false);
+  yield testCopyUrlParams(0, "a");
+  yield testCopyPostDataHidden(0, false);
+  yield testCopyPostData(0, "{ \"foo\": \"bar\" }");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(1);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a=b");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("{ \"foo\": \"bar\" }");
+  yield testCopyUrlParamsHidden(1, false);
+  yield testCopyUrlParams(1, "a=b");
+  yield testCopyPostDataHidden(1, false);
+  yield testCopyPostData(1, "{ \"foo\": \"bar\" }");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(2);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a=b");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("foo=bar");
+  yield testCopyUrlParamsHidden(2, false);
+  yield testCopyUrlParams(2, "a=b");
+  yield testCopyPostDataHidden(2, false);
+  yield testCopyPostData(2, "foo=bar");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(3);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("{ \"foo\": \"bar\" }");
+  yield testCopyUrlParamsHidden(3, false);
+  yield testCopyUrlParams(3, "a");
+  yield testCopyPostDataHidden(3, false);
+  yield testCopyPostData(3, "{ \"foo\": \"bar\" }");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(4);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a=b");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("{ \"foo\": \"bar\" }");
+  yield testCopyUrlParamsHidden(4, false);
+  yield testCopyUrlParams(4, "a=b");
+  yield testCopyPostDataHidden(4, false);
+  yield testCopyPostData(4, "{ \"foo\": \"bar\" }");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(5);
-  yield testCopyUrlParamsHidden(false);
-  yield testCopyUrlParams("a=b");
-  yield testCopyPostDataHidden(false);
-  yield testCopyPostData("?foo=bar");
+  yield testCopyUrlParamsHidden(5, false);
+  yield testCopyUrlParams(5, "a=b");
+  yield testCopyPostDataHidden(5, false);
+  yield testCopyPostData(5, "?foo=bar");
 
-  RequestsMenu.selectedItem = RequestsMenu.getItemAtIndex(6);
-  yield testCopyUrlParamsHidden(true);
-  yield testCopyPostDataHidden(true);
+  yield testCopyUrlParamsHidden(6, true);
+  yield testCopyPostDataHidden(6, true);
 
   return teardown(monitor);
 
-  function testCopyUrlParamsHidden(hidden) {
-    let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
-    let copyUrlParamsNode = allMenuItems.find(item =>
-      item.id === "request-menu-context-copy-url-params");
-    is(copyUrlParamsNode.visible, !hidden,
+  function testCopyUrlParamsHidden(index, hidden) {
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      document.querySelectorAll(".request-list-item")[index]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[index]);
+    let copyUrlParamsNode = monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-url-params");
+    is(!!copyUrlParamsNode, !hidden,
       "The \"Copy URL Parameters\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
-  function* testCopyUrlParams(queryString) {
+  function* testCopyUrlParams(index, queryString) {
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      document.querySelectorAll(".request-list-item")[index]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[index]);
     yield waitForClipboardPromise(function setup() {
-      RequestsMenu.contextMenu.copyUrlParams();
+      monitor._toolbox.doc
+        .querySelector("#request-menu-context-copy-url-params").click();
     }, queryString);
     ok(true, "The url query string copied from the selected item is correct.");
   }
 
-  function testCopyPostDataHidden(hidden) {
-    let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
-    let copyPostDataNode = allMenuItems.find(item =>
-      item.id === "request-menu-context-copy-post-data");
-    is(copyPostDataNode.visible, !hidden,
+  function testCopyPostDataHidden(index, hidden) {
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      document.querySelectorAll(".request-list-item")[index]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[index]);
+    let copyPostDataNode = monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-post-data");
+    is(!!copyPostDataNode, !hidden,
       "The \"Copy POST Data\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
-  function* testCopyPostData(postData) {
+  function* testCopyPostData(index, postData) {
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      document.querySelectorAll(".request-list-item")[index]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[index]);
     yield waitForClipboardPromise(function setup() {
-      RequestsMenu.contextMenu.copyPostData();
+      monitor._toolbox.doc
+        .querySelector("#request-menu-context-copy-post-data").click();
     }, postData);
     ok(true, "The post data string copied from the selected item is correct.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_response.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -8,28 +8,30 @@
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   const EXPECTED_RESULT = '{ "greeting": "Hello JSON!" }';
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
+  let { document } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, CONTENT_TYPE_WITHOUT_CACHE_REQUESTS);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(3);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[3]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[3]);
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyResponse();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-response").click();
   }, EXPECTED_RESULT);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
@@ -8,30 +8,32 @@
  */
 
 const SVG_URL = EXAMPLE_URL + "dropmarker.svg";
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CURL_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
+  let { document } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, SVG_URL, function* (url) {
     content.wrappedJSObject.performRequest(url);
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyImageAsDataUri();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-image-as-data-uri").click();
   }, function check(text) {
     return text.startsWith("data:") && !/undefined/.test(text);
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_url.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_url.js
@@ -6,26 +6,33 @@
 /**
  * Tests if copying a request's url works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1);
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
+
+  let requestItem = getSortedRequests(gStore.getState()).get(0);
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.contextMenu.copyUrl();
+    // Context menu is appending in XUL document, we must select it from
+    // _toolbox.doc
+    monitor._toolbox.doc
+      .querySelector("#request-menu-context-copy-url").click();
   }, requestItem.url);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_cors_requests.js
+++ b/devtools/client/netmonitor/test/browser_net_cors_requests.js
@@ -4,30 +4,42 @@
 "use strict";
 
 /**
  * Test that CORS preflight requests are displayed by network monitor
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CORS_URL);
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 1);
 
   info("Performing a CORS request");
   let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
   yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
     content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
   });
 
   info("Waiting until the requests appear in netmonitor");
   yield wait;
 
   info("Checking the preflight and flight methods");
-  ["OPTIONS", "POST"].forEach((method, i) => {
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
-      method, requestUrl);
+  ["OPTIONS", "POST"].forEach((method, index) => {
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(index),
+      method,
+      requestUrl
+    );
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -8,32 +8,33 @@
  */
 
 const { CurlUtils } = require("devtools/client/shared/curl");
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CURL_UTILS_URL);
   info("Starting test... ");
 
-  let { NetMonitorView, gNetwork } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire, gNetwork } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 3);
   yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
     content.wrappedJSObject.performRequests(url);
   });
   yield wait;
 
   let requests = {
-    get: RequestsMenu.getItemAtIndex(0),
-    post: RequestsMenu.getItemAtIndex(1),
-    multipart: RequestsMenu.getItemAtIndex(2),
-    multipartForm: RequestsMenu.getItemAtIndex(3)
+    get: getSortedRequests(gStore.getState()).get(0),
+    post: getSortedRequests(gStore.getState()).get(1),
+    multipart: getSortedRequests(gStore.getState()).get(2),
+    multipartForm: getSortedRequests(gStore.getState()).get(3),
   };
 
   let data = yield createCurlData(requests.get, gNetwork);
   testFindHeader(data);
 
   data = yield createCurlData(requests.post, gNetwork);
   testIsUrlEncodedRequest(data);
   testWritePostDataTextParams(data);
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -6,37 +6,50 @@
 /**
  * Tests if cyrillic text is rendered correctly in the source editor.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=txt", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=txt",
+    {
       status: 200,
       statusText: "DA DA DA"
-    });
+    }
+  );
 
+  wait = waitForDOM(document, "#headers-panel");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  yield wait;
   wait = waitForDOM(document, "#response-panel .editor-mount iframe");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-line").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -7,35 +7,45 @@
  * Tests if cyrillic text is rendered correctly in the source editor
  * when loaded directly from an HTML page.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CYRILLIC_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CYRILLIC_URL, {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CYRILLIC_URL,
+    {
       status: 200,
       statusText: "OK"
     });
 
-  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
+  wait = waitForDOM(document, "#headers-panel");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+    document.querySelectorAll(".request-list-item")[0]);
+  yield wait;
+  wait = waitForDOM(document, "#response-panel .editor-mount iframe");
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   let text = editor.contentDocument
           .querySelector(".CodeMirror-code").textContent;
 
   ok(text.includes("\u0411\u0440\u0430\u0442\u0430\u043d"),
     "The text shown in the source editor is correct.");
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -124,46 +124,47 @@ const EXPECTED_REQUESTS = [
       fuzzyUrl: true,
       status: 101,
       statusText: "Switching Protocols",
     }
   }
 ];
 
 add_task(function* () {
-  let Actions = require("devtools/client/netmonitor/actions/index");
+  let { monitor } = yield initNetMonitor(FILTERING_URL);
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  let { monitor } = yield initNetMonitor(FILTERING_URL);
-  let { gStore } = monitor.panelWin;
+  gStore.dispatch(Actions.batchEnable(false));
 
   function setFreetextFilter(value) {
     gStore.dispatch(Actions.setRequestFilterText(value));
   }
 
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  RequestsMenu.lazyUpdate = false;
-
   let wait = waitForNetworkEvents(monitor, 9);
   loadCommonFrameScript();
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
+    document.querySelectorAll(".request-list-item")[0]);
 
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), null,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
-      "The network details panel should render correctly.");
+    "The network details panel should render correctly.");
 
   // First test with single filters...
   testFilterButtons(monitor, "all");
   testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-filter-html-button"));
   testFilterButtons(monitor, "html");
@@ -301,38 +302,50 @@ add_task(function* () {
   testFilterButtonsCustom(monitor, [0, 1, 1, 0, 0, 0, 0, 0, 0, 1]);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-filter-all-button"));
   testFilterButtons(monitor, "all");
   testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   yield teardown(monitor);
 
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
   function testContents(visibility) {
-    isnot(RequestsMenu.selectedItem, null,
+    isnot(getSelectedRequest(gStore.getState()), undefined,
       "There should still be a selected item after filtering.");
-    is(RequestsMenu.selectedIndex, 0,
+    is(getSelectedIndex(gStore.getState()), 0,
       "The first item should be still selected after filtering.");
-    is(!!document.querySelector(".network-details-panel"), true,
-      "The network details panel should render correctly.");
 
-    const items = RequestsMenu.items;
-    const visibleItems = RequestsMenu.visibleItems;
+    const items = getSortedRequests(gStore.getState());
+    const visibleItems = getDisplayedRequests(gStore.getState());
 
     is(items.size, visibility.length,
       "There should be a specific amount of items in the requests menu.");
     is(visibleItems.size, visibility.filter(e => e).length,
       "There should be a specific amount of visible items in the requests menu.");
 
     for (let i = 0; i < visibility.length; i++) {
       let itemId = items.get(i).id;
       let shouldBeVisible = !!visibility[i];
       let isThere = visibleItems.some(r => r.id == itemId);
       is(isThere, shouldBeVisible,
         `The item at index ${i} has visibility=${shouldBeVisible}`);
 
       if (shouldBeVisible) {
         let { method, url, data } = EXPECTED_REQUESTS[i];
-        verifyRequestItemTarget(RequestsMenu, items.get(i), method, url, data);
+        verifyRequestItemTarget(
+          document,
+          getDisplayedRequests(gStore.getState()),
+          getSortedRequests(gStore.getState()).get(i),
+          method,
+          url,
+          data
+        );
       }
     }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_filter-02.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-02.js
@@ -131,32 +131,37 @@ const EXPECTED_REQUESTS = [
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on Ubuntu builds running on ec2.
   requestLongerTimeout(2);
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 9);
   loadCommonFrameScript();
   yield performRequestsInContent(REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
+    document.querySelectorAll(".request-list-item")[0]);
 
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), null,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testFilterButtons(monitor, "all");
   testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   info("Testing html filtering.");
@@ -188,26 +193,33 @@ add_task(function* () {
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-filter-all-button"));
   testFilterButtons(monitor, "all");
   testContents([1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
                 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]);
 
   yield teardown(monitor);
 
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
   function testContents(visibility) {
-    isnot(RequestsMenu.selectedItem, null,
+    isnot(getSelectedRequest(gStore.getState()), null,
       "There should still be a selected item after filtering.");
-    is(RequestsMenu.selectedIndex, 0,
+    is(getSelectedIndex(gStore.getState()), 0,
       "The first item should be still selected after filtering.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after filtering.");
 
-    const items = RequestsMenu.items;
-    const visibleItems = RequestsMenu.visibleItems;
+    const items = getSortedRequests(gStore.getState());
+    const visibleItems = getDisplayedRequests(gStore.getState());
 
     is(items.size, visibility.length,
       "There should be a specific amount of items in the requests menu.");
     is(visibleItems.size, visibility.filter(e => e).length,
       "There should be a specific amount of visible items in the requests menu.");
 
     for (let i = 0; i < visibility.length; i++) {
       let itemId = items.get(i).id;
@@ -216,14 +228,21 @@ add_task(function* () {
       is(isThere, shouldBeVisible,
         `The item at index ${i} has visibility=${shouldBeVisible}`);
     }
 
     for (let i = 0; i < EXPECTED_REQUESTS.length; i++) {
       let { method, url, data } = EXPECTED_REQUESTS[i];
       for (let j = i; j < visibility.length; j += EXPECTED_REQUESTS.length) {
         if (visibility[j]) {
-          verifyRequestItemTarget(RequestsMenu, items.get(j), method, url, data);
+          verifyRequestItemTarget(
+            document,
+            getDisplayedRequests(gStore.getState()),
+            getSortedRequests(gStore.getState()).get(i),
+            method,
+            url,
+            data
+          );
         }
       }
     }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_filter-03.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-03.js
@@ -22,39 +22,44 @@ const REQUESTS_WITH_MEDIA = BASIC_REQUES
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on Ubuntu builds running on ec2.
   requestLongerTimeout(2);
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   // The test assumes that the first HTML request here has a longer response
   // body than the other HTML requests performed later during the test.
   let requests = Cu.cloneInto(REQUESTS_WITH_MEDIA, {});
   let newres = "res=<p>" + new Array(10).join(Math.random(10)) + "</p>";
   requests[0].url = requests[0].url.replace("res=undefined", newres);
 
   loadCommonFrameScript();
 
   let wait = waitForNetworkEvents(monitor, 7);
   yield performRequestsInContent(requests);
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
+    document.querySelectorAll(".request-list-item")[0]);
 
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), null,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testFilterButtons(monitor, "all");
   testContents([0, 1, 2, 3, 4, 5, 6], 7, 0);
 
   info("Sorting by size, ascending.");
@@ -93,22 +98,29 @@ add_task(function* () {
 
   function resetSorting() {
     EventUtils.sendMouseEvent({ type: "click" },
       document.querySelector("#requests-menu-waterfall-button"));
     EventUtils.sendMouseEvent({ type: "click" },
       document.querySelector("#requests-menu-size-button"));
   }
 
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
   function testContents(order, visible, selection) {
-    isnot(RequestsMenu.selectedItem, null,
+    isnot(getSelectedRequest(gStore.getState()), null,
       "There should still be a selected item after filtering.");
-    is(RequestsMenu.selectedIndex, selection,
+    is(getSelectedIndex(gStore.getState()), selection,
       "The first item should be still selected after filtering.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after filtering.");
 
-    is(RequestsMenu.items.length, order.length,
+    is(getSortedRequests(gStore.getState()).length, order.length,
       "There should be a specific amount of items in the requests menu.");
-    is(RequestsMenu.visibleItems.length, visible,
+    is(getDisplayedRequests(gStore.getState()).length, visible,
       "There should be a specific amount of visible items in the requests menu.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_filter-04.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-04.js
@@ -30,20 +30,21 @@ const REQUESTS_WITH_MEDIA_AND_FLASH_AND_
 ]);
 
 add_task(function* () {
   Services.prefs.setCharPref("devtools.netmonitor.filters", '["js", "bogus"]');
 
   let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
-  let { Prefs, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   is(Prefs.filters.length, 2,
     "All filter types were loaded as an array from the preferences.");
   is(Prefs.filters[0], "js",
     "The first filter type is correct.");
   is(Prefs.filters[1], "bogus",
     "The second filter type is invalid, but loaded anyway.");
 
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -8,49 +8,48 @@
  */
 
 add_task(function* () {
   requestLongerTimeout(2);
 
   let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
-  let { $, NetMonitorView, gStore, windowRequire } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { getDisplayedRequestsSummary } =
     windowRequire("devtools/client/netmonitor/selectors/index");
   let { L10N } = windowRequire("devtools/client/netmonitor/l10n");
   let { PluralForm } = windowRequire("devtools/shared/plural-form");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
   testStatus();
 
   for (let i = 0; i < 2; i++) {
     info(`Performing requests in batch #${i}`);
     let wait = waitForNetworkEvents(monitor, 8);
     yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests('{ "getMedia": true, "getFlash": true }');
     });
     yield wait;
 
     testStatus();
 
     let buttons = ["html", "css", "js", "xhr", "fonts", "images", "media", "flash"];
     for (let button of buttons) {
-      let buttonEl = $(`#requests-menu-filter-${button}-button`);
+      let buttonEl = document.querySelector(`#requests-menu-filter-${button}-button`);
       EventUtils.sendMouseEvent({ type: "click" }, buttonEl);
       testStatus();
     }
   }
 
   yield teardown(monitor);
 
   function testStatus() {
-    let value = $("#requests-menu-network-summary-button").textContent;
+    let value = document.querySelector("#requests-menu-network-summary-button").textContent;
     info("Current summary: " + value);
 
     let state = gStore.getState();
     let totalRequestsCount = state.requests.requests.size;
     let requestsSummary = getDisplayedRequestsSummary(state);
     info(`Current requests: ${requestsSummary.count} of ${totalRequestsCount}.`);
 
     if (!totalRequestsCount || !requestsSummary.count) {
--- a/devtools/client/netmonitor/test/browser_net_frame.js
+++ b/devtools/client/netmonitor/test/browser_net_frame.js
@@ -152,47 +152,58 @@ add_task(function* () {
 
   // the initNetMonitor function clears the network request list after the
   // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
   // and only then load the real thing from TOP_URL - we want to catch
   // all the requests the page is making, not only the XHRs.
   // We can't use about:blank here, because initNetMonitor checks that the
   // page has actually made at least one request.
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   tab.linkedBrowser.loadURI(TOP_URL, null, null);
 
   yield waitForNetworkEvents(monitor, REQUEST_COUNT);
 
-  is(RequestsMenu.itemCount, REQUEST_COUNT,
+  is(gStore.getState().requests.requests.size, REQUEST_COUNT,
     "All the page events should be recorded.");
 
   // While there is a defined order for requests in each document separately, the requests
   // from different documents may interleave in various ways that change per test run, so
   // there is not a single order when considering all the requests together.
   let currentTop = 0;
   let currentSub = 0;
   for (let i = 0; i < REQUEST_COUNT; i++) {
-    let requestItem = RequestsMenu.getItemAtIndex(i);
+    let requestItem = getSortedRequests(gStore.getState()).get(i);
 
     let itemUrl = requestItem.url;
     let itemCauseUri = requestItem.cause.loadingDocumentUri;
     let spec;
     if (itemUrl == SUB_URL || itemCauseUri == SUB_URL) {
       spec = EXPECTED_REQUESTS_SUB[currentSub++];
     } else {
       spec = EXPECTED_REQUESTS_TOP[currentTop++];
     }
     let { method, url, causeType, causeUri, stack } = spec;
 
-    verifyRequestItemTarget(RequestsMenu, requestItem,
-      method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      requestItem,
+      method,
+      url,
+      { cause: { type: causeType, loadingDocumentUri: causeUri } }
     );
 
     let { stacktrace } = requestItem.cause;
     let stackLen = stacktrace ? stacktrace.length : 0;
 
     if (stack) {
       ok(stacktrace, `Request #${i} has a stacktrace`);
       ok(stackLen > 0,
--- a/devtools/client/netmonitor/test/browser_net_header-docs.js
+++ b/devtools/client/netmonitor/test/browser_net_header-docs.js
@@ -8,34 +8,35 @@ const HeadersMDN = require("devtools/cli
 /**
  * Tests if "Learn More" links are correctly displayed
  * next to headers.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let origItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = origItem;
-
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelectorAll(".request-list-item")[0]);
 
-  testShowLearnMore(origItem);
+  testShowLearnMore(getSortedRequests(gStore.getState()).get(0));
 
   return teardown(monitor);
 
   /*
    * Tests that a "Learn More" button is only shown if
    * and only if a header is documented in MDN.
    */
   function testShowLearnMore(data) {
--- a/devtools/client/netmonitor/test/browser_net_html-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_html-preview.js
@@ -6,28 +6,28 @@
 /**
  * Tests if html responses show and properly populate a "Preview" tab.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
 
   ok(document.querySelector("#headers-tab[aria-selected=true]"),
     "The headers tab in the details panel should be selected.");
   ok(!document.querySelector("#preview-tab"),
     "The preview tab should be hidden for non html responses.");
   ok(!document.querySelector("#preview-panel"),
     "The preview panel is hidden for non html responses.");
@@ -37,19 +37,17 @@ add_task(function* () {
   document.querySelector("#preview-tab").click();
 
   ok(document.querySelector("#preview-tab[aria-selected=true]"),
     "The preview tab in the details panel should be selected.");
   ok(document.querySelector("#preview-panel"),
     "The preview panel should be visible now.");
 
   let iframe = document.querySelector("#preview-panel iframe");
-  console.log(123)
   yield once(iframe, "DOMContentLoaded");
-  console.log(123)
 
   ok(iframe,
     "There should be a response preview iframe available.");
   ok(iframe.contentDocument,
     "The iframe's content document should be available.");
   is(iframe.contentDocument.querySelector("blink").textContent, "Not Found",
     "The iframe's content document should be loaded and correct.");
 
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -3,26 +3,26 @@
 
 "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,
-        gStore } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire, NetMonitorController } =
+    monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForEvents();
   yield performRequests();
   yield wait;
 
   info("Checking the image thumbnail when all items are shown.");
   checkImageThumbnail();
 
@@ -58,16 +58,16 @@ add_task(function* () {
   }
 
   function* reloadAndPerformRequests() {
     yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
     yield performRequests();
   }
 
   function checkImageThumbnail() {
-    is($all(".requests-menu-icon[data-type=thumbnail]").length, 1,
+    is(document.querySelectorAll(".requests-menu-icon[data-type=thumbnail]").length, 1,
       "There should be only one image request with a thumbnail displayed.");
-    is($(".requests-menu-icon[data-type=thumbnail]").src, TEST_IMAGE_DATA_URI,
+    is(document.querySelector(".requests-menu-icon[data-type=thumbnail]").src, TEST_IMAGE_DATA_URI,
       "The image requests-menu-icon thumbnail is displayed correctly.");
-    is($(".requests-menu-icon[data-type=thumbnail]").hidden, false,
+    is(document.querySelector(".requests-menu-icon[data-type=thumbnail]").hidden, false,
       "The image requests-menu-icon thumbnail should not be hidden.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -8,94 +8,99 @@ const IMAGE_TOOLTIP_REQUESTS = 1;
 
 /**
  * Tests if image responses show a popup in the requests menu when hovered.
  */
 add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
   info("Starting test... ");
 
-  let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
-    monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = true;
+  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+  let toolboxDoc = monitor._toolbox.doc;
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS);
   let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
-
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a few requests were made...");
-  yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(0));
+  yield showTooltipAndVerify(toolboxDoc,
+    document.querySelectorAll(".request-list-item")[0]);
 
   // Hide tooltip before next test, to avoid the situation that tooltip covers
   // the icon for the request of the next test.
   info("Checking the image thumbnail gets hidden...");
-  yield hideTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(0));
+  yield hideTooltipAndVerify(monitor._toolbox.doc,
+    document.querySelectorAll(".request-list-item")[0]);
 
   // +1 extra document reload
   onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
   onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   info("Reloading the debuggee and performing all requests again...");
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a reload.");
-  yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(1));
+  yield showTooltipAndVerify(toolboxDoc,
+    document.querySelectorAll(".request-list-item")[1]);
 
   info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
-  let requestsMenuEl = $(".requests-menu-contents");
-  let onHidden = RequestsMenu.tooltip.once("hidden");
-  EventUtils.synthesizeMouse(requestsMenuEl, 0, 0, {type: "mouseout"}, monitor.panelWin);
-  yield onHidden;
+  let requestsListContents = document.querySelector(".requests-menu-contents");
+  EventUtils.synthesizeMouse(requestsListContents, 0, 0, { type: "mouseout" }, monitor.panelWin);
+  yield waitUntil(() => !toolboxDoc.querySelector(".tooltip-container.tooltip-visible"));
 
   yield teardown(monitor);
 
   function performRequests() {
     return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests();
     });
   }
 
   /**
-   * Show a tooltip on the {requestItem} and verify that it was displayed
+   * Show a tooltip on the {target} and verify that it was displayed
    * with the expected content.
    */
-  function* showTooltipAndVerify(tooltip, requestItem) {
-    let anchor = $(".requests-menu-file", getItemTarget(RequestsMenu, requestItem));
-    yield showTooltipOn(tooltip, anchor);
+  function* showTooltipAndVerify(toolboxDoc, target) {
+    let anchor = target.querySelector(".requests-menu-file");
+    yield showTooltipOn(toolboxDoc, anchor);
 
     info("Tooltip was successfully opened for the image request.");
-    is(tooltip.panel.querySelector("img").src, TEST_IMAGE_DATA_URI,
+    is(toolboxDoc.querySelector(".tooltip-panel img").src, TEST_IMAGE_DATA_URI,
       "The tooltip's image content is displayed correctly.");
   }
 
   /**
    * Trigger a tooltip over an element by sending mousemove event.
    * @return a promise that resolves when the tooltip is shown
    */
-  function showTooltipOn(tooltip, element) {
-    let onShown = tooltip.once("shown");
+  function* showTooltipOn(toolboxDoc, element) {
     let win = element.ownerDocument.defaultView;
-    EventUtils.synthesizeMouseAtCenter(element, {type: "mousemove"}, win);
-    return onShown;
+    EventUtils.synthesizeMouseAtCenter(element, { type: "mousemove" }, win);
+    yield waitUntil(() => toolboxDoc.querySelector(".tooltip-panel img"));
   }
 
   /**
-   * Hide a tooltip on the {requestItem} and verify that it was closed.
+   * Hide a tooltip on the {target} and verify that it was closed.
    */
-  function* hideTooltipAndVerify(tooltip, requestItem) {
+  function* hideTooltipAndVerify(toolboxDoc, target) {
     // Hovering over the "method" column hides the tooltip.
-    let anchor = $(".requests-menu-method", getItemTarget(RequestsMenu, requestItem));
+    let anchor = target.querySelector(".requests-menu-method");
+    let win = anchor.ownerDocument.defaultView;
+    EventUtils.synthesizeMouseAtCenter(anchor, { type: "mousemove" }, win);
 
-    let onTooltipHidden = tooltip.once("hidden");
-    let win = anchor.ownerDocument.defaultView;
-    EventUtils.synthesizeMouseAtCenter(anchor, {type: "mousemove"}, win);
-    yield onTooltipHidden;
-
+    yield waitUntil(() => !toolboxDoc.querySelector(".tooltip-container.tooltip-visible"));
     info("Tooltip was successfully closed.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_json-b64.js
+++ b/devtools/client/netmonitor/test/browser_net_json-b64.js
@@ -7,31 +7,32 @@
  * Tests if JSON responses encoded in base64 are handled correctly.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
   let { tab, monitor } = yield initNetMonitor(JSON_B64_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
   let tabpanel = document.querySelector("#response-panel");
 
   is(tabpanel.querySelector(".response-error-header") === null, true,
     "The response error header doesn't have the intended visibility.");
   let jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
   is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -12,42 +12,52 @@ add_task(function* () {
 
   let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
   info("Starting test... ");
 
   // This is receiving over 80 KB of json and will populate over 6000 items
   // in a variables view instance. Debug builds are slow.
   requestLongerTimeout(4);
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=json-long",
+    {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStr("networkMenu.sizeKB",
         L10N.numberWithDecimals(85975 / 1024, 2)),
       time: true
     });
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
     let tabpanel = document.querySelector("#response-panel");
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -7,39 +7,49 @@
  * Tests if malformed JSON responses are handled correctly.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
   let { tab, monitor } = yield initNetMonitor(JSON_MALFORMED_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=json-malformed",
+    {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8"
     });
 
   wait = waitForDOM(document, "#response-panel .editor-mount iframe");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   let [editor] = yield wait;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
   let tabpanel = document.querySelector("#response-panel");
   is(tabpanel.querySelector(".response-error-header") === null, false,
     "The response error header doesn't have the intended visibility.");
   is(tabpanel.querySelector(".response-error-header").textContent,
--- a/devtools/client/netmonitor/test/browser_net_json-null.js
+++ b/devtools/client/netmonitor/test/browser_net_json-null.js
@@ -8,20 +8,24 @@ const { L10N } = require("devtools/clien
 /**
  * Tests if JSON responses containing null values are properly displayed.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(JSON_BASIC_URL + "?name=null");
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   yield openResponsePanel(document);
@@ -64,15 +68,14 @@ function checkResponsePanelDisplaysJSON(
 }
 
 /**
  * Open the netmonitor details panel and switch to the response tab.
  * Returns a promise that will resolve when the response panel DOM element is available.
  */
 function openResponsePanel(document) {
   let onReponsePanelReady = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent(
-    { type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle")
-  );
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".network-details-panel-toggle"));
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   return onReponsePanelReady;
 }
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -8,41 +8,51 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=json-custom-mime",
+    {
       status: 200,
       statusText: "OK",
       type: "x-bigcorp-json",
       fullMimeType: "text/x-bigcorp-json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
     let tabpanel = document.querySelector("#response-panel");
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -8,41 +8,51 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json-text-mime", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=json-text-mime",
+    {
       status: 200,
       statusText: "OK",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
   testResponseTab();
 
   yield teardown(monitor);
 
   function testResponseTab() {
     let tabpanel = document.querySelector("#response-panel");
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -8,56 +8,72 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSONP_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun",
+    {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
-    "GET", CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(1),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad",
+    {
       status: 200,
       statusText: "OK",
       type: "json",
       fullMimeType: "text/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 54),
       time: true
     });
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
   testResponseTab("$_0123Fun", "\"Hello JSONP!\"");
 
   wait = waitForDOM(document, "#response-panel .tree-section");
-  RequestsMenu.selectedIndex = 1;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[1]);
   yield wait;
 
   testResponseTab("$_4567Sad", "\"Hello weird JSONP!\"");
 
   yield teardown(monitor);
 
   function testResponseTab(func, greeting) {
     let tabpanel = document.querySelector("#response-panel");
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -12,37 +12,47 @@ const HTML_LONG_URL = CONTENT_TYPE_SJS +
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   // This test could potentially be slow because over 100 KB of stuff
   // is going to be requested and displayed in the source editor.
   requestLongerTimeout(2);
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, HTML_LONG_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "GET",
+    CONTENT_TYPE_SJS + "?fmt=html-long",
+    {
       status: 200,
       statusText: "OK"
     });
 
   let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   let [editor] = yield waitDOM;
   yield once(editor, "DOMContentLoaded");
   yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
 
   let text = editor.contentDocument
         .querySelector(".CodeMirror-line").textContent;
 
   ok(text.match(/^<p>/), "The text shown in the source editor is incorrect.");
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -6,32 +6,41 @@
 /**
  * Tests if Open in new tab works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test...");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1);
   });
   yield wait;
 
-  let requestItem = RequestsMenu.getItemAtIndex(0);
-  RequestsMenu.selectedItem = requestItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "contextmenu" },
+    document.querySelectorAll(".request-list-item")[0]);
 
   let onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
-  RequestsMenu.contextMenu.openRequestInTab();
+  // Context menu is appending in XUL document, we must select it from
+  // _toolbox.doc
+  monitor._toolbox.doc
+    .querySelector("#request-menu-context-newtab").click();
   yield onTabOpen;
 
   ok(true, "A new tab has been opened");
 
   yield teardown(monitor);
 
   gBrowser.removeCurrentTab();
 });
--- a/devtools/client/netmonitor/test/browser_net_page-nav.js
+++ b/devtools/client/netmonitor/test/browser_net_page-nav.js
@@ -7,17 +7,18 @@
  * Tests if page navigation ("close", "navigate", etc.) triggers an appropriate
  * action in the network monitor.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { EVENTS } = monitor.panelWin;
+  let { windowRequire } = monitor.panelWin;
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
 
   yield testNavigate();
   yield testNavigateBack();
   yield testClose();
 
   function* testNavigate() {
     info("Navigating forward...");
 
--- a/devtools/client/netmonitor/test/browser_net_pane-collapse.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
@@ -6,43 +6,44 @@
 /**
  * Tests if the network monitor panes collapse properly.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, Prefs } = monitor.panelWin;
+  let { document, windowRequire } = monitor.panelWin;
+  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
   let detailsPaneToggleButton = document.querySelector(".network-details-panel-toggle");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   ok(!document.querySelector(".network-details-panel") &&
      detailsPaneToggleButton.classList.contains("pane-collapsed"),
     "The details panel should initially be hidden.");
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, detailsPaneToggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, detailsPaneToggleButton);
 
   is(~~(document.querySelector(".network-details-panel").clientWidth),
     Prefs.networkDetailsWidth,
     "The details panel has an incorrect width.");
   ok(document.querySelector(".network-details-panel") &&
      !detailsPaneToggleButton.classList.contains("pane-collapsed"),
     "The details panel should at this point be visible.");
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, detailsPaneToggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, detailsPaneToggleButton);
 
   ok(!document.querySelector(".network-details-panel") &&
      detailsPaneToggleButton.classList.contains("pane-collapsed"),
     "The details panel should not be visible after collapsing.");
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, detailsPaneToggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, detailsPaneToggleButton);
 
   is(~~(document.querySelector(".network-details-panel").clientWidth),
     Prefs.networkDetailsWidth,
     "The details panel has an incorrect width after uncollapsing.");
   ok(document.querySelector(".network-details-panel") &&
      !detailsPaneToggleButton.classList.contains("pane-collapsed"),
     "The details panel should be visible again after uncollapsing.");
 
--- a/devtools/client/netmonitor/test/browser_net_pane-toggle.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -6,67 +6,79 @@
 /**
  * Tests if toggling the details pane works as expected.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  let { NETWORK_EVENT } = monitor.panelWin.EVENTS;
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let {
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let toggleButton = document.querySelector(".network-details-panel-toggle");
 
   is(toggleButton.hasAttribute("disabled"), true,
     "The pane toggle button should be disabled when the frontend is opened.");
   is(toggleButton.classList.contains("pane-collapsed"), true,
     "The pane toggle button should indicate that the details pane is " +
     "collapsed when the frontend is opened.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The details pane should be hidden when the frontend is opened.");
-  is(RequestsMenu.selectedItem, null,
+  is(getSelectedRequest(gStore.getState()), null,
     "There should be no selected item in the requests menu.");
 
-  let networkEvent = monitor.panelWin.once(NETWORK_EVENT);
+  let networkEvent = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
   tab.linkedBrowser.reload();
   yield networkEvent;
 
   is(toggleButton.hasAttribute("disabled"), false,
     "The pane toggle button should be enabled after the first request.");
   is(toggleButton.classList.contains("pane-collapsed"), true,
     "The pane toggle button should still indicate that the details pane is " +
     "collapsed after the first request.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The details pane should still be hidden after the first request.");
-  is(RequestsMenu.selectedItem, null,
+  is(getSelectedRequest(gStore.getState()), null,
     "There should still be no selected item in the requests menu.");
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, toggleButton);
 
   is(toggleButton.hasAttribute("disabled"), false,
     "The pane toggle button should still be enabled after being pressed.");
   is(toggleButton.classList.contains("pane-collapsed"), false,
     "The pane toggle button should now indicate that the details pane is " +
     "not collapsed anymore after being pressed.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The details pane should not be hidden after toggle button was pressed.");
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), null,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
 
-  EventUtils.sendMouseEvent({ type: "mousedown" }, toggleButton);
+  EventUtils.sendMouseEvent({ type: "click" }, toggleButton);
 
   is(toggleButton.hasAttribute("disabled"), false,
     "The pane toggle button should still be enabled after being pressed again.");
   is(toggleButton.classList.contains("pane-collapsed"), true,
     "The pane toggle button should now indicate that the details pane is " +
     "collapsed after being pressed again.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The details pane should now be hidden after the toggle button was pressed again.");
-  is(RequestsMenu.selectedItem, null,
+  is(getSelectedRequest(gStore.getState()), null,
     "There should now be no selected item in the requests menu.");
 
   yield teardown(monitor);
+
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
 });
--- a/devtools/client/netmonitor/test/browser_net_persistent_logs.js
+++ b/devtools/client/netmonitor/test/browser_net_persistent_logs.js
@@ -7,40 +7,39 @@
  * Tests if the network monitor leaks on initialization and sudden destruction.
  * You can also use this initialization format as a template for other tests.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SINGLE_GET_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, windowRequire } = monitor.panelWin;
 
   Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
 
   yield reloadAndWait();
 
-  is(RequestsMenu.itemCount, 2,
-    "The request menu should have two items at this point.");
+  is(document.querySelectorAll(".request-list-item").length, 2,
+    "The request list should have two items at this point.");
 
   yield reloadAndWait();
 
   // Since the reload clears the log, we still expect two requests in the log
-  is(RequestsMenu.itemCount, 2,
-    "The request menu should still have two items at this point.");
+  is(document.querySelectorAll(".request-list-item").length, 2,
+    "The request list should still have two items at this point.");
 
   // Now we toggle the persistence logs on
   Services.prefs.setBoolPref("devtools.webconsole.persistlog", true);
 
   yield reloadAndWait();
 
   // Since we togged the persistence logs, we expect four items after the reload
-  is(RequestsMenu.itemCount, 4,
-    "The request menu should now have four items at this point.");
+  is(document.querySelectorAll(".request-list-item").length, 4,
+    "The request list should now have four items at this point.");
 
   Services.prefs.setBoolPref("devtools.webconsole.persistlog", false);
   return teardown(monitor);
 
   /**
    * Reload the page and wait for 2 GET requests. Race-free.
    */
   function reloadAndWait() {
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -11,58 +11,74 @@ add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   // Set a higher panel height in order to get full CodeMirror content
   Services.prefs.setIntPref("devtools.toolbox.footer.height", 400);
 
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
-    "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded", {
+  verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(0),
+    "POST",
+    SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded",
+    {
       status: 200,
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
-    "POST", SIMPLE_SJS + "?foo=bar&baz=42&type=multipart", {
+   verifyRequestItemTarget(
+    document,
+    getDisplayedRequests(gStore.getState()),
+    getSortedRequests(gStore.getState()).get(1),
+    "POST",
+    SIMPLE_SJS + "?foo=bar&baz=42&type=multipart",
+    {
       status: 200,
       statusText: "Och Aye",
       type: "plain",
       fullMimeType: "text/plain; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
       time: true
     });
 
   // Wait for all tree sections updated by react
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#params-tab").click();
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
   yield wait;
   yield testParamsTab("urlencoded");
 
   // Wait for all tree sections and editor updated by react
   let waitForSections = waitForDOM(document, "#params-panel .tree-section", 2);
   let waitForEditor = waitForDOM(document, "#params-panel .editor-mount iframe");
-  RequestsMenu.selectedIndex = 1;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[1]);
   let [, editorFrames] = yield Promise.all([waitForSections, waitForEditor]);
   yield once(editorFrames[0], "DOMContentLoaded");
   yield waitForDOM(editorFrames[0].contentDocument, ".CodeMirror-code");
   yield testParamsTab("multipart");
 
   return teardown(monitor);
 
   function* testParamsTab(type) {
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -9,32 +9,37 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#params-tab").click();
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
   yield wait;
 
   let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -9,32 +9,33 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#headers-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#headers-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#headers-tab"));
   yield wait;
 
   let tabpanel = document.querySelector("#headers-panel");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel").length, 3,
     "There should be 3 header sections displayed in this tabpanel.");
 
   is(tabpanel.querySelectorAll(".tree-section .treeLabel")[2].textContent,
@@ -53,17 +54,18 @@ add_task(function* () {
     "The first request header value was incorrect.");
   is(labels[labels.length - 1].textContent, "custom-header",
     "The second request header name was incorrect.");
   is(values[values.length - 1].textContent, "\"hello world!\"",
     "The second request header value was incorrect.");
 
   // Wait for all tree sections updated by react
   wait = waitForDOM(document, "#params-panel .tree-section");
-  document.querySelector("#params-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
   yield wait;
 
   tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The params tree view should be displayed.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The post data shouldn't be displayed.");
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -9,32 +9,33 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_JSON_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#params-panel .tree-section");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#params-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
   yield wait;
 
   let tabpanel = document.querySelector("#params-panel");
 
   ok(tabpanel.querySelector(".treeTable"),
     "The request params doesn't have the indended visibility.");
   ok(tabpanel.querySelector(".editor-mount") === null,
     "The request post data doesn't have the indended visibility.");
--- a/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -8,34 +8,32 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  ok(monitor.panelWin.Prefs,
-    "Should have a preferences object available on the panel window.");
+  let { windowRequire } = monitor.panelWin;
+  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
 
   testL10N();
   testPrefs();
 
   return teardown(monitor);
 
   function testL10N() {
     is(typeof L10N.getStr("netmonitor.security.enabled"), "string",
       "The getStr() method didn't return a valid string.");
     is(typeof L10N.getFormatStr("networkMenu.totalMS", "foo"), "string",
       "The getFormatStr() method didn't return a valid string.");
   }
 
   function testPrefs() {
-    let { Prefs } = monitor.panelWin;
-
     is(Prefs.networkDetailsWidth,
       Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
       "Getting a pref should work correctly.");
 
     let previousValue = Prefs.networkDetailsWidth;
     let bogusValue = ~~(Math.random() * 100);
     Prefs.networkDetailsWidth = bogusValue;
     is(Prefs.networkDetailsWidth,
--- a/devtools/client/netmonitor/test/browser_net_raw_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -6,39 +6,39 @@
 /**
  * Tests if showing raw headers works.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let origItem = RequestsMenu.getItemAtIndex(0);
-
   wait = waitForDOM(document, ".headers-overview");
-  RequestsMenu.selectedItem = origItem;
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
   yield wait;
 
   wait = waitForDOM(document, ".raw-headers-container textarea", 2);
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelectorAll(".headers-summary .tool-button")[1]);
   yield wait;
 
-  testShowRawHeaders(origItem);
+  testShowRawHeaders(getSortedRequests(gStore.getState()).get(0));
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelectorAll(".headers-summary .tool-button")[1]);
 
   testHideRawHeaders(document);
 
   return teardown(monitor);
 
--- a/devtools/client/netmonitor/test/browser_net_reload-button.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-button.js
@@ -6,20 +6,20 @@
 /**
  * Tests if the empty-requests reload button works.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document } = monitor.panelWin;
 
   let wait = waitForNetworkEvents(monitor, 1);
-  let button = document.querySelector("#requests-menu-reload-notice-button");
-  button.click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-reload-notice-button"));
   yield wait;
 
-  is(RequestsMenu.itemCount, 1, "The request menu should have one item after reloading");
+  is(document.querySelectorAll(".request-list-item").length, 1,
+    "The request list should have one item after reloading");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_reload-markers.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-markers.js
@@ -6,17 +6,18 @@
 /**
  * Tests if the empty-requests reload button works.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, EVENTS } = monitor.panelWin;
+  let { document, windowRequire } = monitor.panelWin;
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
   let button = document.querySelector("#requests-menu-reload-notice-button");
   button.click();
 
   let markers = [];
 
   monitor.panelWin.on(EVENTS.TIMELINE_EVENT, (_, marker) => {
     markers.push(marker);
   });
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -8,20 +8,24 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   // Perform first batch of requests.
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
@@ -48,19 +52,24 @@ add_task(function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   verifyRequest(1);
 
   return teardown(monitor);
 
-  function verifyRequest(offset) {
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(offset),
-      "GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
+  function verifyRequest(index) {
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(index),
+      "GET",
+      CONTENT_TYPE_SJS + "?fmt=json-long",
+      {
         status: 200,
         statusText: "OK",
         type: "json",
         fullMimeType: "text/json; charset=utf-8",
         size: L10N.getFormatStr("networkMenu.sizeKB",
           L10N.numberWithDecimals(85975 / 1024, 2)),
         time: true
       });
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -11,53 +11,55 @@ const ADD_QUERY = "t1=t2";
 const ADD_HEADER = "Test-header: true";
 const ADD_UA_HEADER = "User-Agent: Custom-Agent";
 const ADD_POSTDATA = "&t3=t4";
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
-  let { panelWin } = monitor;
-  let { document, gStore, NetMonitorView, windowRequire } = panelWin;
+  let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { RequestsMenu } = NetMonitorView;
+  let {
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 0, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
-  let origItem = RequestsMenu.getItemAtIndex(0);
+  let origItem = getSortedRequests(gStore.getState()).get(0);
 
-  RequestsMenu.selectedItem = origItem;
+  gStore.dispatch(Actions.selectRequest(origItem.id));
 
   // add a new custom request cloned from selected request
   gStore.dispatch(Actions.cloneSelectedRequest());
 
   testCustomForm(origItem);
 
-  let customItem = RequestsMenu.selectedItem;
+  let customItem = getSelectedRequest(gStore.getState());
   testCustomItem(customItem, origItem);
 
   // edit the custom request
   yield editCustomForm();
   // FIXME: reread the customItem, it's been replaced by a new object (immutable!)
-  customItem = RequestsMenu.selectedItem;
+  customItem = getSelectedRequest(gStore.getState());
   testCustomItemChanged(customItem, origItem);
 
   // send the new request
   wait = waitForNetworkEvents(monitor, 0, 1);
   gStore.dispatch(Actions.sendCustomRequest());
   yield wait;
 
-  let sentItem = RequestsMenu.selectedItem;
+  let sentItem = getSelectedRequest(gStore.getState());
   testSentRequest(sentItem, origItem);
 
   return teardown(monitor);
 
   function testCustomItem(item, orig) {
     is(item.method, orig.method, "item is showing the same method as original request");
     is(item.url, orig.url, "item is showing the same URL as original request");
   }
@@ -68,16 +70,17 @@ add_task(function* () {
 
     is(url, expectedUrl, "menu item is updated to reflect url entered in form");
   }
 
   /*
    * Test that the New Request form was populated correctly
    */
   function testCustomForm(data) {
+    yield waitUntil(() => document.querySelector(".custom-request-panel"));
     is(document.getElementById("custom-method-value").value, data.method,
        "new request form showing correct method");
 
     is(document.getElementById("custom-url-value").value, data.url,
        "new request form showing correct url");
 
     let query = document.getElementById("custom-query-value");
     is(query.value, "foo=bar\nbaz=42\ntype=urlencoded",
@@ -92,17 +95,17 @@ add_task(function* () {
     is(postData.value, data.requestPostData.postData.text,
        "new request form showing correct post data");
   }
 
   /*
    * Add some params and headers to the request form
    */
   function* editCustomForm() {
-    panelWin.focus();
+    monitor.panelWin.focus();
 
     let query = document.getElementById("custom-query-value");
     let queryFocus = once(query, "focus", false);
     // Bug 1195825: Due to some unexplained dark-matter with promise,
     // focus only works if delayed by one tick.
     executeSoon(() => query.focus());
     yield queryFocus;
 
@@ -149,12 +152,12 @@ add_task(function* () {
 
     is(data.requestPostData.postData.text,
        origData.requestPostData.postData.text + ADD_POSTDATA,
        "post data added to sent request");
   }
 
   function type(string) {
     for (let ch of string) {
-      EventUtils.synthesizeKey(ch, {}, panelWin);
+      EventUtils.synthesizeKey(ch, {}, monitor.panelWin);
     }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -7,46 +7,49 @@
  * Tests if resending a CORS request avoids the security checks and doesn't send
  * a preflight OPTIONS request (bug 1270096 and friends)
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CORS_URL);
   info("Starting test... ");
 
-  let { gStore, NetMonitorView, windowRequire } = monitor.panelWin;
+  let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { RequestsMenu } = NetMonitorView;
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
 
   info("Waiting for OPTIONS, then POST");
   let wait = waitForNetworkEvents(monitor, 1, 1);
   yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
     content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
   });
   yield wait;
 
   const METHODS = ["OPTIONS", "POST"];
-  const ITEMS = METHODS.map((val, i) => RequestsMenu.getItemAtIndex(i));
+  const ITEMS = METHODS.map((val, i) => getSortedRequests(gStore.getState()).get(i));
 
   // Check the requests that were sent
   ITEMS.forEach((item, i) => {
     is(item.method, METHODS[i], `The ${item.method} request has the right method`);
     is(item.url, requestUrl, `The ${item.method} request has the right URL`);
   });
 
   // Resend both requests without modification. Wait for resent OPTIONS, then POST.
   // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
   let onRequests = waitForNetworkEvents(monitor, 1, 0);
   ITEMS.forEach((item) => {
     info(`Selecting the ${item.method} request`);
-    RequestsMenu.selectedItem = item;
+    gStore.dispatch(Actions.selectRequest(item.id))
 
     info("Cloning the selected request into a custom clone");
     gStore.dispatch(Actions.cloneSelectedRequest());
 
     info("Sending the cloned request (without change)");
     gStore.dispatch(Actions.sendCustomRequest());
   });
 
--- a/devtools/client/netmonitor/test/browser_net_resend_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -6,20 +6,24 @@
 /**
  * Test if custom request headers are not ignored (bug 1270096 and friends)
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
-  let { NetMonitorView, NetMonitorController } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let requestUrl = SIMPLE_SJS;
   let requestHeaders = [
     { name: "Host", value: "fakehost.example.com" },
     { name: "User-Agent", value: "Testzilla" },
     { name: "Referer", value: "http://example.com/referrer" },
     { name: "Accept", value: "application/jarda"},
     { name: "Accept-Encoding", value: "compress, identity, funcoding" },
@@ -30,17 +34,17 @@ add_task(function* () {
   NetMonitorController.webConsoleClient.sendHTTPRequest({
     url: requestUrl,
     method: "POST",
     headers: requestHeaders,
     body: "Hello"
   });
   yield wait;
 
-  let item = RequestsMenu.getItemAtIndex(0);
+  let item = getSortedRequests(gStore.getState()).get(0);
   is(item.method, "POST", "The request has the right method");
   is(item.url, requestUrl, "The request has the right URL");
 
   for (let { name, value } of item.requestHeaders.headers) {
     info(`Request header: ${name}: ${value}`);
   }
 
   function hasRequestHeader(name, value) {
--- a/devtools/client/netmonitor/test/browser_net_security-details.js
+++ b/devtools/client/netmonitor/test/browser_net_security-details.js
@@ -4,33 +4,34 @@
 "use strict";
 
 /**
  * Test that Security details tab contains the expected data.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   info("Performing a secure request.");
   const REQUESTS_URL = "https://example.com" + CORS_SJS_PATH;
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, REQUESTS_URL, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
   wait = waitForDOM(document, "#security-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#security-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#security-tab"));
   yield wait;
 
   let tabpanel = document.querySelector("#security-panel");
   let textboxes = tabpanel.querySelectorAll(".textbox-input");
 
   // Connection
   // The protocol will be TLS but the exact version depends on which protocol
   // the test server example.com supports.
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -4,32 +4,35 @@
 "use strict";
 
 /**
  * Test that Security details tab shows an error message with broken connections.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
 
-  NetMonitorView.RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   info("Requesting a resource that has a certificate problem.");
 
   let wait = waitForSecurityBrokenNetworkEvent();
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1, "https://nocert.example.com");
   });
   yield wait;
 
   wait = waitForDOM(document, "#security-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#security-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#security-tab"));
   yield wait;
 
   let errormsg = document.querySelector(".security-info-value");
   isnot(errormsg.textContent, "", "Error message is not empty.");
 
   return teardown(monitor);
 
   /**
--- a/devtools/client/netmonitor/test/browser_net_security-icon-click.js
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -4,55 +4,56 @@
 "use strict";
 
 /**
  * Test that clicking on the security indicator opens the security details tab.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   info("Requesting a resource over HTTPS.");
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_2");
   yield performRequestAndWait("https://example.com" + CORS_SJS_PATH + "?request_1");
 
-  is(RequestsMenu.itemCount, 2, "Two events event logged.");
-console.log(123)
+  is(gStore.getState().requests.requests.size, 2, "Two events event logged.");
+
   yield clickAndTestSecurityIcon();
-console.log(123)
 
   info("Selecting headers panel again.");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#headers-tab"));
 
   info("Sorting the items by filename.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-file-button"));
 
   info("Testing that security icon can be clicked after the items were sorted.");
-console.log(123)
+
   yield clickAndTestSecurityIcon();
-console.log(123)
 
   return teardown(monitor);
 
   function* performRequestAndWait(url) {
     let wait = waitForNetworkEvents(monitor, 1);
     yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
       content.wrappedJSObject.performRequests(1, args.url);
     });
     return wait;
   }
 
   function* clickAndTestSecurityIcon() {
-    let item = RequestsMenu.getItemAtIndex(0);
     let icon = document.querySelector(".requests-security-state-icon");
 
     info("Clicking security icon of the first request and waiting for panel update.");
     EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
 
     ok(document.querySelector("#security-tab[aria-selected=true]"), "Security tab is selected.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_security-redirect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-redirect.js
@@ -5,35 +5,38 @@
 
 /**
  * Test a http -> https redirect shows secure icon only for redirected https
  * request.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { $, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, HTTPS_REDIRECT_SJS, function* (url) {
     content.wrappedJSObject.performRequests(1, url);
   });
   yield wait;
 
-  is(RequestsMenu.itemCount, 2, "There were two requests due to redirect.");
-
-  let initial = RequestsMenu.getItemAtIndex(0);
-  let redirect = RequestsMenu.getItemAtIndex(1);
+  is(gStore.getState().requests.requests.size, 2, "There were two requests due to redirect.");
 
-  let initialSecurityIcon =
-    $(".requests-security-state-icon", getItemTarget(RequestsMenu, initial));
-  let redirectSecurityIcon =
-    $(".requests-security-state-icon", getItemTarget(RequestsMenu, redirect));
+  let initial = getSortedRequests(gStore.getState()).get(0);
+  let redirect = getSortedRequests(gStore.getState()).get(1);
+
+  let initialSecurityIcon = document.querySelectorAll(".requests-security-state-icon")[0];
+  let redirectSecurityIcon = document.querySelectorAll(".requests-security-state-icon")[1];
 
   ok(initialSecurityIcon.classList.contains("security-state-insecure"),
      "Initial request was marked insecure.");
 
   ok(redirectSecurityIcon.classList.contains("security-state-secure"),
      "Redirected request was marked secure.");
 
   yield teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -12,30 +12,35 @@ add_task(function* () {
   const EXPECTED_SECURITY_STATES = {
     "test1.example.com": "security-state-insecure",
     "example.com": "security-state-secure",
     "nocert.example.com": "security-state-broken",
     "localhost": "security-state-local",
   };
 
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { $, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   yield performRequests();
 
-  for (let item of RequestsMenu.items) {
-    let target = getItemTarget(RequestsMenu, item);
-    let domain = $(".requests-menu-domain", target).textContent;
+  for (let subitemNode of Array.from(document.querySelectorAll(
+    "requests-menu-subitem.requests-menu-security-and-domain"))) {
+    let domain = subitemNode.querySelector(".requests-menu-domain").textContent;
 
     info("Found a request to " + domain);
     ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");
 
-    let classes = $(".requests-security-state-icon", target).classList;
+    let classes = subitemNode.querySelector(".requests-security-state-icon").classList;
     let expectedClass = EXPECTED_SECURITY_STATES[domain];
 
     info("Classes of security state icon are: " + classes);
     info("Security state icon is expected to contain class: " + expectedClass);
     ok(classes.contains(expectedClass), "Icon contained the correct class name.");
   }
 
   return teardown(monitor);
@@ -80,17 +85,19 @@ add_task(function* () {
     yield done;
 
     done = waitForSecurityBrokenNetworkEvent(true);
     info("Requesting a resource over HTTP to localhost.");
     yield executeRequests(1, "http://localhost" + CORS_SJS_PATH);
     yield done;
 
     const expectedCount = Object.keys(EXPECTED_SECURITY_STATES).length;
-    is(RequestsMenu.itemCount, expectedCount, expectedCount + " events logged.");
+    is(gStore.getState().requests.requests.size,
+      expectedCount,
+      expectedCount + " events logged.");
   }
 
   /**
    * Returns a promise that's resolved once a request with security issues is
    * completed.
    */
   function waitForSecurityBrokenNetworkEvent(networkError) {
     let awaitedEvents = [
--- a/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
@@ -5,20 +5,20 @@
 
 /**
  * Test that security details tab is no longer selected if an insecure request
  * is selected.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   info("Performing requests.");
   let wait = waitForNetworkEvents(monitor, 2);
   const REQUEST_URLS = [
     "https://example.com" + CORS_SJS_PATH,
     "http://example.com" + CORS_SJS_PATH,
   ];
   yield ContentTask.spawn(tab.linkedBrowser, REQUEST_URLS, function* (urls) {
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -27,20 +27,21 @@ add_task(function* () {
       isBroken: true,
       visibleOnNewEvent: false,
       visibleOnSecurityInfo: true,
       visibleOnceComplete: true,
     }
   ];
 
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, EVENTS, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   for (let testcase of TEST_DATA) {
     info("Testing Security tab visibility for " + testcase.desc);
     let onNewItem = monitor.panelWin.once(EVENTS.NETWORK_EVENT);
     let onSecurityInfo = monitor.panelWin.once(EVENTS.RECEIVED_SECURITY_INFO);
     let onComplete = testcase.isBroken ?
                        waitForSecurityBrokenNetworkEvent() :
                        waitForNetworkEvents(monitor, 1);
@@ -49,42 +50,43 @@ add_task(function* () {
     yield ContentTask.spawn(tab.linkedBrowser, testcase.uri, function* (url) {
       content.wrappedJSObject.performRequests(1, url);
     });
 
     info("Waiting for new network event.");
     yield onNewItem;
 
     info("Selecting the request.");
-    RequestsMenu.selectedIndex = 0;
+    EventUtils.sendMouseEvent({ type: "mousedown" },
+      document.querySelectorAll(".request-list-item")[0]);
 
-    is(RequestsMenu.selectedItem.securityState, undefined,
+    is(getSelectedRequest(gStore.getState()).securityState, undefined,
        "Security state has not yet arrived.");
     is(!!document.querySelector("#security-tab"), testcase.visibleOnNewEvent,
       "Security tab is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
       " after new request was added to the menu.");
 
     info("Waiting for security information to arrive.");
     yield onSecurityInfo;
 
-    ok(RequestsMenu.selectedItem.securityState,
+    ok(getSelectedRequest(gStore.getState()).securityState,
        "Security state arrived.");
     is(!!document.querySelector("#security-tab"), testcase.visibleOnSecurityInfo,
        "Security tab is " + (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
        " after security information arrived.");
 
     info("Waiting for request to complete.");
     yield onComplete;
 
     is(!!document.querySelector("#security-tab"), testcase.visibleOnceComplete,
        "Security tab is " + (testcase.visibleOnceComplete ? "visible" : "hidden") +
        " after request has been completed.");
 
     info("Clearing requests.");
-    RequestsMenu.clear();
+    gStore.dispatch(Actions.clearRequests());
   }
 
   return teardown(monitor);
 
   /**
    * Returns a promise that's resolved once a request with security issues is
    * completed.
    */
--- a/devtools/client/netmonitor/test/browser_net_security-warnings.js
+++ b/devtools/client/netmonitor/test/browser_net_security-warnings.js
@@ -12,20 +12,20 @@ const TEST_CASES = [
     desc: "no warnings",
     uri: "https://example.com" + CORS_SJS_PATH,
     warnCipher: null,
   },
 ];
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   for (let test of TEST_CASES) {
     info("Testing site with " + test.desc);
 
     info("Performing request to " + test.uri);
     let wait = waitForNetworkEvents(monitor, 1);
     yield ContentTask.spawn(tab.linkedBrowser, test.uri, function* (url) {
       content.wrappedJSObject.performRequests(1, url);
@@ -36,21 +36,22 @@ add_task(function* () {
     wait = waitForDOM(document, ".tabs");
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[0]);
     yield wait;
 
     if (!document.querySelector("#security-tab[aria-selected=true]")) {
       info("Selecting security tab.");
       wait = waitForDOM(document, "#security-panel .properties-view");
-      document.querySelector("#security-tab").click();
+      EventUtils.sendMouseEvent({ type: "click" },
+        document.querySelector("#security-tab"));
       yield wait;
     }
 
     is(document.querySelector("#security-warning-cipher"),
       test.warnCipher,
       "Cipher suite warning is hidden.");
 
-    RequestsMenu.clear();
+    gStore.dispatch(Actions.clearRequests());
   }
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
@@ -4,31 +4,34 @@
 "use strict";
 
 /**
  * Tests if beacons from other tabs are properly ignored.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let beaconTab = yield addTab(SEND_BEACON_URL);
   info("Beacon tab added successfully.");
 
-  is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
+  is(gStore.getState().requests.requests.size, 0, "The requests menu should be empty.");
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(beaconTab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequest();
   });
   tab.linkedBrowser.reload();
   yield wait;
 
-  is(RequestsMenu.itemCount, 1, "Only the reload should be recorded.");
-  let request = RequestsMenu.getItemAtIndex(0);
+  is(gStore.getState().requests.requests.size, 1, "Only the reload should be recorded.");
+  let request = getSortedRequests(gStore.getState()).get(0);
   is(request.method, "GET", "The method is correct.");
   is(request.status, "200", "The status is correct.");
 
   yield removeTab(beaconTab);
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_send-beacon.js
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon.js
@@ -4,28 +4,30 @@
 "use strict";
 
 /**
  * Tests if beacons are handled correctly.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SEND_BEACON_URL);
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+ let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
-  is(RequestsMenu.itemCount, 0, "The requests menu should be empty.");
+  is(gStore.getState().requests.requests.size, 0, "The requests menu should be empty.");
 
   let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequest();
   });
   yield wait;
 
-  is(RequestsMenu.itemCount, 1, "The beacon should be recorded.");
-  let request = RequestsMenu.getItemAtIndex(0);
+  is(gStore.getState().requests.requests.size, 1, "The beacon should be recorded.");
+  let request = getSortedRequests(gStore.getState()).get(0);
   is(request.method, "POST", "The method is correct.");
   ok(request.url.endsWith("beacon_request"), "The URL is correct.");
   is(request.status, "404", "The status is correct.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -11,18 +11,24 @@
 const URL = EXAMPLE_URL.replace("http:", "https:");
 
 const TEST_URL = URL + "service-workers/status-codes.html";
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(TEST_URL, null, true);
   info("Starting test... ");
 
-  let { NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   const REQUEST_DATA = [
     {
       method: "GET",
       uri: URL + "service-workers/test/200",
       details: {
         status: 200,
         statusText: "OK (service worker)",
@@ -43,21 +49,27 @@ add_task(function* () {
   let wait = waitForNetworkEvents(monitor, REQUEST_DATA.length);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let index = 0;
   for (let request of REQUEST_DATA) {
-    let item = RequestsMenu.getItemAtIndex(index);
+    let item = getSortedRequests(gStore.getState()).get(index);
 
     info(`Verifying request #${index}`);
-    yield verifyRequestItemTarget(RequestsMenu, item,
-      request.method, request.uri, request.details);
+    yield verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      item,
+      request.method,
+      request.uri,
+      request.details
+    );
 
     let { stacktrace } = item.cause;
     let stackLen = stacktrace ? stacktrace.length : 0;
 
     ok(stacktrace, `Request #${index} has a stacktrace`);
     ok(stackLen >= request.stackFunctions.length,
       `Request #${index} has a stacktrace with enough (${stackLen}) items`);
 
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -8,34 +8,40 @@
  */
 
 function test() {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   initNetMonitor(SIMPLE_SJS).then(({ tab, monitor }) => {
     info("Starting test... ");
 
-    let { NetMonitorView } = monitor.panelWin;
-    let { RequestsMenu } = NetMonitorView;
+    let { document, gStore, windowRequire } = monitor.panelWin;
+    let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+    let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+    let {
+      getDisplayedRequests,
+      getSelectedRequest,
+      getSortedRequests,
+    } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-    RequestsMenu.lazyUpdate = false;
+    gStore.dispatch(Actions.batchEnable(false));
 
     waitForNetworkEvents(monitor, 1)
       .then(() => teardown(monitor))
       .then(finish);
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.NETWORK_EVENT, () => {
-      is(RequestsMenu.selectedItem, null,
+    monitor.panelWin.once(EVENTS.NETWORK_EVENT, () => {
+      is(getSelectedRequest(gStore.getState()), null,
         "There shouldn't be any selected item in the requests menu.");
-      is(RequestsMenu.itemCount, 1,
+      is(gStore.getState().requests.requests.size, 1,
         "The requests menu should not be empty after the first request.");
       is(!!document.querySelector(".network-details-panel"), false,
         "The network details panel should still be hidden after first request.");
 
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(typeof requestItem.id, "string",
         "The attached request id is incorrect.");
       isnot(requestItem.id, "",
         "The attached request id should not be empty.");
 
       is(typeof requestItem.startedMillis, "number",
         "The attached startedMillis is incorrect.");
@@ -63,156 +69,212 @@ function test() {
 
       is(requestItem.headersSize, undefined,
         "The headersSize should not yet be set.");
       is(requestItem.transferredSize, undefined,
         "The transferredSize should not yet be set.");
       is(requestItem.contentSize, undefined,
         "The contentSize should not yet be set.");
 
-      is(requestItem.mimeType, undefined,
-        "The mimeType should not yet be set.");
       is(requestItem.responseContent, undefined,
         "The responseContent should not yet be set.");
 
       is(requestItem.totalTime, undefined,
         "The totalTime should not yet be set.");
       is(requestItem.eventTimings, undefined,
         "The eventTimings should not yet be set.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_HEADERS, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_HEADERS, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
       ok(requestItem.requestHeaders,
         "There should be a requestHeaders data available.");
       is(requestItem.requestHeaders.headers.length, 10,
         "The requestHeaders data has an incorrect |headers| property.");
       isnot(requestItem.requestHeaders.headersSize, 0,
         "The requestHeaders data has an incorrect |headersSize| property.");
       // Can't test for the exact request headers size because the value may
       // vary across platforms ("User-Agent" header differs).
 
-      verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_COOKIES, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_COOKIES, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.requestCookies,
         "There should be a requestCookies data available.");
       is(requestItem.requestCookies.cookies.length, 2,
         "The requestCookies data has an incorrect |cookies| property.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
       ok(false, "Trap listener: this request doesn't have any post data.");
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_HEADERS, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_HEADERS, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseHeaders,
         "There should be a responseHeaders data available.");
       is(requestItem.responseHeaders.headers.length, 10,
         "The responseHeaders data has an incorrect |headers| property.");
       is(requestItem.responseHeaders.headersSize, 330,
         "The responseHeaders data has an incorrect |headersSize| property.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_COOKIES, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_COOKIES, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseCookies,
         "There should be a responseCookies data available.");
       is(requestItem.responseCookies.cookies.length, 2,
         "The responseCookies data has an incorrect |cookies| property.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.STARTED_RECEIVING_RESPONSE, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.STARTED_RECEIVING_RESPONSE, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(requestItem.httpVersion, "HTTP/1.1",
         "The httpVersion data has an incorrect value.");
       is(requestItem.status, "200",
         "The status data has an incorrect value.");
       is(requestItem.statusText, "Och Aye",
         "The statusText data has an incorrect value.");
       is(requestItem.headersSize, 330,
         "The headersSize data has an incorrect value.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
-        status: "200",
-        statusText: "Och Aye"
-      });
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS,
+        {
+          status: "200",
+          statusText: "Och Aye"
+        }
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_RESPONSE_CONTENT, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.UPDATING_RESPONSE_CONTENT, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(requestItem.transferredSize, "12",
         "The transferredSize data has an incorrect value.");
       is(requestItem.contentSize, "12",
         "The contentSize data has an incorrect value.");
       is(requestItem.mimeType, "text/plain; charset=utf-8",
         "The mimeType data has an incorrect value.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-      });
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS,
+        {
+          type: "plain",
+          fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+        }
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseContent,
         "There should be a responseContent data available.");
       is(requestItem.responseContent.content.mimeType,
         "text/plain; charset=utf-8",
         "The responseContent data has an incorrect |content.mimeType| property.");
       is(requestItem.responseContent.content.text,
         "Hello world!",
         "The responseContent data has an incorrect |content.text| property.");
       is(requestItem.responseContent.content.size,
         12,
         "The responseContent data has an incorrect |content.size| property.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
-      });
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS,
+        {
+          type: "plain",
+          fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+        }
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_EVENT_TIMINGS, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.UPDATING_EVENT_TIMINGS, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(typeof requestItem.totalTime, "number",
         "The attached totalTime is incorrect.");
       ok(requestItem.totalTime >= 0,
         "The attached totalTime should be positive.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
-        time: true
-      });
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS,
+        {
+          time: true
+        }
+      );
     });
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_EVENT_TIMINGS, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
+    monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS, () => {
+      let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.eventTimings,
         "There should be a eventTimings data available.");
       is(typeof requestItem.eventTimings.timings.blocked, "number",
         "The eventTimings data has an incorrect |timings.blocked| property.");
       is(typeof requestItem.eventTimings.timings.dns, "number",
         "The eventTimings data has an incorrect |timings.dns| property.");
       is(typeof requestItem.eventTimings.timings.connect, "number",
@@ -221,16 +283,23 @@ function test() {
         "The eventTimings data has an incorrect |timings.send| property.");
       is(typeof requestItem.eventTimings.timings.wait, "number",
         "The eventTimings data has an incorrect |timings.wait| property.");
       is(typeof requestItem.eventTimings.timings.receive, "number",
         "The eventTimings data has an incorrect |timings.receive| property.");
       is(typeof requestItem.eventTimings.totalTime, "number",
         "The eventTimings data has an incorrect |totalTime| property.");
 
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
-        time: true
-      });
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        requestItem,
+        "GET",
+        SIMPLE_SJS,
+        {
+          time: true
+        }
+      );
     });
 
     tab.linkedBrowser.reload();
   });
 }
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -8,50 +8,61 @@
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
-  let { document, EVENTS, Editor, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
-  is(RequestsMenu.selectedItem, null,
+  is(getSelectedRequest(gStore.getState()), undefined,
     "There shouldn't be any selected item in the requests menu.");
-  is(RequestsMenu.itemCount, 1,
+  is(gStore.getState().requests.requests.size, 1,
     "The requests menu should not be empty after the first request.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The network details panel should still be hidden after first request.");
 
-  let onTabUpdated = monitor.panelWin.once(EVENTS.TAB_UPDATED);
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  yield onTabUpdated;
 
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), undefined,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should not be hidden after toggle button was pressed.");
 
   testHeadersTab();
   yield testCookiesTab();
   testParamsTab();
   yield testResponseTab();
   testTimingsTab();
   return teardown(monitor);
 
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
   function testHeadersTab() {
     let tabEl = document.querySelectorAll("#details-pane tab")[0];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
 
     is(tabEl.getAttribute("selected"), "true",
       "The headers tab in the network details pane should be selected.");
 
     is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
--- a/devtools/client/netmonitor/test/browser_net_simple-request.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -11,63 +11,68 @@
  * 2) Side panel toggle button
  * 3) Empty user message visibility
  * 4) Number of requests displayed
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
     true,
     "The pane toggle button should be disabled when the frontend is opened.");
   ok(document.querySelector("#requests-menu-empty-notice"),
     "An empty notice should be displayed when the frontend is opened.");
-  is(RequestsMenu.itemCount, 0,
+  is(gStore.getState().requests.requests.size, 0,
     "The requests menu should be empty when the frontend is opened.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The network details panel should be hidden when the frontend is opened.");
 
   yield reloadAndWait();
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
     false,
     "The pane toggle button should be enabled after the first request.");
   ok(!document.querySelector("#requests-menu-empty-notice"),
     "The empty notice should be hidden after the first request.");
-  is(RequestsMenu.itemCount, 1,
+  is(gStore.getState().requests.requests.size, 1,
     "The requests menu should not be empty after the first request.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The network details panel should still be hidden after the first request.");
 
   yield reloadAndWait();
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
     false,
     "The pane toggle button should be still be enabled after a reload.");
   ok(!document.querySelector("#requests-menu-empty-notice"),
     "The empty notice should be still hidden after a reload.");
-  is(RequestsMenu.itemCount, 1,
+  is(gStore.getState().requests.requests.size, 1,
     "The requests menu should not be empty after a reload.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The network details panel should still be hidden after a reload.");
 
-  RequestsMenu.clear();
+  gStore.dispatch(Actions.clearRequests());
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
     true,
     "The pane toggle button should be disabled when after clear.");
   ok(document.querySelector("#requests-menu-empty-notice"),
     "An empty notice should be displayed again after clear.");
-  is(RequestsMenu.itemCount, 0,
+  is(gStore.getState().requests.requests.size, 0,
     "The requests menu should be empty after clear.");
   is(!!document.querySelector(".network-details-panel"), false,
     "The network details panel should still be hidden after clear.");
 
   return teardown(monitor);
 
   function* reloadAndWait() {
     let wait = waitForNetworkEvents(monitor, 1);
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -1,219 +1,236 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Test if the sorting mechanism works correctly.
+ * Test if sorting columns in the network table works correctly with new requests.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
-  let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
+  let { monitor } = yield initNetMonitor(SORTING_URL);
   info("Starting test... ");
 
-  let { $all, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  // It seems that this test may be slow on debug builds. This could be because
+  // of the heavy dom manipulation associated with sorting.
+  requestLongerTimeout(2);
+
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
-  RequestsMenu.lazyUpdate = false;
+  // Loading the frame script and preparing the xhr request URLs so we can
+  // generate some requests later.
+  loadCommonFrameScript();
+  let requests = [{
+    url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
+    method: "GET1"
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
+    method: "GET5"
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
+    method: "GET2"
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
+    method: "GET4"
+  }, {
+    url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
+    method: "GET3"
+  }];
 
   let wait = waitForNetworkEvents(monitor, 5);
-  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
-    content.wrappedJSObject.performRequests();
-  });
+  yield performRequestsInContent(requests);
   yield wait;
 
-  testContents([0, 1, 2, 3, 4]);
-
-  info("Testing swap(0, 0)");
-  RequestsMenu.swapItemsAtIndices(0, 0);
-  RequestsMenu.refreshZebra();
-  testContents([0, 1, 2, 3, 4]);
-
-  info("Testing swap(0, 1)");
-  RequestsMenu.swapItemsAtIndices(0, 1);
-  RequestsMenu.refreshZebra();
-  testContents([1, 0, 2, 3, 4]);
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector(".network-details-panel-toggle"));
 
-  info("Testing swap(0, 2)");
-  RequestsMenu.swapItemsAtIndices(0, 2);
-  RequestsMenu.refreshZebra();
-  testContents([1, 2, 0, 3, 4]);
-
-  info("Testing swap(0, 3)");
-  RequestsMenu.swapItemsAtIndices(0, 3);
-  RequestsMenu.refreshZebra();
-  testContents([1, 2, 3, 0, 4]);
-
-  info("Testing swap(0, 4)");
-  RequestsMenu.swapItemsAtIndices(0, 4);
-  RequestsMenu.refreshZebra();
-  testContents([1, 2, 3, 4, 0]);
+  isnot(getSelectedRequest(gStore.getState()), undefined,
+    "There should be a selected item in the requests menu.");
+  is(getSelectedIndex(gStore.getState()), 0,
+    "The first item should be selected in the requests menu.");
+  is(!!document.querySelector(".network-details-panel"), true,
+    "The network details panel should be visible after toggle button was pressed.");
 
-  info("Testing swap(1, 0)");
-  RequestsMenu.swapItemsAtIndices(1, 0);
-  RequestsMenu.refreshZebra();
-  testContents([0, 2, 3, 4, 1]);
-
-  info("Testing swap(1, 1)");
-  RequestsMenu.swapItemsAtIndices(1, 1);
-  RequestsMenu.refreshZebra();
-  testContents([0, 2, 3, 4, 1]);
-
-  info("Testing swap(1, 2)");
-  RequestsMenu.swapItemsAtIndices(1, 2);
-  RequestsMenu.refreshZebra();
-  testContents([0, 1, 3, 4, 2]);
+  testHeaders();
+  testContents([0, 2, 4, 3, 1], 0);
 
-  info("Testing swap(1, 3)");
-  RequestsMenu.swapItemsAtIndices(1, 3);
-  RequestsMenu.refreshZebra();
-  testContents([0, 3, 1, 4, 2]);
-
-  info("Testing swap(1, 4)");
-  RequestsMenu.swapItemsAtIndices(1, 4);
-  RequestsMenu.refreshZebra();
-  testContents([0, 3, 4, 1, 2]);
+  info("Testing status sort, ascending.");
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-status-button"));
+  testHeaders("status", "ascending");
+  testContents([0, 1, 2, 3, 4], 0);
 
-  info("Testing swap(2, 0)");
-  RequestsMenu.swapItemsAtIndices(2, 0);
-  RequestsMenu.refreshZebra();
-  testContents([2, 3, 4, 1, 0]);
-
-  info("Testing swap(2, 1)");
-  RequestsMenu.swapItemsAtIndices(2, 1);
-  RequestsMenu.refreshZebra();
-  testContents([1, 3, 4, 2, 0]);
+  info("Performing more requests.");
+  wait = waitForNetworkEvents(monitor, 5);
+  yield performRequestsInContent(requests);
+  yield wait;
 
-  info("Testing swap(2, 2)");
-  RequestsMenu.swapItemsAtIndices(2, 2);
-  RequestsMenu.refreshZebra();
-  testContents([1, 3, 4, 2, 0]);
-
-  info("Testing swap(2, 3)");
-  RequestsMenu.swapItemsAtIndices(2, 3);
-  RequestsMenu.refreshZebra();
-  testContents([1, 2, 4, 3, 0]);
-
-  info("Testing swap(2, 4)");
-  RequestsMenu.swapItemsAtIndices(2, 4);
-  RequestsMenu.refreshZebra();
-  testContents([1, 4, 2, 3, 0]);
+  info("Testing status sort again, ascending.");
+  testHeaders("status", "ascending");
+  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
 
-  info("Testing swap(3, 0)");
-  RequestsMenu.swapItemsAtIndices(3, 0);
-  RequestsMenu.refreshZebra();
-  testContents([1, 4, 2, 0, 3]);
+  info("Testing status sort, descending.");
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-status-button"));
+  testHeaders("status", "descending");
+  testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
 
-  info("Testing swap(3, 1)");
-  RequestsMenu.swapItemsAtIndices(3, 1);
-  RequestsMenu.refreshZebra();
-  testContents([3, 4, 2, 0, 1]);
-
-  info("Testing swap(3, 2)");
-  RequestsMenu.swapItemsAtIndices(3, 2);
-  RequestsMenu.refreshZebra();
-  testContents([2, 4, 3, 0, 1]);
+  info("Performing more requests.");
+  wait = waitForNetworkEvents(monitor, 5);
+  yield performRequestsInContent(requests);
+  yield wait;
 
-  info("Testing swap(3, 3)");
-  RequestsMenu.swapItemsAtIndices(3, 3);
-  RequestsMenu.refreshZebra();
-  testContents([2, 4, 3, 0, 1]);
-
-  info("Testing swap(3, 4)");
-  RequestsMenu.swapItemsAtIndices(3, 4);
-  RequestsMenu.refreshZebra();
-  testContents([2, 3, 4, 0, 1]);
-
-  info("Testing swap(4, 0)");
-  RequestsMenu.swapItemsAtIndices(4, 0);
-  RequestsMenu.refreshZebra();
-  testContents([2, 3, 0, 4, 1]);
+  info("Testing status sort again, descending.");
+  testHeaders("status", "descending");
+  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
 
-  info("Testing swap(4, 1)");
-  RequestsMenu.swapItemsAtIndices(4, 1);
-  RequestsMenu.refreshZebra();
-  testContents([2, 3, 0, 1, 4]);
-
-  info("Testing swap(4, 2)");
-  RequestsMenu.swapItemsAtIndices(4, 2);
-  RequestsMenu.refreshZebra();
-  testContents([4, 3, 0, 1, 2]);
+  info("Testing status sort yet again, ascending.");
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-status-button"));
+  testHeaders("status", "ascending");
+  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0);
 
-  info("Testing swap(4, 3)");
-  RequestsMenu.swapItemsAtIndices(4, 3);
-  RequestsMenu.refreshZebra();
-  testContents([3, 4, 0, 1, 2]);
-
-  info("Testing swap(4, 4)");
-  RequestsMenu.swapItemsAtIndices(4, 4);
-  RequestsMenu.refreshZebra();
-  testContents([3, 4, 0, 1, 2]);
-
-  info("Clearing sort.");
-  RequestsMenu.sortBy();
-  testContents([0, 1, 2, 3, 4]);
+  info("Testing status sort yet again, descending.");
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#requests-menu-status-button"));
+  testHeaders("status", "descending");
+  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
 
   return teardown(monitor);
 
-  function testContents([a, b, c, d, e]) {
-    is(RequestsMenu.items.length, 5,
-      "There should be a total of 5 items in the requests menu.");
-    is(RequestsMenu.visibleItems.length, 5,
-      "There should be a total of 5 visbile items in the requests menu.");
-    is($all(".request-list-item").length, 5,
+  function testHeaders(sortType, direction) {
+    let doc = monitor.panelWin.document;
+    let target = doc.querySelector("#requests-menu-" + sortType + "-button");
+    let headers = doc.querySelectorAll(".requests-menu-header-button");
+
+    for (let header of headers) {
+      if (header != target) {
+        ok(!header.hasAttribute("data-sorted"),
+          "The " + header.id + " header does not have a 'data-sorted' attribute.");
+        ok(!header.getAttribute("title"),
+          "The " + header.id + " header does not have a 'title' attribute.");
+      } else {
+        is(header.getAttribute("data-sorted"), direction,
+          "The " + header.id + " header has a correct 'data-sorted' attribute.");
+        is(header.getAttribute("title"), direction == "ascending"
+          ? L10N.getStr("networkMenu.sortedAsc")
+          : L10N.getStr("networkMenu.sortedDesc"),
+          "The " + header.id + " header has a correct 'title' attribute.");
+      }
+    }
+  }
+
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
+  function testContents(order, selection) {
+    isnot(getSelectedRequest(gStore.getState()), undefined,
+      "There should still be a selected item after sorting.");
+    is(getSelectedIndex(gStore.getState()), selection,
+      "The first item should be still selected after sorting.");
+    is(!!document.querySelector(".network-details-panel"), true,
+      "The network details panel should still be visible after sorting.");
+
+    is(getSortedRequests(gStore.getState()).length, order.length,
+      "There should be a specific number of items in the requests menu.");
+    is(getDisplayedRequests(gStore.getState()).length, order.length,
+      "There should be a specific number of visbile items in the requests menu.");
+    is(document.querySelectorAll(".request-list-item").length, order.length,
       "The visible items in the requests menu are, in fact, visible!");
 
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(a),
-      "GET", STATUS_CODES_SJS + "?sts=100", {
-        status: 101,
-        statusText: "Switching Protocols",
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getStr("networkMenu.sizeUnavailable"),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
-        time: true
-      });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(b),
-      "GET", STATUS_CODES_SJS + "?sts=200", {
-        status: 202,
-        statusText: "Created",
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        time: true
-      });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(c),
-      "GET", STATUS_CODES_SJS + "?sts=300", {
-        status: 303,
-        statusText: "See Other",
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
-        time: true
-      });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(d),
-      "GET", STATUS_CODES_SJS + "?sts=400", {
-        status: 404,
-        statusText: "Not Found",
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        time: true
-      });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(e),
-      "GET", STATUS_CODES_SJS + "?sts=500", {
-        status: 501,
-        statusText: "Not Implemented",
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
-        time: true
-      });
+    for (let i = 0, len = order.length / 5; i < len; i++) {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(order[i]),
+        "GET1", SORTING_SJS + "?index=1", {
+          fuzzyUrl: true,
+          status: 101,
+          statusText: "Meh",
+          type: "1",
+          fullMimeType: "text/1",
+          transferred: L10N.getStr("networkMenu.sizeUnavailable"),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
+          time: true
+        });
+    }
+    for (let i = 0, len = order.length / 5; i < len; i++) {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(order[i + len]),
+        "GET2", SORTING_SJS + "?index=2", {
+          fuzzyUrl: true,
+          status: 200,
+          statusText: "Meh",
+          type: "2",
+          fullMimeType: "text/2",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
+          time: true
+        });
+    }
+    for (let i = 0, len = order.length / 5; i < len; i++) {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(order[i + len * 2]),
+        "GET3", SORTING_SJS + "?index=3", {
+          fuzzyUrl: true,
+          status: 300,
+          statusText: "Meh",
+          type: "3",
+          fullMimeType: "text/3",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
+          time: true
+        });
+    }
+    for (let i = 0, len = order.length / 5; i < len; i++) {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(order[i + len * 3]),
+        "GET4", SORTING_SJS + "?index=4", {
+          fuzzyUrl: true,
+          status: 400,
+          statusText: "Meh",
+          type: "4",
+          fullMimeType: "text/4",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
+          time: true
+        });
+    }
+    for (let i = 0, len = order.length / 5; i < len; i++) {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(order[i + len * 4]),
+        "GET5", SORTING_SJS + "?index=5", {
+          fuzzyUrl: true,
+          status: 500,
+          statusText: "Meh",
+          type: "5",
+          fullMimeType: "text/5",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
+          time: true
+        });
+    }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -12,18 +12,25 @@ add_task(function* () {
 
   let { monitor } = yield initNetMonitor(SORTING_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on debug builds. This could be because
   // of the heavy dom manipulation associated with sorting.
   requestLongerTimeout(2);
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSelectedRequest,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   // Loading the frame script and preparing the xhr request URLs so we can
   // generate some requests later.
   loadCommonFrameScript();
   let requests = [{
     url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
     method: "GET1"
   }, {
@@ -35,28 +42,26 @@ add_task(function* () {
   }, {
     url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
     method: "GET4"
   }, {
     url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
     method: "GET3"
   }];
 
-  RequestsMenu.lazyUpdate = false;
-
   let wait = waitForNetworkEvents(monitor, 5);
   yield performRequestsInContent(requests);
   yield wait;
 
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
 
-  isnot(RequestsMenu.selectedItem, null,
+  isnot(getSelectedRequest(gStore.getState()), undefined,
     "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
+  is(getSelectedIndex(gStore.getState()), 0,
     "The first item should be selected in the requests menu.");
   is(!!document.querySelector(".network-details-panel"), true,
     "The network details panel should be visible after toggle button was pressed.");
 
   testHeaders();
   testContents([0, 2, 4, 3, 1]);
 
   info("Testing status sort, ascending.");
@@ -182,16 +187,23 @@ add_task(function* () {
   info("Testing waterfall sort, ascending. Checking sort loops correctly.");
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-waterfall-button"));
   testHeaders("waterfall", "ascending");
   testContents([0, 2, 4, 3, 1]);
 
   return teardown(monitor);
 
+  function getSelectedIndex(state) {
+    if (!state.requests.selectedId) {
+      return -1;
+    }
+    return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+  }
+
   function testHeaders(sortType, direction) {
     let doc = monitor.panelWin.document;
     let target = doc.querySelector("#requests-menu-" + sortType + "-button");
     let headers = doc.querySelectorAll(".requests-menu-header-button");
 
     for (let header of headers) {
       if (header != target) {
         ok(!header.hasAttribute("data-sorted"),
@@ -205,75 +217,90 @@ add_task(function* () {
           ? L10N.getStr("networkMenu.sortedAsc")
           : L10N.getStr("networkMenu.sortedDesc"),
           "The " + header.id + " header has a correct 'title' attribute.");
       }
     }
   }
 
   function testContents([a, b, c, d, e]) {
-    isnot(RequestsMenu.selectedItem, null,
+    isnot(getSelectedRequest(gStore.getState()), undefined,
       "There should still be a selected item after sorting.");
-    is(RequestsMenu.selectedIndex, a,
+    is(getSelectedIndex(gStore.getState()), a,
       "The first item should be still selected after sorting.");
     is(!!document.querySelector(".network-details-panel"), true,
       "The network details panel should still be visible after sorting.");
 
-    is(RequestsMenu.items.length, 5,
+    is(getSortedRequests(gStore.getState()).length, 5,
       "There should be a total of 5 items in the requests menu.");
-    is(RequestsMenu.visibleItems.length, 5,
+    is(getDisplayedRequests(gStore.getState()).length, 5,
       "There should be a total of 5 visible items in the requests menu.");
     is(document.querySelectorAll(".request-list-item").length, 5,
       "The visible items in the requests menu are, in fact, visible!");
 
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(a),
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(a),
       "GET1", SORTING_SJS + "?index=1", {
         fuzzyUrl: true,
         status: 101,
         statusText: "Meh",
         type: "1",
         fullMimeType: "text/1",
         transferred: L10N.getStr("networkMenu.sizeUnavailable"),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
         time: true
       });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(b),
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(b),
       "GET2", SORTING_SJS + "?index=2", {
         fuzzyUrl: true,
         status: 200,
         statusText: "Meh",
         type: "2",
         fullMimeType: "text/2",
         transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
         time: true
       });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(c),
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(c),
       "GET3", SORTING_SJS + "?index=3", {
         fuzzyUrl: true,
         status: 300,
         statusText: "Meh",
         type: "3",
         fullMimeType: "text/3",
         transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
         time: true
       });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(d),
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(d),
       "GET4", SORTING_SJS + "?index=4", {
         fuzzyUrl: true,
         status: 400,
         statusText: "Meh",
         type: "4",
         fullMimeType: "text/4",
         transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
         time: true
       });
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(e),
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(e),
       "GET5", SORTING_SJS + "?index=5", {
         fuzzyUrl: true,
         status: 500,
         statusText: "Meh",
         type: "5",
         fullMimeType: "text/5",
         transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
         size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
deleted file mode 100644
--- a/devtools/client/netmonitor/test/browser_net_sort-03.js
+++ /dev/null
@@ -1,214 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/**
- * Test if sorting columns in the network table works correctly with new requests.
- */
-
-add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
-
-  let { monitor } = yield initNetMonitor(SORTING_URL);
-  info("Starting test... ");
-
-  // It seems that this test may be slow on debug builds. This could be because
-  // of the heavy dom manipulation associated with sorting.
-  requestLongerTimeout(2);
-
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
-
-  // Loading the frame script and preparing the xhr request URLs so we can
-  // generate some requests later.
-  loadCommonFrameScript();
-  let requests = [{
-    url: "sjs_sorting-test-server.sjs?index=1&" + Math.random(),
-    method: "GET1"
-  }, {
-    url: "sjs_sorting-test-server.sjs?index=5&" + Math.random(),
-    method: "GET5"
-  }, {
-    url: "sjs_sorting-test-server.sjs?index=2&" + Math.random(),
-    method: "GET2"
-  }, {
-    url: "sjs_sorting-test-server.sjs?index=4&" + Math.random(),
-    method: "GET4"
-  }, {
-    url: "sjs_sorting-test-server.sjs?index=3&" + Math.random(),
-    method: "GET3"
-  }];
-
-  RequestsMenu.lazyUpdate = false;
-
-  let wait = waitForNetworkEvents(monitor, 5);
-  yield performRequestsInContent(requests);
-  yield wait;
-
-  EventUtils.sendMouseEvent({ type: "mousedown" },
-    document.querySelector(".network-details-panel-toggle"));
-
-  isnot(RequestsMenu.selectedItem, null,
-    "There should be a selected item in the requests menu.");
-  is(RequestsMenu.selectedIndex, 0,
-    "The first item should be selected in the requests menu.");
-  is(!!document.querySelector(".network-details-panel"), true,
-    "The network details panel should be visible after toggle button was pressed.");
-
-  testHeaders();
-  testContents([0, 2, 4, 3, 1], 0);
-
-  info("Testing status sort, ascending.");
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector("#requests-menu-status-button"));
-  testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4], 0);
-
-  info("Performing more requests.");
-  wait = waitForNetworkEvents(monitor, 5);
-  yield performRequestsInContent(requests);
-  yield wait;
-
-  info("Testing status sort again, ascending.");
-  testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 0);
-
-  info("Testing status sort, descending.");
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector("#requests-menu-status-button"));
-  testHeaders("status", "descending");
-  testContents([9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 9);
-
-  info("Performing more requests.");
-  wait = waitForNetworkEvents(monitor, 5);
-  yield performRequestsInContent(requests);
-  yield wait;
-
-  info("Testing status sort again, descending.");
-  testHeaders("status", "descending");
-  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
-
-  info("Testing status sort yet again, ascending.");
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector("#requests-menu-status-button"));
-  testHeaders("status", "ascending");
-  testContents([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], 0);
-
-  info("Testing status sort yet again, descending.");
-  EventUtils.sendMouseEvent({ type: "click" },
-    document.querySelector("#requests-menu-status-button"));
-  testHeaders("status", "descending");
-  testContents([14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0], 14);
-
-  return teardown(monitor);
-
-  function testHeaders(sortType, direction) {
-    let doc = monitor.panelWin.document;
-    let target = doc.querySelector("#requests-menu-" + sortType + "-button");
-    let headers = doc.querySelectorAll(".requests-menu-header-button");
-
-    for (let header of headers) {
-      if (header != target) {
-        ok(!header.hasAttribute("data-sorted"),
-          "The " + header.id + " header does not have a 'data-sorted' attribute.");
-        ok(!header.getAttribute("title"),
-          "The " + header.id + " header does not have a 'title' attribute.");
-      } else {
-        is(header.getAttribute("data-sorted"), direction,
-          "The " + header.id + " header has a correct 'data-sorted' attribute.");
-        is(header.getAttribute("title"), direction == "ascending"
-          ? L10N.getStr("networkMenu.sortedAsc")
-          : L10N.getStr("networkMenu.sortedDesc"),
-          "The " + header.id + " header has a correct 'title' attribute.");
-      }
-    }
-  }
-
-  function testContents(order, selection) {
-    isnot(RequestsMenu.selectedItem, null,
-      "There should still be a selected item after sorting.");
-    is(RequestsMenu.selectedIndex, selection,
-      "The first item should be still selected after sorting.");
-    is(!!document.querySelector(".network-details-panel"), true,
-      "The network details panel should still be visible after sorting.");
-
-    is(RequestsMenu.items.length, order.length,
-      "There should be a specific number of items in the requests menu.");
-    is(RequestsMenu.visibleItems.length, order.length,
-      "There should be a specific number of visbile items in the requests menu.");
-    is(document.querySelectorAll(".request-list-item").length, order.length,
-      "The visible items in the requests menu are, in fact, visible!");
-
-    for (let i = 0, len = order.length / 5; i < len; i++) {
-      verifyRequestItemTarget(RequestsMenu,
-        RequestsMenu.getItemAtIndex(order[i]),
-        "GET1", SORTING_SJS + "?index=1", {
-          fuzzyUrl: true,
-          status: 101,
-          statusText: "Meh",
-          type: "1",
-          fullMimeType: "text/1",
-          transferred: L10N.getStr("networkMenu.sizeUnavailable"),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
-          time: true
-        });
-    }
-    for (let i = 0, len = order.length / 5; i < len; i++) {
-      verifyRequestItemTarget(RequestsMenu,
-        RequestsMenu.getItemAtIndex(order[i + len]),
-        "GET2", SORTING_SJS + "?index=2", {
-          fuzzyUrl: true,
-          status: 200,
-          statusText: "Meh",
-          type: "2",
-          fullMimeType: "text/2",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
-          time: true
-        });
-    }
-    for (let i = 0, len = order.length / 5; i < len; i++) {
-      verifyRequestItemTarget(RequestsMenu,
-        RequestsMenu.getItemAtIndex(order[i + len * 2]),
-        "GET3", SORTING_SJS + "?index=3", {
-          fuzzyUrl: true,
-          status: 300,
-          statusText: "Meh",
-          type: "3",
-          fullMimeType: "text/3",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
-          time: true
-        });
-    }
-    for (let i = 0, len = order.length / 5; i < len; i++) {
-      verifyRequestItemTarget(RequestsMenu,
-        RequestsMenu.getItemAtIndex(order[i + len * 3]),
-        "GET4", SORTING_SJS + "?index=4", {
-          fuzzyUrl: true,
-          status: 400,
-          statusText: "Meh",
-          type: "4",
-          fullMimeType: "text/4",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
-          time: true
-        });
-    }
-    for (let i = 0, len = order.length / 5; i < len; i++) {
-      verifyRequestItemTarget(RequestsMenu,
-        RequestsMenu.getItemAtIndex(order[i + len * 4]),
-        "GET5", SORTING_SJS + "?index=5", {
-          fuzzyUrl: true,
-          status: 500,
-          statusText: "Meh",
-          type: "5",
-          fullMimeType: "text/5",
-          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
-          size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 49),
-          time: true
-        });
-    }
-  }
-});
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -9,22 +9,27 @@
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
   let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
 
   info("Starting test... ");
 
-  let { document, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
+
   let requestItems = [];
 
-  RequestsMenu.lazyUpdate = false;
-
   const REQUEST_DATA = [
     {
       // request #0
       method: "GET",
       uri: STATUS_CODES_SJS + "?sts=100",
       details: {
         status: 101,
         statusText: "Switching Protocols",
@@ -98,28 +103,34 @@ add_task(function* () {
   yield verifyRequests();
   yield testTab(0, testHeaders);
   yield testTab(2, testParams);
 
   return teardown(monitor);
 
   /**
    * A helper that verifies all requests show the correct information and caches
-   * RequestsMenu items to requestItems array.
+   * request list items to requestItems array.
    */
   function* verifyRequests() {
     info("Verifying requests contain correct information.");
     let index = 0;
     for (let request of REQUEST_DATA) {
-      let item = RequestsMenu.getItemAtIndex(index);
+      let item = getSortedRequests(gStore.getState()).get(index);
       requestItems[index] = item;
 
       info("Verifying request #" + index);
-      yield verifyRequestItemTarget(RequestsMenu, item,
-        request.method, request.uri, request.details);
+      yield verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        item,
+        request.method,
+        request.uri,
+        request.details
+      );
 
       index++;
     }
   }
 
   /**
    * A helper that opens a given tab of request details pane, selects and passes
    * all requests to the given test function.
@@ -161,17 +172,18 @@ add_task(function* () {
   }
 
   /**
    * A function that tests "Params" panel contains correct information.
    */
   function* testParams(data, index) {
     EventUtils.sendMouseEvent({ type: "mousedown" },
       document.querySelectorAll(".request-list-item")[index]);
-    document.querySelector("#params-tab").click();
+    EventUtils.sendMouseEvent({ type: "click" },
+      document.querySelector("#params-tab"));
 
     let panel = document.querySelector("#params-panel");
     let statusParamValue = data.uri.split("=").pop();
     let statusParamShownValue = "\"" + statusParamValue + "\"";
     let treeSections = panel.querySelectorAll(".tree-section");
 
     is(treeSections.length, 1,
       "There should be 1 param section displayed in this panel.");
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -7,72 +7,83 @@
  * Tests if reponses from streaming content types (MPEG-DASH, HLS) are
  * displayed as XML or plain text
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
 
   info("Starting test... ");
-  let { panelWin } = monitor;
-  let { document, NetMonitorView } = panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let {
+    getDisplayedRequests,
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   const REQUESTS = [
     [ "hls-m3u8", /^#EXTM3U/ ],
     [ "mpeg-dash", /^<\?xml/ ]
   ];
 
-  RequestsMenu.lazyUpdate = false;
-
   let wait = waitForNetworkEvents(monitor, REQUESTS.length);
   for (let [fmt] of REQUESTS) {
     let url = CONTENT_TYPE_SJS + "?fmt=" + fmt;
     yield ContentTask.spawn(tab.linkedBrowser, { url }, function* (args) {
       content.wrappedJSObject.performRequests(1, args.url);
     });
   }
   yield wait;
 
   REQUESTS.forEach(([ fmt ], i) => {
-    verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
-      "GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
+    verifyRequestItemTarget(
+      document,
+      getDisplayedRequests(gStore.getState()),
+      getSortedRequests(gStore.getState()).get(i),
+      "GET",
+      CONTENT_TYPE_SJS + "?fmt=" + fmt,
+      {
         status: 200,
         statusText: "OK"
       });
   });
 
   wait = waitForDOM(document, "#response-panel");
-  EventUtils.sendMouseEvent({ type: "mousedown" },
+  EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".network-details-panel-toggle"));
-  document.querySelector("#response-tab").click();
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
   yield wait;
 
-  RequestsMenu.selectedIndex = -1;
+  gStore.dispatch(Actions.selectRequest(null));
 
   yield selectIndexAndWaitForEditor(0);
   // the hls-m3u8 part
   testEditorContent(REQUESTS[0]);
 
   yield selectIndexAndWaitForEditor(1);
   // the mpeg-dash part
   testEditorContent(REQUESTS[1]);
 
   return teardown(monitor);
 
   function* selectIndexAndWaitForEditor(index) {
     let editor = document.querySelector("#response-panel .editor-mount iframe");
     if (!editor) {
       let waitDOM = waitForDOM(document, "#response-panel .editor-mount iframe");
-      RequestsMenu.selectedIndex = index;
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+        document.querySelectorAll(".request-list-item")[index]);
       document.querySelector("#response-tab").click();
       [editor] = yield waitDOM;
       yield once(editor, "DOMContentLoaded");
     } else {
-      RequestsMenu.selectedIndex = index;
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+        document.querySelectorAll(".request-list-item")[index]);
     }
 
     yield waitForDOM(editor.contentDocument, ".CodeMirror-code");
   }
 
   function testEditorContent([ fmt, textRe ]) {
     let editor = document.querySelector("#response-panel .editor-mount iframe");
     let text = editor.contentDocument
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -9,17 +9,23 @@ add_task(function* () {
   yield throttleTest(true);
   yield throttleTest(false);
 });
 
 function* throttleTest(actuallyThrottle) {
   requestLongerTimeout(2);
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
-  const {ACTIVITY_TYPE, EVENTS, NetMonitorController, NetMonitorView} = monitor.panelWin;
+  let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let {
+    getSortedRequests,
+  } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
   // When throttling, must be smaller than the length of the content
   // of SIMPLE_URL in bytes.
   const size = actuallyThrottle ? 200 : 0;
 
   const request = {
@@ -40,17 +46,17 @@ function* throttleTest(actuallyThrottle)
     deferred.resolve(response);
   });
   yield deferred.promise;
 
   let eventPromise = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS);
   yield NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
   yield eventPromise;
 
-  let requestItem = NetMonitorView.RequestsMenu.getItemAtIndex(0);
+  let requestItem = getSortedRequests(gStore.getState()).get(0);
   const reportedOneSecond = requestItem.eventTimings.timings.receive > 1000;
   if (actuallyThrottle) {
     ok(reportedOneSecond, "download reported as taking more than one second");
   } else {
     ok(!reportedOneSecond, "download reported as taking less than one second");
   }
 
   yield teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_timing-division.js
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -6,44 +6,48 @@
 /**
  * Tests if timing intervals are divided againts seconds when appropriate.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
-  let { $all, NetMonitorView } = monitor.panelWin;
-  let { RequestsMenu } = NetMonitorView;
+  let { document, gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  RequestsMenu.lazyUpdate = false;
+  gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 2);
   // Timeout needed for having enough divisions on the time scale.
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(2, null, 3000);
   });
   yield wait;
 
-  let milDivs = $all(".requests-menu-timings-division[data-division-scale=millisecond]");
-  let secDivs = $all(".requests-menu-timings-division[data-division-scale=second]");
-  let minDivs = $all(".requests-menu-timings-division[data-division-scale=minute]");
+  let milDivs = document.querySelectorAll(
+    ".requests-menu-timings-division[data-division-scale=millisecond]");
+  let secDivs = document.querySelectorAll(
+    ".requests-menu-timings-division[data-division-scale=second]");
+  let minDivs = document.querySelectorAll(
+    ".requests-menu-timings-division[data-division-scale=minute]");
 
   info("Number of millisecond divisions: " + milDivs.length);
   info("Number of second divisions: " + secDivs.length);
   info("Number of minute divisions: " + minDivs.length);
 
   milDivs.forEach(div => info(`Millisecond division: ${div.textContent}`));
   secDivs.forEach(div => info(`Second division: ${div.textContent}`));
   minDivs.forEach(div => info(`Minute division: ${div.textContent}`));
 
-  is(RequestsMenu.itemCount, 2, "There should be only two requests made.");
+  is(gStore.getState().requests.requests.size, 2, "There should be only two requests made.");
 
-  let firstRequest = RequestsMenu.getItemAtIndex(0);
-  let lastRequest = RequestsMenu.getItemAtIndex(1);
+  let firstRequest = getSortedRequests(gStore.getState()).get(0);
+  let lastRequest = getSortedRequests(gStore.getState()).get(1);
 
   info("First request happened at: " +
     firstRequest.responseHeaders.headers.find(e => e.name == "Date").value);
   info("Last request happened at: " +
     lastRequest.responseHeaders.headers.find(e => e.name == "Date").value);
 
   ok(secDivs.length,
     "There should be at least one division on the seconds time scale.");
--- a/devtools/client/netmonitor/test/browser_net_truncate.js
+++ b/devtools/client/netmonitor/test/browser_net_truncate.js
@@ -14,31 +14,40 @@ function test() {
   const URL = EXAMPLE_URL + "sjs_truncate-test-server.sjs?limit=" + RESPONSE_BODY_LIMIT;
 
   // Another slow test on Linux debug.
   requestLongerTimeout(2);
 
   initNetMonitor(URL).then(({ tab, monitor }) => {
     info("Starting test... ");
 
-    let { NetMonitorView } = monitor.panelWin;
-    let { RequestsMenu } = NetMonitorView;
+    let { document, gStore, windowRequire } = monitor.panelWin;
+    let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+    let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+    let {
+      getDisplayedRequests,
+      getSortedRequests,
+    } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-    RequestsMenu.lazyUpdate = false;
+    gStore.dispatch(Actions.batchEnable(false));
 
     waitForNetworkEvents(monitor, 1)
       .then(() => teardown(monitor))
       .then(finish);
 
-    monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
-      let requestItem = RequestsMenu.getItemAtIndex(0);
-
-      verifyRequestItemTarget(RequestsMenu, requestItem, "GET", URL, {
-        type: "plain",
-        fullMimeType: "text/plain; charset=utf-8",
-        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
-        size: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
-      });
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
+      verifyRequestItemTarget(
+        document,
+        getDisplayedRequests(gStore.getState()),
+        getSortedRequests(gStore.getState()).get(0),
+        "GET", URL,
+        {
+          type: "plain",
+          fullMimeType: "text/plain; charset=utf-8",
+          transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeMB", 2),
+        }
+      );
     });
 
     tab.linkedBrowser.reload();
   });
 }
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -5,16 +5,17 @@
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
+const { EVENTS } = require("devtools/client/netmonitor/events");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 const {
   decodeUnicodeUrl,
   getUrlBaseName,
   getUrlQuery,
   getUrlHost,
 } = require("devtools/client/netmonitor/request-utils");
 
@@ -171,24 +172,20 @@ function teardown(monitor) {
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
   });
 }
 
 function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
   let deferred = promise.defer();
-
   let panel = aMonitor.panelWin;
-  let events = panel.EVENTS;
-
   let progress = {};
   let genericEvents = 0;
   let postEvents = 0;
-
   let awaitedEventsToListeners = [
     ["UPDATING_REQUEST_HEADERS", onGenericEvent],
     ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
     ["UPDATING_REQUEST_COOKIES", onGenericEvent],
     ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
     ["UPDATING_REQUEST_POST_DATA", onPostEvent],
     ["RECEIVED_REQUEST_POST_DATA", onPostEvent],
     ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
@@ -205,17 +202,17 @@ function waitForNetworkEvents(aMonitor, 
   function initProgressForURL(url) {
     if (progress[url]) return;
     progress[url] = {};
     awaitedEventsToListeners.forEach(([e]) => progress[url][e] = 0);
   }
 
   function updateProgressForURL(url, event) {
     initProgressForURL(url);
-    progress[url][Object.keys(events).find(e => events[e] == event)] = 1;
+    progress[url][Object.keys(EVENTS).find(e => EVENTS[e] == event)] = 1;
   }
 
   function onGenericEvent(event, actor) {
     genericEvents++;
     maybeResolve(event, actor);
   }
 
   function onPostEvent(event, actor) {
@@ -238,49 +235,47 @@ function waitForNetworkEvents(aMonitor, 
     // info("> Current state: " + JSON.stringify(progress, null, 2));
 
     // There are 15 updates which need to be fired for a request to be
     // considered finished. The "requestPostData" packet isn't fired for
     // non-POST requests.
     if (genericEvents >= (aGetRequests + aPostRequests) * 13 &&
         postEvents >= aPostRequests * 2) {
 
-      awaitedEventsToListeners.forEach(([e, l]) => panel.off(events[e], l));
+      awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
       executeSoon(deferred.resolve);
     }
   }
 
-  awaitedEventsToListeners.forEach(([e, l]) => panel.on(events[e], l));
+  awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
   return deferred.promise;
 }
 
 /**
  * Convert a store record (model) to the rendered element. Tests that need to use
  * this should be rewritten - test the rendered markup at unit level, integration
  * mochitest should check only the store state.
  */
 function getItemTarget(requestList, requestItem) {
   const items = requestList.mountPoint.querySelectorAll(".request-list-item");
   return [...items].find(el => el.dataset.id == requestItem.id);
 }
 
-function verifyRequestItemTarget(requestList, requestItem, aMethod, aUrl, aData = {}) {
+function verifyRequestItemTarget(document, requestList, requestItem, aMethod,
+                                 aUrl, aData = {}) {
   info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
-  // This bloats log sizes significantly in automation (bug 992485)
-  // info("> Request: " + requestItem.toSource());
 
-  let visibleIndex = requestList.visibleItems.indexOf(requestItem);
+  let visibleIndex = requestList.indexOf(requestItem);
 
   info("Visible index of item: " + visibleIndex);
 
   let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
         transferred, size, time, displayedStatus } = aData;
 
-  let target = getItemTarget(requestList, requestItem);
-
+  let target = document.querySelectorAll(".request-list-item")[visibleIndex];
   let unicodeUrl = decodeUnicodeUrl(aUrl);
   let name = getUrlBaseName(aUrl);
   let query = getUrlQuery(aUrl);
   let hostPort = getUrlHost(aUrl);
   let remoteAddress = requestItem.remoteAddress;
 
   if (fuzzyUrl) {
     ok(requestItem.method.startsWith(aMethod), "The attached method is correct.");
@@ -359,30 +354,30 @@ function verifyRequestItemTarget(request
     let value = target.querySelector(".requests-menu-timings-total").textContent;
     let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("title");
     info("Displayed time: " + value);
     info("Tooltip time: " + tooltip);
     ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is correct.");
     ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is correct.");
   }
 
-  if (visibleIndex != -1) {
-    if (visibleIndex % 2 == 0) {
+  if (visibleIndex !== -1) {
+    if (visibleIndex % 2 === 0) {
       ok(target.classList.contains("even"), "Item should have 'even' class.");
       ok(!target.classList.contains("odd"), "Item shouldn't have 'odd' class.");
     } else {
       ok(!target.classList.contains("even"), "Item shouldn't have 'even' class.");
       ok(target.classList.contains("odd"), "Item should have 'odd' class.");
     }
   }
 }
 
 /**
  * Helper function for waiting for an event to fire before resolving a promise.
- * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED);
+ * Example: waitFor(aMonitor.panelWin, EVENT_NAME);
  *
  * @param object subject
  *        The event emitter object that is being listened to.
  * @param string eventName
  *        The name of the event to listen to.
  * @return object
  *        Returns a promise that resolves upon firing of the event.
  */
@@ -505,28 +500,8 @@ function waitForContentMessage(name) {
 
   let def = promise.defer();
   mm.addMessageListener(name, function onMessage(msg) {
     mm.removeMessageListener(name, onMessage);
     def.resolve(msg);
   });
   return def.promise;
 }
-
-/**
- * Open the requestMenu menu and return all of it's items in a flat array
- * @param {netmonitorPanel} netmonitor
- * @param {Event} event mouse event with screenX and screenX coordinates
- * @return An array of MenuItems
- */
-function openContextMenuAndGetAllItems(netmonitor, event) {
-  let menu = netmonitor.RequestsMenu.contextMenu.open(event);
-
-  // Flatten all menu items into a single array to make searching through it easier
-  let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
-    if (item.submenu) {
-      return addItem(item.submenu.items);
-    }
-    return item;
-  }));
-
-  return allItems;
-}
--- a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
@@ -8,32 +8,35 @@
 
 const TEST_URL = TEST_BASE_HTTP + "doc_uncached.html";
 
 add_task(function* () {
   info("Opening netmonitor");
   let tab = yield addTab("about:blank");
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
-  let netmonitor = toolbox.getPanel("netmonitor");
-  let { RequestsMenu } = netmonitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+  let monitor = toolbox.getPanel("netmonitor");
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  gStore.dispatch(Actions.batchEnable(false));
 
   info("Navigating to test page");
   yield navigateTo(TEST_URL);
 
   info("Opening Style Editor");
   let styleeditor = yield toolbox.selectTool("styleeditor");
 
   info("Waiting for the source to be loaded.");
   yield styleeditor.UI.editors[0].getSourceEditor();
 
   info("Checking Netmonitor contents.");
   let items = [];
-  for (let item of RequestsMenu.items) {
+  for (let item of getSortedRequests(gStore.getState())) {
     if (item.url.endsWith("doc_uncached.css")) {
       items.push(item);
     }
   }
 
   is(items.length, 2,
      "Got two requests for doc_uncached.css after Style Editor was loaded.");
   ok(items[1].fromCache,
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -78,19 +78,18 @@
   --timing-wait-color: rgba(94, 136, 176, 0.8); /* blue grey */
   --timing-receive-color: rgba(112, 191, 83, 0.8); /* green */
 
   --sort-ascending-image: url(chrome://devtools/skin/images/firebug/arrow-up.svg);
   --sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
 }
 
 .request-list-container {
-  display: -moz-box;
-  -moz-box-orient: vertical;
-  -moz-box-flex: 1;
+  display: flex;
+  flex-direction: column;
 }
 
 .request-list-empty-notice {
   margin: 0;
   padding: 12px;
   font-size: 120%;
 }
 
@@ -130,24 +129,26 @@
   flex-wrap: nowrap;
 }
 
 .theme-firebug #requests-menu-toolbar {
   height: 19px !important;
 }
 
 .requests-menu-contents {
-  display: -moz-box;
-  -moz-box-orient: vertical;
-  -moz-box-flex: 1;
+  display: flex;
+  flex-direction: column;
   overflow-x: hidden;
   overflow-y: auto;
 
   --timings-scale: 1;
   --timings-rev-scale: 1;
+
+  /* Devtools panel view height - tabbar height - toolbar height */
+  height: calc(100vh - 48px);
 }
 
 .requests-menu-subitem {
   display: flex;
   flex: none;
   box-sizing: border-box;
   align-items: center;
   padding: 3px;
@@ -1326,13 +1327,17 @@
   height: 100%;
   overflow: hidden;
 }
 
 #splitter-adjustable-box[hidden=true] {
   display: none;
 }
 
+#react-request-list-hook {
+  -moz-box-flex: 1;
+}
+
 #primed-cache-chart,
 #empty-cache-chart {
   display: -moz-box;
   -moz-box-flex: 1;
 }
--- a/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
+++ b/devtools/client/webconsole/test/browser_netmonitor_shows_reqs_in_webconsole.js
@@ -57,17 +57,21 @@ function loadDocument(browser) {
   }, {capture: true, once: true});
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_PATH);
 
   return deferred.promise;
 }
 
 function testNetmonitor(toolbox) {
   let monitor = toolbox.getCurrentPanel();
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
+
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
+  gStore.dispatch(Actions.batchEnable(false));
 
-  let item = RequestsMenu.getItemAtIndex(0);
+  is(gStore.getState().requests.requests.size, 1, "Network request appears in the network panel");
+
+  let item = getSortedRequests(gStore.getState()).get(0);
   is(item.method, "GET", "The attached method is correct.");
   is(item.url, TEST_PATH, "The attached url is correct.");
 }
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_panel.js
@@ -17,14 +17,19 @@ add_task(function* () {
 
   const hud = yield loadPageAndGetHud(TEST_NETWORK_REQUEST_URI);
   let request = yield finishedRequest;
 
   yield hud.ui.openNetworkPanel(request.actor);
   let toolbox = gDevTools.getToolbox(hud.target);
   is(toolbox.currentToolId, "netmonitor", "Network panel was opened");
   let panel = toolbox.getCurrentPanel();
-  let selected = panel.panelWin.NetMonitorView.RequestsMenu.selectedItem;
+
+  let { gStore, windowRequire } = panel.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  let selected = getSelectedRequest(gStore.getState());
   is(selected.method, request.request.method,
      "The correct request is selected");
   is(selected.url, request.request.url,
      "The correct request is definitely selected");
 });
--- a/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
+++ b/devtools/client/webconsole/test/browser_webconsole_netlogging_reset_filter.js
@@ -11,18 +11,16 @@
 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);
@@ -35,30 +33,34 @@ add_task(function* () {
   let htmlRequest = requests.find(e => e.request.url.endsWith("html"));
   ok(htmlRequest, "htmlRequest was a html");
 
   yield hud.ui.openNetworkPanel(htmlRequest.actor);
   let toolbox = gDevTools.getToolbox(hud.target);
   is(toolbox.currentToolId, "netmonitor", "Network panel was opened");
 
   let panel = toolbox.getCurrentPanel();
-  let selected = panel.panelWin.NetMonitorView.RequestsMenu.selectedItem;
+  let { gStore, windowRequire } = panel.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSelectedRequest } = windowRequire("devtools/client/netmonitor/selectors/index");
+
+  let selected = getSelectedRequest(gStore.getState());
   is(selected.method, htmlRequest.request.method,
      "The correct request is selected");
   is(selected.url, htmlRequest.request.url,
      "The correct request is definitely selected");
 
   // Filter out the HTML request.
-  panel.panelWin.gStore.dispatch(Actions.toggleRequestFilterType("js"));
+  gStore.dispatch(Actions.toggleRequestFilterType("js"));
 
   yield toolbox.selectTool("webconsole");
   is(toolbox.currentToolId, "webconsole", "Web console was selected");
   yield hud.ui.openNetworkPanel(htmlRequest.actor);
 
-  panel.panelWin.NetMonitorView.RequestsMenu.selectedItem;
+  selected = getSelectedRequest(gStore.getState());
   is(selected.method, htmlRequest.request.method,
      "The correct request is selected");
   is(selected.url, htmlRequest.request.url,
      "The correct request is definitely selected");
 
   // All tests are done. Shutdown.
   HUDService.lastFinishedRequest.callback = null;
   htmlRequest = browser = requests = hud = null;
--- a/devtools/client/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
+++ b/devtools/client/webconsole/test/browser_webconsole_shows_reqs_in_netmonitor.js
@@ -57,16 +57,21 @@ function loadDocument(browser) {
   }, {capture: true, once: true});
   BrowserTestUtils.loadURI(gBrowser.selectedBrowser, TEST_PATH);
 
   return deferred.promise;
 }
 
 function testNetmonitor(toolbox) {
   let monitor = toolbox.getCurrentPanel();
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
-  RequestsMenu.lazyUpdate = false;
-  is(RequestsMenu.itemCount, 1, "Network request appears in the network panel");
+
+  let { gStore, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+  let { getSortedRequests } = windowRequire("devtools/client/netmonitor/selectors/index");
 
-  let item = RequestsMenu.getItemAtIndex(0);
+  gStore.dispatch(Actions.batchEnable(false));
+
+  is(gStore.getState().requests.requests.size, 1, "Network request appears in the network panel");
+
+  let item = getSortedRequests(gStore.getState()).get(0);
   is(item.method, "GET", "The request method is correct.");
   is(item.url, TEST_PATH, "The request url is correct.");
 }