Bug 1336384 - Implement top level NetworkMonitor component r?honza,gasolin draft
authorRicky Chien <rchien@mozilla.com>
Mon, 13 Feb 2017 13:16:50 +0800
changeset 484621 e526cc5e30ec72bebd7b393918994e4659d4af7a
parent 484620 d58cbdcb741321a8da5ff50854f08d69dce3ea09
child 484622 96296a95beec5c26e22850b56ca3a7c4b76eb3a5
push id45522
push userbmo:rchien@mozilla.com
push dateWed, 15 Feb 2017 15:20:11 +0000
reviewershonza, gasolin
bugs1336384
milestone54.0a1
Bug 1336384 - Implement top level NetworkMonitor component r?honza,gasolin MozReview-Commit-ID: 6E3his8E20A
devtools/client/netmonitor/actions/requests.js
devtools/client/netmonitor/actions/ui.js
devtools/client/netmonitor/components/monitor-panel.js
devtools/client/netmonitor/components/moz.build
devtools/client/netmonitor/components/network-monitor.js
devtools/client/netmonitor/components/request-list-content.js
devtools/client/netmonitor/components/request-list-empty.js
devtools/client/netmonitor/components/request-list-header.js
devtools/client/netmonitor/components/statistics-panel.js
devtools/client/netmonitor/l10n.js
devtools/client/netmonitor/moz.build
devtools/client/netmonitor/netmonitor-controller.js
devtools/client/netmonitor/netmonitor-view.js
devtools/client/netmonitor/netmonitor.js
devtools/client/netmonitor/netmonitor.xul
devtools/client/netmonitor/panel.js
devtools/client/netmonitor/prefs.js
devtools/client/netmonitor/reducers/ui.js
devtools/client/netmonitor/request-list-context-menu.js
devtools/client/netmonitor/request-list-tooltip.js
devtools/client/netmonitor/shared/components/headers-panel.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_image-tooltip.js
devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
devtools/client/netmonitor/test/browser_net_prefs-reload.js
devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
devtools/client/netmonitor/test/browser_net_simple-init.js
devtools/client/netmonitor/test/browser_net_statistics-01.js
devtools/client/netmonitor/test/browser_net_statistics-02.js
devtools/client/netmonitor/test/head.js
devtools/client/themes/netmonitor.css
--- a/devtools/client/netmonitor/actions/requests.js
+++ b/devtools/client/netmonitor/actions/requests.js
@@ -1,14 +1,12 @@
 /* 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 */
-
 "use strict";
 
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SEND_CUSTOM_REQUEST,
@@ -43,17 +41,17 @@ function cloneSelectedRequest() {
     type: CLONE_SELECTED_REQUEST
   };
 }
 
 /**
  * Send a new HTTP request using the data in the custom request form.
  */
 function sendCustomRequest() {
-  if (!NetMonitorController.supportsCustomRequest) {
+  if (!window.NetMonitorController.supportsCustomRequest) {
     return cloneSelectedRequest();
   }
 
   return (dispatch, getState) => {
     const selected = getSelectedRequest(getState());
 
     if (!selected) {
       return;
@@ -67,17 +65,17 @@ function sendCustomRequest() {
     };
     if (selected.requestHeaders) {
       data.headers = selected.requestHeaders.headers;
     }
     if (selected.requestPostData) {
       data.body = selected.requestPostData.postData.text;
     }
 
-    NetMonitorController.webConsoleClient.sendHTTPRequest(data, (response) => {
+    window.NetMonitorController.webConsoleClient.sendHTTPRequest(data, (response) => {
       return dispatch({
         type: SEND_CUSTOM_REQUEST,
         id: response.eventActor.actor,
       });
     });
   };
 }
 
--- a/devtools/client/netmonitor/actions/ui.js
+++ b/devtools/client/netmonitor/actions/ui.js
@@ -1,15 +1,16 @@
 /* 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 {
+  ACTIVITY_TYPE,
   OPEN_NETWORK_DETAILS,
   OPEN_STATISTICS,
   SELECT_DETAILS_PANEL_TAB,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 /**
  * Change network details panel.
@@ -24,16 +25,19 @@ function openNetworkDetails(open) {
 }
 
 /**
  * Change performance statistics panel open state.
  *
  * @param {boolean} visible - expected performance statistics panel open state
  */
 function openStatistics(open) {
+  if (open) {
+    window.NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+  }
   return {
     type: OPEN_STATISTICS,
     open,
   };
 }
 
 /**
  * Waterfall width has changed (likely on window resize). Update the UI.
--- a/devtools/client/netmonitor/components/monitor-panel.js
+++ b/devtools/client/netmonitor/components/monitor-panel.js
@@ -1,14 +1,12 @@
 /* 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 gNetwork, NetMonitorController */
-
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
@@ -50,40 +48,33 @@ const MonitorPanel = createClass({
   },
 
   componentDidMount() {
     MediaQueryList.addListener(this.onLayoutChange);
   },
 
   componentWillReceiveProps(nextProps) {
     let {
-      openNetworkDetails,
       request = {},
       updateRequest,
     } = nextProps;
     let {
       formDataSections,
       requestHeaders,
       requestHeadersFromUploadStream,
       requestPostData,
     } = request;
 
-    if (nextProps.isEmpty) {
-      openNetworkDetails(false);
-    } else if (nextProps.request && nextProps.request !== this.props.request) {
-      openNetworkDetails(true);
-    }
-
     if (!formDataSections && requestHeaders &&
         requestHeadersFromUploadStream && requestPostData) {
       getFormDataSections(
         requestHeaders,
         requestHeadersFromUploadStream,
         requestPostData,
-        gNetwork.getString.bind(gNetwork),
+        window.gNetwork.getString.bind(window.gNetwork),
       ).then((newFormDataSections) => {
         updateRequest(
           request.id,
           { formDataSections: newFormDataSections },
           true,
         );
       });
     }
@@ -117,17 +108,17 @@ const MonitorPanel = createClass({
           initialHeight: `${Prefs.networkDetailsHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty }),
           endPanel: networkDetailsOpen ?
             NetworkDetailsPanel({
               ref: "networkDetailsPanel",
-              toolbox: NetMonitorController._toolbox,
+              toolbox: window.NetMonitorController._toolbox,
             }) : null,
           endPanelControl: true,
           vert: this.state.isVerticalSpliter,
         }),
       )
     );
   }
 });
--- 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(
     'monitor-panel.js',
+    'network-monitor.js',
     'request-list-content.js',
     'request-list-empty.js',
     'request-list-header.js',
     'request-list-item.js',
     'request-list.js',
     'statistics-panel.js',
     'toolbar.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/components/network-monitor.js
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+// Components
+const MonitoPanel = createFactory(require("./monitor-panel"));
+const StatisticsPanel = createFactory(require("./statistics-panel"));
+
+const { div } = DOM;
+
+/*
+ * Network monitor component
+ */
+function NetworkMonitor({ statisticsOpen }) {
+  return (
+    div({ className: "network-monitor theme-sidebar" },
+      !statisticsOpen ? MonitoPanel() : StatisticsPanel()
+    )
+  );
+}
+
+NetworkMonitor.displayName = "NetworkMonitor";
+
+NetworkMonitor.propTypes = {
+  statisticsOpen: PropTypes.bool.isRequired,
+};
+
+module.exports = connect(
+  (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
+)(NetworkMonitor);
--- a/devtools/client/netmonitor/components/request-list-content.js
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -1,14 +1,12 @@
 /* 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 */
-
 "use strict";
 
 const { KeyCodes } = require("devtools/client/shared/keycodes");
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
@@ -53,17 +51,20 @@ const RequestListContent = createClass({
   },
 
   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" });
+    this.tooltip = new HTMLTooltip(
+      window.NetMonitorController._toolbox.doc,
+      { type: "arrow" }
+     );
   },
 
   componentDidMount() {
     // Set the CSS variables for waterfall scaling
     this.setScalingStyles();
 
     // Install event handler for displaying a tooltip
     this.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
--- a/devtools/client/netmonitor/components/request-list-empty.js
+++ b/devtools/client/netmonitor/components/request-list-empty.js
@@ -1,14 +1,12 @@
 /* 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 */
-
 "use strict";
 
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
@@ -64,11 +62,12 @@ const RequestListEmptyNotice = createCla
   }
 });
 
 module.exports = connect(
   undefined,
   dispatch => ({
     onPerfClick: () => dispatch(Actions.openStatistics(true)),
     onReloadClick: () =>
-      NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
+      window.NetMonitorController
+        .triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
   })
 )(RequestListEmptyNotice);
--- a/devtools/client/netmonitor/components/request-list-header.js
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -1,14 +1,12 @@
 /* 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 document */
-
 "use strict";
 
 const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
 const { div, button } = DOM;
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
 const { L10N } = require("../l10n");
 const { getWaterfallScale } = require("../selectors/index");
--- a/devtools/client/netmonitor/components/statistics-panel.js
+++ b/devtools/client/netmonitor/components/statistics-panel.js
@@ -38,17 +38,17 @@ const StatisticsPanel = createClass({
   propTypes: {
     closeStatistics: PropTypes.func.isRequired,
     enableRequestFilterTypeOnly: PropTypes.func.isRequired,
     requests: PropTypes.object,
   },
 
   componentDidUpdate(prevProps) {
     const { requests } = this.props;
-    let ready = requests && requests.every((req) =>
+    let ready = requests && !requests.isEmpty() && requests.every((req) =>
       req.contentSize !== undefined && req.mimeType && req.responseHeaders &&
       req.status !== undefined && req.totalTime !== undefined
     );
 
     this.createChart({
       id: "primedCacheChart",
       title: CHARTS_CACHE_ENABLED,
       data: ready ? this.sanitizeChartDataSource(requests, false) : null,
--- a/devtools/client/netmonitor/l10n.js
+++ b/devtools/client/netmonitor/l10n.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {LocalizationHelper} = require("devtools/shared/l10n");
+const { LocalizationHelper } = require("devtools/shared/l10n");
 
 const NET_STRINGS_URI = "devtools/client/locales/netmonitor.properties";
 const WEBCONSOLE_STRINGS_URI = "devtools/client/locales/webconsole.properties";
 
 exports.L10N = new LocalizationHelper(NET_STRINGS_URI);
 exports.WEBCONSOLE_L10N = new LocalizationHelper(WEBCONSOLE_STRINGS_URI);
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -14,17 +14,16 @@ DIRS += [
 ]
 
 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-list-tooltip.js',
     'request-utils.js',
     'sort-predicates.js',
     'store.js',
     'waterfall-background.js',
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -1,14 +1,12 @@
 /* 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, NetMonitorView, gStore, gNetwork */
-
 "use strict";
 
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { TimelineFront } = require("devtools/shared/fronts/timeline");
 const { CurlUtils } = require("devtools/client/shared/curl");
 const { Task } = require("devtools/shared/task");
@@ -20,60 +18,53 @@ const {
   fetchHeaders,
   formDataURI,
 } = require("./request-utils");
 const {
   getRequestById,
   getDisplayedRequestById,
 } = require("./selectors/index");
 
-// Initialize the global Redux store
-window.gStore = configureStore();
+const gStore = window.gStore = configureStore();
 
 /**
  * Object defining the network monitor controller components.
  */
 var NetMonitorController = {
   /**
    * Initializes the view and connects the monitor client.
    *
    * @return object
    *         A promise that is resolved when the monitor finishes startup.
    */
   startupNetMonitor: Task.async(function* () {
     if (this._startup) {
       return this._startup.promise;
     }
     this._startup = promise.defer();
-    {
-      NetMonitorView.initialize();
-      yield this.connect();
-    }
+    yield this.connect();
     this._startup.resolve();
     return undefined;
   }),
 
   /**
    * Destroys the view and disconnects the monitor client from the server.
    *
    * @return object
    *         A promise that is resolved when the monitor finishes shutdown.
    */
   shutdownNetMonitor: Task.async(function* () {
     if (this._shutdown) {
       return this._shutdown.promise;
     }
     this._shutdown = promise.defer();
-    {
-      gStore.dispatch(Actions.batchReset());
-      NetMonitorView.destroy();
-      this.TargetEventsHandler.disconnect();
-      this.NetworkEventsHandler.disconnect();
-      yield this.disconnect();
-    }
+    gStore.dispatch(Actions.batchReset());
+    this.TargetEventsHandler.disconnect();
+    this.NetworkEventsHandler.disconnect();
+    yield this.disconnect();
     this._shutdown.resolve();
     return undefined;
   }),
 
   /**
    * Initiates remote or chrome network monitoring based on the current target,
    * wiring event handlers as necessary. Since the TabTarget will have already
    * started listening to network requests by now, this is largely
@@ -441,16 +432,17 @@ TargetEventsHandler.prototype = {
 };
 
 /**
  * Functions handling target network events.
  */
 function NetworkEventsHandler() {
   this.addRequest = this.addRequest.bind(this);
   this.updateRequest = this.updateRequest.bind(this);
+  this.getString = this.getString.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);
@@ -583,43 +575,41 @@ NetworkEventsHandler.prototype = {
       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));
+      let headers = yield fetchHeaders(requestHeaders, this.getString);
       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));
+      let headers = yield fetchHeaders(responseHeaders, this.getString);
       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 response = yield this.getString(text);
       let payload = {};
 
       if (mimeType.includes("image/")) {
         payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
       }
 
       responseContent.content.text = response;
       payload.responseContent = responseContent;
@@ -630,17 +620,17 @@ NetworkEventsHandler.prototype = {
         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);
+      let postData = yield this.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 };
@@ -655,17 +645,17 @@ NetworkEventsHandler.prototype = {
       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),
+            value: yield this.getString(cookie.value),
           }));
         }
         if (reqCookies.length) {
           yield gStore.dispatch(Actions.updateRequest(
             action.id,
             { requestCookies: reqCookies },
             true));
         }
@@ -676,17 +666,17 @@ NetworkEventsHandler.prototype = {
       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),
+            value: yield this.getString(cookie.value),
           }));
         }
         if (resCookies.length) {
           yield gStore.dispatch(Actions.updateRequest(
             action.id,
             { responseCookies: resCookies },
             true));
         }
deleted file mode 100644
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ /dev/null
@@ -1,79 +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 gStore, NetMonitorController */
-
-"use strict";
-
-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 MonitorPanel = createFactory(require("./components/monitor-panel"));
-const StatisticsPanel = createFactory(require("./components/statistics-panel"));
-
-/**
- * Object defining the network monitor view components.
- */
-exports.NetMonitorView = {
-  /**
-   * Initializes the network monitor view.
-   */
-  initialize: function () {
-    this._body = document.querySelector("#body");
-
-    this.monitorPanel = document.querySelector("#react-monitor-panel-hook");
-    ReactDOM.render(Provider(
-      { store: gStore },
-      MonitorPanel(),
-    ), this.monitorPanel);
-
-    this.statisticsPanel = document.querySelector("#react-statistics-panel-hook");
-    ReactDOM.render(Provider(
-      { store: gStore },
-      StatisticsPanel(),
-    ), this.statisticsPanel);
-
-    // 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 () {
-    ReactDOM.unmountComponentAtNode(this.monitorPanel);
-    ReactDOM.unmountComponentAtNode(this.statisticsPanel);
-    this.unsubscribeStore();
-  },
-
-  toggleFrontendMode: function () {
-    if (gStore.getState().ui.statisticsOpen) {
-      this._body.selectedPanel = this.statisticsPanel;
-      NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
-    } else {
-      this._body.selectedPanel = this.monitorPanel;
-    }
-  },
-};
-
-// 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;
-    }
-  };
-}
--- a/devtools/client/netmonitor/netmonitor.js
+++ b/devtools/client/netmonitor/netmonitor.js
@@ -1,40 +1,52 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* globals window, document, NetMonitorController, NetMonitorView */
-/* exported Netmonitor, NetMonitorController, NetMonitorView */
+/* exported Netmonitor, NetMonitorController */
 
 "use strict";
 
 const Cu = Components.utils;
 const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 
 function Netmonitor(toolbox) {
-  const { require } = BrowserLoader({
+  const require = window.windowRequire = BrowserLoader({
     baseURI: "resource://devtools/client/netmonitor/",
     window,
     commonLibRequire: toolbox.browserRequire,
-  });
-
-  window.windowRequire = require;
+  }).require;
 
-  const { NetMonitorController } = require("./netmonitor-controller.js");
-  const { NetMonitorView } = require("./netmonitor-view.js");
-
-  window.NetMonitorController = NetMonitorController;
-  window.NetMonitorView = NetMonitorView;
-
-  NetMonitorController._toolbox = toolbox;
-  NetMonitorController._target = toolbox.target;
+  window.NetMonitorController = require("./netmonitor-controller").NetMonitorController;
+  window.NetMonitorController._toolbox = toolbox;
+  window.NetMonitorController._target = toolbox.target;
 }
 
 Netmonitor.prototype = {
   init() {
+    const require = window.windowRequire;
+    const { createFactory } = require("devtools/client/shared/vendor/react");
+    const { render } = require("devtools/client/shared/vendor/react-dom");
+    const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+
+    // Components
+    const NetworkMonitor = createFactory(require("./components/network-monitor"));
+
+    this.networkMonitor = document.querySelector("#react-network-monitor-hook");
+
+    render(Provider(
+      { store: window.gStore },
+      NetworkMonitor({ toolbox: window.NetMonitorController._toolbox }),
+    ), this.networkMonitor);
+
     return window.NetMonitorController.startupNetMonitor();
   },
 
   destroy() {
+    const require = window.windowRequire;
+    const { unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
+
+    unmountComponentAtNode(this.networkMonitor);
+
     return window.NetMonitorController.shutdownNetMonitor();
   }
 };
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -9,21 +9,11 @@
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"/>
   <script type="text/javascript" src="netmonitor.js"/>
 
-  <deck id="body"
-        class="theme-sidebar"
-        flex="1"
-        data-localization-bundle="devtools/client/locales/netmonitor.properties">
-    <html:div xmlns="http://www.w3.org/1999/xhtml"
-              id="react-monitor-panel-hook">
-    </html:div>
-    <html:div xmlns="http://www.w3.org/1999/xhtml"
-              id="react-statistics-panel-hook">
-    </html:div>
-  </deck>
-
+  <html:div xmlns="http://www.w3.org/1999/xhtml"
+               id="react-network-monitor-hook"/>
 </window>
--- a/devtools/client/netmonitor/panel.js
+++ b/devtools/client/netmonitor/panel.js
@@ -1,74 +1,32 @@
 /* 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 promise = require("promise");
-const EventEmitter = require("devtools/shared/event-emitter");
-const { Task } = require("devtools/shared/task");
-const { localizeMarkup } = require("devtools/shared/l10n");
-
 function NetMonitorPanel(iframeWindow, toolbox) {
   this.panelWin = iframeWindow;
   this.panelDoc = iframeWindow.document;
-  this._toolbox = toolbox;
-
-  this._netmonitor = new iframeWindow.Netmonitor(toolbox);
-
-  EventEmitter.decorate(this);
+  this.toolbox = toolbox;
+  this.netmonitor = new iframeWindow.Netmonitor(toolbox);
 }
 
-exports.NetMonitorPanel = NetMonitorPanel;
-
 NetMonitorPanel.prototype = {
-  /**
-   * Open is effectively an asynchronous constructor.
-   *
-   * @return object
-   *         A promise that is resolved when the NetMonitor completes opening.
-   */
-  open: Task.async(function* () {
-    if (this._opening) {
-      return this._opening;
+  open: async function () {
+    if (!this.toolbox.target.isRemote) {
+      await this.toolbox.target.makeRemote();
     }
-    // Localize all the nodes containing a data-localization attribute.
-    localizeMarkup(this.panelDoc);
-
-    let deferred = promise.defer();
-    this._opening = deferred.promise;
-
-    // Local monitoring needs to make the target remote.
-    if (!this.target.isRemote) {
-      yield this.target.makeRemote();
-    }
-
-    yield this._netmonitor.init();
-
+    await this.netmonitor.init();
+    this.emit("ready");
     this.isReady = true;
-    this.emit("ready");
-
-    deferred.resolve(this);
-    return this._opening;
-  }),
-
-  // DevToolPanel API
-
-  get target() {
-    return this._toolbox.target;
+    return this;
   },
 
-  destroy: Task.async(function* () {
-    if (this._destroying) {
-      return this._destroying;
-    }
-    let deferred = promise.defer();
-    this._destroying = deferred.promise;
+  destroy: async function () {
+    await this.netmonitor.destroy();
+    this.emit("destroyed");
+    return this;
+  },
+};
 
-    yield this._netmonitor.destroy();
-    this.emit("destroyed");
-
-    deferred.resolve();
-    return this._destroying;
-  })
-};
+exports.NetMonitorPanel = NetMonitorPanel;
--- a/devtools/client/netmonitor/prefs.js
+++ b/devtools/client/netmonitor/prefs.js
@@ -1,17 +1,16 @@
 /* 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 {PrefsHelper} = require("devtools/client/shared/prefs");
+const { PrefsHelper } = require("devtools/client/shared/prefs");
 
 /**
  * Shortcuts for accessing various network monitor preferences.
  */
-
 exports.Prefs = new PrefsHelper("devtools.netmonitor", {
   networkDetailsWidth: ["Int", "panes-network-details-width"],
   networkDetailsHeight: ["Int", "panes-network-details-height"],
   filters: ["Json", "filters"]
 });
--- a/devtools/client/netmonitor/reducers/ui.js
+++ b/devtools/client/netmonitor/reducers/ui.js
@@ -1,21 +1,23 @@
 /* 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 I = require("devtools/client/shared/vendor/immutable");
 const {
+  CLEAR_REQUESTS,
   OPEN_NETWORK_DETAILS,
   OPEN_STATISTICS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SELECT_DETAILS_PANEL_TAB,
   SEND_CUSTOM_REQUEST,
+  SELECT_REQUEST,
   WATERFALL_RESIZE,
 } = require("../constants");
 
 const UI = I.Record({
   detailsPanelSelectedTab: "headers",
   networkDetailsOpen: false,
   statisticsOpen: false,
   waterfallWidth: null,
@@ -37,25 +39,29 @@ function openStatistics(state, action) {
 }
 
 function setDetailsPanelTab(state, action) {
   return state.set("detailsPanelSelectedTab", action.id);
 }
 
 function ui(state = new UI(), action) {
   switch (action.type) {
+    case CLEAR_REQUESTS:
+      return openNetworkDetails(state, { open: false });
     case OPEN_NETWORK_DETAILS:
       return openNetworkDetails(state, action);
     case OPEN_STATISTICS:
       return openStatistics(state, action);
     case REMOVE_SELECTED_CUSTOM_REQUEST:
     case SEND_CUSTOM_REQUEST:
       return openNetworkDetails(state, { open: false });
     case SELECT_DETAILS_PANEL_TAB:
       return setDetailsPanelTab(state, action);
+    case SELECT_REQUEST:
+      return openNetworkDetails(state, { open: true });
     case WATERFALL_RESIZE:
       return resizeWaterfall(state, action);
     default:
       return state;
   }
 }
 
 module.exports = ui;
--- a/devtools/client/netmonitor/request-list-context-menu.js
+++ b/devtools/client/netmonitor/request-list-context-menu.js
@@ -1,14 +1,12 @@
 /* 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");
@@ -35,21 +33,21 @@ function RequestListContextMenu({
   openStatistics,
 }) {
   this.cloneSelectedRequest = cloneSelectedRequest;
   this.openStatistics = openStatistics;
 }
 
 RequestListContextMenu.prototype = {
   get selectedRequest() {
-    return getSelectedRequest(gStore.getState());
+    return getSelectedRequest(window.gStore.getState());
   },
 
   get sortedRequests() {
-    return getSortedRequests(gStore.getState());
+    return getSortedRequests(window.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 } = {}) {
@@ -155,17 +153,17 @@ RequestListContextMenu.prototype = {
       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 &&
+      visible: !!(window.NetMonitorController.supportsCustomRequest &&
                selectedRequest && !selectedRequest.isCustom),
       click: this.cloneSelectedRequest,
     }));
 
     menu.append(new MenuItem({
       type: "separator",
       visible: !!selectedRequest,
     }));
@@ -177,21 +175,21 @@ RequestListContextMenu.prototype = {
       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,
+      visible: !!window.NetMonitorController.supportsPerfStats,
       click: () => this.openStatistics(true)
     }));
 
-    menu.popup(screenX, screenY, NetMonitorController._toolbox);
+    menu.popup(screenX, screenY, window.NetMonitorController._toolbox);
     return menu;
   },
 
   /**
    * Opens selected item in a new tab.
    */
   openRequestInTab() {
     let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
@@ -223,34 +221,34 @@ RequestListContextMenu.prototype = {
   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));
+      window.gNetwork.getString.bind(window.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);
+      string = yield window.gNetwork.getString(postData);
       if (Services.appinfo.OS !== "WINNT") {
         string = string.replace(/\r/g, "");
       }
     }
 
     clipboardHelper.copyString(string);
   }),
 
@@ -266,24 +264,24 @@ RequestListContextMenu.prototype = {
       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);
+      let text = yield window.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);
+      data.postDataText = yield window.gNetwork.getString(postData);
     }
 
     clipboardHelper.copyString(Curl.generateCommand(data));
   }),
 
   /**
    * Copy the raw request headers from the currently selected item.
    */
@@ -307,29 +305,29 @@ RequestListContextMenu.prototype = {
   },
 
   /**
    * Copy image as data uri.
    */
   copyImageAsDataUri() {
     const { mimeType, text, encoding } = this.selectedRequest.responseContent.content;
 
-    gNetwork.getString(text).then(string => {
+    window.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 => {
+    window.gNetwork.getString(text).then(string => {
       clipboardHelper.copyString(string);
     });
   },
 
   /**
    * Copy HAR from the network panel content to the clipboard.
    */
   copyAllAsHar() {
@@ -341,20 +339,20 @@ RequestListContextMenu.prototype = {
    * 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 form = window.NetMonitorController._target.form;
     let title = form.title || form.url;
 
     return {
-      getString: gNetwork.getString.bind(gNetwork),
+      getString: window.gNetwork.getString.bind(window.gNetwork),
       items: this.sortedRequests,
       title: title
     };
   }
 };
 
 module.exports = RequestListContextMenu;
--- a/devtools/client/netmonitor/request-list-tooltip.js
+++ b/devtools/client/netmonitor/request-list-tooltip.js
@@ -1,14 +1,12 @@
 /* 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 gNetwork, NetMonitorController */
-
 "use strict";
 
 const { Task } = require("devtools/shared/task");
 const {
   setImageTooltip,
   getImageDimensions,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const { WEBCONSOLE_L10N } = require("./l10n");
@@ -23,17 +21,17 @@ const HTML_NS = "http://www.w3.org/1999/
 
 const setTooltipImageContent = Task.async(function* (tooltip, itemEl, requestItem) {
   let { mimeType, text, encoding } = requestItem.responseContent.content;
 
   if (!mimeType || !mimeType.includes("image/")) {
     return false;
   }
 
-  let string = yield gNetwork.getString(text);
+  let string = yield window.gNetwork.getString(text);
   let src = formDataURI(mimeType, encoding, string);
   let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
   let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src);
   let options = { maxDim, naturalWidth, naturalHeight };
   setImageTooltip(tooltip, tooltip.doc, src, options);
 
   return itemEl.querySelector(".requests-menu-icon");
 });
@@ -87,17 +85,17 @@ const setTooltipStackTraceContent = Task
     let lineEl = doc.createElementNS(HTML_NS, "span");
     lineEl.className = "stack-frame-line";
     lineEl.textContent = `:${lineNumber}:${columnNumber}`;
     sourceInnerEl.appendChild(lineEl);
 
     frameEl.addEventListener("click", () => {
       // hide the tooltip immediately, not after delay
       tooltip.hide();
-      NetMonitorController.viewSourceInDebugger(filename, lineNumber);
+      window.NetMonitorController.viewSourceInDebugger(filename, lineNumber);
     });
 
     el.appendChild(frameEl);
   }
 
   tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
 
   return true;
--- a/devtools/client/netmonitor/shared/components/headers-panel.js
+++ b/devtools/client/netmonitor/shared/components/headers-panel.js
@@ -1,14 +1,12 @@
 /* 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 */
-
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
@@ -157,17 +155,17 @@ const HeadersPanel = createClass({
             className: "requests-menu-status-icon",
             "data-code": code,
           }),
           input({
             className: "tabpanel-summary-value textbox-input devtools-monospace",
             readOnly: true,
             value: `${status} ${statusText}`,
           }),
-          NetMonitorController.supportsCustomRequest && button({
+          window.NetMonitorController.supportsCustomRequest && button({
             className: "devtools-button",
             onClick: cloneSelectedRequest,
           }, EDIT_AND_RESEND),
           button({
             className: "devtools-button",
             onClick: this.toggleRawHeaders,
           }, RAW_HEADERS),
         )
--- a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -52,18 +52,18 @@ add_task(function* () {
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // 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).
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -37,18 +37,18 @@ add_task(function* () {
     "Connection: keep-alive",
     "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache"
   ].join("\n");
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // 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.");
 
@@ -62,18 +62,18 @@ add_task(function* () {
     "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() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // _oolbox.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;
   });
--- 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
@@ -21,17 +21,17 @@ add_task(function* () {
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[5]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[5]);
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // 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
@@ -57,51 +57,51 @@ add_task(function* () {
 
   return teardown(monitor);
 
   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
+    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(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() {
-      monitor._toolbox.doc
+      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(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
+    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(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() {
-      monitor._toolbox.doc
+      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
@@ -23,15 +23,15 @@ add_task(function* () {
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[3]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[3]);
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // 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
@@ -23,17 +23,17 @@ add_task(function* () {
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   EventUtils.sendMouseEvent({ type: "contextmenu" },
     document.querySelectorAll(".request-list-item")[0]);
 
   yield waitForClipboardPromise(function setup() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // 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
@@ -24,15 +24,15 @@ add_task(function* () {
     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() {
     // Context menu is appending in XUL document, we must select it from
-    // _toolbox.doc
-    monitor._toolbox.doc
+    // toolbox.doc
+    monitor.toolbox.doc
       .querySelector("#request-menu-context-copy-url").click();
   }, requestItem.url);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -16,34 +16,34 @@ add_task(function* test() {
   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;
+  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(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(monitor._toolbox.doc,
+  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);
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -28,18 +28,18 @@ add_task(function* () {
 
   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);
   // Context menu is appending in XUL document, we must select it from
-  // _toolbox.doc
-  monitor._toolbox.doc
+  // 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_prefs-reload.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -57,17 +57,17 @@ add_task(function* () {
     /* add more prefs here... */
   };
 
   yield testBottom();
   yield testSide();
   yield testWindow();
 
   info("Moving toolbox back to the bottom...");
-  yield monitor._toolbox.switchHost(Toolbox.HostType.BOTTOM);
+  yield monitor.toolbox.switchHost(Toolbox.HostType.BOTTOM);
   return teardown(monitor);
 
   function storeFirstPrefValues() {
     info("Caching initial pref values.");
 
     for (let name in prefsToCheck) {
       let currentValue = getPrefs()[name];
       prefsToCheck[name].firstValue = currentValue;
@@ -207,17 +207,17 @@ add_task(function* () {
     validateFirstPrefValues(true);
   }
 
   function* testSide() {
     yield restartNetMonitorAndSetupEnv();
 
     info("Moving toolbox to the side...");
 
-    yield monitor._toolbox.switchHost(Toolbox.HostType.SIDE);
+    yield monitor.toolbox.switchHost(Toolbox.HostType.SIDE);
     info("Testing prefs reload for a side host.");
     storeFirstPrefValues();
 
     // Validate and modify frontend while toolbox is on the side.
     validateFirstPrefValues(false);
     modifyFrontend(false);
 
     yield restartNetMonitorAndSetupEnv();
@@ -232,17 +232,17 @@ add_task(function* () {
     validateFirstPrefValues(false);
   }
 
   function* testWindow() {
     yield restartNetMonitorAndSetupEnv();
 
     info("Moving toolbox into a window...");
 
-    yield monitor._toolbox.switchHost(Toolbox.HostType.WINDOW);
+    yield monitor.toolbox.switchHost(Toolbox.HostType.WINDOW);
     info("Testing prefs reload for a window host.");
     storeFirstPrefValues();
 
     // Validate and modify frontend while toolbox is in a window.
     validateFirstPrefValues(true);
     modifyFrontend(true);
 
     yield restartNetMonitorAndSetupEnv();
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -27,23 +27,23 @@ add_task(function* () {
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   verifyRequest(0);
 
   // Switch to the webconsole.
-  let onWebConsole = monitor._toolbox.once("webconsole-selected");
-  monitor._toolbox.selectTool("webconsole");
+  let onWebConsole = monitor.toolbox.once("webconsole-selected");
+  monitor.toolbox.selectTool("webconsole");
   yield onWebConsole;
 
   // Switch back to the netmonitor.
-  let onNetMonitor = monitor._toolbox.once("netmonitor-selected");
-  monitor._toolbox.selectTool("netmonitor");
+  let onNetMonitor = monitor.toolbox.once("netmonitor-selected");
+  monitor.toolbox.selectTool("netmonitor");
   yield onNetMonitor;
 
   // Reload debugee.
   wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   // Perform another batch of requests.
--- a/devtools/client/netmonitor/test/browser_net_simple-init.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-init.js
@@ -17,18 +17,16 @@ function test() {
     info("Starting test... ");
 
     is(tab.linkedBrowser.currentURI.spec, SIMPLE_URL,
       "The current tab's location is the correct one.");
 
     function checkIfInitialized(tag) {
       info(`Checking if initialization is ok (${tag}).`);
 
-      ok(monitor.panelWin.NetMonitorView,
-        `The network monitor view object exists (${tag}).`);
       ok(monitor.panelWin.NetMonitorController,
         `The network monitor controller object exists (${tag}).`);
       ok(monitor.panelWin.NetMonitorController._startup,
         `The network monitor controller object exists and is initialized (${tag}).`);
 
       ok(monitor.isReady,
         `The network monitor panel appears to be ready (${tag}).`);
 
@@ -38,18 +36,16 @@ function test() {
         `There should be a webConsoleClient available at this point (${tag}).`);
       ok(monitor.panelWin.NetMonitorController.timelineFront,
         `There should be a timelineFront available at this point (${tag}).`);
     }
 
     function checkIfDestroyed(tag) {
       gInfo("Checking if destruction is ok.");
 
-      gOk(monitor.panelWin.NetMonitorView,
-        `The network monitor view object still exists (${tag}).`);
       gOk(monitor.panelWin.NetMonitorController,
         `The network monitor controller object still exists (${tag}).`);
       gOk(monitor.panelWin.NetMonitorController._shutdown,
         `The network monitor controller object still exists and is destroyed (${tag}).`);
 
       gOk(!monitor.panelWin.NetMonitorController.tabClient,
         `There shouldn't be a tabClient available after destruction (${tag}).`);
       gOk(!monitor.panelWin.NetMonitorController.webConsoleClient,
--- a/devtools/client/netmonitor/test/browser_net_statistics-01.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-01.js
@@ -1,55 +1,46 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
- * Tests if the statistics view is populated correctly.
+ * Tests if the statistics panel displays correctly.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(STATISTICS_URL);
   info("Starting test... ");
 
   let panel = monitor.panelWin;
   let { document, gStore, windowRequire } = panel;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
-  let body = document.querySelector("#body");
-
-  is(body.selectedPanel.id, "react-monitor-panel-hook",
+  ok(document.querySelector(".monitor-panel"),
     "The current main panel is correct.");
 
-  info("Displaying statistics view");
+  info("Displaying statistics panel");
   gStore.dispatch(Actions.openStatistics(true));
 
-  is(body.selectedPanel.id, "react-statistics-panel-hook",
+  ok(document.querySelector(".statistics-panel"),
     "The current main panel is correct.");
 
   info("Waiting for placeholder to display");
 
-  is(document.querySelector(".primed-cache-chart").childNodes.length, 1,
-    "There should be a placeholder primed cache chart created now.");
-  is(document.querySelector(".empty-cache-chart").childNodes.length, 1,
-    "There should be a placeholder empty cache chart created now.");
+  yield waitUntil(
+    () => document.querySelectorAll(".pie-chart-container[placeholder=true]").length == 2);
+  ok(true, "Two placeholder pie charts appear to be rendered correctly.");
 
-  is(document.querySelectorAll(".pie-chart-container[placeholder=true]").length, 2,
-    "Two placeholder pie chart appear to be rendered correctly.");
-  is(document.querySelectorAll(".table-chart-container[placeholder=true]").length, 2,
-    "Two placeholder table chart appear to be rendered correctly.");
+  yield waitUntil(
+    () => document.querySelectorAll(".table-chart-container[placeholder=true]").length == 2);
+  ok(true, "Two placeholde table charts appear to be rendered correctly.");
 
   info("Waiting for chart to display");
 
-  is(document.querySelector(".primed-cache-chart").childNodes.length, 1,
-    "There should be a real primed cache chart created now.");
-  is(document.querySelector(".empty-cache-chart").childNodes.length, 1,
-    "There should be a real empty cache chart created now.");
-
   yield waitUntil(
     () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
   ok(true, "Two real pie charts appear to be rendered correctly.");
 
   yield waitUntil(
     () => document.querySelectorAll(".table-chart-container:not([placeholder=true])").length == 2);
   ok(true, "Two real table charts appear to be rendered correctly.");
 
--- a/devtools/client/netmonitor/test/browser_net_statistics-02.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-02.js
@@ -26,28 +26,26 @@ add_task(function* () {
     document.querySelector("#requests-menu-filter-ws-button"));
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#requests-menu-filter-other-button"));
   testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
   info("The correct filtering predicates are used before entering perf. analysis mode.");
 
   gStore.dispatch(Actions.openStatistics(true));
 
-  let body = document.querySelector("#body");
-
-  is(body.selectedPanel.id, "react-statistics-panel-hook",
+  ok(document.querySelector(".statistics-panel"),
     "The main panel is switched to the statistics panel.");
 
   yield waitUntil(
     () => document.querySelectorAll(".pie-chart-container:not([placeholder=true])").length == 2);
   ok(true, "Two real pie charts appear to be rendered correctly.");
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector(".pie-chart-slice"));
 
-  is(body.selectedPanel.id, "react-monitor-panel-hook",
-    "The main panel is switched back to the inspector panel.");
+  ok(document.querySelector(".monitor-panel"),
+    "The main panel is switched back to the monitor panel.");
 
   testFilterButtons(monitor, "html");
   info("The correct filtering predicate is used when exiting perf. analysis mode.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -147,32 +147,32 @@ function initNetMonitor(aUrl, aWindow, a
     return {tab, monitor};
   });
 }
 
 function restartNetMonitor(monitor, newUrl) {
   info("Restarting the specified network monitor.");
 
   return Task.spawn(function* () {
-    let tab = monitor.target.tab;
+    let tab = monitor.toolbox.target.tab;
     let url = newUrl || tab.linkedBrowser.currentURI.spec;
 
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
 
     return initNetMonitor(url);
   });
 }
 
 function teardown(monitor) {
   info("Destroying the specified network monitor.");
 
   return Task.spawn(function* () {
-    let tab = monitor.target.tab;
+    let tab = monitor.toolbox.target.tab;
 
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
   });
 }
 
 function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1265,8 +1265,13 @@
   width: 100%;
   height: 100%;
   overflow: hidden;
 }
 
 .split-box {
   width: 100vw;
 }
+
+.network-monitor {
+  width: 100vw;
+  height: 100vh;
+}