Bug 1344160 - Refactor netmonitor-controller.js r?honza draft
authorRicky Chien <ricky060709@gmail.com>
Sat, 29 Apr 2017 23:20:24 +0800
changeset 572707 7b7e8eabaa79e91cc9da3214dd6d9e9a5a72b68b
parent 572570 33b92d9c40562dab3d7b602368c75619f1d793f7
child 572708 8c4b59c9be7865320ca8998b5972d6de3e5cae6a
push id57152
push userbmo:rchien@mozilla.com
push dateThu, 04 May 2017 15:08:05 +0000
reviewershonza
bugs1344160
milestone55.0a1
Bug 1344160 - Refactor netmonitor-controller.js r?honza MozReview-Commit-ID: JPUB83Qsmav
devtools/client/netmonitor/index.html
devtools/client/netmonitor/index.js
devtools/client/netmonitor/src/actions/requests.js
devtools/client/netmonitor/src/actions/ui.js
devtools/client/netmonitor/src/components/headers-panel.js
devtools/client/netmonitor/src/components/monitor-panel.js
devtools/client/netmonitor/src/components/request-list-empty-notice.js
devtools/client/netmonitor/src/components/stack-trace-panel.js
devtools/client/netmonitor/src/connector/firefox-connector.js
devtools/client/netmonitor/src/connector/index.js
devtools/client/netmonitor/src/connector/moz.build
devtools/client/netmonitor/src/constants.js
devtools/client/netmonitor/src/har/har-builder.js
devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
devtools/client/netmonitor/src/moz.build
devtools/client/netmonitor/src/netmonitor-controller.js
devtools/client/netmonitor/src/request-list-context-menu.js
devtools/client/netmonitor/src/request-list-header-context-menu.js
devtools/client/netmonitor/src/request-list-tooltip.js
devtools/client/netmonitor/src/utils/client.js
devtools/client/netmonitor/src/utils/moz.build
devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
devtools/client/webconsole/webconsole.js
--- a/devtools/client/netmonitor/index.html
+++ b/devtools/client/netmonitor/index.html
@@ -21,39 +21,42 @@
       }).require;
 
       const EventEmitter = require("devtools/shared/event-emitter");
       const { createFactory } = require("devtools/client/shared/vendor/react");
       const { render, unmountComponentAtNode } = require("devtools/client/shared/vendor/react-dom");
       const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
       const { bindActionCreators } = require("devtools/client/shared/vendor/redux");
       const { configureStore } = require("./src/utils/create-store");
-      const store = window.gStore = configureStore();
+      const store = configureStore();
       const actions = bindActionCreators(require("./src/actions/index"), store.dispatch);
-      const { NetMonitorController } = require("./src/netmonitor-controller");
+      const { onFirefoxConnect, onDisconnect } = require("./src/connector/index");
 
       // Inject EventEmitter into global window.
       EventEmitter.decorate(window);
+      // Inject to global window for testing
+      window.store = store;
 
       window.Netmonitor = {
         bootstrap({ toolbox }) {
           this.mount = document.querySelector("#mount");
-          const App = createFactory(require("./src/components/app"));
-          render(Provider({ store }, App()), this.mount);
-          return NetMonitorController.startupNetMonitor({
+          const connection = {
             tabConnection: {
               tabTarget: toolbox.target,
             },
             toolbox,
-          }, actions);
+          };
+          const App = createFactory(require("./src/components/app"));
+          render(Provider({ store }, App()), this.mount);
+          return onFirefoxConnect(connection, actions, store.getState);
         },
 
         destroy() {
           unmountComponentAtNode(this.mount);
-          return NetMonitorController.shutdownNetMonitor();
+          return onDisconnect();
         }
       };
 
       // Implement support for chrome://devtools/content/netmonitor/index.html?type=tab&id=1234 URLs
       // where 1234 is the tab id, you can retrieve from about:debugging#tabs links.
       // Simply copy the id from about:devtools-toolbox?type=tab&id=1234 URLs.
 
       // URL constructor doesn't support chrome: scheme
--- a/devtools/client/netmonitor/index.js
+++ b/devtools/client/netmonitor/index.js
@@ -34,19 +34,22 @@ pref("devtools.netmonitor.har.jsonpCallb
 pref("devtools.netmonitor.har.includeResponseBodies", true);
 pref("devtools.netmonitor.har.compress", false);
 pref("devtools.netmonitor.har.forceExport", false);
 pref("devtools.netmonitor.har.pageLoadedTimeout", 1500);
 pref("devtools.netmonitor.har.enableAutoExportToFile", false);
 pref("devtools.webconsole.persistlog", false);
 
 const App = require("./src/components/app");
-const store = window.gStore = configureStore();
+const store = configureStore();
 const actions = bindActionCreators(require("./src/actions"), store.dispatch);
-const { NetMonitorController } = require("./src/netmonitor-controller");
+const { onConnect } = require("./src/connector");
+
+// Inject to global window for testing
+window.store = store;
 
 /**
  * Stylesheet links in devtools xhtml files are using chrome or resource URLs.
  * Rewrite the href attribute to remove the protocol. web-server.js contains redirects
  * to map CSS urls to the proper file. Supports urls using:
  *   - devtools/client/
  *   - devtools/content/
  *   - skin/
@@ -62,15 +65,15 @@ window.addEventListener("DOMContentLoade
     document.documentElement.setAttribute("platform", "mac");
   } else if (appinfo.OS === "Linux") {
     document.documentElement.setAttribute("platform", "linux");
   } else {
     document.documentElement.setAttribute("platform", "win");
   }
 });
 
-bootstrap(React, ReactDOM).then(connection => {
+bootstrap(React, ReactDOM).then((connection) => {
   if (!connection) {
     return;
   }
   renderRoot(React, ReactDOM, App, store);
-  NetMonitorController.startupNetMonitor(connection, actions);
+  onConnect(connection, actions, store.getState);
 });
--- a/devtools/client/netmonitor/src/actions/requests.js
+++ b/devtools/client/netmonitor/src/actions/requests.js
@@ -1,23 +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 { sendHTTPRequest } = require("../connector/index");
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SEND_CUSTOM_REQUEST,
   UPDATE_REQUEST,
 } = require("../constants");
-const { NetMonitorController } = require("../netmonitor-controller");
 const { getSelectedRequest } = require("../selectors/index");
 
 function addRequest(id, data, batch) {
   return {
     type: ADD_REQUEST,
     id,
     data,
     meta: { batch },
@@ -42,20 +42,16 @@ function cloneSelectedRequest() {
     type: CLONE_SELECTED_REQUEST
   };
 }
 
 /**
  * Send a new HTTP request using the data in the custom request form.
  */
 function sendCustomRequest() {
-  if (!NetMonitorController.supportsCustomRequest) {
-    return cloneSelectedRequest();
-  }
-
   return (dispatch, getState) => {
     const selected = getSelectedRequest(getState());
 
     if (!selected) {
       return;
     }
 
     // Send a new HTTP request using the data in the custom request form
@@ -66,17 +62,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) => {
+    sendHTTPRequest(data, (response) => {
       return dispatch({
         type: SEND_CUSTOM_REQUEST,
         id: response.eventActor.actor,
       });
     });
   };
 }
 
--- a/devtools/client/netmonitor/src/actions/ui.js
+++ b/devtools/client/netmonitor/src/actions/ui.js
@@ -8,17 +8,17 @@ const {
   ACTIVITY_TYPE,
   OPEN_NETWORK_DETAILS,
   OPEN_STATISTICS,
   RESET_COLUMNS,
   SELECT_DETAILS_PANEL_TAB,
   TOGGLE_COLUMN,
   WATERFALL_RESIZE,
 } = require("../constants");
-const { NetMonitorController } = require("../netmonitor-controller");
+const { triggerActivity } = require("../connector/index");
 
 /**
  * Change network details panel.
  *
  * @param {boolean} open - expected network details panel open state
  */
 function openNetworkDetails(open) {
   return {
@@ -29,17 +29,17 @@ function openNetworkDetails(open) {
 
 /**
  * Change performance statistics panel open state.
  *
  * @param {boolean} visible - expected performance statistics panel open state
  */
 function openStatistics(open) {
   if (open) {
-    NetMonitorController.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
+    triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
   }
   return {
     type: OPEN_STATISTICS,
     open,
   };
 }
 
 /**
--- a/devtools/client/netmonitor/src/components/headers-panel.js
+++ b/devtools/client/netmonitor/src/components/headers-panel.js
@@ -5,17 +5,16 @@
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { NetMonitorController } = require("../netmonitor-controller");
 const {
   getFormattedIPAndPort,
   getFormattedSize,
 } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const {
   getHeadersURL,
   getHTTPStatusCodeURL,
@@ -196,17 +195,17 @@ const HeadersPanel = createClass({
               + " status-text",
             readOnly: true,
             value: `${status} ${statusText}`,
             size: `${inputWidth}`,
           }),
           statusCodeDocURL ? MDNLink({
             url: statusCodeDocURL,
           }) : null,
-          NetMonitorController.supportsCustomRequest && button({
+          button({
             className: "devtools-button",
             onClick: cloneSelectedRequest,
           }, EDIT_AND_RESEND),
           button({
             className: "devtools-button",
             onClick: this.toggleRawHeaders,
           }, RAW_HEADERS),
         )
--- a/devtools/client/netmonitor/src/components/monitor-panel.js
+++ b/devtools/client/netmonitor/src/components/monitor-panel.js
@@ -9,17 +9,17 @@ const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const Actions = require("../actions/index");
-const { getLongString } = require("../utils/client");
+const { getLongString } = require("../connector/index");
 const { getFormDataSections } = require("../utils/request-utils");
 const { getSelectedRequest } = require("../selectors/index");
 
 // Components
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/split-box"));
 const NetworkDetailsPanel = createFactory(require("./network-details-panel"));
 const RequestList = createFactory(require("./request-list"));
 const Toolbar = createFactory(require("./toolbar"));
--- a/devtools/client/netmonitor/src/components/request-list-empty-notice.js
+++ b/devtools/client/netmonitor/src/components/request-list-empty-notice.js
@@ -7,18 +7,18 @@
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
+const { triggerActivity } = require("../connector/index");
 const { ACTIVITY_TYPE } = require("../constants");
-const { NetMonitorController } = require("../netmonitor-controller");
 const { L10N } = require("../utils/l10n");
 const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
 
 // Components
 const MDNLink = createFactory(require("./mdn-link"));
 
 const { button, div, span } = DOM;
 
@@ -65,13 +65,11 @@ const RequestListEmptyNotice = createCla
     );
   }
 });
 
 module.exports = connect(
   undefined,
   dispatch => ({
     onPerfClick: () => dispatch(Actions.openStatistics(true)),
-    onReloadClick: () =>
-      NetMonitorController
-        .triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
+    onReloadClick: () => triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT),
   })
 )(RequestListEmptyNotice);
--- a/devtools/client/netmonitor/src/components/stack-trace-panel.js
+++ b/devtools/client/netmonitor/src/components/stack-trace-panel.js
@@ -4,32 +4,31 @@
 
 "use strict";
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
+const { viewSourceInDebugger } = require("../connector/index");
 
 const { div } = DOM;
 
 // Components
 const StackTrace = createFactory(require("devtools/client/shared/components/stack-trace"));
 
 function StackTracePanel({ request }) {
   let { stacktrace } = request.cause;
 
   return (
     div({ className: "panel-container" },
       StackTrace({
         stacktrace,
-        onViewSourceInDebugger: (frame) => {
-          window.NetMonitorController.viewSourceInDebugger(frame.url, frame.line);
-        },
+        onViewSourceInDebugger: ({ url, line }) => viewSourceInDebugger(url, line),
       }),
     )
   );
 }
 
 StackTracePanel.displayName = "StackTracePanel";
 
 StackTracePanel.propTypes = {
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -0,0 +1,724 @@
+/* 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 Services = require("Services");
+const { CurlUtils } = require("devtools/client/shared/curl");
+const { TimelineFront } = require("devtools/shared/fronts/timeline");
+const { ACTIVITY_TYPE, EVENTS } = require("../constants");
+const { getDisplayedRequestById } = require("../selectors/index");
+const { fetchHeaders, formDataURI } = require("../utils/request-utils");
+
+class FirefoxConnector {
+  constructor() {
+    this.connect = this.connect.bind(this);
+    this.disconnect = this.disconnect.bind(this);
+    this.willNavigate = this.willNavigate.bind(this);
+    this.displayCachedEvents = this.displayCachedEvents.bind(this);
+    this.onDocLoadingMarker = this.onDocLoadingMarker.bind(this);
+    this.addRequest = this.addRequest.bind(this);
+    this.updateRequest = this.updateRequest.bind(this);
+    this.fetchImage = this.fetchImage.bind(this);
+    this.fetchRequestHeaders = this.fetchRequestHeaders.bind(this);
+    this.fetchResponseHeaders = this.fetchResponseHeaders.bind(this);
+    this.fetchPostData = this.fetchPostData.bind(this);
+    this.fetchResponseCookies = this.fetchResponseCookies.bind(this);
+    this.fetchRequestCookies = this.fetchRequestCookies.bind(this);
+    this.getPayloadFromQueue = this.getPayloadFromQueue.bind(this);
+    this.isQueuePayloadReady = this.isQueuePayloadReady.bind(this);
+    this.pushPayloadToQueue = this.pushPayloadToQueue.bind(this);
+    this.sendHTTPRequest = this.sendHTTPRequest.bind(this);
+    this.setPreferences = this.setPreferences.bind(this);
+    this.triggerActivity = this.triggerActivity.bind(this);
+    this.inspectRequest = this.inspectRequest.bind(this);
+    this.getLongString = this.getLongString.bind(this);
+    this.getNetworkRequest = this.getNetworkRequest.bind(this);
+    this.getTabTarget = this.getTabTarget.bind(this);
+    this.viewSourceInDebugger = this.viewSourceInDebugger.bind(this);
+
+    // Event handlers
+    this.onNetworkEvent = this.onNetworkEvent.bind(this);
+    this.onNetworkEventUpdate = this.onNetworkEventUpdate.bind(this);
+    this.onRequestHeaders = this.onRequestHeaders.bind(this);
+    this.onRequestCookies = this.onRequestCookies.bind(this);
+    this.onRequestPostData = this.onRequestPostData.bind(this);
+    this.onSecurityInfo = this.onSecurityInfo.bind(this);
+    this.onResponseHeaders = this.onResponseHeaders.bind(this);
+    this.onResponseCookies = this.onResponseCookies.bind(this);
+    this.onResponseContent = this.onResponseContent.bind(this);
+    this.onEventTimings = this.onEventTimings.bind(this);
+  }
+
+  async connect(connection, actions, getState) {
+    this.actions = actions;
+    this.getState = getState;
+    this.tabTarget = connection.tabConnection.tabTarget;
+    this.tabClient = this.tabTarget.isTabActor ? this.tabTarget.activeTab : null;
+    this.webConsoleClient = this.tabTarget.activeConsole;
+
+    this.tabTarget.on("will-navigate", this.willNavigate);
+    this.tabTarget.on("close", this.disconnect);
+    this.webConsoleClient.on("networkEvent", this.onNetworkEvent);
+    this.webConsoleClient.on("networkEventUpdate", this.onNetworkEventUpdate);
+
+    // Don't start up waiting for timeline markers if the server isn't
+    // recent enough to emit the markers we're interested in.
+    if (this.tabTarget.getTrait("documentLoadingMarkers")) {
+      this.timelineFront = new TimelineFront(this.tabTarget.client, this.tabTarget.form);
+      this.timelineFront.on("doc-loading", this.onDocLoadingMarker);
+      await this.timelineFront.start({ withDocLoadingEvents: true });
+    }
+
+    this.displayCachedEvents();
+  }
+
+  async disconnect() {
+    // When debugging local or a remote instance, the connection is closed by
+    // the RemoteTarget. The webconsole actor is stopped on disconnect.
+    this.tabClient = null;
+    this.webConsoleClient = null;
+
+    // The timeline front wasn't initialized and started if the server wasn't
+    // recent enough to emit the markers we were interested in.
+    if (this.tabTarget.getTrait("documentLoadingMarkers") && this.timelineFront) {
+      this.timelineFront.off("doc-loading", this.onDocLoadingMarker);
+      await this.timelineFront.destroy();
+      this.timelineFront = null;
+    }
+  }
+
+  willNavigate() {
+    if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
+      this.actions.batchReset();
+      this.actions.clearRequests();
+    } else {
+      // If the log is persistent, just clear all accumulated timing markers.
+      this.actions.clearTimingMarkers();
+    }
+  }
+
+  /**
+   * Display any network events already in the cache.
+   */
+  displayCachedEvents() {
+    for (let networkInfo of this.webConsoleClient.getNetworkEvents()) {
+      // First add the request to the timeline.
+      this.onNetworkEvent("networkEvent", networkInfo);
+      // Then replay any updates already received.
+      for (let updateType of networkInfo.updates) {
+        this.onNetworkEventUpdate("networkEventUpdate", {
+          packet: { updateType },
+          networkInfo,
+        });
+      }
+    }
+  }
+
+  /**
+   * The "DOMContentLoaded" and "Load" events sent by the timeline actor.
+   *
+   * @param {object} marker
+   */
+  onDocLoadingMarker(marker) {
+    window.emit(EVENTS.TIMELINE_EVENT, marker);
+    this.actions.addTimingMarker(marker);
+  }
+
+  /**
+   * Add a new network request to application state.
+   *
+   * @param {string} id request id
+   * @param {object} data data payload will be added to application state
+   */
+  addRequest(id, data) {
+    let {
+      method,
+      url,
+      isXHR,
+      cause,
+      startedDateTime,
+      fromCache,
+      fromServiceWorker,
+    } = data;
+
+    this.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));
+  }
+
+  /**
+   * Update a network request if it already exists in application state.
+   *
+   * @param {string} id request id
+   * @param {object} data data payload will be updated to application state
+   */
+  async updateRequest(id, data) {
+    let {
+      mimeType,
+      responseContent,
+      responseCookies,
+      responseHeaders,
+      requestCookies,
+      requestHeaders,
+      requestPostData,
+    } = data;
+
+    // fetch request detail contents in parallel
+    let [
+      imageObj,
+      requestHeadersObj,
+      responseHeadersObj,
+      postDataObj,
+      requestCookiesObj,
+      responseCookiesObj,
+    ] = await Promise.all([
+      this.fetchImage(mimeType, responseContent),
+      this.fetchRequestHeaders(requestHeaders),
+      this.fetchResponseHeaders(responseHeaders),
+      this.fetchPostData(requestPostData),
+      this.fetchRequestCookies(requestCookies),
+      this.fetchResponseCookies(responseCookies),
+    ]);
+
+    let payload = Object.assign({}, data,
+                                    imageObj, requestHeadersObj, responseHeadersObj,
+                                    postDataObj, requestCookiesObj, responseCookiesObj);
+    await this.actions.updateRequest(id, payload, true);
+  }
+
+  async fetchImage(mimeType, responseContent) {
+    let payload = {};
+    if (mimeType && responseContent && responseContent.content) {
+      let { encoding, text } = responseContent.content;
+      let response = await this.getLongString(text);
+
+      if (mimeType.includes("image/")) {
+        payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
+      }
+
+      responseContent.content.text = response;
+      payload.responseContent = responseContent;
+    }
+    return payload;
+  }
+
+  async fetchRequestHeaders(requestHeaders) {
+    let payload = {};
+    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
+      let headers = await fetchHeaders(requestHeaders, this.getLongString);
+      if (headers) {
+        payload.requestHeaders = headers;
+      }
+    }
+    return payload;
+  }
+
+  async fetchResponseHeaders(responseHeaders) {
+    let payload = {};
+    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
+      let headers = await fetchHeaders(responseHeaders, this.getLongString);
+      if (headers) {
+        payload.responseHeaders = headers;
+      }
+    }
+    return payload;
+  }
+
+  async fetchPostData(requestPostData) {
+    let payload = {};
+    if (requestPostData && requestPostData.postData) {
+      let { text } = requestPostData.postData;
+      let postData = await this.getLongString(text);
+      const headers = CurlUtils.getHeadersFromMultipartText(postData);
+      const headersSize = headers.reduce((acc, { name, value }) => {
+        return acc + name.length + value.length + 2;
+      }, 0);
+      requestPostData.postData.text = postData;
+      payload.requestPostData = Object.assign({}, requestPostData);
+      payload.requestHeadersFromUploadStream = { headers, headersSize };
+    }
+    return payload;
+  }
+
+  async fetchResponseCookies(responseCookies) {
+    let payload = {};
+    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: await this.getLongString(cookie.value),
+          }));
+        }
+        if (resCookies.length) {
+          payload.responseCookies = resCookies;
+        }
+      }
+    }
+    return payload;
+  }
+
+  async fetchRequestCookies(requestCookies) {
+    let payload = {};
+    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: await this.getLongString(cookie.value),
+          }));
+        }
+        if (reqCookies.length) {
+          payload.requestCookies = reqCookies;
+        }
+      }
+    }
+    return payload;
+  }
+
+  /**
+   * Access a payload item from payload queue.
+   *
+   * @param {string} id request id
+   * @return {boolean} return a queued payload item from queue.
+   */
+  getPayloadFromQueue(id) {
+    return this.payloadQueue.find((item) => item.id === id);
+  }
+
+  /**
+   * Packet order of "networkUpdateEvent" is predictable, as a result we can wait for
+   * the last one "eventTimings" packet arrives to check payload is ready.
+   *
+   * @param {string} id request id
+   * @return {boolean} return whether a specific networkEvent has been updated completely.
+   */
+  isQueuePayloadReady(id) {
+    let queuedPayload = this.getPayloadFromQueue(id);
+    return queuedPayload && queuedPayload.payload.eventTimings;
+  }
+
+  /**
+   * Push a request payload into a queue if request doesn't exist. Otherwise update the
+   * request itself.
+   *
+   * @param {string} id request id
+   * @param {object} payload request data payload
+   */
+  pushPayloadToQueue(id, payload) {
+    let queuedPayload = this.getPayloadFromQueue(id);
+    if (!queuedPayload) {
+      this.payloadQueue.push({ id, payload });
+    } else {
+      // Merge upcoming networkEventUpdate payload into existing one
+      queuedPayload.payload = Object.assign({}, queuedPayload.payload, payload);
+    }
+  }
+
+  /**
+   * Send a HTTP request data payload
+   *
+   * @param {object} data data payload would like to sent to backend
+   * @param {function} callback callback will be invoked after the request finished
+   */
+  sendHTTPRequest(data, callback) {
+    this.webConsoleClient.sendHTTPRequest(data, callback);
+  }
+
+  /**
+   * Set network preferences to control network flow
+   *
+   * @param {object} request request payload would like to sent to backend
+   * @param {function} callback callback will be invoked after the request finished
+   */
+  setPreferences(request, callback) {
+    this.webConsoleClient.setPreferences(request, callback);
+  }
+
+  /**
+   * Triggers a specific "activity" to be performed by the frontend.
+   * This can be, for example, triggering reloads or enabling/disabling cache.
+   *
+   * @param {number} type The activity type. See the ACTIVITY_TYPE const.
+   * @return {object} A promise resolved once the activity finishes and the frontend
+   *                  is back into "standby" mode.
+   */
+  triggerActivity(type) {
+    // Puts the frontend into "standby" (when there's no particular activity).
+    let standBy = () => {
+      this.currentActivity = ACTIVITY_TYPE.NONE;
+    };
+
+    // Waits for a series of "navigation start" and "navigation stop" events.
+    let waitForNavigation = () => {
+      return new Promise((resolve) => {
+        this.tabTarget.once("will-navigate", () => {
+          this.tabTarget.once("navigate", () => {
+            resolve();
+          });
+        });
+      });
+    };
+
+    // Reconfigures the tab, optionally triggering a reload.
+    let reconfigureTab = (options) => {
+      return new Promise((resolve) => {
+        this.tabTarget.activeTab.reconfigure(options, resolve);
+      });
+    };
+
+    // Reconfigures the tab and waits for the target to finish navigating.
+    let reconfigureTabAndWaitForNavigation = (options) => {
+      options.performReload = true;
+      let navigationFinished = waitForNavigation();
+      return reconfigureTab(options).then(() => navigationFinished);
+    };
+    switch (type) {
+      case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT:
+        return reconfigureTabAndWaitForNavigation({}).then(standBy);
+      case ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED:
+        this.currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
+        this.tabTarget.once("will-navigate", () => {
+          this.currentActivity = type;
+        });
+        return reconfigureTabAndWaitForNavigation({
+          cacheDisabled: false,
+          performReload: true,
+        }).then(standBy);
+      case ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED:
+        this.currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
+        this.tabTarget.once("will-navigate", () => {
+          this.currentActivity = type;
+        });
+        return reconfigureTabAndWaitForNavigation({
+          cacheDisabled: true,
+          performReload: true,
+        }).then(standBy);
+      case ACTIVITY_TYPE.ENABLE_CACHE:
+        this.currentActivity = type;
+        return reconfigureTab({
+          cacheDisabled: false,
+          performReload: false,
+        }).then(standBy);
+      case ACTIVITY_TYPE.DISABLE_CACHE:
+        this.currentActivity = type;
+        return reconfigureTab({
+          cacheDisabled: true,
+          performReload: false,
+        }).then(standBy);
+    }
+    this.currentActivity = ACTIVITY_TYPE.NONE;
+    return Promise.reject(new Error("Invalid activity type"));
+  }
+
+  /**
+   * Selects the specified request in the waterfall and opens the details view.
+   *
+   * @param {string} requestId The actor ID of the request to inspect.
+   * @return {object} A promise resolved once the task finishes.
+   */
+  inspectRequest(requestId) {
+    // Look for the request in the existing ones or wait for it to appear, if
+    // the network monitor is still loading.
+    return new Promise((resolve) => {
+      let request = null;
+      let inspector = () => {
+        request = getDisplayedRequestById(this.getState(), requestId);
+        if (!request) {
+          // Reset filters so that the request is visible.
+          this.actions.toggleRequestFilterType("all");
+          request = getDisplayedRequestById(this.getState(), requestId);
+        }
+
+        // If the request was found, select it. Otherwise this function will be
+        // called again once new requests arrive.
+        if (request) {
+          window.off(EVENTS.REQUEST_ADDED, inspector);
+          this.actions.selectRequest(request.id);
+          resolve();
+        }
+      };
+
+      inspector();
+
+      if (!request) {
+        window.on(EVENTS.REQUEST_ADDED, inspector);
+      }
+    });
+  }
+
+  /**
+   * Fetches the network information packet from actor server
+   *
+   * @param {string} id request id
+   * @return {object} networkInfo data packet
+   */
+  getNetworkRequest(id) {
+    return this.webConsoleClient.getNetworkRequest(id);
+  }
+
+  /**
+   * Fetches the full text of a LongString.
+   *
+   * @param {object|string} stringGrip
+   *        The long string grip containing the corresponding actor.
+   *        If you pass in a plain string (by accident or because you're lazy),
+   *        then a promise of the same string is simply returned.
+   * @return {object}
+   *         A promise that is resolved when the full string contents
+   *         are available, or rejected if something goes wrong.
+   */
+  getLongString(stringGrip) {
+    return this.webConsoleClient.getString(stringGrip);
+  }
+
+  /**
+   * Getter that access tab target instance.
+   * @return {object} browser tab target instance
+   */
+  getTabTarget() {
+    return this.tabTarget;
+  }
+
+  /**
+   * Open a given source in Debugger
+   * @param {string} sourceURL source url
+   * @param {number} sourceLine source line number
+   */
+  viewSourceInDebugger(sourceURL, sourceLine) {
+    if (this.toolbox) {
+      this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
+    }
+  }
+
+  /**
+   * The "networkEvent" message type handler.
+   *
+   * @param {string} type message type
+   * @param {object} networkInfo network request information
+   */
+  onNetworkEvent(type, networkInfo) {
+    let {
+      actor,
+      cause,
+      fromCache,
+      fromServiceWorker,
+      isXHR,
+      request: {
+        method,
+        url,
+      },
+      startedDateTime,
+    } = networkInfo;
+
+    this.addRequest(actor, {
+      cause,
+      fromCache,
+      fromServiceWorker,
+      isXHR,
+      method,
+      startedDateTime,
+      url,
+    });
+
+    window.emit(EVENTS.NETWORK_EVENT, actor);
+  }
+
+  /**
+   * 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(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":
+        this.updateRequest(actor, {
+          securityState: networkInfo.securityInfo,
+        }).then(() => {
+          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":
+        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
+        }).then(() => {
+          window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
+        });
+        break;
+      case "responseContent":
+        this.webConsoleClient.getResponseContent(actor,
+          this.onResponseContent.bind(this, {
+            contentSize: networkInfo.response.bodySize,
+            transferredSize: networkInfo.response.transferredSize,
+            mimeType: networkInfo.response.content.mimeType
+          }));
+        window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
+        break;
+      case "eventTimings":
+        this.updateRequest(actor, { totalTime: networkInfo.totalTime })
+          .then(() => {
+            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(response) {
+    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(response) {
+    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(response) {
+    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(response) {
+    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(response) {
+    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(response) {
+    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} data the message received from the server event.
+   * @param {object} response the message received from the server.
+   */
+  onResponseContent(data, response) {
+    let payload = Object.assign({ responseContent: response }, data);
+    this.updateRequest(response.from, payload).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(response) {
+    this.updateRequest(response.from, {
+      eventTimings: response
+    }).then(() => {
+      window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
+    });
+  }
+}
+
+module.exports = new FirefoxConnector();
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/connector/index.js
@@ -0,0 +1,85 @@
+/* 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";
+
+let connector = {};
+
+function onConnect(connection, actions, getState) {
+  if (!connection || !connection.tab) {
+    return;
+  }
+
+  let { clientType } = connection.tab;
+  switch (clientType) {
+    case "chrome":
+      onChromeConnect(connection, actions, getState);
+      break;
+    case "firefox":
+      onFirefoxConnect(connection, actions, getState);
+      break;
+    default:
+      throw Error(`Unknown client type - ${clientType}`);
+  }
+}
+
+function onDisconnect() {
+  connector && connector.disconnect();
+}
+
+function onChromeConnect(connection, actions, getState) {
+  // TODO: support chrome debugging protocol
+}
+
+function onFirefoxConnect(connection, actions, getState) {
+  connector = require("./firefox-connector");
+  connector.connect(connection, actions, getState);
+}
+
+function inspectRequest() {
+  return connector.inspectRequest(...arguments);
+}
+
+function getLongString() {
+  return connector.getLongString(...arguments);
+}
+
+function getNetworkRequest() {
+  return connector.getNetworkRequest(...arguments);
+}
+
+function getTabTarget() {
+  return connector.getTabTarget();
+}
+
+function sendHTTPRequest() {
+  return connector.sendHTTPRequest(...arguments);
+}
+
+function setPreferences() {
+  return connector.setPreferences(...arguments);
+}
+
+function triggerActivity() {
+  return connector.triggerActivity(...arguments);
+}
+
+function viewSourceInDebugger() {
+  return connector.viewSourceInDebugger();
+}
+
+module.exports = {
+  onConnect,
+  onChromeConnect,
+  onFirefoxConnect,
+  onDisconnect,
+  getLongString,
+  getNetworkRequest,
+  getTabTarget,
+  inspectRequest,
+  sendHTTPRequest,
+  setPreferences,
+  triggerActivity,
+  viewSourceInDebugger,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/connector/moz.build
@@ -0,0 +1,8 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'firefox-connector.js',
+    'index.js',
+)
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -42,20 +42,16 @@ const ACTIVITY_TYPE = {
 
   // Enabling or disabling the cache without triggering a reload.
   ENABLE_CACHE: 3,
   DISABLE_CACHE: 4
 };
 
 // The panel's window global is an EventEmitter firing the following events:
 const EVENTS = {
-  // When the monitored target begins and finishes navigating.
-  TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
-  TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
-
   // When a network or timeline event is received.
   // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
   // more information about what each packet is supposed to deliver.
   NETWORK_EVENT: "NetMonitor:NetworkEvent",
   TIMELINE_EVENT: "NetMonitor:TimelineEvent",
 
   // When a network event is added to the view
   REQUEST_ADDED: "NetMonitor:RequestAdded",
@@ -88,18 +84,17 @@ const EVENTS = {
   UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
   RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
 
   // When response content begins, updates and finishes receiving.
   STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
   UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
   RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
 
-  // Fired once the NetMonitorController establishes a connection to the debug
-  // target.
+  // Fired once the connection is established
   CONNECTED: "connected",
 };
 
 const HEADERS = [
   {
     name: "status",
     label: "status3",
     canFilter: true,
--- a/devtools/client/netmonitor/src/har/har-builder.js
+++ b/devtools/client/netmonitor/src/har/har-builder.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const appInfo = Services.appinfo;
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const { CurlUtils } = require("devtools/client/shared/curl");
-const { getLongString } = require("../utils/client");
+const { getLongString } = require("../connector/index");
 const {
   getFormDataSections,
   getUrlQuery,
   parseQueryString,
 } = require("../utils/request-utils");
 
 const L10N = new LocalizationHelper("devtools/client/locales/har.properties");
 const HAR_VERSION = "1.1";
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_copy_all_as_har.js
@@ -6,22 +6,21 @@
 /**
  * Basic tests for exporting Network panel content into HAR format.
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
 
   info("Starting test... ");
 
-  let { gStore, windowRequire } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  let { actions, windowRequire } = monitor.panelWin;
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/src/request-list-context-menu");
 
-  gStore.dispatch(Actions.batchEnable(false));
+  actions.batchEnable(false);
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   let contextMenu = new RequestListContextMenu({});
   yield contextMenu.copyAllAsHar();
 
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
@@ -7,22 +7,21 @@
  * 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 { gStore, windowRequire } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  let { actions, windowRequire } = monitor.panelWin;
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/src/request-list-context-menu");
 
-  gStore.dispatch(Actions.batchEnable(false));
+  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;
 
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
@@ -11,43 +11,41 @@ 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 { gStore, windowRequire } = monitor.panelWin;
-  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
-  let { NetMonitorController } =
-    windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
+  let { actions, windowRequire } = monitor.panelWin;
+  let { setPreferences } =
+    windowRequire("devtools/client/netmonitor/src/connector/index");
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/src/request-list-context-menu");
 
-  gStore.dispatch(Actions.batchEnable(false));
+  actions.batchEnable(false);
 
   const size = 4096;
   const uploadSize = actuallyThrottle ? size / 3 : 0;
 
   const request = {
     "NetworkMonitor.throttleData": {
       latencyMean: 0,
       latencyMax: 0,
       downloadBPSMean: 200000,
       downloadBPSMax: 200000,
       uploadBPSMean: uploadSize,
       uploadBPSMax: uploadSize,
     },
   };
-  let client = NetMonitorController.webConsoleClient;
 
   info("sending throttle request");
   yield new Promise((resolve) => {
-    client.setPreferences(request, response => {
+    setPreferences(request, (response) => {
       resolve(response);
     });
   });
 
   // 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);
--- a/devtools/client/netmonitor/src/moz.build
+++ b/devtools/client/netmonitor/src/moz.build
@@ -1,22 +1,22 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'actions',
     'components',
+    'connector',
     'har',
     'middleware',
     'reducers',
     'selectors',
     'utils',
 ]
 
 DevToolsModules(
     'constants.js',
-    'netmonitor-controller.js',
     'request-list-context-menu.js',
     'request-list-header-context-menu.js',
     'request-list-tooltip.js',
     'waterfall-background.js',
 )
deleted file mode 100644
--- a/devtools/client/netmonitor/src/netmonitor-controller.js
+++ /dev/null
@@ -1,781 +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/. */
-
-"use strict";
-
-const { TimelineFront } = require("devtools/shared/fronts/timeline");
-const { CurlUtils } = require("devtools/client/shared/curl");
-const { ACTIVITY_TYPE, EVENTS } = require("./constants");
-const { getDisplayedRequestById } = require("./selectors/index");
-const {
-  fetchHeaders,
-  formDataURI,
-} = require("./utils/request-utils");
-const {
-  getLongString,
-  getWebConsoleClient,
-  onFirefoxConnect,
-  onFirefoxDisconnect,
-} = require("./utils/client");
-
-/**
- * Object defining the network monitor controller components.
- */
-var NetMonitorController = {
-  /**
-   * Initializes the view and connects the monitor client.
-   *
-   * @param {Object} connection connection data wrapper
-   * @return {Object} A promise that is resolved when the monitor finishes startup.
-   */
-  startupNetMonitor(connection, actions) {
-    if (this._startup) {
-      return this._startup;
-    }
-    this.actions = actions;
-    this._startup = new Promise(async (resolve) => {
-      await this.connect(connection);
-      resolve();
-    });
-    return this._startup;
-  },
-
-  /**
-   * Destroys the view and disconnects the monitor client from the server.
-   *
-   * @return object
-   *         A promise that is resolved when the monitor finishes shutdown.
-   */
-  shutdownNetMonitor() {
-    if (this._shutdown) {
-      return this._shutdown;
-    }
-    this._shutdown = new Promise(async (resolve) => {
-      this.actions.batchReset();
-      onFirefoxDisconnect(this._target);
-      this._target.off("close", this._onTabDetached);
-      this.NetworkEventsHandler.disconnect();
-      await this.disconnect();
-      resolve();
-    });
-
-    return this._shutdown;
-  },
-
-  /**
-   * 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
-   * netmonitor-specific initialization.
-   *
-   * @param {Object} connection connection data wrapper
-   * @return {Object} A promise that is resolved when the monitor finishes connecting.
-   */
-  connect(connection) {
-    if (this._connection) {
-      return this._connection;
-    }
-    this._onTabDetached = this.shutdownNetMonitor.bind(this);
-
-    this._connection = new Promise(async (resolve) => {
-      // Some actors like AddonActor or RootActor for chrome debugging
-      // aren't actual tabs.
-      this.toolbox = connection.toolbox;
-      this._target = connection.tabConnection.tabTarget;
-      this.tabClient = this._target.isTabActor ? this._target.activeTab : null;
-
-      let connectTimeline = () => {
-        // Don't start up waiting for timeline markers if the server isn't
-        // recent enough to emit the markers we're interested in.
-        if (this._target.getTrait("documentLoadingMarkers")) {
-          this.timelineFront = new TimelineFront(this._target.client,
-            this._target.form);
-          return this.timelineFront.start({ withDocLoadingEvents: true });
-        }
-        return undefined;
-      };
-      await connectTimeline();
-
-      onFirefoxConnect(this._target);
-      this._target.on("close", this._onTabDetached);
-
-      this.webConsoleClient = getWebConsoleClient();
-      this.NetworkEventsHandler = new NetworkEventsHandler();
-      this.NetworkEventsHandler.connect(this.actions);
-
-      window.emit(EVENTS.CONNECTED);
-
-      resolve();
-      this._connected = true;
-    });
-    return this._connection;
-  },
-
-  /**
-   * Disconnects the debugger client and removes event handlers as necessary.
-   */
-  disconnect() {
-    if (this._disconnection) {
-      return this._disconnection;
-    }
-    this._disconnection = new Promise(async (resolve) => {
-      // Wait for the connection to finish first.
-      if (!this._connected) {
-        await this._connection;
-      }
-
-      // When debugging local or a remote instance, the connection is closed by
-      // the RemoteTarget. The webconsole actor is stopped on disconnect.
-      this.tabClient = null;
-      this.webConsoleClient = null;
-
-      // The timeline front wasn't initialized and started if the server wasn't
-      // recent enough to emit the markers we were interested in.
-      if (this._target.getTrait("documentLoadingMarkers")) {
-        await this.timelineFront.destroy();
-        this.timelineFront = null;
-      }
-
-      resolve();
-      this._connected = false;
-    });
-    return this._disconnection;
-  },
-
-  /**
-   * Triggers a specific "activity" to be performed by the frontend.
-   * This can be, for example, triggering reloads or enabling/disabling cache.
-   *
-   * @param number type
-   *        The activity type. See the ACTIVITY_TYPE const.
-   * @return object
-   *         A promise resolved once the activity finishes and the frontend
-   *         is back into "standby" mode.
-   */
-  triggerActivity: function (type) {
-    // Puts the frontend into "standby" (when there's no particular activity).
-    let standBy = () => {
-      this._currentActivity = ACTIVITY_TYPE.NONE;
-    };
-
-    // Waits for a series of "navigation start" and "navigation stop" events.
-    let waitForNavigation = () => {
-      return new Promise((resolve) => {
-        this._target.once("will-navigate", () => {
-          this._target.once("navigate", () => {
-            resolve();
-          });
-        });
-      });
-    };
-
-    // Reconfigures the tab, optionally triggering a reload.
-    let reconfigureTab = options => {
-      return new Promise((resolve) => {
-        this._target.activeTab.reconfigure(options, resolve);
-      });
-    };
-
-    // Reconfigures the tab and waits for the target to finish navigating.
-    let reconfigureTabAndWaitForNavigation = options => {
-      options.performReload = true;
-      let navigationFinished = waitForNavigation();
-      return reconfigureTab(options).then(() => navigationFinished);
-    };
-    if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT) {
-      return reconfigureTabAndWaitForNavigation({}).then(standBy);
-    }
-    if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED) {
-      this._currentActivity = ACTIVITY_TYPE.ENABLE_CACHE;
-      this._target.once("will-navigate", () => {
-        this._currentActivity = type;
-      });
-      return reconfigureTabAndWaitForNavigation({
-        cacheDisabled: false,
-        performReload: true
-      }).then(standBy);
-    }
-    if (type == ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED) {
-      this._currentActivity = ACTIVITY_TYPE.DISABLE_CACHE;
-      this._target.once("will-navigate", () => {
-        this._currentActivity = type;
-      });
-      return reconfigureTabAndWaitForNavigation({
-        cacheDisabled: true,
-        performReload: true
-      }).then(standBy);
-    }
-    if (type == ACTIVITY_TYPE.ENABLE_CACHE) {
-      this._currentActivity = type;
-      return reconfigureTab({
-        cacheDisabled: false,
-        performReload: false
-      }).then(standBy);
-    }
-    if (type == ACTIVITY_TYPE.DISABLE_CACHE) {
-      this._currentActivity = type;
-      return reconfigureTab({
-        cacheDisabled: true,
-        performReload: false
-      }).then(standBy);
-    }
-    this._currentActivity = ACTIVITY_TYPE.NONE;
-    return Promise.reject(new Error("Invalid activity type"));
-  },
-
-  /**
-   * Selects the specified request in the waterfall and opens the details view.
-   *
-   * @param string requestId
-   *        The actor ID of the request to inspect.
-   * @return object
-   *         A promise resolved once the task finishes.
-   */
-  inspectRequest(requestId) {
-    // Look for the request in the existing ones or wait for it to appear, if
-    // the network monitor is still loading.
-    return new Promise((resolve) => {
-      let request = null;
-      let inspector = () => {
-        request = getDisplayedRequestById(window.gStore.getState(), requestId);
-        if (!request) {
-          // Reset filters so that the request is visible.
-          this.actions.toggleRequestFilterType("all");
-          request = getDisplayedRequestById(window.gStore.getState(), requestId);
-        }
-
-        // If the request was found, select it. Otherwise this function will be
-        // called again once new requests arrive.
-        if (request) {
-          window.off(EVENTS.REQUEST_ADDED, inspector);
-          this.actions.selectRequest(request.id);
-          resolve();
-        }
-      };
-
-      inspector();
-      if (!request) {
-        window.on(EVENTS.REQUEST_ADDED, inspector);
-      }
-    });
-  },
-
-  /**
-   * Getter that tells if the server supports sending custom network requests.
-   * @type boolean
-   */
-  get supportsCustomRequest() {
-    return this.webConsoleClient &&
-       (this.webConsoleClient.traits.customNetworkRequest ||
-        !this._target.isApp);
-  },
-
-  /**
-   * Getter that tells if the server can do network performance statistics.
-   * @type boolean
-   */
-  get supportsPerfStats() {
-    return this.tabClient &&
-      (this.tabClient.traits.reconfigure || !this._target.isApp);
-  },
-
-  /**
-   * Open a given source in Debugger
-   */
-  viewSourceInDebugger(sourceURL, sourceLine) {
-    if (this.toolbox) {
-      this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
-    }
-  },
-};
-
-/**
- * Functions handling target network events.
- */
-function NetworkEventsHandler() {
-  this.payloadQueue = [];
-  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._onSecurityInfo = this._onSecurityInfo.bind(this);
-  this._onEventTimings = this._onEventTimings.bind(this);
-}
-
-NetworkEventsHandler.prototype = {
-  get client() {
-    return NetMonitorController._target.client;
-  },
-
-  get webConsoleClient() {
-    return NetMonitorController.webConsoleClient;
-  },
-
-  get timelineFront() {
-    return NetMonitorController.timelineFront;
-  },
-
-  /**
-   * Connect to the current target client.
-   */
-  connect(actions) {
-    this.actions = actions;
-    this.webConsoleClient.on("networkEvent", this._onNetworkEvent);
-    this.webConsoleClient.on("networkEventUpdate", this._onNetworkEventUpdate);
-
-    if (this.timelineFront) {
-      this.timelineFront.on("doc-loading", this._onDocLoadingMarker);
-    }
-
-    this._displayCachedEvents();
-  },
-
-  /**
-   * Disconnect from the client.
-   */
-  disconnect() {
-    if (!this.client) {
-      return;
-    }
-    this.webConsoleClient.off("networkEvent", this._onNetworkEvent);
-    this.webConsoleClient.off("networkEventUpdate", this._onNetworkEventUpdate);
-
-    if (this.timelineFront) {
-      this.timelineFront.off("doc-loading", this._onDocLoadingMarker);
-    }
-  },
-
-  /**
-   * Display any network events already in the cache.
-   */
-  _displayCachedEvents: function () {
-    for (let cachedEvent of this.webConsoleClient.getNetworkEvents()) {
-      // First add the request to the timeline.
-      this._onNetworkEvent("networkEvent", cachedEvent);
-      // Then replay any updates already received.
-      for (let update of cachedEvent.updates) {
-        this._onNetworkEventUpdate("networkEventUpdate", {
-          packet: {
-            updateType: update
-          },
-          networkInfo: cachedEvent
-        });
-      }
-    }
-  },
-
-  /**
-   * The "DOMContentLoaded" and "Load" events sent by the timeline actor.
-   * @param object marker
-   */
-  _onDocLoadingMarker: function (marker) {
-    this.actions.addTimingMarker(marker);
-    window.emit(EVENTS.TIMELINE_EVENT, marker);
-  },
-
-  /**
-   * The "networkEvent" message type handler.
-   *
-   * @param string type
-   *        Message type.
-   * @param object networkInfo
-   *        The network request information.
-   */
-  _onNetworkEvent: function (type, networkInfo) {
-    let { actor,
-      startedDateTime,
-      request: { method, url },
-      isXHR,
-      cause,
-      fromCache,
-      fromServiceWorker
-    } = networkInfo;
-
-    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;
-
-    this.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));
-  },
-
-  async fetchImage(mimeType, responseContent) {
-    let payload = {};
-    if (mimeType && responseContent && responseContent.content) {
-      let { encoding, text } = responseContent.content;
-      let response = await getLongString(text);
-
-      if (mimeType.includes("image/")) {
-        payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
-      }
-
-      responseContent.content.text = response;
-      payload.responseContent = responseContent;
-    }
-    return payload;
-  },
-
-  async fetchRequestHeaders(requestHeaders) {
-    let payload = {};
-    if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
-      let headers = await fetchHeaders(requestHeaders, getLongString);
-      if (headers) {
-        payload.requestHeaders = headers;
-      }
-    }
-    return payload;
-  },
-
-  async fetchResponseHeaders(responseHeaders) {
-    let payload = {};
-    if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
-      let headers = await fetchHeaders(responseHeaders, getLongString);
-      if (headers) {
-        payload.responseHeaders = headers;
-      }
-    }
-    return payload;
-  },
-
-  // Search the POST data upload stream for request headers and add
-  // them as a separate property, different from the classic headers.
-  async fetchPostData(requestPostData) {
-    let payload = {};
-    if (requestPostData && requestPostData.postData) {
-      let { text } = requestPostData.postData;
-      let postData = await getLongString(text);
-      const headers = CurlUtils.getHeadersFromMultipartText(postData);
-      const headersSize = headers.reduce((acc, { name, value }) => {
-        return acc + name.length + value.length + 2;
-      }, 0);
-      requestPostData.postData.text = postData;
-      payload.requestPostData = Object.assign({}, requestPostData);
-      payload.requestHeadersFromUploadStream = { headers, headersSize };
-    }
-    return payload;
-  },
-
-  async fetchResponseCookies(responseCookies) {
-    let payload = {};
-    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: await getLongString(cookie.value),
-          }));
-        }
-        if (resCookies.length) {
-          payload.responseCookies = resCookies;
-        }
-      }
-    }
-    return payload;
-  },
-
-  // 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.
-  async fetchRequestCookies(requestCookies) {
-    let payload = {};
-    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: await getLongString(cookie.value),
-          }));
-        }
-        if (reqCookies.length) {
-          payload.requestCookies = reqCookies;
-        }
-      }
-    }
-    return payload;
-  },
-
-  getPayloadFromQueue(id) {
-    return this.payloadQueue.find((item) => item.id === id);
-  },
-
-  // Packet order of "networkUpdateEvent" is predictable, as a result we can wait for
-  // the last one "eventTimings" packet arrives to check payload is ready
-  isQueuePayloadReady(id) {
-    let queuedPayload = this.getPayloadFromQueue(id);
-    return queuedPayload && queuedPayload.payload.eventTimings;
-  },
-
-  pushPayloadToQueue(id, payload) {
-    let queuedPayload = this.getPayloadFromQueue(id);
-    if (!queuedPayload) {
-      this.payloadQueue.push({ id, payload });
-    } else {
-      // Merge upcoming networkEventUpdate payload into existing one
-      queuedPayload.payload = Object.assign({}, queuedPayload.payload, payload);
-    }
-  },
-
-  async updateRequest(id, data) {
-    let {
-      mimeType,
-      responseContent,
-      responseCookies,
-      responseHeaders,
-      requestCookies,
-      requestHeaders,
-      requestPostData,
-    } = data;
-
-    // fetch request detail contents in parallel
-    let [
-      imageObj,
-      requestHeadersObj,
-      responseHeadersObj,
-      postDataObj,
-      requestCookiesObj,
-      responseCookiesObj,
-    ] = await Promise.all([
-      this.fetchImage(mimeType, responseContent),
-      this.fetchRequestHeaders(requestHeaders),
-      this.fetchResponseHeaders(responseHeaders),
-      this.fetchPostData(requestPostData),
-      this.fetchRequestCookies(requestCookies),
-      this.fetchResponseCookies(responseCookies),
-    ]);
-
-    let payload = Object.assign({}, data,
-                                    imageObj, requestHeadersObj, responseHeadersObj,
-                                    postDataObj, requestCookiesObj, responseCookiesObj);
-
-    this.pushPayloadToQueue(id, payload);
-
-    if (this.isQueuePayloadReady(id)) {
-      await this.actions.updateRequest(id, this.getPayloadFromQueue(id).payload, 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":
-        this.updateRequest(actor, {
-          securityState: networkInfo.securityInfo,
-        }).then(() => {
-          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":
-        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
-        }).then(() => {
-          window.emit(EVENTS.STARTED_RECEIVING_RESPONSE, actor);
-        });
-        break;
-      case "responseContent":
-        this.webConsoleClient.getResponseContent(actor,
-          this._onResponseContent.bind(this, {
-            contentSize: networkInfo.response.bodySize,
-            transferredSize: networkInfo.response.transferredSize,
-            mimeType: networkInfo.response.content.mimeType
-          }));
-        window.emit(EVENTS.UPDATING_RESPONSE_CONTENT, actor);
-        break;
-      case "eventTimings":
-        this.updateRequest(actor, {
-          totalTime: networkInfo.totalTime
-        }).then(() => {
-          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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    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) {
-    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 data
-   *        The message received from the server event.
-   * @param object response
-   *        The message received from the server.
-   */
-  _onResponseContent: function (data, response) {
-    let payload = Object.assign({ responseContent: response }, data);
-    this.updateRequest(response.from, payload).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) {
-    this.updateRequest(response.from, {
-      eventTimings: response
-    }).then(() => {
-      window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
-    });
-  }
-};
-
-exports.NetMonitorController = NetMonitorController;
--- a/devtools/client/netmonitor/src/request-list-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-context-menu.js
@@ -5,22 +5,24 @@
 "use strict";
 
 const Services = require("Services");
 const { Curl } = require("devtools/client/shared/curl");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const { saveAs } = require("devtools/client/shared/file-saver");
 const { copyString } = require("devtools/shared/platform/clipboard");
 const { HarExporter } = require("./har/har-exporter");
-const { NetMonitorController } = require("./netmonitor-controller");
+const {
+  getLongString,
+  getTabTarget,
+} = require("./connector/index");
 const {
   getSelectedRequest,
   getSortedRequests,
 } = require("./selectors/index");
-const { getLongString } = require("./utils/client");
 const { L10N } = require("./utils/l10n");
 const { showMenu } = require("./utils/menu");
 const {
   getUrlQuery,
   parseQueryString,
 } = require("./utils/request-utils");
 
 function RequestListContextMenu({
@@ -28,21 +30,25 @@ function RequestListContextMenu({
   openStatistics,
 }) {
   this.cloneSelectedRequest = cloneSelectedRequest;
   this.openStatistics = openStatistics;
 }
 
 RequestListContextMenu.prototype = {
   get selectedRequest() {
-    return getSelectedRequest(window.gStore.getState());
+    // FIXME: Bug 1336382 - Implement RequestListContextMenu React component
+    // Remove window.store
+    return getSelectedRequest(window.store.getState());
   },
 
   get sortedRequests() {
-    return getSortedRequests(window.gStore.getState());
+    // FIXME: Bug 1336382 - Implement RequestListContextMenu React component
+    // Remove window.store
+    return getSortedRequests(window.store.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(event = {}) {
@@ -159,26 +165,24 @@ RequestListContextMenu.prototype = {
       visible: !!(selectedRequest &&
                selectedRequest.responseContent &&
                selectedRequest.responseContent.content.mimeType.includes("image/")),
       click: () => this.saveImageAs(),
     });
 
     menu.push({
       type: "separator",
-      visible: !!(NetMonitorController.supportsCustomRequest &&
-               selectedRequest && !selectedRequest.isCustom),
+      visible: !!(selectedRequest && !selectedRequest.isCustom),
     });
 
     menu.push({
       id: "request-list-context-resend",
       label: L10N.getStr("netmonitor.context.editAndResend"),
       accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
-      visible: !!(NetMonitorController.supportsCustomRequest &&
-               selectedRequest && !selectedRequest.isCustom),
+      visible: !!(selectedRequest && !selectedRequest.isCustom),
       click: this.cloneSelectedRequest,
     });
 
     menu.push({
       type: "separator",
       visible: !!selectedRequest,
     });
 
@@ -340,17 +344,17 @@ RequestListContextMenu.prototype = {
     // FIXME: This will not work in launchpad
     // document.execCommand(‘cut’/‘copy’) was denied because it was not called from
     // inside a short running user-generated event handler.
     // https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
     return HarExporter.save(this.getDefaultHarOptions());
   },
 
   getDefaultHarOptions() {
-    let form = NetMonitorController._target.form;
+    let form = getTabTarget().form;
     let title = form.title || form.url;
 
     return {
       getString: getLongString,
       items: this.sortedRequests,
       title: title
     };
   }
--- a/devtools/client/netmonitor/src/request-list-header-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-header-context-menu.js
@@ -14,17 +14,19 @@ const stringMap = HEADERS
 
 class RequestListHeaderContextMenu {
   constructor({ toggleColumn, resetColumns }) {
     this.toggleColumn = toggleColumn;
     this.resetColumns = resetColumns;
   }
 
   get columns() {
-    return window.gStore.getState().ui.columns;
+    // FIXME: Bug 1362059 - Implement RequestListHeaderContextMenu React component
+    // Remove window.store
+    return window.store.getState().ui.columns;
   }
 
   get visibleColumns() {
     return [...this.columns].filter(([_, shown]) => shown);
   }
 
   /**
    * Handle the context menu opening.
--- a/devtools/client/netmonitor/src/request-list-tooltip.js
+++ b/devtools/client/netmonitor/src/request-list-tooltip.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   setImageTooltip,
   getImageDimensions,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-const { getLongString } = require("./utils/client");
+const { getLongString } = require("./connector/index");
 const { formDataURI } = require("./utils/request-utils");
 
 const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400; // px
 
 async function setTooltipImageContent(tooltip, itemEl, requestItem) {
   let { mimeType, text, encoding } = requestItem.responseContent.content;
 
   if (!mimeType || !mimeType.includes("image/")) {
deleted file mode 100644
--- a/devtools/client/netmonitor/src/utils/client.js
+++ /dev/null
@@ -1,101 +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/. */
-
-/* global gStore */
-
-"use strict";
-
-const Services = require("Services");
-const Actions = require("../actions/index");
-const { EVENTS } = require("../constants");
-
-let activeConsole;
-
-/**
- * Called for each location change in the monitored tab.
- *
- * @param {String} type Packet type.
- * @param {Object} packet Packet received from the server.
- */
-function navigated(type) {
-  window.emit(EVENTS.TARGET_DID_NAVIGATE);
-}
-
-/**
- * Called for each location change in the monitored tab.
- *
- * @param {String} type Packet type.
- * @param {Object} packet Packet received from the server.
- */
-function willNavigate(type) {
-  // Reset UI.
-  if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
-    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);
-}
-
-/**
- * Process connection events.
- *
- * @param {Object} tabTarget
- */
-function onFirefoxConnect(tabTarget) {
-  activeConsole = tabTarget.activeConsole;
-  tabTarget.on("navigate", navigated);
-  tabTarget.on("will-navigate", willNavigate);
-}
-
-/**
- * Process disconnect events.
- *
- * @param {Object} tabTarget
- */
-function onFirefoxDisconnect(tabTarget) {
-  activeConsole = null;
-  tabTarget.off("navigate", navigated);
-  tabTarget.off("will-navigate", willNavigate);
-}
-
-/**
- * Retrieve webconsole object
- *
- * @returns {Object} webConsole
- */
-function getWebConsoleClient() {
-  return activeConsole;
-}
-
-/**
- * Fetches the full text of a LongString.
- *
- * @param object | string stringGrip
- *        The long string grip containing the corresponding actor.
- *        If you pass in a plain string (by accident or because you're lazy),
- *        then a promise of the same string is simply returned.
- * @return object Promise
- *         A promise that is resolved when the full string contents
- *         are available, or rejected if something goes wrong.
- */
-function getLongString(stringGrip) {
-  // FIXME: this.webConsoleClient will be undefined in mochitest,
-  // so we return string instantly to skip undefined error
-  if (typeof stringGrip === "string") {
-    return Promise.resolve(stringGrip);
-  }
-
-  return activeConsole.getString(stringGrip);
-}
-
-module.exports = {
-  getLongString,
-  getWebConsoleClient,
-  onFirefoxConnect,
-  onFirefoxDisconnect,
-};
--- a/devtools/client/netmonitor/src/utils/moz.build
+++ b/devtools/client/netmonitor/src/utils/moz.build
@@ -1,15 +1,14 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'client.js',
     'create-store.js',
     'filter-predicates.js',
     'filter-text-utils.js',
     'format-utils.js',
     'l10n.js',
     'mdn-utils.js',
     'menu.js',
     'prefs.js',
--- a/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
+++ b/devtools/client/webconsole/new-console-output/new-console-output-wrapper.js
@@ -83,18 +83,20 @@ NewConsoleOutputWrapper.prototype = {
           frame.url,
           frame.line
         ),
         onViewSourceInStyleEditor: frame => this.toolbox.viewSourceInStyleEditor(
           frame.url,
           frame.line
         ),
         openNetworkPanel: (requestId) => {
-          return this.toolbox.selectTool("netmonitor").then(panel => {
-            return panel.panelWin.NetMonitorController.inspectRequest(requestId);
+          return this.toolbox.selectTool("netmonitor").then((panel) => {
+            let { inspectRequest } = panel.panelWin.windowRequire(
+              "devtools/client/netmonitor/src/connector/index");
+            return inspectRequest(requestId);
           });
         },
         sourceMapService: this.toolbox ? this.toolbox.sourceMapURLService : null,
         highlightDomElement: (grip, options = {}) => {
           return this.toolbox.highlighterUtils
             ? this.toolbox.highlighterUtils.highlightDomValueGrip(grip, options)
             : null;
         },
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -1932,19 +1932,19 @@ WebConsoleFrame.prototype = {
    */
   openNetworkPanel: function (requestId) {
     let toolbox = gDevTools.getToolbox(this.owner.target);
     // The browser console doesn't have a toolbox.
     if (!toolbox) {
       return;
     }
     return toolbox.selectTool("netmonitor").then(panel => {
-      let { NetMonitorController } = panel.panelWin
-        .windowRequire("devtools/client/netmonitor/src/netmonitor-controller");
-      return NetMonitorController.inspectRequest(requestId);
+      let { inspectRequest } = panel.panelWin.windowRequire(
+        "devtools/client/netmonitor/src/connector/index");
+      return inspectRequest(requestId);
     });
   },
 
   /**
    * Handler for page location changes.
    *
    * @param string uri
    *        New page location.