Bug 1350215 - Rename components from camelcase to hyphens r?honza draft
authorRicky Chien <ricky060709@gmail.com>
Tue, 28 Mar 2017 18:51:26 +0800
changeset 552928 480e0f2ec3866ef25f1afd54713bfe4add8a0e46
parent 552927 858fe18d54ba3363e372b73c479b8aebf666fd04
child 552929 2dcd3f91009ee18788310f7286d2a1d0f754b4f8
child 552931 820a63c32cc5c1c9134864bd4eaca45a42fd63a8
push id51510
push userbmo:rchien@mozilla.com
push dateWed, 29 Mar 2017 07:47:25 +0000
reviewershonza
bugs1350215
milestone55.0a1
Bug 1350215 - Rename components from camelcase to hyphens r?honza MozReview-Commit-ID: Em8AYdGzTcb
devtools/client/netmonitor/index.html
devtools/client/netmonitor/src/components/App.js
devtools/client/netmonitor/src/components/CookiesPanel.js
devtools/client/netmonitor/src/components/CustomRequestPanel.js
devtools/client/netmonitor/src/components/Editor.js
devtools/client/netmonitor/src/components/HeadersPanel.js
devtools/client/netmonitor/src/components/MDNLink.js
devtools/client/netmonitor/src/components/MonitorPanel.js
devtools/client/netmonitor/src/components/NetworkDetailsPanel.js
devtools/client/netmonitor/src/components/ParamsPanel.js
devtools/client/netmonitor/src/components/PreviewPanel.js
devtools/client/netmonitor/src/components/PropertiesView.js
devtools/client/netmonitor/src/components/RequestList.js
devtools/client/netmonitor/src/components/RequestListContent.js
devtools/client/netmonitor/src/components/RequestListEmptyNotice.js
devtools/client/netmonitor/src/components/RequestListHeader.js
devtools/client/netmonitor/src/components/RequestListItem.js
devtools/client/netmonitor/src/components/ResponsePanel.js
devtools/client/netmonitor/src/components/SecurityPanel.js
devtools/client/netmonitor/src/components/StatisticsPanel.js
devtools/client/netmonitor/src/components/TabboxPanel.js
devtools/client/netmonitor/src/components/TimingsPanel.js
devtools/client/netmonitor/src/components/Toolbar.js
devtools/client/netmonitor/src/components/app.js
devtools/client/netmonitor/src/components/cookies-panel.js
devtools/client/netmonitor/src/components/custom-request-panel.js
devtools/client/netmonitor/src/components/editor.js
devtools/client/netmonitor/src/components/headers-panel.js
devtools/client/netmonitor/src/components/mdn-link.js
devtools/client/netmonitor/src/components/monitor-panel.js
devtools/client/netmonitor/src/components/moz.build
devtools/client/netmonitor/src/components/network-details-panel.js
devtools/client/netmonitor/src/components/params-panel.js
devtools/client/netmonitor/src/components/preview-panel.js
devtools/client/netmonitor/src/components/properties-view.js
devtools/client/netmonitor/src/components/request-list-content.js
devtools/client/netmonitor/src/components/request-list-empty-notice.js
devtools/client/netmonitor/src/components/request-list-header.js
devtools/client/netmonitor/src/components/request-list-item.js
devtools/client/netmonitor/src/components/request-list.js
devtools/client/netmonitor/src/components/response-panel.js
devtools/client/netmonitor/src/components/security-panel.js
devtools/client/netmonitor/src/components/statistics-panel.js
devtools/client/netmonitor/src/components/tabbox-panel.js
devtools/client/netmonitor/src/components/timings-panel.js
devtools/client/netmonitor/src/components/toolbar.js
--- a/devtools/client/netmonitor/index.html
+++ b/devtools/client/netmonitor/index.html
@@ -35,17 +35,17 @@
 
       // Export NetMonitorController to global window
       // FIXME: Use module export mechanism instead of this tricky global variables
       window.NetMonitorController = NetMonitorController;
 
       window.Netmonitor = {
         bootstrap({ toolbox }) {
           this.mount = document.querySelector("#mount");
-          const App = createFactory(require("./src/components/App"));
+          const App = createFactory(require("./src/components/app"));
           render(Provider({ store }, App()), this.mount);
           return NetMonitorController.startupNetMonitor({
             client: {
               getTabTarget: () => toolbox.target,
             },
             toolbox,
           });
         },
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/App.js
+++ /dev/null
@@ -1,40 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-
-// Components
-const MonitorPanel = createFactory(require("./MonitorPanel"));
-const StatisticsPanel = createFactory(require("./StatisticsPanel"));
-
-const { div } = DOM;
-
-/*
- * App component
- * The top level component for representing main panel
- */
-function App({ statisticsOpen }) {
-  return (
-    div({ className: "network-monitor" },
-      !statisticsOpen ? MonitorPanel() : StatisticsPanel()
-    )
-  );
-}
-
-App.displayName = "App";
-
-App.propTypes = {
-  statisticsOpen: PropTypes.bool.isRequired,
-};
-
-module.exports = connect(
-  (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
-)(App);
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/CookiesPanel.js
+++ /dev/null
@@ -1,99 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../utils/l10n");
-
-// Component
-const PropertiesView = createFactory(require("./PropertiesView"));
-
-const { div } = DOM;
-
-const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
-const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
-const REQUEST_COOKIES = L10N.getStr("requestCookies");
-const RESPONSE_COOKIES = L10N.getStr("responseCookies");
-const SECTION_NAMES = [
-  RESPONSE_COOKIES,
-  REQUEST_COOKIES,
-];
-
-/*
- * Cookies panel component
- * This tab lists full details of any cookies sent with the request or response
- */
-function CookiesPanel({
-  request,
-}) {
-  let {
-    requestCookies = { cookies: [] },
-    responseCookies = { cookies: [] },
-  } = request;
-
-  requestCookies = requestCookies.cookies || requestCookies;
-  responseCookies = responseCookies.cookies || responseCookies;
-
-  if (!requestCookies.length && !responseCookies.length) {
-    return div({ className: "empty-notice" },
-      COOKIES_EMPTY_TEXT
-    );
-  }
-
-  let object = {};
-
-  if (responseCookies.length) {
-    object[RESPONSE_COOKIES] = getProperties(responseCookies);
-  }
-
-  if (requestCookies.length) {
-    object[REQUEST_COOKIES] = getProperties(requestCookies);
-  }
-
-  return (
-    div({ className: "panel-container" },
-      PropertiesView({
-        object,
-        filterPlaceHolder: COOKIES_FILTER_TEXT,
-        sectionNames: SECTION_NAMES,
-      })
-    )
-  );
-}
-
-CookiesPanel.displayName = "CookiesPanel";
-
-CookiesPanel.propTypes = {
-  request: PropTypes.object.isRequired,
-};
-
-/**
- * Mapping array to dict for TreeView usage.
- * Since TreeView only support Object(dict) format.
- *
- * @param {Object[]} arr - key-value pair array like cookies or params
- * @returns {Object}
- */
-function getProperties(arr) {
-  return arr.reduce((map, obj) => {
-    // Generally cookies object contains only name and value properties and can
-    // be rendered as name: value pair.
-    // When there are more properties in cookies object such as extra or path,
-    // We will pass the object to display these extra information
-    if (Object.keys(obj).length > 2) {
-      map[obj.name] = Object.assign({}, obj);
-      delete map[obj.name].name;
-    } else {
-      map[obj.name] = obj.value;
-    }
-    return map;
-  }, {});
-}
-
-module.exports = CookiesPanel;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/HeadersPanel.js
+++ /dev/null
@@ -1,260 +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 {
-  createClass,
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { getFormattedSize } = require("../utils/format-utils");
-const { L10N } = require("../utils/l10n");
-const {
-  getHeadersURL,
-  getHTTPStatusCodeURL,
-} = require("../utils/mdn-utils");
-const { writeHeaderText } = require("../utils/request-utils");
-
-// Components
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const MDNLink = createFactory(require("./MDNLink"));
-const PropertiesView = createFactory(require("./PropertiesView"));
-
-const Rep = createFactory(REPS.Rep);
-const { button, div, input, textarea } = DOM;
-
-const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend");
-const RAW_HEADERS = L10N.getStr("netmonitor.summary.rawHeaders");
-const RAW_HEADERS_REQUEST = L10N.getStr("netmonitor.summary.rawHeaders.requestHeaders");
-const RAW_HEADERS_RESPONSE = L10N.getStr("netmonitor.summary.rawHeaders.responseHeaders");
-const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText");
-const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText");
-const REQUEST_HEADERS = L10N.getStr("requestHeaders");
-const REQUEST_HEADERS_FROM_UPLOAD = L10N.getStr("requestHeadersFromUpload");
-const RESPONSE_HEADERS = L10N.getStr("responseHeaders");
-const SUMMARY_ADDRESS = L10N.getStr("netmonitor.summary.address");
-const SUMMARY_METHOD = L10N.getStr("netmonitor.summary.method");
-const SUMMARY_URL = L10N.getStr("netmonitor.summary.url");
-const SUMMARY_STATUS = L10N.getStr("netmonitor.summary.status");
-const SUMMARY_VERSION = L10N.getStr("netmonitor.summary.version");
-
-/*
- * Headers panel component
- * Lists basic information about the request
- */
-const HeadersPanel = createClass({
-  displayName: "HeadersPanel",
-
-  propTypes: {
-    cloneSelectedRequest: PropTypes.func.isRequired,
-    request: PropTypes.object.isRequired,
-    renderValue: PropTypes.func
-  },
-
-  getInitialState() {
-    return {
-      rawHeadersOpened: false,
-    };
-  },
-
-  getProperties(headers, title) {
-    if (headers && headers.headers.length) {
-      return {
-        [`${title} (${getFormattedSize(headers.headersSize, 3)})`]:
-          headers.headers.reduce((acc, { name, value }) =>
-            name ? Object.assign(acc, { [name]: value }) : acc
-          , {})
-      };
-    }
-
-    return null;
-  },
-
-  toggleRawHeaders() {
-    this.setState({
-      rawHeadersOpened: !this.state.rawHeadersOpened,
-    });
-  },
-
-  renderSummary(label, value) {
-    return (
-      div({ className: "tabpanel-summary-container headers-summary" },
-        div({
-          className: "tabpanel-summary-label headers-summary-label",
-        }, label),
-        input({
-          className: "tabpanel-summary-value textbox-input devtools-monospace",
-          readOnly: true,
-          value,
-        }),
-      )
-    );
-  },
-
-  renderValue(props) {
-    const member = props.member;
-    const value = props.value;
-
-    if (typeof value !== "string") {
-      return null;
-    }
-
-    let headerDocURL = getHeadersURL(member.name);
-
-    return (
-      div({ className: "treeValueCellDivider" },
-        Rep(Object.assign(props, {
-          // FIXME: A workaround for the issue in StringRep
-          // Force StringRep to crop the text everytime
-          member: Object.assign({}, member, { open: false }),
-          mode: MODE.TINY,
-          cropLimit: 60,
-        })),
-        headerDocURL ? MDNLink({
-          url: headerDocURL,
-        }) : null
-      )
-    );
-  },
-
-  render() {
-    const {
-      cloneSelectedRequest,
-      request: {
-        fromCache,
-        fromServiceWorker,
-        httpVersion,
-        method,
-        remoteAddress,
-        remotePort,
-        requestHeaders,
-        requestHeadersFromUploadStream: uploadHeaders,
-        responseHeaders,
-        status,
-        statusText,
-        urlDetails,
-      },
-    } = this.props;
-
-    if ((!requestHeaders || !requestHeaders.headers.length) &&
-        (!uploadHeaders || !uploadHeaders.headers.length) &&
-        (!responseHeaders || !responseHeaders.headers.length)) {
-      return div({ className: "empty-notice" },
-        HEADERS_EMPTY_TEXT
-      );
-    }
-
-    let object = Object.assign({},
-      this.getProperties(responseHeaders, RESPONSE_HEADERS),
-      this.getProperties(requestHeaders, REQUEST_HEADERS),
-      this.getProperties(uploadHeaders, REQUEST_HEADERS_FROM_UPLOAD),
-    );
-
-    let summaryUrl = urlDetails.unicodeUrl ?
-      this.renderSummary(SUMMARY_URL, urlDetails.unicodeUrl) : null;
-
-    let summaryMethod = method ?
-      this.renderSummary(SUMMARY_METHOD, method) : null;
-
-    let summaryAddress = remoteAddress ?
-      this.renderSummary(SUMMARY_ADDRESS,
-        remotePort ? `${remoteAddress}:${remotePort}` : remoteAddress) : null;
-
-    let summaryStatus;
-
-    if (status) {
-      let code;
-      if (fromCache) {
-        code = "cached";
-      } else if (fromServiceWorker) {
-        code = "service worker";
-      } else {
-        code = status;
-      }
-
-      let statusCodeDocURL = getHTTPStatusCodeURL(code);
-      let inputWidth = status.length + statusText.length + 1;
-
-      summaryStatus = (
-        div({ className: "tabpanel-summary-container headers-summary" },
-          div({
-            className: "tabpanel-summary-label headers-summary-label",
-          }, SUMMARY_STATUS),
-          div({
-            className: "requests-list-status-icon",
-            "data-code": code,
-          }),
-          input({
-            className: "tabpanel-summary-value textbox-input devtools-monospace"
-              + " status-text",
-            readOnly: true,
-            value: `${status} ${statusText}`,
-            size: `${inputWidth}`,
-          }),
-          statusCodeDocURL ? MDNLink({
-            url: statusCodeDocURL,
-          }) : null,
-          window.NetMonitorController.supportsCustomRequest && button({
-            className: "devtools-button",
-            onClick: cloneSelectedRequest,
-          }, EDIT_AND_RESEND),
-          button({
-            className: "devtools-button",
-            onClick: this.toggleRawHeaders,
-          }, RAW_HEADERS),
-        )
-      );
-    }
-
-    let summaryVersion = httpVersion ?
-      this.renderSummary(SUMMARY_VERSION, httpVersion) : null;
-
-    let summaryRawHeaders;
-    if (this.state.rawHeadersOpened) {
-      summaryRawHeaders = (
-        div({ className: "tabpanel-summary-container headers-summary" },
-          div({ className: "raw-headers-container" },
-            div({ className: "raw-headers" },
-              div({ className: "tabpanel-summary-label" }, RAW_HEADERS_REQUEST),
-              textarea({
-                value: writeHeaderText(requestHeaders.headers),
-                readOnly: true,
-              }),
-            ),
-            div({ className: "raw-headers" },
-              div({ className: "tabpanel-summary-label" }, RAW_HEADERS_RESPONSE),
-              textarea({
-                value: writeHeaderText(responseHeaders.headers),
-                readOnly: true,
-              }),
-            ),
-          )
-        )
-      );
-    }
-
-    return (
-      div({ className: "panel-container" },
-        div({ className: "headers-overview" },
-          summaryUrl,
-          summaryMethod,
-          summaryAddress,
-          summaryStatus,
-          summaryVersion,
-          summaryRawHeaders,
-        ),
-        PropertiesView({
-          object,
-          filterPlaceHolder: HEADERS_FILTER_TEXT,
-          sectionNames: Object.keys(object),
-          renderValue: this.renderValue,
-        }),
-      )
-    );
-  }
-});
-
-module.exports = HeadersPanel;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ /dev/null
@@ -1,135 +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 {
-  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 { Prefs } = require("../utils/prefs");
-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("./NetworkDetailsPanel"));
-const RequestList = createFactory(require("./RequestList"));
-const Toolbar = createFactory(require("./Toolbar"));
-
-const { div } = DOM;
-const MediaQueryList = window.matchMedia("(min-width: 700px)");
-
-/*
- * Monitor panel component
- * The main panel for displaying various network request information
- */
-const MonitorPanel = createClass({
-  displayName: "MonitorPanel",
-
-  propTypes: {
-    isEmpty: PropTypes.bool.isRequired,
-    networkDetailsOpen: PropTypes.bool.isRequired,
-    openNetworkDetails: PropTypes.func.isRequired,
-    request: PropTypes.object,
-    updateRequest: PropTypes.func.isRequired,
-  },
-
-  getInitialState() {
-    return {
-      isVerticalSpliter: MediaQueryList.matches,
-    };
-  },
-
-  componentDidMount() {
-    MediaQueryList.addListener(this.onLayoutChange);
-  },
-
-  componentWillReceiveProps(nextProps) {
-    let {
-      request = {},
-      updateRequest,
-    } = nextProps;
-    let {
-      formDataSections,
-      requestHeaders,
-      requestHeadersFromUploadStream,
-      requestPostData,
-    } = request;
-
-    if (!formDataSections && requestHeaders &&
-        requestHeadersFromUploadStream && requestPostData) {
-      getFormDataSections(
-        requestHeaders,
-        requestHeadersFromUploadStream,
-        requestPostData,
-        getLongString,
-      ).then((newFormDataSections) => {
-        updateRequest(
-          request.id,
-          { formDataSections: newFormDataSections },
-          true,
-        );
-      });
-    }
-  },
-
-  componentWillUnmount() {
-    MediaQueryList.removeListener(this.onLayoutChange);
-
-    let { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};
-
-    if (this.state.isVerticalSpliter && clientWidth) {
-      Prefs.networkDetailsWidth = clientWidth;
-    }
-    if (!this.state.isVerticalSpliter && clientHeight) {
-      Prefs.networkDetailsHeight = clientHeight;
-    }
-  },
-
-  onLayoutChange() {
-    this.setState({
-      isVerticalSpliter: MediaQueryList.matches,
-    });
-  },
-
-  render() {
-    let { isEmpty, networkDetailsOpen } = this.props;
-    return (
-      div({ className: "monitor-panel" },
-        Toolbar(),
-        SplitBox({
-          className: "devtools-responsive-container",
-          initialWidth: `${Prefs.networkDetailsWidth}px`,
-          initialHeight: `${Prefs.networkDetailsHeight}px`,
-          minSize: "50px",
-          maxSize: "80%",
-          splitterSize: "1px",
-          startPanel: RequestList({ isEmpty }),
-          endPanel: networkDetailsOpen && NetworkDetailsPanel({ ref: "endPanel" }),
-          endPanelControl: true,
-          vert: this.state.isVerticalSpliter,
-        }),
-      )
-    );
-  }
-});
-
-module.exports = connect(
-  (state) => ({
-    isEmpty: state.requests.requests.isEmpty(),
-    networkDetailsOpen: state.ui.networkDetailsOpen,
-    request: getSelectedRequest(state),
-  }),
-  (dispatch) => ({
-    openNetworkDetails: (open) => dispatch(Actions.openNetworkDetails(open)),
-    updateRequest: (id, data, batch) => dispatch(Actions.updateRequest(id, data, batch)),
-  }),
-)(MonitorPanel);
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/NetworkDetailsPanel.js
+++ /dev/null
@@ -1,70 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-const Actions = require("../actions/index");
-const { getSelectedRequest } = require("../selectors/index");
-
-// Components
-const CustomRequestPanel = createFactory(require("./CustomRequestPanel"));
-const TabboxPanel = createFactory(require("./TabboxPanel"));
-
-const { div } = DOM;
-
-/*
- * Network details panel component
- */
-function NetworkDetailsPanel({
-  activeTabId,
-  cloneSelectedRequest,
-  request,
-  selectTab,
-}) {
-  if (!request) {
-    return null;
-  }
-
-  return (
-    div({ className: "network-details-panel" },
-      !request.isCustom ?
-        TabboxPanel({
-          activeTabId,
-          request,
-          selectTab,
-        }) :
-        CustomRequestPanel({
-          cloneSelectedRequest,
-          request,
-        })
-    )
-  );
-}
-
-NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
-
-NetworkDetailsPanel.propTypes = {
-  activeTabId: PropTypes.string,
-  cloneSelectedRequest: PropTypes.func.isRequired,
-  open: PropTypes.bool,
-  request: PropTypes.object,
-  selectTab: PropTypes.func.isRequired,
-};
-
-module.exports = connect(
-  (state) => ({
-    activeTabId: state.ui.detailsPanelSelectedTab,
-    request: getSelectedRequest(state),
-  }),
-  (dispatch) => ({
-    cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
-    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
-  }),
-)(NetworkDetailsPanel);
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/ParamsPanel.js
+++ /dev/null
@@ -1,130 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../utils/l10n");
-const { getUrlQuery, parseQueryString } = require("../utils/request-utils");
-
-// Components
-const PropertiesView = createFactory(require("./PropertiesView"));
-
-const { div } = DOM;
-
-const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
-const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
-const PARAMS_FILTER_TEXT = L10N.getStr("paramsFilterText");
-const PARAMS_FORM_DATA = L10N.getStr("paramsFormData");
-const PARAMS_POST_PAYLOAD = L10N.getStr("paramsPostPayload");
-const PARAMS_QUERY_STRING = L10N.getStr("paramsQueryString");
-const SECTION_NAMES = [
-  JSON_SCOPE_NAME,
-  PARAMS_FORM_DATA,
-  PARAMS_POST_PAYLOAD,
-  PARAMS_QUERY_STRING,
-];
-
-/*
- * Params panel component
- * Displays the GET parameters and POST data of a request
- */
-function ParamsPanel({ request }) {
-  let {
-    formDataSections,
-    mimeType,
-    requestPostData,
-    url,
-  } = request;
-  let postData = requestPostData ? requestPostData.postData.text : null;
-  let query = getUrlQuery(url);
-
-  if (!formDataSections && !postData && !query) {
-    return div({ className: "empty-notice" },
-      PARAMS_EMPTY_TEXT
-    );
-  }
-
-  let object = {};
-  let json;
-
-  // Query String section
-  if (query) {
-    object[PARAMS_QUERY_STRING] = getProperties(parseQueryString(query));
-  }
-
-  // Form Data section
-  if (formDataSections && formDataSections.length > 0) {
-    let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
-    object[PARAMS_FORM_DATA] = getProperties(parseQueryString(sections));
-  }
-
-  // Request payload section
-  if (formDataSections && formDataSections.length === 0 && postData) {
-    try {
-      json = JSON.parse(postData);
-    } catch (error) {
-      // Continue regardless of parsing error
-    }
-
-    if (json) {
-      object[JSON_SCOPE_NAME] = json;
-    } else {
-      object[PARAMS_POST_PAYLOAD] = {
-        EDITOR_CONFIG: {
-          text: postData,
-          mode: mimeType.replace(/;.+/, ""),
-        },
-      };
-    }
-  } else {
-    postData = "";
-  }
-
-  return (
-    div({ className: "panel-container" },
-      PropertiesView({
-        object,
-        filterPlaceHolder: PARAMS_FILTER_TEXT,
-        sectionNames: SECTION_NAMES,
-      })
-    )
-  );
-}
-
-ParamsPanel.displayName = "ParamsPanel";
-
-ParamsPanel.propTypes = {
-  request: PropTypes.object.isRequired,
-};
-
-/**
- * Mapping array to dict for TreeView usage.
- * Since TreeView only support Object(dict) format.
- * This function also deal with duplicate key case
- * (for multiple selection and query params with same keys)
- *
- * @param {Object[]} arr - key-value pair array like query or form params
- * @returns {Object} Rep compatible object
- */
-function getProperties(arr) {
-  return arr.reduce((map, obj) => {
-    let value = map[obj.name];
-    if (value) {
-      if (typeof value !== "object") {
-        map[obj.name] = [value];
-      }
-      map[obj.name].push(obj.value);
-    } else {
-      map[obj.name] = obj.value;
-    }
-    return map;
-  }, {});
-}
-
-module.exports = ParamsPanel;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/PropertiesView.js
+++ /dev/null
@@ -1,218 +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/. */
-
-/* eslint-disable react/prop-types */
-
-"use strict";
-
-const {
-  createClass,
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-
-const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
-const Rep = createFactory(REPS.Rep);
-
-const { FILTER_SEARCH_DELAY } = require("../constants");
-
-// Components
-const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
-const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
-const TreeRow = createFactory(require("devtools/client/shared/components/tree/tree-row"));
-const Editor = createFactory(require("./Editor"));
-
-const { div, tr, td } = DOM;
-const AUTO_EXPAND_MAX_LEVEL = 7;
-const AUTO_EXPAND_MAX_NODES = 50;
-const EDITOR_CONFIG_ID = "EDITOR_CONFIG";
-
-/*
- * Properties View component
- * A scrollable tree view component which provides some useful features for
- * representing object properties.
- *
- * Search filter - Set enableFilter to enable / disable SearchBox feature.
- * Tree view - Default enabled.
- * Source editor - Enable by specifying object level 1 property name to EDITOR_CONFIG_ID.
- * Rep - Default enabled.
- */
-const PropertiesView = createClass({
-  displayName: "PropertiesView",
-
-  propTypes: {
-    object: PropTypes.object,
-    enableInput: PropTypes.bool,
-    expandableStrings: PropTypes.bool,
-    filterPlaceHolder: PropTypes.string,
-    sectionNames: PropTypes.array,
-  },
-
-  getDefaultProps() {
-    return {
-      enableInput: true,
-      enableFilter: true,
-      expandableStrings: false,
-      filterPlaceHolder: "",
-      sectionNames: [],
-    };
-  },
-
-  getInitialState() {
-    return {
-      filterText: "",
-    };
-  },
-
-  getRowClass(object, sectionNames) {
-    return sectionNames.includes(object.name) ? "tree-section" : "";
-  },
-
-  onFilter(object, whiteList) {
-    let { name, value } = object;
-    let filterText = this.state.filterText;
-
-    if (!filterText || whiteList.includes(name)) {
-      return true;
-    }
-
-    let jsonString = JSON.stringify({ [name]: value }).toLowerCase();
-    return jsonString.includes(filterText.toLowerCase());
-  },
-
-  renderRowWithEditor(props) {
-    const { level, name, value, path } = props.member;
-
-    // Display source editor when specifying to EDITOR_CONFIG_ID along with config
-    if (level === 1 && name === EDITOR_CONFIG_ID) {
-      return (
-        tr({ className: "editor-row-container" },
-          td({ colSpan: 2 },
-            Editor(value)
-          )
-        )
-      );
-    }
-
-    // Skip for editor config
-    if (level >= 1 && path.includes(EDITOR_CONFIG_ID)) {
-      return null;
-    }
-
-    return TreeRow(props);
-  },
-
-  renderValueWithRep(props) {
-    const { member } = props;
-
-    // Hide strings with following conditions
-    // 1. this row is a togglable section and content is object ('cause it shouldn't hide
-    //    when string or number)
-    // 2. the `value` object has a `value` property, only happened in Cookies panel
-    // Put 2 here to not dup this method
-    if (member.level === 0 && member.type === "object" ||
-      (typeof member.value === "object" && member.value && member.value.value)) {
-      return null;
-    }
-
-    return Rep(Object.assign(props, {
-      // FIXME: A workaround for the issue in StringRep
-      // Force StringRep to crop the text everytime
-      member: Object.assign({}, member, { open: false }),
-      mode: MODE.TINY,
-      cropLimit: 60,
-    }));
-  },
-
-  shouldRenderSearchBox(object) {
-    return this.props.enableFilter && object && Object.keys(object)
-      .filter((section) => !object[section][EDITOR_CONFIG_ID]).length > 0;
-  },
-
-  updateFilterText(filterText) {
-    this.setState({
-      filterText,
-    });
-  },
-
-  getExpandedNodes: function (object, path = "", level = 0) {
-    if (typeof object != "object") {
-      return null;
-    }
-
-    if (level > AUTO_EXPAND_MAX_LEVEL) {
-      return null;
-    }
-
-    let expandedNodes = new Set();
-    for (let prop in object) {
-      if (expandedNodes.size > AUTO_EXPAND_MAX_NODES) {
-        // If we reached the limit of expandable nodes, bail out to avoid performance
-        // issues.
-        break;
-      }
-
-      let nodePath = path + "/" + prop;
-      expandedNodes.add(nodePath);
-
-      let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
-      if (nodes) {
-        let newSize = expandedNodes.size + nodes.size;
-        if (newSize < AUTO_EXPAND_MAX_NODES) {
-          // Avoid having a subtree half expanded.
-          expandedNodes = new Set([...expandedNodes, ...nodes]);
-        }
-      }
-    }
-    return expandedNodes;
-  },
-
-  render() {
-    const {
-      decorator,
-      enableInput,
-      expandableStrings,
-      filterPlaceHolder,
-      object,
-      renderRow,
-      renderValue,
-      sectionNames,
-    } = this.props;
-
-    return (
-      div({ className: "properties-view" },
-        this.shouldRenderSearchBox(object) &&
-          div({ className: "searchbox-section" },
-            SearchBox({
-              delay: FILTER_SEARCH_DELAY,
-              type: "filter",
-              onChange: this.updateFilterText,
-              placeholder: filterPlaceHolder,
-            }),
-          ),
-        div({ className: "tree-container" },
-          TreeView({
-            object,
-            columns: [{
-              id: "value",
-              width: "100%",
-            }],
-            decorator: decorator || {
-              getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames),
-            },
-            enableInput,
-            expandableStrings,
-            expandedNodes: this.getExpandedNodes(object),
-            onFilter: (props) => this.onFilter(props, sectionNames),
-            renderRow: renderRow || this.renderRowWithEditor,
-            renderValue: renderValue || this.renderValueWithRep,
-          }),
-        ),
-      )
-    );
-  }
-});
-
-module.exports = PropertiesView;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/RequestList.js
+++ /dev/null
@@ -1,38 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-
-// Components
-const RequestListContent = createFactory(require("./RequestListContent"));
-const RequestListEmptyNotice = createFactory(require("./RequestListEmptyNotice"));
-const RequestListHeader = createFactory(require("./RequestListHeader"));
-
-const { div } = DOM;
-
-/**
- * Request panel component
- */
-function RequestList({ isEmpty }) {
-  return (
-    div({ className: "request-list-container" },
-      RequestListHeader(),
-      isEmpty ? RequestListEmptyNotice() : RequestListContent(),
-    )
-  );
-}
-
-RequestList.displayName = "RequestList";
-
-RequestList.propTypes = {
-  isEmpty: PropTypes.bool.isRequired,
-};
-
-module.exports = RequestList;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ /dev/null
@@ -1,280 +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 { KeyCodes } = require("devtools/client/shared/keycodes");
-const {
-  createClass,
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const Actions = require("../actions/index");
-const {
-  setTooltipImageContent,
-  setTooltipStackTraceContent,
-} = require("../request-list-tooltip");
-const {
-  getDisplayedRequests,
-  getWaterfallScale,
-} = require("../selectors/index");
-
-// Components
-const RequestListItem = createFactory(require("./RequestListItem"));
-const RequestListContextMenu = require("../request-list-context-menu");
-
-const { div } = DOM;
-
-// tooltip show/hide delay in ms
-const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
-
-/**
- * Renders the actual contents of the request list.
- */
-const RequestListContent = createClass({
-  displayName: "RequestListContent",
-
-  propTypes: {
-    dispatch: PropTypes.func.isRequired,
-    displayedRequests: PropTypes.object.isRequired,
-    firstRequestStartedMillis: PropTypes.number.isRequired,
-    fromCache: PropTypes.bool.isRequired,
-    onItemMouseDown: PropTypes.func.isRequired,
-    onSecurityIconClick: PropTypes.func.isRequired,
-    onSelectDelta: PropTypes.func.isRequired,
-    scale: PropTypes.number,
-    selectedRequestId: PropTypes.string,
-  },
-
-  componentWillMount() {
-    const { dispatch } = this.props;
-    this.contextMenu = new RequestListContextMenu({
-      cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
-      openStatistics: (open) => dispatch(Actions.openStatistics(open)),
-    });
-    this.tooltip = new HTMLTooltip(window.parent.document, { 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, {
-      toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
-      interactive: true
-    });
-
-    // Install event handler to hide the tooltip on scroll
-    this.refs.contentEl.addEventListener("scroll", this.onScroll, true);
-  },
-
-  componentWillUpdate(nextProps) {
-    // Check if the list is scrolled to bottom before the UI update.
-    // The scroll is ever needed only if new rows are added to the list.
-    const delta = nextProps.displayedRequests.size - this.props.displayedRequests.size;
-    this.shouldScrollBottom = delta > 0 && this.isScrolledToBottom();
-  },
-
-  componentDidUpdate(prevProps) {
-    // Update the CSS variables for waterfall scaling after props change
-    this.setScalingStyles(prevProps);
-
-    // Keep the list scrolled to bottom if a new row was added
-    if (this.shouldScrollBottom) {
-      let node = this.refs.contentEl;
-      node.scrollTop = node.scrollHeight;
-    }
-  },
-
-  componentWillUnmount() {
-    this.refs.contentEl.removeEventListener("scroll", this.onScroll, true);
-
-    // Uninstall the tooltip event handler
-    this.tooltip.stopTogglingOnHover();
-  },
-
-  /**
-   * Set the CSS variables for waterfall scaling. If React supported setting CSS
-   * variables as part of the "style" property of a DOM element, we would use that.
-   *
-   * However, React doesn't support this, so we need to use a hack and update the
-   * DOM element directly: https://github.com/facebook/react/issues/6411
-   */
-  setScalingStyles(prevProps) {
-    const { scale } = this.props;
-    if (prevProps && prevProps.scale === scale) {
-      return;
-    }
-
-    const { style } = this.refs.contentEl;
-    style.removeProperty("--timings-scale");
-    style.removeProperty("--timings-rev-scale");
-    style.setProperty("--timings-scale", scale);
-    style.setProperty("--timings-rev-scale", 1 / scale);
-  },
-
-  isScrolledToBottom() {
-    const { contentEl } = this.refs;
-    const lastChildEl = contentEl.lastElementChild;
-
-    if (!lastChildEl) {
-      return false;
-    }
-
-    let lastChildRect = lastChildEl.getBoundingClientRect();
-    let contentRect = contentEl.getBoundingClientRect();
-
-    return (lastChildRect.height + lastChildRect.top) <= contentRect.bottom;
-  },
-
-  /**
-   * The predicate used when deciding whether a popup should be shown
-   * over a request item or not.
-   *
-   * @param nsIDOMNode target
-   *        The element node currently being hovered.
-   * @param object tooltip
-   *        The current tooltip instance.
-   * @return {Promise}
-   */
-  onHover(target, tooltip) {
-    let itemEl = target.closest(".request-list-item");
-    if (!itemEl) {
-      return false;
-    }
-    let itemId = itemEl.dataset.id;
-    if (!itemId) {
-      return false;
-    }
-    let requestItem = this.props.displayedRequests.find(r => r.id == itemId);
-    if (!requestItem) {
-      return false;
-    }
-
-    if (requestItem.responseContent && target.closest(".requests-list-icon-and-file")) {
-      return setTooltipImageContent(tooltip, itemEl, requestItem);
-    } else if (requestItem.cause && target.closest(".requests-list-cause-stack")) {
-      return setTooltipStackTraceContent(tooltip, requestItem);
-    }
-
-    return false;
-  },
-
-  /**
-   * Scroll listener for the requests menu view.
-   */
-  onScroll() {
-    this.tooltip.hide();
-  },
-
-  /**
-   * Handler for keyboard events. For arrow up/down, page up/down, home/end,
-   * move the selection up or down.
-   */
-  onKeyDown(e) {
-    let delta;
-
-    switch (e.keyCode) {
-      case KeyCodes.DOM_VK_UP:
-      case KeyCodes.DOM_VK_LEFT:
-        delta = -1;
-        break;
-      case KeyCodes.DOM_VK_DOWN:
-      case KeyCodes.DOM_VK_RIGHT:
-        delta = +1;
-        break;
-      case KeyCodes.DOM_VK_PAGE_UP:
-        delta = "PAGE_UP";
-        break;
-      case KeyCodes.DOM_VK_PAGE_DOWN:
-        delta = "PAGE_DOWN";
-        break;
-      case KeyCodes.DOM_VK_HOME:
-        delta = -Infinity;
-        break;
-      case KeyCodes.DOM_VK_END:
-        delta = +Infinity;
-        break;
-    }
-
-    if (delta) {
-      // Prevent scrolling when pressing navigation keys.
-      e.preventDefault();
-      e.stopPropagation();
-      this.props.onSelectDelta(delta);
-    }
-  },
-
-  onContextMenu(evt) {
-    evt.preventDefault();
-    this.contextMenu.open(evt);
-  },
-
-  /**
-   * If selection has just changed (by keyboard navigation), don't keep the list
-   * scrolled to bottom, but allow scrolling up with the selection.
-   */
-  onFocusedNodeChange() {
-    this.shouldScrollBottom = false;
-  },
-
-  render() {
-    const {
-      displayedRequests,
-      firstRequestStartedMillis,
-      selectedRequestId,
-      onItemMouseDown,
-      onSecurityIconClick,
-    } = this.props;
-
-    return (
-      div({
-        ref: "contentEl",
-        className: "requests-list-contents",
-        tabIndex: 0,
-        onKeyDown: this.onKeyDown,
-      },
-        displayedRequests.map((item, index) => RequestListItem({
-          firstRequestStartedMillis,
-          fromCache: item.status === "304" || item.fromCache,
-          item,
-          index,
-          isSelected: item.id === selectedRequestId,
-          key: item.id,
-          onContextMenu: this.onContextMenu,
-          onFocusedNodeChange: this.onFocusedNodeChange,
-          onMouseDown: () => onItemMouseDown(item.id),
-          onSecurityIconClick: () => onSecurityIconClick(item.securityState),
-        }))
-      )
-    );
-  },
-});
-
-module.exports = connect(
-  (state) => ({
-    displayedRequests: getDisplayedRequests(state),
-    firstRequestStartedMillis: state.requests.firstStartedMillis,
-    selectedRequestId: state.requests.selectedId,
-    scale: getWaterfallScale(state),
-  }),
-  (dispatch) => ({
-    dispatch,
-    onItemMouseDown: (id) => dispatch(Actions.selectRequest(id)),
-    /**
-     * A handler that opens the security tab in the details view if secure or
-     * broken security indicator is clicked.
-     */
-    onSecurityIconClick: (securityState) => {
-      if (securityState && securityState !== "insecure") {
-        dispatch(Actions.selectDetailsPanelTab("security"));
-      }
-    },
-    onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
-  }),
-)(RequestListContent);
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/ResponsePanel.js
+++ /dev/null
@@ -1,185 +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 {
-  createClass,
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../utils/l10n");
-const { formDataURI, getUrlBaseName } = require("../utils/request-utils");
-
-// Components
-const PropertiesView = createFactory(require("./PropertiesView"));
-
-const { div, img } = DOM;
-const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
-const JSON_FILTER_TEXT = L10N.getStr("jsonFilterText");
-const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name");
-const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions");
-const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime");
-const RESPONSE_PAYLOAD = L10N.getStr("responsePayload");
-
-/*
- * Response panel component
- * Displays the GET parameters and POST data of a request
- */
-const ResponsePanel = createClass({
-  displayName: "ResponsePanel",
-
-  propTypes: {
-    request: PropTypes.object.isRequired,
-  },
-
-  getInitialState() {
-    return {
-      imageDimensions: {
-        width: 0,
-        height: 0,
-      },
-    };
-  },
-
-  updateImageDimemsions({ target }) {
-    this.setState({
-      imageDimensions: {
-        width: target.naturalWidth,
-        height: target.naturalHeight,
-      },
-    });
-  },
-
-  // Handle json, which we tentatively identify by checking the MIME type
-  // for "json" after any word boundary. This works for the standard
-  // "application/json", and also for custom types like "x-bigcorp-json".
-  // Additionally, we also directly parse the response text content to
-  // verify whether it's json or not, to handle responses incorrectly
-  // labeled as text/plain instead.
-  isJSON(mimeType, response) {
-    let json, error;
-    try {
-      json = JSON.parse(response);
-    } catch (err) {
-      try {
-        json = JSON.parse(atob(response));
-      } catch (err64) {
-        error = err;
-      }
-    }
-
-    if (/\bjson/.test(mimeType) || json) {
-      // Extract the actual json substring in case this might be a "JSONP".
-      // This regex basically parses a function call and captures the
-      // function name and arguments in two separate groups.
-      let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
-      let [, jsonpCallback, jsonp] = response.match(jsonpRegex) || [];
-      let result = {};
-
-      // Make sure this is a valid JSON object first. If so, nicely display
-      // the parsing results in a tree view.
-      if (jsonpCallback && jsonp) {
-        error = null;
-        try {
-          json = JSON.parse(jsonp);
-        } catch (err) {
-          error = err;
-        }
-      }
-
-      // Valid JSON
-      if (json) {
-        result.json = json;
-      }
-      // Valid JSONP
-      if (jsonpCallback) {
-        result.jsonpCallback = jsonpCallback;
-      }
-      // Malformed JSON
-      if (error) {
-        result.error = "" + error;
-      }
-
-      return result;
-    }
-
-    return null;
-  },
-
-  render() {
-    let { responseContent, url } = this.props.request;
-
-    if (!responseContent || typeof responseContent.content.text !== "string") {
-      return null;
-    }
-
-    let { encoding, mimeType, text } = responseContent.content;
-
-    if (mimeType.includes("image/")) {
-      let { width, height } = this.state.imageDimensions;
-
-      return (
-        div({ className: "panel-container response-image-box devtools-monospace" },
-          img({
-            className: "response-image",
-            src: formDataURI(mimeType, encoding, text),
-            onLoad: this.updateImageDimemsions,
-          }),
-          div({ className: "response-summary" },
-            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME),
-            div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)),
-          ),
-          div({ className: "response-summary" },
-            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS),
-            div({ className: "tabpanel-summary-value" }, `${width} × ${height}`),
-          ),
-          div({ className: "response-summary" },
-            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_MIMETYPE),
-            div({ className: "tabpanel-summary-value" }, mimeType),
-          ),
-        )
-      );
-    }
-
-    // Display Properties View
-    let { json, jsonpCallback, error } = this.isJSON(mimeType, text) || {};
-    let object = {};
-    let sectionName;
-
-    if (json) {
-      if (jsonpCallback) {
-        sectionName = L10N.getFormatStr("jsonpScopeName", jsonpCallback);
-      } else {
-        sectionName = JSON_SCOPE_NAME;
-      }
-      object[sectionName] = json;
-    } else {
-      sectionName = RESPONSE_PAYLOAD;
-
-      object[sectionName] = {
-        EDITOR_CONFIG: {
-          text,
-          mode: mimeType.replace(/;.+/, ""),
-        },
-      };
-    }
-
-    return (
-      div({ className: "panel-container" },
-        error && div({ className: "response-error-header", title: error },
-          error
-        ),
-        PropertiesView({
-          object,
-          filterPlaceHolder: JSON_FILTER_TEXT,
-          sectionNames: [sectionName],
-        }),
-      )
-    );
-  }
-});
-
-module.exports = ResponsePanel;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/SecurityPanel.js
+++ /dev/null
@@ -1,160 +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 {
-  createFactory,
-  DOM,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../utils/l10n");
-const { getUrlHost } = require("../utils/request-utils");
-
-// Components
-const PropertiesView = createFactory(require("./PropertiesView"));
-
-const { div, input, span } = DOM;
-
-/*
- * Security panel component
- * If the site is being served over HTTPS, you get an extra tab labeled "Security".
- * This contains details about the secure connection used including the protocol,
- * the cipher suite, and certificate details
- */
-function SecurityPanel({ request }) {
-  const { securityInfo, url } = request;
-
-  if (!securityInfo || !url) {
-    return null;
-  }
-
-  const notAvailable = L10N.getStr("netmonitor.security.notAvailable");
-  let object;
-
-  if (securityInfo.state === "secure" || securityInfo.state === "weak") {
-    const { subject, issuer, validity, fingerprint } = securityInfo.cert;
-    const enabledLabel = L10N.getStr("netmonitor.security.enabled");
-    const disabledLabel = L10N.getStr("netmonitor.security.disabled");
-
-    object = {
-      [L10N.getStr("netmonitor.security.connection")]: {
-        [L10N.getStr("netmonitor.security.protocolVersion")]:
-          securityInfo.protocolVersion || notAvailable,
-        [L10N.getStr("netmonitor.security.cipherSuite")]:
-          securityInfo.cipherSuite || notAvailable,
-      },
-      [L10N.getFormatStr("netmonitor.security.hostHeader", getUrlHost(url))]: {
-        [L10N.getStr("netmonitor.security.hsts")]:
-          securityInfo.hsts ? enabledLabel : disabledLabel,
-        [L10N.getStr("netmonitor.security.hpkp")]:
-          securityInfo.hpkp ? enabledLabel : disabledLabel,
-      },
-      [L10N.getStr("netmonitor.security.certificate")]: {
-        [L10N.getStr("certmgr.subjectinfo.label")]: {
-          [L10N.getStr("certmgr.certdetail.cn")]:
-            subject.commonName || notAvailable,
-          [L10N.getStr("certmgr.certdetail.o")]:
-            subject.organization || notAvailable,
-          [L10N.getStr("certmgr.certdetail.ou")]:
-            subject.organizationUnit || notAvailable,
-        },
-        [L10N.getStr("certmgr.issuerinfo.label")]: {
-          [L10N.getStr("certmgr.certdetail.cn")]:
-            issuer.commonName || notAvailable,
-          [L10N.getStr("certmgr.certdetail.o")]:
-            issuer.organization || notAvailable,
-          [L10N.getStr("certmgr.certdetail.ou")]:
-            issuer.organizationUnit || notAvailable,
-        },
-        [L10N.getStr("certmgr.periodofvalidity.label")]: {
-          [L10N.getStr("certmgr.begins")]:
-            validity.start || notAvailable,
-          [L10N.getStr("certmgr.expires")]:
-            validity.end || notAvailable,
-        },
-        [L10N.getStr("certmgr.fingerprints.label")]: {
-          [L10N.getStr("certmgr.certdetail.sha256fingerprint")]:
-            fingerprint.sha256 || notAvailable,
-          [L10N.getStr("certmgr.certdetail.sha1fingerprint")]:
-            fingerprint.sha1 || notAvailable,
-        },
-      },
-    };
-  } else {
-    object = {
-      [L10N.getStr("netmonitor.security.error")]:
-        new DOMParser().parseFromString(securityInfo.errorMessage, "text/html")
-          .body.textContent || notAvailable
-    };
-  }
-
-  return div({ className: "panel-container security-panel" },
-    PropertiesView({
-      object,
-      renderValue: (props) => renderValue(props, securityInfo.weaknessReasons),
-      enableFilter: false,
-      expandedNodes: getExpandedNodes(object),
-    })
-  );
-}
-
-SecurityPanel.displayName = "SecurityPanel";
-
-SecurityPanel.propTypes = {
-  request: PropTypes.object.isRequired,
-};
-
-function renderValue(props, weaknessReasons = []) {
-  const { member, value } = props;
-
-  // Hide object summary
-  if (typeof member.value === "object") {
-    return null;
-  }
-
-  return span({ className: "security-info-value" },
-    member.name === L10N.getStr("netmonitor.security.error") ?
-      // Display multiline text for security error
-      value
-      :
-      // Display one line selectable text for security details
-      input({
-        className: "textbox-input",
-        readOnly: "true",
-        value,
-      })
-    ,
-    weaknessReasons.indexOf("cipher") !== -1 &&
-    member.name === L10N.getStr("netmonitor.security.cipherSuite") ?
-      // Display an extra warning icon after the cipher suite
-      div({
-        id: "security-warning-cipher",
-        className: "security-warning-icon",
-        title: L10N.getStr("netmonitor.security.warning.cipher"),
-      })
-      :
-      null
-  );
-}
-
-function getExpandedNodes(object, path = "", level = 0) {
-  if (typeof object !== "object") {
-    return null;
-  }
-
-  let expandedNodes = new Set();
-  for (let prop in object) {
-    let nodePath = path + "/" + prop;
-    expandedNodes.add(nodePath);
-
-    let nodes = getExpandedNodes(object[prop], nodePath, level + 1);
-    if (nodes) {
-      expandedNodes = new Set([...expandedNodes, ...nodes]);
-    }
-  }
-  return expandedNodes;
-}
-
-module.exports = SecurityPanel;
deleted file mode 100644
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ /dev/null
@@ -1,123 +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 {
-  createFactory,
-  PropTypes,
-} = require("devtools/client/shared/vendor/react");
-const { connect } = require("devtools/client/shared/vendor/react-redux");
-const Actions = require("../actions/index");
-const { Filters } = require("../utils/filter-predicates");
-const { L10N } = require("../utils/l10n");
-const { getSelectedRequest } = require("../selectors/index");
-
-// Components
-const Tabbar = createFactory(require("devtools/client/shared/components/tabs/tabbar"));
-const TabPanel = createFactory(require("devtools/client/shared/components/tabs/tabs").TabPanel);
-const CookiesPanel = createFactory(require("./CookiesPanel"));
-const HeadersPanel = createFactory(require("./HeadersPanel"));
-const ParamsPanel = createFactory(require("./ParamsPanel"));
-const PreviewPanel = createFactory(require("./PreviewPanel"));
-const ResponsePanel = createFactory(require("./ResponsePanel"));
-const SecurityPanel = createFactory(require("./SecurityPanel"));
-const TimingsPanel = createFactory(require("./TimingsPanel"));
-
-const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers");
-const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies");
-const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params");
-const RESPONSE_TITLE = L10N.getStr("netmonitor.tab.response");
-const TIMINGS_TITLE = L10N.getStr("netmonitor.tab.timings");
-const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
-const PREVIEW_TITLE = L10N.getStr("netmonitor.tab.preview");
-
-/*
- * Tabbox panel component
- * Display the network request details
- */
-function TabboxPanel({
-  activeTabId,
-  cloneSelectedRequest,
-  request,
-  selectTab,
-}) {
-  if (!request) {
-    return null;
-  }
-
-  return (
-    Tabbar({
-      activeTabId,
-      onSelect: selectTab,
-      renderOnlySelected: true,
-      showAllTabsMenu: true,
-    },
-      TabPanel({
-        id: "headers",
-        title: HEADERS_TITLE,
-      },
-        HeadersPanel({ request, cloneSelectedRequest }),
-      ),
-      TabPanel({
-        id: "cookies",
-        title: COOKIES_TITLE,
-      },
-        CookiesPanel({ request }),
-      ),
-      TabPanel({
-        id: "params",
-        title: PARAMS_TITLE,
-      },
-        ParamsPanel({ request }),
-      ),
-      TabPanel({
-        id: "response",
-        title: RESPONSE_TITLE,
-      },
-        ResponsePanel({ request }),
-      ),
-      TabPanel({
-        id: "timings",
-        title: TIMINGS_TITLE,
-      },
-        TimingsPanel({ request }),
-      ),
-      request.securityState && request.securityState !== "insecure" &&
-      TabPanel({
-        id: "security",
-        title: SECURITY_TITLE,
-      },
-        SecurityPanel({ request }),
-      ),
-      Filters.html(request) &&
-      TabPanel({
-        id: "preview",
-        title: PREVIEW_TITLE,
-      },
-        PreviewPanel({ request }),
-      ),
-    )
-  );
-}
-
-TabboxPanel.displayName = "TabboxPanel";
-
-TabboxPanel.propTypes = {
-  activeTabId: PropTypes.string,
-  cloneSelectedRequest: PropTypes.func.isRequired,
-  request: PropTypes.object,
-  selectTab: PropTypes.func.isRequired,
-};
-
-module.exports = connect(
-  (state) => ({
-    activeTabId: state.ui.detailsPanelSelectedTab,
-    request: getSelectedRequest(state),
-  }),
-  (dispatch) => ({
-    cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
-    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
-  }),
-)(TabboxPanel);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/app.js
@@ -0,0 +1,40 @@
+/* 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 MonitorPanel = createFactory(require("./monitor-panel"));
+const StatisticsPanel = createFactory(require("./statistics-panel"));
+
+const { div } = DOM;
+
+/*
+ * App component
+ * The top level component for representing main panel
+ */
+function App({ statisticsOpen }) {
+  return (
+    div({ className: "network-monitor" },
+      !statisticsOpen ? MonitorPanel() : StatisticsPanel()
+    )
+  );
+}
+
+App.displayName = "App";
+
+App.propTypes = {
+  statisticsOpen: PropTypes.bool.isRequired,
+};
+
+module.exports = connect(
+  (state) => ({ statisticsOpen: state.ui.statisticsOpen }),
+)(App);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/cookies-panel.js
@@ -0,0 +1,99 @@
+/* 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 { L10N } = require("../utils/l10n");
+
+// Component
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div } = DOM;
+
+const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
+const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
+const REQUEST_COOKIES = L10N.getStr("requestCookies");
+const RESPONSE_COOKIES = L10N.getStr("responseCookies");
+const SECTION_NAMES = [
+  RESPONSE_COOKIES,
+  REQUEST_COOKIES,
+];
+
+/*
+ * Cookies panel component
+ * This tab lists full details of any cookies sent with the request or response
+ */
+function CookiesPanel({
+  request,
+}) {
+  let {
+    requestCookies = { cookies: [] },
+    responseCookies = { cookies: [] },
+  } = request;
+
+  requestCookies = requestCookies.cookies || requestCookies;
+  responseCookies = responseCookies.cookies || responseCookies;
+
+  if (!requestCookies.length && !responseCookies.length) {
+    return div({ className: "empty-notice" },
+      COOKIES_EMPTY_TEXT
+    );
+  }
+
+  let object = {};
+
+  if (responseCookies.length) {
+    object[RESPONSE_COOKIES] = getProperties(responseCookies);
+  }
+
+  if (requestCookies.length) {
+    object[REQUEST_COOKIES] = getProperties(requestCookies);
+  }
+
+  return (
+    div({ className: "panel-container" },
+      PropertiesView({
+        object,
+        filterPlaceHolder: COOKIES_FILTER_TEXT,
+        sectionNames: SECTION_NAMES,
+      })
+    )
+  );
+}
+
+CookiesPanel.displayName = "CookiesPanel";
+
+CookiesPanel.propTypes = {
+  request: PropTypes.object.isRequired,
+};
+
+/**
+ * Mapping array to dict for TreeView usage.
+ * Since TreeView only support Object(dict) format.
+ *
+ * @param {Object[]} arr - key-value pair array like cookies or params
+ * @returns {Object}
+ */
+function getProperties(arr) {
+  return arr.reduce((map, obj) => {
+    // Generally cookies object contains only name and value properties and can
+    // be rendered as name: value pair.
+    // When there are more properties in cookies object such as extra or path,
+    // We will pass the object to display these extra information
+    if (Object.keys(obj).length > 2) {
+      map[obj.name] = Object.assign({}, obj);
+      delete map[obj.name].name;
+    } else {
+      map[obj.name] = obj.value;
+    }
+    return map;
+  }, {});
+}
+
+module.exports = CookiesPanel;
rename from devtools/client/netmonitor/src/components/CustomRequestPanel.js
rename to devtools/client/netmonitor/src/components/custom-request-panel.js
rename from devtools/client/netmonitor/src/components/Editor.js
rename to devtools/client/netmonitor/src/components/editor.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/headers-panel.js
@@ -0,0 +1,260 @@
+/* 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 {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { getFormattedSize } = require("../utils/format-utils");
+const { L10N } = require("../utils/l10n");
+const {
+  getHeadersURL,
+  getHTTPStatusCodeURL,
+} = require("../utils/mdn-utils");
+const { writeHeaderText } = require("../utils/request-utils");
+
+// Components
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const MDNLink = createFactory(require("./mdn-link"));
+const PropertiesView = createFactory(require("./properties-view"));
+
+const Rep = createFactory(REPS.Rep);
+const { button, div, input, textarea } = DOM;
+
+const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend");
+const RAW_HEADERS = L10N.getStr("netmonitor.summary.rawHeaders");
+const RAW_HEADERS_REQUEST = L10N.getStr("netmonitor.summary.rawHeaders.requestHeaders");
+const RAW_HEADERS_RESPONSE = L10N.getStr("netmonitor.summary.rawHeaders.responseHeaders");
+const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText");
+const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText");
+const REQUEST_HEADERS = L10N.getStr("requestHeaders");
+const REQUEST_HEADERS_FROM_UPLOAD = L10N.getStr("requestHeadersFromUpload");
+const RESPONSE_HEADERS = L10N.getStr("responseHeaders");
+const SUMMARY_ADDRESS = L10N.getStr("netmonitor.summary.address");
+const SUMMARY_METHOD = L10N.getStr("netmonitor.summary.method");
+const SUMMARY_URL = L10N.getStr("netmonitor.summary.url");
+const SUMMARY_STATUS = L10N.getStr("netmonitor.summary.status");
+const SUMMARY_VERSION = L10N.getStr("netmonitor.summary.version");
+
+/*
+ * Headers panel component
+ * Lists basic information about the request
+ */
+const HeadersPanel = createClass({
+  displayName: "HeadersPanel",
+
+  propTypes: {
+    cloneSelectedRequest: PropTypes.func.isRequired,
+    request: PropTypes.object.isRequired,
+    renderValue: PropTypes.func
+  },
+
+  getInitialState() {
+    return {
+      rawHeadersOpened: false,
+    };
+  },
+
+  getProperties(headers, title) {
+    if (headers && headers.headers.length) {
+      return {
+        [`${title} (${getFormattedSize(headers.headersSize, 3)})`]:
+          headers.headers.reduce((acc, { name, value }) =>
+            name ? Object.assign(acc, { [name]: value }) : acc
+          , {})
+      };
+    }
+
+    return null;
+  },
+
+  toggleRawHeaders() {
+    this.setState({
+      rawHeadersOpened: !this.state.rawHeadersOpened,
+    });
+  },
+
+  renderSummary(label, value) {
+    return (
+      div({ className: "tabpanel-summary-container headers-summary" },
+        div({
+          className: "tabpanel-summary-label headers-summary-label",
+        }, label),
+        input({
+          className: "tabpanel-summary-value textbox-input devtools-monospace",
+          readOnly: true,
+          value,
+        }),
+      )
+    );
+  },
+
+  renderValue(props) {
+    const member = props.member;
+    const value = props.value;
+
+    if (typeof value !== "string") {
+      return null;
+    }
+
+    let headerDocURL = getHeadersURL(member.name);
+
+    return (
+      div({ className: "treeValueCellDivider" },
+        Rep(Object.assign(props, {
+          // FIXME: A workaround for the issue in StringRep
+          // Force StringRep to crop the text everytime
+          member: Object.assign({}, member, { open: false }),
+          mode: MODE.TINY,
+          cropLimit: 60,
+        })),
+        headerDocURL ? MDNLink({
+          url: headerDocURL,
+        }) : null
+      )
+    );
+  },
+
+  render() {
+    const {
+      cloneSelectedRequest,
+      request: {
+        fromCache,
+        fromServiceWorker,
+        httpVersion,
+        method,
+        remoteAddress,
+        remotePort,
+        requestHeaders,
+        requestHeadersFromUploadStream: uploadHeaders,
+        responseHeaders,
+        status,
+        statusText,
+        urlDetails,
+      },
+    } = this.props;
+
+    if ((!requestHeaders || !requestHeaders.headers.length) &&
+        (!uploadHeaders || !uploadHeaders.headers.length) &&
+        (!responseHeaders || !responseHeaders.headers.length)) {
+      return div({ className: "empty-notice" },
+        HEADERS_EMPTY_TEXT
+      );
+    }
+
+    let object = Object.assign({},
+      this.getProperties(responseHeaders, RESPONSE_HEADERS),
+      this.getProperties(requestHeaders, REQUEST_HEADERS),
+      this.getProperties(uploadHeaders, REQUEST_HEADERS_FROM_UPLOAD),
+    );
+
+    let summaryUrl = urlDetails.unicodeUrl ?
+      this.renderSummary(SUMMARY_URL, urlDetails.unicodeUrl) : null;
+
+    let summaryMethod = method ?
+      this.renderSummary(SUMMARY_METHOD, method) : null;
+
+    let summaryAddress = remoteAddress ?
+      this.renderSummary(SUMMARY_ADDRESS,
+        remotePort ? `${remoteAddress}:${remotePort}` : remoteAddress) : null;
+
+    let summaryStatus;
+
+    if (status) {
+      let code;
+      if (fromCache) {
+        code = "cached";
+      } else if (fromServiceWorker) {
+        code = "service worker";
+      } else {
+        code = status;
+      }
+
+      let statusCodeDocURL = getHTTPStatusCodeURL(code);
+      let inputWidth = status.length + statusText.length + 1;
+
+      summaryStatus = (
+        div({ className: "tabpanel-summary-container headers-summary" },
+          div({
+            className: "tabpanel-summary-label headers-summary-label",
+          }, SUMMARY_STATUS),
+          div({
+            className: "requests-list-status-icon",
+            "data-code": code,
+          }),
+          input({
+            className: "tabpanel-summary-value textbox-input devtools-monospace"
+              + " status-text",
+            readOnly: true,
+            value: `${status} ${statusText}`,
+            size: `${inputWidth}`,
+          }),
+          statusCodeDocURL ? MDNLink({
+            url: statusCodeDocURL,
+          }) : null,
+          window.NetMonitorController.supportsCustomRequest && button({
+            className: "devtools-button",
+            onClick: cloneSelectedRequest,
+          }, EDIT_AND_RESEND),
+          button({
+            className: "devtools-button",
+            onClick: this.toggleRawHeaders,
+          }, RAW_HEADERS),
+        )
+      );
+    }
+
+    let summaryVersion = httpVersion ?
+      this.renderSummary(SUMMARY_VERSION, httpVersion) : null;
+
+    let summaryRawHeaders;
+    if (this.state.rawHeadersOpened) {
+      summaryRawHeaders = (
+        div({ className: "tabpanel-summary-container headers-summary" },
+          div({ className: "raw-headers-container" },
+            div({ className: "raw-headers" },
+              div({ className: "tabpanel-summary-label" }, RAW_HEADERS_REQUEST),
+              textarea({
+                value: writeHeaderText(requestHeaders.headers),
+                readOnly: true,
+              }),
+            ),
+            div({ className: "raw-headers" },
+              div({ className: "tabpanel-summary-label" }, RAW_HEADERS_RESPONSE),
+              textarea({
+                value: writeHeaderText(responseHeaders.headers),
+                readOnly: true,
+              }),
+            ),
+          )
+        )
+      );
+    }
+
+    return (
+      div({ className: "panel-container" },
+        div({ className: "headers-overview" },
+          summaryUrl,
+          summaryMethod,
+          summaryAddress,
+          summaryStatus,
+          summaryVersion,
+          summaryRawHeaders,
+        ),
+        PropertiesView({
+          object,
+          filterPlaceHolder: HEADERS_FILTER_TEXT,
+          sectionNames: Object.keys(object),
+          renderValue: this.renderValue,
+        }),
+      )
+    );
+  }
+});
+
+module.exports = HeadersPanel;
rename from devtools/client/netmonitor/src/components/MDNLink.js
rename to devtools/client/netmonitor/src/components/mdn-link.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/monitor-panel.js
@@ -0,0 +1,135 @@
+/* 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 {
+  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 { Prefs } = require("../utils/prefs");
+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"));
+
+const { div } = DOM;
+const MediaQueryList = window.matchMedia("(min-width: 700px)");
+
+/*
+ * Monitor panel component
+ * The main panel for displaying various network request information
+ */
+const MonitorPanel = createClass({
+  displayName: "MonitorPanel",
+
+  propTypes: {
+    isEmpty: PropTypes.bool.isRequired,
+    networkDetailsOpen: PropTypes.bool.isRequired,
+    openNetworkDetails: PropTypes.func.isRequired,
+    request: PropTypes.object,
+    updateRequest: PropTypes.func.isRequired,
+  },
+
+  getInitialState() {
+    return {
+      isVerticalSpliter: MediaQueryList.matches,
+    };
+  },
+
+  componentDidMount() {
+    MediaQueryList.addListener(this.onLayoutChange);
+  },
+
+  componentWillReceiveProps(nextProps) {
+    let {
+      request = {},
+      updateRequest,
+    } = nextProps;
+    let {
+      formDataSections,
+      requestHeaders,
+      requestHeadersFromUploadStream,
+      requestPostData,
+    } = request;
+
+    if (!formDataSections && requestHeaders &&
+        requestHeadersFromUploadStream && requestPostData) {
+      getFormDataSections(
+        requestHeaders,
+        requestHeadersFromUploadStream,
+        requestPostData,
+        getLongString,
+      ).then((newFormDataSections) => {
+        updateRequest(
+          request.id,
+          { formDataSections: newFormDataSections },
+          true,
+        );
+      });
+    }
+  },
+
+  componentWillUnmount() {
+    MediaQueryList.removeListener(this.onLayoutChange);
+
+    let { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};
+
+    if (this.state.isVerticalSpliter && clientWidth) {
+      Prefs.networkDetailsWidth = clientWidth;
+    }
+    if (!this.state.isVerticalSpliter && clientHeight) {
+      Prefs.networkDetailsHeight = clientHeight;
+    }
+  },
+
+  onLayoutChange() {
+    this.setState({
+      isVerticalSpliter: MediaQueryList.matches,
+    });
+  },
+
+  render() {
+    let { isEmpty, networkDetailsOpen } = this.props;
+    return (
+      div({ className: "monitor-panel" },
+        Toolbar(),
+        SplitBox({
+          className: "devtools-responsive-container",
+          initialWidth: `${Prefs.networkDetailsWidth}px`,
+          initialHeight: `${Prefs.networkDetailsHeight}px`,
+          minSize: "50px",
+          maxSize: "80%",
+          splitterSize: "1px",
+          startPanel: RequestList({ isEmpty }),
+          endPanel: networkDetailsOpen && NetworkDetailsPanel({ ref: "endPanel" }),
+          endPanelControl: true,
+          vert: this.state.isVerticalSpliter,
+        }),
+      )
+    );
+  }
+});
+
+module.exports = connect(
+  (state) => ({
+    isEmpty: state.requests.requests.isEmpty(),
+    networkDetailsOpen: state.ui.networkDetailsOpen,
+    request: getSelectedRequest(state),
+  }),
+  (dispatch) => ({
+    openNetworkDetails: (open) => dispatch(Actions.openNetworkDetails(open)),
+    updateRequest: (id, data, batch) => dispatch(Actions.updateRequest(id, data, batch)),
+  }),
+)(MonitorPanel);
--- a/devtools/client/netmonitor/src/components/moz.build
+++ b/devtools/client/netmonitor/src/components/moz.build
@@ -1,28 +1,28 @@
 # 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(
-    'App.js',
-    'CookiesPanel.js',
-    'CustomRequestPanel.js',
-    'Editor.js',
-    'HeadersPanel.js',
-    'MDNLink.js',
-    'MonitorPanel.js',
-    'NetworkDetailsPanel.js',
-    'ParamsPanel.js',
-    'PreviewPanel.js',
-    'PropertiesView.js',
-    'RequestList.js',
-    'RequestListContent.js',
-    'RequestListEmptyNotice.js',
-    'RequestListHeader.js',
-    'RequestListItem.js',
-    'ResponsePanel.js',
-    'SecurityPanel.js',
-    'StatisticsPanel.js',
-    'TabboxPanel.js',
-    'TimingsPanel.js',
-    'Toolbar.js',
+    'app.js',
+    'cookies-panel.js',
+    'custom-request-panel.js',
+    'editor.js',
+    'headers-panel.js',
+    'mdn-link.js',
+    'monitor-panel.js',
+    'network-details-panel.js',
+    'params-panel.js',
+    'preview-panel.js',
+    'properties-view.js',
+    'request-list-content.js',
+    'request-list-empty-notice.js',
+    'request-list-header.js',
+    'request-list-item.js',
+    'request-list.js',
+    'response-panel.js',
+    'security-panel.js',
+    'statistics-panel.js',
+    'tabbox-panel.js',
+    'timings-panel.js',
+    'toolbar.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/network-details-panel.js
@@ -0,0 +1,70 @@
+/* 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");
+const Actions = require("../actions/index");
+const { getSelectedRequest } = require("../selectors/index");
+
+// Components
+const CustomRequestPanel = createFactory(require("./custom-request-panel"));
+const TabboxPanel = createFactory(require("./tabbox-panel"));
+
+const { div } = DOM;
+
+/*
+ * Network details panel component
+ */
+function NetworkDetailsPanel({
+  activeTabId,
+  cloneSelectedRequest,
+  request,
+  selectTab,
+}) {
+  if (!request) {
+    return null;
+  }
+
+  return (
+    div({ className: "network-details-panel" },
+      !request.isCustom ?
+        TabboxPanel({
+          activeTabId,
+          request,
+          selectTab,
+        }) :
+        CustomRequestPanel({
+          cloneSelectedRequest,
+          request,
+        })
+    )
+  );
+}
+
+NetworkDetailsPanel.displayName = "NetworkDetailsPanel";
+
+NetworkDetailsPanel.propTypes = {
+  activeTabId: PropTypes.string,
+  cloneSelectedRequest: PropTypes.func.isRequired,
+  open: PropTypes.bool,
+  request: PropTypes.object,
+  selectTab: PropTypes.func.isRequired,
+};
+
+module.exports = connect(
+  (state) => ({
+    activeTabId: state.ui.detailsPanelSelectedTab,
+    request: getSelectedRequest(state),
+  }),
+  (dispatch) => ({
+    cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
+  }),
+)(NetworkDetailsPanel);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/params-panel.js
@@ -0,0 +1,130 @@
+/* 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 { L10N } = require("../utils/l10n");
+const { getUrlQuery, parseQueryString } = require("../utils/request-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div } = DOM;
+
+const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
+const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
+const PARAMS_FILTER_TEXT = L10N.getStr("paramsFilterText");
+const PARAMS_FORM_DATA = L10N.getStr("paramsFormData");
+const PARAMS_POST_PAYLOAD = L10N.getStr("paramsPostPayload");
+const PARAMS_QUERY_STRING = L10N.getStr("paramsQueryString");
+const SECTION_NAMES = [
+  JSON_SCOPE_NAME,
+  PARAMS_FORM_DATA,
+  PARAMS_POST_PAYLOAD,
+  PARAMS_QUERY_STRING,
+];
+
+/*
+ * Params panel component
+ * Displays the GET parameters and POST data of a request
+ */
+function ParamsPanel({ request }) {
+  let {
+    formDataSections,
+    mimeType,
+    requestPostData,
+    url,
+  } = request;
+  let postData = requestPostData ? requestPostData.postData.text : null;
+  let query = getUrlQuery(url);
+
+  if (!formDataSections && !postData && !query) {
+    return div({ className: "empty-notice" },
+      PARAMS_EMPTY_TEXT
+    );
+  }
+
+  let object = {};
+  let json;
+
+  // Query String section
+  if (query) {
+    object[PARAMS_QUERY_STRING] = getProperties(parseQueryString(query));
+  }
+
+  // Form Data section
+  if (formDataSections && formDataSections.length > 0) {
+    let sections = formDataSections.filter((str) => /\S/.test(str)).join("&");
+    object[PARAMS_FORM_DATA] = getProperties(parseQueryString(sections));
+  }
+
+  // Request payload section
+  if (formDataSections && formDataSections.length === 0 && postData) {
+    try {
+      json = JSON.parse(postData);
+    } catch (error) {
+      // Continue regardless of parsing error
+    }
+
+    if (json) {
+      object[JSON_SCOPE_NAME] = json;
+    } else {
+      object[PARAMS_POST_PAYLOAD] = {
+        EDITOR_CONFIG: {
+          text: postData,
+          mode: mimeType.replace(/;.+/, ""),
+        },
+      };
+    }
+  } else {
+    postData = "";
+  }
+
+  return (
+    div({ className: "panel-container" },
+      PropertiesView({
+        object,
+        filterPlaceHolder: PARAMS_FILTER_TEXT,
+        sectionNames: SECTION_NAMES,
+      })
+    )
+  );
+}
+
+ParamsPanel.displayName = "ParamsPanel";
+
+ParamsPanel.propTypes = {
+  request: PropTypes.object.isRequired,
+};
+
+/**
+ * Mapping array to dict for TreeView usage.
+ * Since TreeView only support Object(dict) format.
+ * This function also deal with duplicate key case
+ * (for multiple selection and query params with same keys)
+ *
+ * @param {Object[]} arr - key-value pair array like query or form params
+ * @returns {Object} Rep compatible object
+ */
+function getProperties(arr) {
+  return arr.reduce((map, obj) => {
+    let value = map[obj.name];
+    if (value) {
+      if (typeof value !== "object") {
+        map[obj.name] = [value];
+      }
+      map[obj.name].push(obj.value);
+    } else {
+      map[obj.name] = obj.value;
+    }
+    return map;
+  }, {});
+}
+
+module.exports = ParamsPanel;
rename from devtools/client/netmonitor/src/components/PreviewPanel.js
rename to devtools/client/netmonitor/src/components/preview-panel.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/properties-view.js
@@ -0,0 +1,218 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-disable react/prop-types */
+
+"use strict";
+
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
+const Rep = createFactory(REPS.Rep);
+
+const { FILTER_SEARCH_DELAY } = require("../constants");
+
+// Components
+const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
+const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const TreeRow = createFactory(require("devtools/client/shared/components/tree/tree-row"));
+const Editor = createFactory(require("./editor"));
+
+const { div, tr, td } = DOM;
+const AUTO_EXPAND_MAX_LEVEL = 7;
+const AUTO_EXPAND_MAX_NODES = 50;
+const EDITOR_CONFIG_ID = "EDITOR_CONFIG";
+
+/*
+ * Properties View component
+ * A scrollable tree view component which provides some useful features for
+ * representing object properties.
+ *
+ * Search filter - Set enableFilter to enable / disable SearchBox feature.
+ * Tree view - Default enabled.
+ * Source editor - Enable by specifying object level 1 property name to EDITOR_CONFIG_ID.
+ * Rep - Default enabled.
+ */
+const PropertiesView = createClass({
+  displayName: "PropertiesView",
+
+  propTypes: {
+    object: PropTypes.object,
+    enableInput: PropTypes.bool,
+    expandableStrings: PropTypes.bool,
+    filterPlaceHolder: PropTypes.string,
+    sectionNames: PropTypes.array,
+  },
+
+  getDefaultProps() {
+    return {
+      enableInput: true,
+      enableFilter: true,
+      expandableStrings: false,
+      filterPlaceHolder: "",
+      sectionNames: [],
+    };
+  },
+
+  getInitialState() {
+    return {
+      filterText: "",
+    };
+  },
+
+  getRowClass(object, sectionNames) {
+    return sectionNames.includes(object.name) ? "tree-section" : "";
+  },
+
+  onFilter(object, whiteList) {
+    let { name, value } = object;
+    let filterText = this.state.filterText;
+
+    if (!filterText || whiteList.includes(name)) {
+      return true;
+    }
+
+    let jsonString = JSON.stringify({ [name]: value }).toLowerCase();
+    return jsonString.includes(filterText.toLowerCase());
+  },
+
+  renderRowWithEditor(props) {
+    const { level, name, value, path } = props.member;
+
+    // Display source editor when specifying to EDITOR_CONFIG_ID along with config
+    if (level === 1 && name === EDITOR_CONFIG_ID) {
+      return (
+        tr({ className: "editor-row-container" },
+          td({ colSpan: 2 },
+            Editor(value)
+          )
+        )
+      );
+    }
+
+    // Skip for editor config
+    if (level >= 1 && path.includes(EDITOR_CONFIG_ID)) {
+      return null;
+    }
+
+    return TreeRow(props);
+  },
+
+  renderValueWithRep(props) {
+    const { member } = props;
+
+    // Hide strings with following conditions
+    // 1. this row is a togglable section and content is object ('cause it shouldn't hide
+    //    when string or number)
+    // 2. the `value` object has a `value` property, only happened in Cookies panel
+    // Put 2 here to not dup this method
+    if (member.level === 0 && member.type === "object" ||
+      (typeof member.value === "object" && member.value && member.value.value)) {
+      return null;
+    }
+
+    return Rep(Object.assign(props, {
+      // FIXME: A workaround for the issue in StringRep
+      // Force StringRep to crop the text everytime
+      member: Object.assign({}, member, { open: false }),
+      mode: MODE.TINY,
+      cropLimit: 60,
+    }));
+  },
+
+  shouldRenderSearchBox(object) {
+    return this.props.enableFilter && object && Object.keys(object)
+      .filter((section) => !object[section][EDITOR_CONFIG_ID]).length > 0;
+  },
+
+  updateFilterText(filterText) {
+    this.setState({
+      filterText,
+    });
+  },
+
+  getExpandedNodes: function (object, path = "", level = 0) {
+    if (typeof object != "object") {
+      return null;
+    }
+
+    if (level > AUTO_EXPAND_MAX_LEVEL) {
+      return null;
+    }
+
+    let expandedNodes = new Set();
+    for (let prop in object) {
+      if (expandedNodes.size > AUTO_EXPAND_MAX_NODES) {
+        // If we reached the limit of expandable nodes, bail out to avoid performance
+        // issues.
+        break;
+      }
+
+      let nodePath = path + "/" + prop;
+      expandedNodes.add(nodePath);
+
+      let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
+      if (nodes) {
+        let newSize = expandedNodes.size + nodes.size;
+        if (newSize < AUTO_EXPAND_MAX_NODES) {
+          // Avoid having a subtree half expanded.
+          expandedNodes = new Set([...expandedNodes, ...nodes]);
+        }
+      }
+    }
+    return expandedNodes;
+  },
+
+  render() {
+    const {
+      decorator,
+      enableInput,
+      expandableStrings,
+      filterPlaceHolder,
+      object,
+      renderRow,
+      renderValue,
+      sectionNames,
+    } = this.props;
+
+    return (
+      div({ className: "properties-view" },
+        this.shouldRenderSearchBox(object) &&
+          div({ className: "searchbox-section" },
+            SearchBox({
+              delay: FILTER_SEARCH_DELAY,
+              type: "filter",
+              onChange: this.updateFilterText,
+              placeholder: filterPlaceHolder,
+            }),
+          ),
+        div({ className: "tree-container" },
+          TreeView({
+            object,
+            columns: [{
+              id: "value",
+              width: "100%",
+            }],
+            decorator: decorator || {
+              getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames),
+            },
+            enableInput,
+            expandableStrings,
+            expandedNodes: this.getExpandedNodes(object),
+            onFilter: (props) => this.onFilter(props, sectionNames),
+            renderRow: renderRow || this.renderRowWithEditor,
+            renderValue: renderValue || this.renderValueWithRep,
+          }),
+        ),
+      )
+    );
+  }
+});
+
+module.exports = PropertiesView;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/request-list-content.js
@@ -0,0 +1,280 @@
+/* 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 { KeyCodes } = require("devtools/client/shared/keycodes");
+const {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+const Actions = require("../actions/index");
+const {
+  setTooltipImageContent,
+  setTooltipStackTraceContent,
+} = require("../request-list-tooltip");
+const {
+  getDisplayedRequests,
+  getWaterfallScale,
+} = require("../selectors/index");
+
+// Components
+const RequestListItem = createFactory(require("./request-list-item"));
+const RequestListContextMenu = require("../request-list-context-menu");
+
+const { div } = DOM;
+
+// tooltip show/hide delay in ms
+const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
+
+/**
+ * Renders the actual contents of the request list.
+ */
+const RequestListContent = createClass({
+  displayName: "RequestListContent",
+
+  propTypes: {
+    dispatch: PropTypes.func.isRequired,
+    displayedRequests: PropTypes.object.isRequired,
+    firstRequestStartedMillis: PropTypes.number.isRequired,
+    fromCache: PropTypes.bool.isRequired,
+    onItemMouseDown: PropTypes.func.isRequired,
+    onSecurityIconClick: PropTypes.func.isRequired,
+    onSelectDelta: PropTypes.func.isRequired,
+    scale: PropTypes.number,
+    selectedRequestId: PropTypes.string,
+  },
+
+  componentWillMount() {
+    const { dispatch } = this.props;
+    this.contextMenu = new RequestListContextMenu({
+      cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+      openStatistics: (open) => dispatch(Actions.openStatistics(open)),
+    });
+    this.tooltip = new HTMLTooltip(window.parent.document, { 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, {
+      toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
+      interactive: true
+    });
+
+    // Install event handler to hide the tooltip on scroll
+    this.refs.contentEl.addEventListener("scroll", this.onScroll, true);
+  },
+
+  componentWillUpdate(nextProps) {
+    // Check if the list is scrolled to bottom before the UI update.
+    // The scroll is ever needed only if new rows are added to the list.
+    const delta = nextProps.displayedRequests.size - this.props.displayedRequests.size;
+    this.shouldScrollBottom = delta > 0 && this.isScrolledToBottom();
+  },
+
+  componentDidUpdate(prevProps) {
+    // Update the CSS variables for waterfall scaling after props change
+    this.setScalingStyles(prevProps);
+
+    // Keep the list scrolled to bottom if a new row was added
+    if (this.shouldScrollBottom) {
+      let node = this.refs.contentEl;
+      node.scrollTop = node.scrollHeight;
+    }
+  },
+
+  componentWillUnmount() {
+    this.refs.contentEl.removeEventListener("scroll", this.onScroll, true);
+
+    // Uninstall the tooltip event handler
+    this.tooltip.stopTogglingOnHover();
+  },
+
+  /**
+   * Set the CSS variables for waterfall scaling. If React supported setting CSS
+   * variables as part of the "style" property of a DOM element, we would use that.
+   *
+   * However, React doesn't support this, so we need to use a hack and update the
+   * DOM element directly: https://github.com/facebook/react/issues/6411
+   */
+  setScalingStyles(prevProps) {
+    const { scale } = this.props;
+    if (prevProps && prevProps.scale === scale) {
+      return;
+    }
+
+    const { style } = this.refs.contentEl;
+    style.removeProperty("--timings-scale");
+    style.removeProperty("--timings-rev-scale");
+    style.setProperty("--timings-scale", scale);
+    style.setProperty("--timings-rev-scale", 1 / scale);
+  },
+
+  isScrolledToBottom() {
+    const { contentEl } = this.refs;
+    const lastChildEl = contentEl.lastElementChild;
+
+    if (!lastChildEl) {
+      return false;
+    }
+
+    let lastChildRect = lastChildEl.getBoundingClientRect();
+    let contentRect = contentEl.getBoundingClientRect();
+
+    return (lastChildRect.height + lastChildRect.top) <= contentRect.bottom;
+  },
+
+  /**
+   * The predicate used when deciding whether a popup should be shown
+   * over a request item or not.
+   *
+   * @param nsIDOMNode target
+   *        The element node currently being hovered.
+   * @param object tooltip
+   *        The current tooltip instance.
+   * @return {Promise}
+   */
+  onHover(target, tooltip) {
+    let itemEl = target.closest(".request-list-item");
+    if (!itemEl) {
+      return false;
+    }
+    let itemId = itemEl.dataset.id;
+    if (!itemId) {
+      return false;
+    }
+    let requestItem = this.props.displayedRequests.find(r => r.id == itemId);
+    if (!requestItem) {
+      return false;
+    }
+
+    if (requestItem.responseContent && target.closest(".requests-list-icon-and-file")) {
+      return setTooltipImageContent(tooltip, itemEl, requestItem);
+    } else if (requestItem.cause && target.closest(".requests-list-cause-stack")) {
+      return setTooltipStackTraceContent(tooltip, requestItem);
+    }
+
+    return false;
+  },
+
+  /**
+   * Scroll listener for the requests menu view.
+   */
+  onScroll() {
+    this.tooltip.hide();
+  },
+
+  /**
+   * Handler for keyboard events. For arrow up/down, page up/down, home/end,
+   * move the selection up or down.
+   */
+  onKeyDown(e) {
+    let delta;
+
+    switch (e.keyCode) {
+      case KeyCodes.DOM_VK_UP:
+      case KeyCodes.DOM_VK_LEFT:
+        delta = -1;
+        break;
+      case KeyCodes.DOM_VK_DOWN:
+      case KeyCodes.DOM_VK_RIGHT:
+        delta = +1;
+        break;
+      case KeyCodes.DOM_VK_PAGE_UP:
+        delta = "PAGE_UP";
+        break;
+      case KeyCodes.DOM_VK_PAGE_DOWN:
+        delta = "PAGE_DOWN";
+        break;
+      case KeyCodes.DOM_VK_HOME:
+        delta = -Infinity;
+        break;
+      case KeyCodes.DOM_VK_END:
+        delta = +Infinity;
+        break;
+    }
+
+    if (delta) {
+      // Prevent scrolling when pressing navigation keys.
+      e.preventDefault();
+      e.stopPropagation();
+      this.props.onSelectDelta(delta);
+    }
+  },
+
+  onContextMenu(evt) {
+    evt.preventDefault();
+    this.contextMenu.open(evt);
+  },
+
+  /**
+   * If selection has just changed (by keyboard navigation), don't keep the list
+   * scrolled to bottom, but allow scrolling up with the selection.
+   */
+  onFocusedNodeChange() {
+    this.shouldScrollBottom = false;
+  },
+
+  render() {
+    const {
+      displayedRequests,
+      firstRequestStartedMillis,
+      selectedRequestId,
+      onItemMouseDown,
+      onSecurityIconClick,
+    } = this.props;
+
+    return (
+      div({
+        ref: "contentEl",
+        className: "requests-list-contents",
+        tabIndex: 0,
+        onKeyDown: this.onKeyDown,
+      },
+        displayedRequests.map((item, index) => RequestListItem({
+          firstRequestStartedMillis,
+          fromCache: item.status === "304" || item.fromCache,
+          item,
+          index,
+          isSelected: item.id === selectedRequestId,
+          key: item.id,
+          onContextMenu: this.onContextMenu,
+          onFocusedNodeChange: this.onFocusedNodeChange,
+          onMouseDown: () => onItemMouseDown(item.id),
+          onSecurityIconClick: () => onSecurityIconClick(item.securityState),
+        }))
+      )
+    );
+  },
+});
+
+module.exports = connect(
+  (state) => ({
+    displayedRequests: getDisplayedRequests(state),
+    firstRequestStartedMillis: state.requests.firstStartedMillis,
+    selectedRequestId: state.requests.selectedId,
+    scale: getWaterfallScale(state),
+  }),
+  (dispatch) => ({
+    dispatch,
+    onItemMouseDown: (id) => dispatch(Actions.selectRequest(id)),
+    /**
+     * A handler that opens the security tab in the details view if secure or
+     * broken security indicator is clicked.
+     */
+    onSecurityIconClick: (securityState) => {
+      if (securityState && securityState !== "insecure") {
+        dispatch(Actions.selectDetailsPanelTab("security"));
+      }
+    },
+    onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
+  }),
+)(RequestListContent);
rename from devtools/client/netmonitor/src/components/RequestListEmptyNotice.js
rename to devtools/client/netmonitor/src/components/request-list-empty-notice.js
rename from devtools/client/netmonitor/src/components/RequestListHeader.js
rename to devtools/client/netmonitor/src/components/request-list-header.js
rename from devtools/client/netmonitor/src/components/RequestListItem.js
rename to devtools/client/netmonitor/src/components/request-list-item.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/request-list.js
@@ -0,0 +1,38 @@
+/* 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");
+
+// Components
+const RequestListContent = createFactory(require("./request-list-content"));
+const RequestListEmptyNotice = createFactory(require("./request-list-empty-notice"));
+const RequestListHeader = createFactory(require("./request-list-header"));
+
+const { div } = DOM;
+
+/**
+ * Request panel component
+ */
+function RequestList({ isEmpty }) {
+  return (
+    div({ className: "request-list-container" },
+      RequestListHeader(),
+      isEmpty ? RequestListEmptyNotice() : RequestListContent(),
+    )
+  );
+}
+
+RequestList.displayName = "RequestList";
+
+RequestList.propTypes = {
+  isEmpty: PropTypes.bool.isRequired,
+};
+
+module.exports = RequestList;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/response-panel.js
@@ -0,0 +1,185 @@
+/* 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 {
+  createClass,
+  createFactory,
+  DOM,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { L10N } = require("../utils/l10n");
+const { formDataURI, getUrlBaseName } = require("../utils/request-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div, img } = DOM;
+const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
+const JSON_FILTER_TEXT = L10N.getStr("jsonFilterText");
+const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name");
+const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions");
+const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime");
+const RESPONSE_PAYLOAD = L10N.getStr("responsePayload");
+
+/*
+ * Response panel component
+ * Displays the GET parameters and POST data of a request
+ */
+const ResponsePanel = createClass({
+  displayName: "ResponsePanel",
+
+  propTypes: {
+    request: PropTypes.object.isRequired,
+  },
+
+  getInitialState() {
+    return {
+      imageDimensions: {
+        width: 0,
+        height: 0,
+      },
+    };
+  },
+
+  updateImageDimemsions({ target }) {
+    this.setState({
+      imageDimensions: {
+        width: target.naturalWidth,
+        height: target.naturalHeight,
+      },
+    });
+  },
+
+  // Handle json, which we tentatively identify by checking the MIME type
+  // for "json" after any word boundary. This works for the standard
+  // "application/json", and also for custom types like "x-bigcorp-json".
+  // Additionally, we also directly parse the response text content to
+  // verify whether it's json or not, to handle responses incorrectly
+  // labeled as text/plain instead.
+  isJSON(mimeType, response) {
+    let json, error;
+    try {
+      json = JSON.parse(response);
+    } catch (err) {
+      try {
+        json = JSON.parse(atob(response));
+      } catch (err64) {
+        error = err;
+      }
+    }
+
+    if (/\bjson/.test(mimeType) || json) {
+      // Extract the actual json substring in case this might be a "JSONP".
+      // This regex basically parses a function call and captures the
+      // function name and arguments in two separate groups.
+      let jsonpRegex = /^\s*([\w$]+)\s*\(\s*([^]*)\s*\)\s*;?\s*$/;
+      let [, jsonpCallback, jsonp] = response.match(jsonpRegex) || [];
+      let result = {};
+
+      // Make sure this is a valid JSON object first. If so, nicely display
+      // the parsing results in a tree view.
+      if (jsonpCallback && jsonp) {
+        error = null;
+        try {
+          json = JSON.parse(jsonp);
+        } catch (err) {
+          error = err;
+        }
+      }
+
+      // Valid JSON
+      if (json) {
+        result.json = json;
+      }
+      // Valid JSONP
+      if (jsonpCallback) {
+        result.jsonpCallback = jsonpCallback;
+      }
+      // Malformed JSON
+      if (error) {
+        result.error = "" + error;
+      }
+
+      return result;
+    }
+
+    return null;
+  },
+
+  render() {
+    let { responseContent, url } = this.props.request;
+
+    if (!responseContent || typeof responseContent.content.text !== "string") {
+      return null;
+    }
+
+    let { encoding, mimeType, text } = responseContent.content;
+
+    if (mimeType.includes("image/")) {
+      let { width, height } = this.state.imageDimensions;
+
+      return (
+        div({ className: "panel-container response-image-box devtools-monospace" },
+          img({
+            className: "response-image",
+            src: formDataURI(mimeType, encoding, text),
+            onLoad: this.updateImageDimemsions,
+          }),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME),
+            div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)),
+          ),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS),
+            div({ className: "tabpanel-summary-value" }, `${width} × ${height}`),
+          ),
+          div({ className: "response-summary" },
+            div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_MIMETYPE),
+            div({ className: "tabpanel-summary-value" }, mimeType),
+          ),
+        )
+      );
+    }
+
+    // Display Properties View
+    let { json, jsonpCallback, error } = this.isJSON(mimeType, text) || {};
+    let object = {};
+    let sectionName;
+
+    if (json) {
+      if (jsonpCallback) {
+        sectionName = L10N.getFormatStr("jsonpScopeName", jsonpCallback);
+      } else {
+        sectionName = JSON_SCOPE_NAME;
+      }
+      object[sectionName] = json;
+    } else {
+      sectionName = RESPONSE_PAYLOAD;
+
+      object[sectionName] = {
+        EDITOR_CONFIG: {
+          text,
+          mode: mimeType.replace(/;.+/, ""),
+        },
+      };
+    }
+
+    return (
+      div({ className: "panel-container" },
+        error && div({ className: "response-error-header", title: error },
+          error
+        ),
+        PropertiesView({
+          object,
+          filterPlaceHolder: JSON_FILTER_TEXT,
+          sectionNames: [sectionName],
+        }),
+      )
+    );
+  }
+});
+
+module.exports = ResponsePanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/security-panel.js
@@ -0,0 +1,160 @@
+/* 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 { L10N } = require("../utils/l10n");
+const { getUrlHost } = require("../utils/request-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div, input, span } = DOM;
+
+/*
+ * Security panel component
+ * If the site is being served over HTTPS, you get an extra tab labeled "Security".
+ * This contains details about the secure connection used including the protocol,
+ * the cipher suite, and certificate details
+ */
+function SecurityPanel({ request }) {
+  const { securityInfo, url } = request;
+
+  if (!securityInfo || !url) {
+    return null;
+  }
+
+  const notAvailable = L10N.getStr("netmonitor.security.notAvailable");
+  let object;
+
+  if (securityInfo.state === "secure" || securityInfo.state === "weak") {
+    const { subject, issuer, validity, fingerprint } = securityInfo.cert;
+    const enabledLabel = L10N.getStr("netmonitor.security.enabled");
+    const disabledLabel = L10N.getStr("netmonitor.security.disabled");
+
+    object = {
+      [L10N.getStr("netmonitor.security.connection")]: {
+        [L10N.getStr("netmonitor.security.protocolVersion")]:
+          securityInfo.protocolVersion || notAvailable,
+        [L10N.getStr("netmonitor.security.cipherSuite")]:
+          securityInfo.cipherSuite || notAvailable,
+      },
+      [L10N.getFormatStr("netmonitor.security.hostHeader", getUrlHost(url))]: {
+        [L10N.getStr("netmonitor.security.hsts")]:
+          securityInfo.hsts ? enabledLabel : disabledLabel,
+        [L10N.getStr("netmonitor.security.hpkp")]:
+          securityInfo.hpkp ? enabledLabel : disabledLabel,
+      },
+      [L10N.getStr("netmonitor.security.certificate")]: {
+        [L10N.getStr("certmgr.subjectinfo.label")]: {
+          [L10N.getStr("certmgr.certdetail.cn")]:
+            subject.commonName || notAvailable,
+          [L10N.getStr("certmgr.certdetail.o")]:
+            subject.organization || notAvailable,
+          [L10N.getStr("certmgr.certdetail.ou")]:
+            subject.organizationUnit || notAvailable,
+        },
+        [L10N.getStr("certmgr.issuerinfo.label")]: {
+          [L10N.getStr("certmgr.certdetail.cn")]:
+            issuer.commonName || notAvailable,
+          [L10N.getStr("certmgr.certdetail.o")]:
+            issuer.organization || notAvailable,
+          [L10N.getStr("certmgr.certdetail.ou")]:
+            issuer.organizationUnit || notAvailable,
+        },
+        [L10N.getStr("certmgr.periodofvalidity.label")]: {
+          [L10N.getStr("certmgr.begins")]:
+            validity.start || notAvailable,
+          [L10N.getStr("certmgr.expires")]:
+            validity.end || notAvailable,
+        },
+        [L10N.getStr("certmgr.fingerprints.label")]: {
+          [L10N.getStr("certmgr.certdetail.sha256fingerprint")]:
+            fingerprint.sha256 || notAvailable,
+          [L10N.getStr("certmgr.certdetail.sha1fingerprint")]:
+            fingerprint.sha1 || notAvailable,
+        },
+      },
+    };
+  } else {
+    object = {
+      [L10N.getStr("netmonitor.security.error")]:
+        new DOMParser().parseFromString(securityInfo.errorMessage, "text/html")
+          .body.textContent || notAvailable
+    };
+  }
+
+  return div({ className: "panel-container security-panel" },
+    PropertiesView({
+      object,
+      renderValue: (props) => renderValue(props, securityInfo.weaknessReasons),
+      enableFilter: false,
+      expandedNodes: getExpandedNodes(object),
+    })
+  );
+}
+
+SecurityPanel.displayName = "SecurityPanel";
+
+SecurityPanel.propTypes = {
+  request: PropTypes.object.isRequired,
+};
+
+function renderValue(props, weaknessReasons = []) {
+  const { member, value } = props;
+
+  // Hide object summary
+  if (typeof member.value === "object") {
+    return null;
+  }
+
+  return span({ className: "security-info-value" },
+    member.name === L10N.getStr("netmonitor.security.error") ?
+      // Display multiline text for security error
+      value
+      :
+      // Display one line selectable text for security details
+      input({
+        className: "textbox-input",
+        readOnly: "true",
+        value,
+      })
+    ,
+    weaknessReasons.indexOf("cipher") !== -1 &&
+    member.name === L10N.getStr("netmonitor.security.cipherSuite") ?
+      // Display an extra warning icon after the cipher suite
+      div({
+        id: "security-warning-cipher",
+        className: "security-warning-icon",
+        title: L10N.getStr("netmonitor.security.warning.cipher"),
+      })
+      :
+      null
+  );
+}
+
+function getExpandedNodes(object, path = "", level = 0) {
+  if (typeof object !== "object") {
+    return null;
+  }
+
+  let expandedNodes = new Set();
+  for (let prop in object) {
+    let nodePath = path + "/" + prop;
+    expandedNodes.add(nodePath);
+
+    let nodes = getExpandedNodes(object[prop], nodePath, level + 1);
+    if (nodes) {
+      expandedNodes = new Set([...expandedNodes, ...nodes]);
+    }
+  }
+  return expandedNodes;
+}
+
+module.exports = SecurityPanel;
rename from devtools/client/netmonitor/src/components/StatisticsPanel.js
rename to devtools/client/netmonitor/src/components/statistics-panel.js
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/components/tabbox-panel.js
@@ -0,0 +1,123 @@
+/* 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,
+  PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const Actions = require("../actions/index");
+const { Filters } = require("../utils/filter-predicates");
+const { L10N } = require("../utils/l10n");
+const { getSelectedRequest } = require("../selectors/index");
+
+// Components
+const Tabbar = createFactory(require("devtools/client/shared/components/tabs/tabbar"));
+const TabPanel = createFactory(require("devtools/client/shared/components/tabs/tabs").TabPanel);
+const CookiesPanel = createFactory(require("./cookies-panel"));
+const HeadersPanel = createFactory(require("./headers-panel"));
+const ParamsPanel = createFactory(require("./params-panel"));
+const PreviewPanel = createFactory(require("./preview-panel"));
+const ResponsePanel = createFactory(require("./response-panel"));
+const SecurityPanel = createFactory(require("./security-panel"));
+const TimingsPanel = createFactory(require("./timings-panel"));
+
+const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers");
+const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies");
+const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params");
+const RESPONSE_TITLE = L10N.getStr("netmonitor.tab.response");
+const TIMINGS_TITLE = L10N.getStr("netmonitor.tab.timings");
+const SECURITY_TITLE = L10N.getStr("netmonitor.tab.security");
+const PREVIEW_TITLE = L10N.getStr("netmonitor.tab.preview");
+
+/*
+ * Tabbox panel component
+ * Display the network request details
+ */
+function TabboxPanel({
+  activeTabId,
+  cloneSelectedRequest,
+  request,
+  selectTab,
+}) {
+  if (!request) {
+    return null;
+  }
+
+  return (
+    Tabbar({
+      activeTabId,
+      onSelect: selectTab,
+      renderOnlySelected: true,
+      showAllTabsMenu: true,
+    },
+      TabPanel({
+        id: "headers",
+        title: HEADERS_TITLE,
+      },
+        HeadersPanel({ request, cloneSelectedRequest }),
+      ),
+      TabPanel({
+        id: "cookies",
+        title: COOKIES_TITLE,
+      },
+        CookiesPanel({ request }),
+      ),
+      TabPanel({
+        id: "params",
+        title: PARAMS_TITLE,
+      },
+        ParamsPanel({ request }),
+      ),
+      TabPanel({
+        id: "response",
+        title: RESPONSE_TITLE,
+      },
+        ResponsePanel({ request }),
+      ),
+      TabPanel({
+        id: "timings",
+        title: TIMINGS_TITLE,
+      },
+        TimingsPanel({ request }),
+      ),
+      request.securityState && request.securityState !== "insecure" &&
+      TabPanel({
+        id: "security",
+        title: SECURITY_TITLE,
+      },
+        SecurityPanel({ request }),
+      ),
+      Filters.html(request) &&
+      TabPanel({
+        id: "preview",
+        title: PREVIEW_TITLE,
+      },
+        PreviewPanel({ request }),
+      ),
+    )
+  );
+}
+
+TabboxPanel.displayName = "TabboxPanel";
+
+TabboxPanel.propTypes = {
+  activeTabId: PropTypes.string,
+  cloneSelectedRequest: PropTypes.func.isRequired,
+  request: PropTypes.object,
+  selectTab: PropTypes.func.isRequired,
+};
+
+module.exports = connect(
+  (state) => ({
+    activeTabId: state.ui.detailsPanelSelectedTab,
+    request: getSelectedRequest(state),
+  }),
+  (dispatch) => ({
+    cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+    selectTab: (tabId) => dispatch(Actions.selectDetailsPanelTab(tabId)),
+  }),
+)(TabboxPanel);
rename from devtools/client/netmonitor/src/components/TimingsPanel.js
rename to devtools/client/netmonitor/src/components/timings-panel.js
rename from devtools/client/netmonitor/src/components/Toolbar.js
rename to devtools/client/netmonitor/src/components/toolbar.js