Bug 1308425 - Move statistics view into standalone module. r=Honza draft
authorSteve Chung <schung@mozilla.com>
Wed, 19 Oct 2016 18:25:00 +0800
changeset 430893 48a3d653c573f7fd095b0383f1fd9c42e178e2ae
parent 429594 f9f3cc95d7282f1fd83f66dd74acbcdbfe821915
child 535293 00b5ddd20a714002e8c94f4ff3bd21755f6d08f2
push id33924
push userschung@mozilla.com
push dateFri, 28 Oct 2016 08:47:24 +0000
reviewersHonza
bugs1308425
milestone52.0a1
Bug 1308425 - Move statistics view into standalone module. r=Honza MozReview-Commit-ID: 2iqKSHWpEWw
devtools/client/netmonitor/moz.build
devtools/client/netmonitor/netmonitor-controller.js
devtools/client/netmonitor/netmonitor-view.js
devtools/client/netmonitor/performance-statistics-view.js
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -13,16 +13,17 @@ DIRS += [
 
 DevToolsModules(
     'constants.js',
     'custom-request-view.js',
     'events.js',
     'filter-predicates.js',
     'l10n.js',
     'panel.js',
+    'performance-statistics-view.js',
     'prefs.js',
     'request-utils.js',
     'requests-menu-view.js',
     'sort-predicates.js',
     'store.js',
     'toolbar-view.js',
 )
 
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -38,16 +38,17 @@ const Services = require("Services");
 /* eslint-disable mozilla/reject-some-requires */
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const EventEmitter = require("devtools/shared/event-emitter");
 const Editor = require("devtools/client/sourceeditor/editor");
 const {TimelineFront} = require("devtools/shared/fronts/timeline");
 const {Task} = require("devtools/shared/task");
 const {Prefs} = require("./prefs");
 const {EVENTS} = require("./events");
+const Actions = require("./actions/index");
 
 XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
 XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
 XPCOMUtils.defineConstant(this, "Editor", Editor);
 XPCOMUtils.defineConstant(this, "Prefs", Prefs);
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chart",
   "resource://devtools/client/shared/widgets/Chart.jsm");
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -14,46 +14,43 @@ XPCOMUtils.defineLazyGetter(this, "Netwo
 
 /* eslint-disable mozilla/reject-some-requires */
 const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 /* eslint-disable mozilla/reject-some-requires */
 const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 const {testing: isTesting} = require("devtools/shared/flags");
 const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
-const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters} = require("./filter-predicates");
 const {getFormDataSections,
        formDataURI,
        getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
 const {RequestsMenuView} = require("./requests-menu-view");
 const {CustomRequestView} = require("./custom-request-view");
 const {ToolbarView} = require("./toolbar-view");
 const {configureStore} = require("./store");
-const Actions = require("./actions/index");
+const {PerformanceStatisticsView} = require("./performance-statistics-view");
 
 // Initialize the global redux variables
 var gStore = configureStore();
 
 // ms
 const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
 // be at least equal to the general mochitest timeout of 45 seconds so that this
 // never gets hit during testing.
 // ms
 const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
 
 // 100 KB in bytes
 const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
-const REQUEST_TIME_DECIMALS = 2;
 const HEADERS_SIZE_DECIMALS = 3;
-const CONTENT_SIZE_DECIMALS = 2;
 const CONTENT_MIME_TYPE_MAPPINGS = {
   "/ecmascript": Editor.modes.js,
   "/javascript": Editor.modes.js,
   "/x-javascript": Editor.modes.js,
   "/html": Editor.modes.html,
   "/xhtml": Editor.modes.html,
   "/xml": Editor.modes.html,
   "/atom": Editor.modes.html,
@@ -75,33 +72,32 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = 
   lazyEmptyDelay: 10,
   searchEnabled: true,
   editableValueTooltip: "",
   editableNameTooltip: "",
   preventDisableOnChange: true,
   preventDescriptorModifiers: true,
   eval: () => {}
 };
-// px
-const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 
 /**
  * Object defining the network monitor view components.
  */
 var NetMonitorView = {
   /**
    * Initializes the network monitor view.
    */
   initialize: function () {
     this._initializePanes();
 
     this.Toolbar.initialize(gStore);
     this.RequestsMenu.initialize(gStore);
     this.NetworkDetails.initialize();
     this.CustomRequest.initialize();
+    this.PerformanceStatistics.initialize(gStore);
   },
 
   /**
    * Destroys the network monitor view.
    */
   destroy: function () {
     this._isDestroyed = true;
     this.Toolbar.destroy();
@@ -1192,259 +1188,23 @@ NetworkDetailsView.prototype = {
   _paramsPostPayload: "",
   _requestHeaders: "",
   _responseHeaders: "",
   _requestCookies: "",
   _responseCookies: ""
 };
 
 /**
- * Functions handling the performance statistics view.
- */
-function PerformanceStatisticsView() {
-}
-
-PerformanceStatisticsView.prototype = {
-  /**
-   * Initializes and displays empty charts in this container.
-   */
-  displayPlaceholderCharts: function () {
-    this._createChart({
-      id: "#primed-cache-chart",
-      title: "charts.cacheEnabled"
-    });
-    this._createChart({
-      id: "#empty-cache-chart",
-      title: "charts.cacheDisabled"
-    });
-    window.emit(EVENTS.PLACEHOLDER_CHARTS_DISPLAYED);
-  },
-
-  /**
-   * Populates and displays the primed cache chart in this container.
-   *
-   * @param array items
-   *        @see this._sanitizeChartDataSource
-   */
-  createPrimedCacheChart: function (items) {
-    this._createChart({
-      id: "#primed-cache-chart",
-      title: "charts.cacheEnabled",
-      data: this._sanitizeChartDataSource(items),
-      strings: this._commonChartStrings,
-      totals: this._commonChartTotals,
-      sorted: true
-    });
-    window.emit(EVENTS.PRIMED_CACHE_CHART_DISPLAYED);
-  },
-
-  /**
-   * Populates and displays the empty cache chart in this container.
-   *
-   * @param array items
-   *        @see this._sanitizeChartDataSource
-   */
-  createEmptyCacheChart: function (items) {
-    this._createChart({
-      id: "#empty-cache-chart",
-      title: "charts.cacheDisabled",
-      data: this._sanitizeChartDataSource(items, true),
-      strings: this._commonChartStrings,
-      totals: this._commonChartTotals,
-      sorted: true
-    });
-    window.emit(EVENTS.EMPTY_CACHE_CHART_DISPLAYED);
-  },
-
-  /**
-   * Common stringifier predicates used for items and totals in both the
-   * "primed" and "empty" cache charts.
-   */
-  _commonChartStrings: {
-    size: value => {
-      let string = L10N.numberWithDecimals(value / 1024, CONTENT_SIZE_DECIMALS);
-      return L10N.getFormatStr("charts.sizeKB", string);
-    },
-    time: value => {
-      let string = L10N.numberWithDecimals(value / 1000, REQUEST_TIME_DECIMALS);
-      return L10N.getFormatStr("charts.totalS", string);
-    }
-  },
-  _commonChartTotals: {
-    size: total => {
-      let string = L10N.numberWithDecimals(total / 1024, CONTENT_SIZE_DECIMALS);
-      return L10N.getFormatStr("charts.totalSize", string);
-    },
-    time: total => {
-      let seconds = total / 1000;
-      let string = L10N.numberWithDecimals(seconds, REQUEST_TIME_DECIMALS);
-      return PluralForm.get(seconds,
-        L10N.getStr("charts.totalSeconds")).replace("#1", string);
-    },
-    cached: total => {
-      return L10N.getFormatStr("charts.totalCached", total);
-    },
-    count: total => {
-      return L10N.getFormatStr("charts.totalCount", total);
-    }
-  },
-
-  /**
-   * Adds a specific chart to this container.
-   *
-   * @param object
-   *        An object containing all or some the following properties:
-   *          - id: either "#primed-cache-chart" or "#empty-cache-chart"
-   *          - title/data/strings/totals/sorted: @see Chart.jsm for details
-   */
-  _createChart: function ({ id, title, data, strings, totals, sorted }) {
-    let container = $(id);
-
-    // Nuke all existing charts of the specified type.
-    while (container.hasChildNodes()) {
-      container.firstChild.remove();
-    }
-
-    // Create a new chart.
-    let chart = Chart.PieTable(document, {
-      diameter: NETWORK_ANALYSIS_PIE_CHART_DIAMETER,
-      title: L10N.getStr(title),
-      data: data,
-      strings: strings,
-      totals: totals,
-      sorted: sorted
-    });
-
-    chart.on("click", (_, item) => {
-      // Reset FilterButtons and enable one filter exclusively
-      gStore.dispatch(Actions.enableFilterOnly(item.label));
-      NetMonitorView.showNetworkInspectorView();
-    });
-
-    container.appendChild(chart.node);
-  },
-
-  /**
-   * Sanitizes the data source used for creating charts, to follow the
-   * data format spec defined in Chart.jsm.
-   *
-   * @param array items
-   *        A collection of request items used as the data source for the chart.
-   * @param boolean emptyCache
-   *        True if the cache is considered enabled, false for disabled.
-   */
-  _sanitizeChartDataSource: function (items, emptyCache) {
-    let data = [
-      "html", "css", "js", "xhr", "fonts", "images", "media", "flash", "ws", "other"
-    ].map(e => ({
-      cached: 0,
-      count: 0,
-      label: e,
-      size: 0,
-      time: 0
-    }));
-
-    for (let requestItem of items) {
-      let details = requestItem.attachment;
-      let type;
-
-      if (Filters.html(details)) {
-        // "html"
-        type = 0;
-      } else if (Filters.css(details)) {
-        // "css"
-        type = 1;
-      } else if (Filters.js(details)) {
-        // "js"
-        type = 2;
-      } else if (Filters.fonts(details)) {
-        // "fonts"
-        type = 4;
-      } else if (Filters.images(details)) {
-        // "images"
-        type = 5;
-      } else if (Filters.media(details)) {
-        // "media"
-        type = 6;
-      } else if (Filters.flash(details)) {
-        // "flash"
-        type = 7;
-      } else if (Filters.ws(details)) {
-        // "ws"
-        type = 8;
-      } else if (Filters.xhr(details)) {
-        // Verify XHR last, to categorize other mime types in their own blobs.
-        // "xhr"
-        type = 3;
-      } else {
-        // "other"
-        type = 9;
-      }
-
-      if (emptyCache || !responseIsFresh(details)) {
-        data[type].time += details.totalTime || 0;
-        data[type].size += details.contentSize || 0;
-      } else {
-        data[type].cached++;
-      }
-      data[type].count++;
-    }
-
-    return data.filter(e => e.count > 0);
-  },
-};
-
-/**
  * DOM query helper.
+ * TODO: Move it into "dom-utils.js" module and "require" it when needed.
  */
 var $ = (selector, target = document) => target.querySelector(selector);
 var $all = (selector, target = document) => target.querySelectorAll(selector);
 
 /**
- * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
- * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
- *
- * @param object
- *        An object containing the { responseHeaders, status } properties.
- * @return boolean
- *         True if the response is fresh and loaded from cache.
- */
-function responseIsFresh({ responseHeaders, status }) {
-  // Check for a "304 Not Modified" status and response headers availability.
-  if (status != 304 || !responseHeaders) {
-    return false;
-  }
-
-  let list = responseHeaders.headers;
-  let cacheControl = list.filter(e => {
-    return e.name.toLowerCase() == "cache-control";
-  })[0];
-
-  let expires = list.filter(e => e.name.toLowerCase() == "expires")[0];
-
-  // Check the "Cache-Control" header for a maximum age value.
-  if (cacheControl) {
-    let maxAgeMatch =
-      cacheControl.value.match(/s-maxage\s*=\s*(\d+)/) ||
-      cacheControl.value.match(/max-age\s*=\s*(\d+)/);
-
-    if (maxAgeMatch && maxAgeMatch.pop() > 0) {
-      return true;
-    }
-  }
-
-  // Check the "Expires" header for a valid date.
-  if (expires && Date.parse(expires.value)) {
-    return true;
-  }
-
-  return false;
-}
-
-/**
  * Makes sure certain properties are available on all objects in a data store.
  *
  * @param array dataStore
  *        A list of objects for which to check the availability of properties.
  * @param array mandatoryFields
  *        A list of strings representing properties of objects in dataStore.
  * @return object
  *         A promise resolved when all objects in dataStore contain the
copy from devtools/client/netmonitor/netmonitor-view.js
copy to devtools/client/netmonitor/performance-statistics-view.js
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/performance-statistics-view.js
@@ -1,1214 +1,43 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 /* import-globals-from ./netmonitor-controller.js */
-/* globals Prefs, gNetwork, setInterval, setTimeout, clearInterval, clearTimeout, btoa */
-/* exported $, $all */
+/* globals $ */
 "use strict";
 
-XPCOMUtils.defineLazyGetter(this, "NetworkHelper", function () {
-  return require("devtools/shared/webconsole/network-helper");
-});
-
-/* eslint-disable mozilla/reject-some-requires */
-const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
-/* eslint-disable mozilla/reject-some-requires */
-const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
-const {ToolSidebar} = require("devtools/client/framework/sidebar");
-const {testing: isTesting} = require("devtools/shared/flags");
-const {ViewHelpers, Heritage} = require("devtools/client/shared/widgets/view-helpers");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters} = require("./filter-predicates");
-const {getFormDataSections,
-       formDataURI,
-       getUriHostPort} = require("./request-utils");
 const {L10N} = require("./l10n");
-const {RequestsMenuView} = require("./requests-menu-view");
-const {CustomRequestView} = require("./custom-request-view");
-const {ToolbarView} = require("./toolbar-view");
-const {configureStore} = require("./store");
 const Actions = require("./actions/index");
 
-// Initialize the global redux variables
-var gStore = configureStore();
-
-// ms
-const WDA_DEFAULT_VERIFY_INTERVAL = 50;
-
-// Use longer timeout during testing as the tests need this process to succeed
-// and two seconds is quite short on slow debug builds. The timeout here should
-// be at least equal to the general mochitest timeout of 45 seconds so that this
-// never gets hit during testing.
-// ms
-const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
-
-// 100 KB in bytes
-const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
 const REQUEST_TIME_DECIMALS = 2;
-const HEADERS_SIZE_DECIMALS = 3;
 const CONTENT_SIZE_DECIMALS = 2;
-const CONTENT_MIME_TYPE_MAPPINGS = {
-  "/ecmascript": Editor.modes.js,
-  "/javascript": Editor.modes.js,
-  "/x-javascript": Editor.modes.js,
-  "/html": Editor.modes.html,
-  "/xhtml": Editor.modes.html,
-  "/xml": Editor.modes.html,
-  "/atom": Editor.modes.html,
-  "/soap": Editor.modes.html,
-  "/vnd.mpeg.dash.mpd": Editor.modes.html,
-  "/rdf": Editor.modes.css,
-  "/rss": Editor.modes.css,
-  "/css": Editor.modes.css
-};
 
-const DEFAULT_EDITOR_CONFIG = {
-  mode: Editor.modes.text,
-  readOnly: true,
-  lineNumbers: true
-};
-const GENERIC_VARIABLES_VIEW_SETTINGS = {
-  lazyEmpty: true,
-  // ms
-  lazyEmptyDelay: 10,
-  searchEnabled: true,
-  editableValueTooltip: "",
-  editableNameTooltip: "",
-  preventDisableOnChange: true,
-  preventDescriptorModifiers: true,
-  eval: () => {}
-};
 // px
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
 
 /**
- * Object defining the network monitor view components.
- */
-var NetMonitorView = {
-  /**
-   * Initializes the network monitor view.
-   */
-  initialize: function () {
-    this._initializePanes();
-
-    this.Toolbar.initialize(gStore);
-    this.RequestsMenu.initialize(gStore);
-    this.NetworkDetails.initialize();
-    this.CustomRequest.initialize();
-  },
-
-  /**
-   * Destroys the network monitor view.
-   */
-  destroy: function () {
-    this._isDestroyed = true;
-    this.Toolbar.destroy();
-    this.RequestsMenu.destroy();
-    this.NetworkDetails.destroy();
-    this.CustomRequest.destroy();
-
-    this._destroyPanes();
-  },
-
-  /**
-   * Initializes the UI for all the displayed panes.
-   */
-  _initializePanes: function () {
-    dumpn("Initializing the NetMonitorView panes");
-
-    this._body = $("#body");
-    this._detailsPane = $("#details-pane");
-    this._detailsPaneToggleButton = $("#details-pane-toggle");
-
-    this._collapsePaneString = L10N.getStr("collapseDetailsPane");
-    this._expandPaneString = L10N.getStr("expandDetailsPane");
-
-    this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
-    this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
-    this.toggleDetailsPane({ visible: false });
-
-    // Disable the performance statistics mode.
-    if (!Prefs.statistics) {
-      $("#request-menu-context-perf").hidden = true;
-      $("#notice-perf-message").hidden = true;
-      $("#requests-menu-network-summary-button").hidden = true;
-    }
-  },
-
-  /**
-   * Destroys the UI for all the displayed panes.
-   */
-  _destroyPanes: Task.async(function* () {
-    dumpn("Destroying the NetMonitorView panes");
-
-    Prefs.networkDetailsWidth = this._detailsPane.getAttribute("width");
-    Prefs.networkDetailsHeight = this._detailsPane.getAttribute("height");
-
-    this._detailsPane = null;
-    this._detailsPaneToggleButton = null;
-
-    for (let p of this._editorPromises.values()) {
-      let editor = yield p;
-      editor.destroy();
-    }
-  }),
-
-  /**
-   * Gets the visibility state of the network details pane.
-   * @return boolean
-   */
-  get detailsPaneHidden() {
-    return this._detailsPane.classList.contains("pane-collapsed");
-  },
-
-  /**
-   * Sets the network details pane hidden or visible.
-   *
-   * @param object flags
-   *        An object containing some of the following properties:
-   *        - visible: true if the pane should be shown, false to hide
-   *        - animated: true to display an animation on toggle
-   *        - delayed: true to wait a few cycles before toggle
-   *        - callback: a function to invoke when the toggle finishes
-   * @param number tabIndex [optional]
-   *        The index of the intended selected tab in the details pane.
-   */
-  toggleDetailsPane: function (flags, tabIndex) {
-    let pane = this._detailsPane;
-    let button = this._detailsPaneToggleButton;
-
-    ViewHelpers.togglePane(flags, pane);
-
-    if (flags.visible) {
-      this._body.classList.remove("pane-collapsed");
-      button.classList.remove("pane-collapsed");
-      button.setAttribute("tooltiptext", this._collapsePaneString);
-    } else {
-      this._body.classList.add("pane-collapsed");
-      button.classList.add("pane-collapsed");
-      button.setAttribute("tooltiptext", this._expandPaneString);
-    }
-
-    if (tabIndex !== undefined) {
-      $("#event-details-pane").selectedIndex = tabIndex;
-    }
-  },
-
-  /**
-   * Gets the current mode for this tool.
-   * @return string (e.g, "network-inspector-view" or "network-statistics-view")
-   */
-  get currentFrontendMode() {
-    // The getter may be called from a timeout after the panel is destroyed.
-    if (!this._body.selectedPanel) {
-      return null;
-    }
-    return this._body.selectedPanel.id;
-  },
-
-  /**
-   * Toggles between the frontend view modes ("Inspector" vs. "Statistics").
-   */
-  toggleFrontendMode: function () {
-    if (this.currentFrontendMode != "network-inspector-view") {
-      this.showNetworkInspectorView();
-    } else {
-      this.showNetworkStatisticsView();
-    }
-  },
-
-  /**
-   * Switches to the "Inspector" frontend view mode.
-   */
-  showNetworkInspectorView: function () {
-    this._body.selectedPanel = $("#network-inspector-view");
-    this.RequestsMenu._flushWaterfallViews(true);
-  },
-
-  /**
-   * Switches to the "Statistics" frontend view mode.
-   */
-  showNetworkStatisticsView: function () {
-    this._body.selectedPanel = $("#network-statistics-view");
-
-    let controller = NetMonitorController;
-    let requestsView = this.RequestsMenu;
-    let statisticsView = this.PerformanceStatistics;
-
-    Task.spawn(function* () {
-      statisticsView.displayPlaceholderCharts();
-      yield controller.triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_ENABLED);
-
-      try {
-        // • The response headers and status code are required for determining
-        // whether a response is "fresh" (cacheable).
-        // • The response content size and request total time are necessary for
-        // populating the statistics view.
-        // • The response mime type is used for categorization.
-        yield whenDataAvailable(requestsView.attachments, [
-          "responseHeaders", "status", "contentSize", "mimeType", "totalTime"
-        ]);
-      } catch (ex) {
-        // Timed out while waiting for data. Continue with what we have.
-        console.error(ex);
-      }
-
-      statisticsView.createPrimedCacheChart(requestsView.items);
-      statisticsView.createEmptyCacheChart(requestsView.items);
-    });
-  },
-
-  reloadPage: function () {
-    NetMonitorController.triggerActivity(
-      ACTIVITY_TYPE.RELOAD.WITH_CACHE_DEFAULT);
-  },
-
-  /**
-   * Lazily initializes and returns a promise for a Editor instance.
-   *
-   * @param string id
-   *        The id of the editor placeholder node.
-   * @return object
-   *         A promise that is resolved when the editor is available.
-   */
-  editor: function (id) {
-    dumpn("Getting a NetMonitorView editor: " + id);
-
-    if (this._editorPromises.has(id)) {
-      return this._editorPromises.get(id);
-    }
-
-    let deferred = promise.defer();
-    this._editorPromises.set(id, deferred.promise);
-
-    // Initialize the source editor and store the newly created instance
-    // in the ether of a resolved promise's value.
-    let editor = new Editor(DEFAULT_EDITOR_CONFIG);
-    editor.appendTo($(id)).then(() => deferred.resolve(editor));
-
-    return deferred.promise;
-  },
-
-  _body: null,
-  _detailsPane: null,
-  _detailsPaneToggleButton: null,
-  _collapsePaneString: "",
-  _expandPaneString: "",
-  _editorPromises: new Map()
-};
-
-/**
- * Functions handling the sidebar details view.
- */
-function SidebarView() {
-  dumpn("SidebarView was instantiated");
-}
-
-SidebarView.prototype = {
-  /**
-   * Sets this view hidden or visible. It's visible by default.
-   *
-   * @param boolean visibleFlag
-   *        Specifies the intended visibility.
-   */
-  toggle: function (visibleFlag) {
-    NetMonitorView.toggleDetailsPane({ visible: visibleFlag });
-    NetMonitorView.RequestsMenu._flushWaterfallViews(true);
-  },
-
-  /**
-   * Populates this view with the specified data.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   * @return object
-   *        Returns a promise that resolves upon population of the subview.
-   */
-  populate: Task.async(function* (data) {
-    let isCustom = data.isCustom;
-    let view = isCustom ?
-      NetMonitorView.CustomRequest :
-      NetMonitorView.NetworkDetails;
-
-    yield view.populate(data);
-    $("#details-pane").selectedIndex = isCustom ? 0 : 1;
-
-    window.emit(EVENTS.SIDEBAR_POPULATED);
-  })
-};
-
-/**
- * Functions handling the requests details view.
- */
-function NetworkDetailsView() {
-  dumpn("NetworkDetailsView was instantiated");
-
-  // The ToolSidebar requires the panel object to be able to emit events.
-  EventEmitter.decorate(this);
-
-  this._onTabSelect = this._onTabSelect.bind(this);
-}
-
-NetworkDetailsView.prototype = {
-  /**
-   * An object containing the state of tabs.
-   */
-  _viewState: {
-    // if updating[tab] is true a task is currently updating the given tab.
-    updating: [],
-    // if dirty[tab] is true, the tab needs to be repopulated once current
-    // update task finishes
-    dirty: [],
-    // the most recently received attachment data for the request
-    latestData: null,
-  },
-
-  /**
-   * Initialization function, called when the network monitor is started.
-   */
-  initialize: function () {
-    dumpn("Initializing the NetworkDetailsView");
-
-    this.widget = $("#event-details-pane");
-    this.sidebar = new ToolSidebar(this.widget, this, "netmonitor", {
-      disableTelemetry: true,
-      showAllTabsMenu: true
-    });
-
-    this._headers = new VariablesView($("#all-headers"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("headersEmptyText"),
-        searchPlaceholder: L10N.getStr("headersFilterText")
-      }));
-    this._cookies = new VariablesView($("#all-cookies"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("cookiesEmptyText"),
-        searchPlaceholder: L10N.getStr("cookiesFilterText")
-      }));
-    this._params = new VariablesView($("#request-params"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        emptyText: L10N.getStr("paramsEmptyText"),
-        searchPlaceholder: L10N.getStr("paramsFilterText")
-      }));
-    this._json = new VariablesView($("#response-content-json"),
-      Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
-        onlyEnumVisible: true,
-        searchPlaceholder: L10N.getStr("jsonFilterText")
-      }));
-    VariablesViewController.attach(this._json);
-
-    this._paramsQueryString = L10N.getStr("paramsQueryString");
-    this._paramsFormData = L10N.getStr("paramsFormData");
-    this._paramsPostPayload = L10N.getStr("paramsPostPayload");
-    this._requestHeaders = L10N.getStr("requestHeaders");
-    this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
-    this._responseHeaders = L10N.getStr("responseHeaders");
-    this._requestCookies = L10N.getStr("requestCookies");
-    this._responseCookies = L10N.getStr("responseCookies");
-
-    $("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
-  },
-
-  /**
-   * Destruction function, called when the network monitor is closed.
-   */
-  destroy: function () {
-    dumpn("Destroying the NetworkDetailsView");
-    this.sidebar.destroy();
-    $("tabpanels", this.widget).removeEventListener("select",
-      this._onTabSelect);
-  },
-
-  /**
-   * Populates this view with the specified data.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   * @return object
-   *        Returns a promise that resolves upon population the view.
-   */
-  populate: function (data) {
-    $("#request-params-box").setAttribute("flex", "1");
-    $("#request-params-box").hidden = false;
-    $("#request-post-data-textarea-box").hidden = true;
-    $("#response-content-info-header").hidden = true;
-    $("#response-content-json-box").hidden = true;
-    $("#response-content-textarea-box").hidden = true;
-    $("#raw-headers").hidden = true;
-    $("#response-content-image-box").hidden = true;
-
-    let isHtml = Filters.html(data);
-
-    // Show the "Preview" tabpanel only for plain HTML responses.
-    this.sidebar.toggleTab(isHtml, "preview-tab");
-
-    // Show the "Security" tab only for requests that
-    //   1) are https (state != insecure)
-    //   2) come from a target that provides security information.
-    let hasSecurityInfo = data.securityState &&
-                          data.securityState !== "insecure";
-    this.sidebar.toggleTab(hasSecurityInfo, "security-tab");
-
-    // Switch to the "Headers" tabpanel if the "Preview" previously selected
-    // and this is not an HTML response or "Security" was selected but this
-    // request has no security information.
-
-    if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
-        !hasSecurityInfo && this.widget.selectedPanel ===
-          $("#security-tabpanel")) {
-      this.widget.selectedIndex = 0;
-    }
-
-    this._headers.empty();
-    this._cookies.empty();
-    this._params.empty();
-    this._json.empty();
-
-    this._dataSrc = { src: data, populated: [] };
-    this._onTabSelect();
-    window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
-
-    return promise.resolve();
-  },
-
-  /**
-   * Listener handling the tab selection event.
-   */
-  _onTabSelect: function () {
-    let { src, populated } = this._dataSrc || {};
-    let tab = this.widget.selectedIndex;
-    let view = this;
-
-    // Make sure the data source is valid and don't populate the same tab twice.
-    if (!src || populated[tab]) {
-      return;
-    }
-
-    let viewState = this._viewState;
-    if (viewState.updating[tab]) {
-      // A task is currently updating this tab. If we started another update
-      // task now it would result in a duplicated content as described in bugs
-      // 997065 and 984687. As there's no way to stop the current task mark the
-      // tab dirty and refresh the panel once the current task finishes.
-      viewState.dirty[tab] = true;
-      viewState.latestData = src;
-      return;
-    }
-
-    Task.spawn(function* () {
-      viewState.updating[tab] = true;
-      switch (tab) {
-        // "Headers"
-        case 0:
-          yield view._setSummary(src);
-          yield view._setResponseHeaders(src.responseHeaders);
-          yield view._setRequestHeaders(
-            src.requestHeaders,
-            src.requestHeadersFromUploadStream);
-          break;
-        // "Cookies"
-        case 1:
-          yield view._setResponseCookies(src.responseCookies);
-          yield view._setRequestCookies(src.requestCookies);
-          break;
-        // "Params"
-        case 2:
-          yield view._setRequestGetParams(src.url);
-          yield view._setRequestPostParams(
-            src.requestHeaders,
-            src.requestHeadersFromUploadStream,
-            src.requestPostData);
-          break;
-        // "Response"
-        case 3:
-          yield view._setResponseBody(src.url, src.responseContent);
-          break;
-        // "Timings"
-        case 4:
-          yield view._setTimingsInformation(src.eventTimings);
-          break;
-        // "Security"
-        case 5:
-          yield view._setSecurityInfo(src.securityInfo, src.url);
-          break;
-        // "Preview"
-        case 6:
-          yield view._setHtmlPreview(src.responseContent);
-          break;
-      }
-      viewState.updating[tab] = false;
-    }).then(() => {
-      if (tab == this.widget.selectedIndex) {
-        if (viewState.dirty[tab]) {
-          // The request information was updated while the task was running.
-          viewState.dirty[tab] = false;
-          view.populate(viewState.latestData);
-        } else {
-          // Tab is selected but not dirty. We're done here.
-          populated[tab] = true;
-          window.emit(EVENTS.TAB_UPDATED);
-
-          if (NetMonitorController.isConnected()) {
-            NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
-          }
-        }
-      } else if (viewState.dirty[tab]) {
-        // Tab is dirty but no longer selected. Don't refresh it now, it'll be
-        // done if the tab is shown again.
-        viewState.dirty[tab] = false;
-      }
-    }, e => console.error(e));
-  },
-
-  /**
-   * Sets the network request summary shown in this view.
-   *
-   * @param object data
-   *        The data source (this should be the attachment of a request item).
-   */
-  _setSummary: function (data) {
-    if (data.url) {
-      let unicodeUrl = NetworkHelper.convertToUnicode(unescape(data.url));
-      $("#headers-summary-url-value").setAttribute("value", unicodeUrl);
-      $("#headers-summary-url-value").setAttribute("tooltiptext", unicodeUrl);
-      $("#headers-summary-url").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-url").setAttribute("hidden", "true");
-    }
-
-    if (data.method) {
-      $("#headers-summary-method-value").setAttribute("value", data.method);
-      $("#headers-summary-method").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-method").setAttribute("hidden", "true");
-    }
-
-    if (data.remoteAddress) {
-      let address = data.remoteAddress;
-      if (address.indexOf(":") != -1) {
-        address = `[${address}]`;
-      }
-      if (data.remotePort) {
-        address += `:${data.remotePort}`;
-      }
-      $("#headers-summary-address-value").setAttribute("value", address);
-      $("#headers-summary-address-value").setAttribute("tooltiptext", address);
-      $("#headers-summary-address").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-address").setAttribute("hidden", "true");
-    }
-
-    if (data.status) {
-      // "code" attribute is only used by css to determine the icon color
-      let code;
-      if (data.fromCache) {
-        code = "cached";
-      } else if (data.fromServiceWorker) {
-        code = "service worker";
-      } else {
-        code = data.status;
-      }
-      $("#headers-summary-status-circle").setAttribute("code", code);
-      $("#headers-summary-status-value").setAttribute("value",
-        data.status + " " + data.statusText);
-      $("#headers-summary-status").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-status").setAttribute("hidden", "true");
-    }
-
-    if (data.httpVersion) {
-      $("#headers-summary-version-value").setAttribute("value",
-        data.httpVersion);
-      $("#headers-summary-version").removeAttribute("hidden");
-    } else {
-      $("#headers-summary-version").setAttribute("hidden", "true");
-    }
-  },
-
-  /**
-   * Sets the network request headers shown in this view.
-   *
-   * @param object headers
-   *        The "requestHeaders" message received from the server.
-   * @param object uploadHeaders
-   *        The "requestHeadersFromUploadStream" inferred from the POST payload.
-   * @return object
-   *        A promise that resolves when request headers are set.
-   */
-  _setRequestHeaders: Task.async(function* (headers, uploadHeaders) {
-    if (headers && headers.headers.length) {
-      yield this._addHeaders(this._requestHeaders, headers);
-    }
-    if (uploadHeaders && uploadHeaders.headers.length) {
-      yield this._addHeaders(this._requestHeadersFromUpload, uploadHeaders);
-    }
-  }),
-
-  /**
-   * Sets the network response headers shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that resolves when response headers are set.
-   */
-  _setResponseHeaders: Task.async(function* (response) {
-    if (response && response.headers.length) {
-      response.headers.sort((a, b) => a.name > b.name);
-      yield this._addHeaders(this._responseHeaders, response);
-    }
-  }),
-
-  /**
-   * Populates the headers container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of headers to populate (request or response).
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that resolves when headers are added.
-   */
-  _addHeaders: Task.async(function* (name, response) {
-    let kb = response.headersSize / 1024;
-    let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
-    let text = L10N.getFormatStr("networkMenu.sizeKB", size);
-
-    let headersScope = this._headers.addScope(name + " (" + text + ")");
-    headersScope.expanded = true;
-
-    for (let header of response.headers) {
-      let headerVar = headersScope.addItem(header.name, {}, {relaxed: true});
-      let headerValue = yield gNetwork.getString(header.value);
-      headerVar.setGrip(headerValue);
-    }
-  }),
-
-  /**
-   * Sets the network request cookies shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the request cookies are set.
-   */
-  _setRequestCookies: Task.async(function* (response) {
-    if (response && response.cookies.length) {
-      response.cookies.sort((a, b) => a.name > b.name);
-      yield this._addCookies(this._requestCookies, response);
-    }
-  }),
-
-  /**
-   * Sets the network response cookies shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the response cookies are set.
-   */
-  _setResponseCookies: Task.async(function* (response) {
-    if (response && response.cookies.length) {
-      yield this._addCookies(this._responseCookies, response);
-    }
-  }),
-
-  /**
-   * Populates the cookies container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of cookies to populate (request or response).
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        Returns a promise that resolves upon the adding of cookies.
-   */
-  _addCookies: Task.async(function* (name, response) {
-    let cookiesScope = this._cookies.addScope(name);
-    cookiesScope.expanded = true;
-
-    for (let cookie of response.cookies) {
-      let cookieVar = cookiesScope.addItem(cookie.name, {}, {relaxed: true});
-      let cookieValue = yield gNetwork.getString(cookie.value);
-      cookieVar.setGrip(cookieValue);
-
-      // By default the cookie name and value are shown. If this is the only
-      // information available, then nothing else is to be displayed.
-      let cookieProps = Object.keys(cookie);
-      if (cookieProps.length == 2) {
-        continue;
-      }
-
-      // Display any other information other than the cookie name and value
-      // which may be available.
-      let rawObject = Object.create(null);
-      let otherProps = cookieProps.filter(e => e != "name" && e != "value");
-      for (let prop of otherProps) {
-        rawObject[prop] = cookie[prop];
-      }
-      cookieVar.populate(rawObject);
-      cookieVar.twisty = true;
-      cookieVar.expanded = true;
-    }
-  }),
-
-  /**
-   * Sets the network request get params shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   */
-  _setRequestGetParams: function (url) {
-    let query = NetworkHelper.nsIURL(url).query;
-    if (query) {
-      this._addParams(this._paramsQueryString, query);
-    }
-  },
-
-  /**
-   * Sets the network request post params shown in this view.
-   *
-   * @param object headers
-   *        The "requestHeaders" message received from the server.
-   * @param object uploadHeaders
-   *        The "requestHeadersFromUploadStream" inferred from the POST payload.
-   * @param object postData
-   *        The "requestPostData" message received from the server.
-   * @return object
-   *        A promise that is resolved when the request post params are set.
-   */
-  _setRequestPostParams: Task.async(function* (headers, uploadHeaders,
-    postData) {
-    if (!headers || !uploadHeaders || !postData) {
-      return;
-    }
-
-    let formDataSections = yield getFormDataSections(
-      headers,
-      uploadHeaders,
-      postData,
-      gNetwork.getString.bind(gNetwork));
-
-    this._params.onlyEnumVisible = false;
-
-    // Handle urlencoded form data sections (e.g. "?foo=bar&baz=42").
-    if (formDataSections.length > 0) {
-      formDataSections.forEach(section => {
-        this._addParams(this._paramsFormData, section);
-      });
-    } else {
-      // Handle JSON and actual forms ("multipart/form-data" content type).
-      let postDataLongString = postData.postData.text;
-      let text = yield gNetwork.getString(postDataLongString);
-      let jsonVal = null;
-      try {
-        jsonVal = JSON.parse(text);
-      } catch (ex) { // eslint-disable-line
-      }
-
-      if (jsonVal) {
-        this._params.onlyEnumVisible = true;
-        let jsonScopeName = L10N.getStr("jsonScopeName");
-        let jsonScope = this._params.addScope(jsonScopeName);
-        jsonScope.expanded = true;
-        let jsonItem = jsonScope.addItem(undefined, { enumerable: true });
-        jsonItem.populate(jsonVal, { sorted: true });
-      } else {
-        // This is really awkward, but hey, it works. Let's show an empty
-        // scope in the params view and place the source editor containing
-        // the raw post data directly underneath.
-        $("#request-params-box").removeAttribute("flex");
-        let paramsScope = this._params.addScope(this._paramsPostPayload);
-        paramsScope.expanded = true;
-        paramsScope.locked = true;
-
-        $("#request-post-data-textarea-box").hidden = false;
-        let editor = yield NetMonitorView.editor("#request-post-data-textarea");
-        editor.setMode(Editor.modes.text);
-        editor.setText(text);
-      }
-    }
-
-    window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
-  }),
-
-  /**
-   * Populates the params container in this view with the specified data.
-   *
-   * @param string name
-   *        The type of params to populate (get or post).
-   * @param string queryString
-   *        A query string of params (e.g. "?foo=bar&baz=42").
-   */
-  _addParams: function (name, queryString) {
-    let paramsArray = NetworkHelper.parseQueryString(queryString);
-    if (!paramsArray) {
-      return;
-    }
-    let paramsScope = this._params.addScope(name);
-    paramsScope.expanded = true;
-
-    for (let param of paramsArray) {
-      let paramVar = paramsScope.addItem(param.name, {}, {relaxed: true});
-      paramVar.setGrip(param.value);
-    }
-  },
-
-  /**
-   * Sets the network response body shown in this view.
-   *
-   * @param string url
-   *        The request's url.
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *         A promise that is resolved when the response body is set.
-   */
-  _setResponseBody: Task.async(function* (url, response) {
-    if (!response) {
-      return;
-    }
-    let { mimeType, text, encoding } = response.content;
-    let responseBody = yield gNetwork.getString(text);
-
-    // 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.
-    let jsonMimeType, jsonObject, jsonObjectParseError;
-    try {
-      jsonMimeType = /\bjson/.test(mimeType);
-      jsonObject = JSON.parse(responseBody);
-    } catch (e) {
-      jsonObjectParseError = e;
-    }
-    if (jsonMimeType || jsonObject) {
-      // 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 [_, callbackPadding, jsonpString] = // eslint-disable-line
-        responseBody.match(jsonpRegex) || [];
-
-      // Make sure this is a valid JSON object first. If so, nicely display
-      // the parsing results in a variables view. Otherwise, simply show
-      // the contents as plain text.
-      if (callbackPadding && jsonpString) {
-        try {
-          jsonObject = JSON.parse(jsonpString);
-        } catch (e) {
-          jsonObjectParseError = e;
-        }
-      }
-
-      // Valid JSON or JSONP.
-      if (jsonObject) {
-        $("#response-content-json-box").hidden = false;
-        let jsonScopeName = callbackPadding
-          ? L10N.getFormatStr("jsonpScopeName", callbackPadding)
-          : L10N.getStr("jsonScopeName");
-
-        let jsonVar = { label: jsonScopeName, rawObject: jsonObject };
-        yield this._json.controller.setSingleVariable(jsonVar).expanded;
-      } else {
-        // Malformed JSON.
-        $("#response-content-textarea-box").hidden = false;
-        let infoHeader = $("#response-content-info-header");
-        infoHeader.setAttribute("value", jsonObjectParseError);
-        infoHeader.setAttribute("tooltiptext", jsonObjectParseError);
-        infoHeader.hidden = false;
-
-        let editor = yield NetMonitorView.editor("#response-content-textarea");
-        editor.setMode(Editor.modes.js);
-        editor.setText(responseBody);
-      }
-    } else if (mimeType.includes("image/")) {
-      // Handle images.
-      $("#response-content-image-box").setAttribute("align", "center");
-      $("#response-content-image-box").setAttribute("pack", "center");
-      $("#response-content-image-box").hidden = false;
-      $("#response-content-image").src = formDataURI(mimeType, encoding, responseBody);
-
-      // Immediately display additional information about the image:
-      // file name, mime type and encoding.
-      $("#response-content-image-name-value").setAttribute("value",
-        NetworkHelper.nsIURL(url).fileName);
-      $("#response-content-image-mime-value").setAttribute("value", mimeType);
-
-      // Wait for the image to load in order to display the width and height.
-      $("#response-content-image").onload = e => {
-        // XUL images are majestic so they don't bother storing their dimensions
-        // in width and height attributes like the rest of the folk. Hack around
-        // this by getting the bounding client rect and subtracting the margins.
-        let { width, height } = e.target.getBoundingClientRect();
-        let dimensions = (width - 2) + " \u00D7 " + (height - 2);
-        $("#response-content-image-dimensions-value").setAttribute("value",
-          dimensions);
-      };
-    } else {
-      $("#response-content-textarea-box").hidden = false;
-      let editor = yield NetMonitorView.editor("#response-content-textarea");
-      editor.setMode(Editor.modes.text);
-      editor.setText(responseBody);
-
-      // Maybe set a more appropriate mode in the Source Editor if possible,
-      // but avoid doing this for very large files.
-      if (responseBody.length < SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE) {
-        let mapping = Object.keys(CONTENT_MIME_TYPE_MAPPINGS).find(key => {
-          return mimeType.includes(key);
-        });
-
-        if (mapping) {
-          editor.setMode(CONTENT_MIME_TYPE_MAPPINGS[mapping]);
-        }
-      }
-    }
-
-    window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
-  }),
-
-  /**
-   * Sets the timings information shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   */
-  _setTimingsInformation: function (response) {
-    if (!response) {
-      return;
-    }
-    let { blocked, dns, connect, send, wait, receive } = response.timings;
-
-    let tabboxWidth = $("#details-pane").getAttribute("width");
-
-    // Other nodes also take some space.
-    let availableWidth = tabboxWidth / 2;
-    let scale = (response.totalTime > 0 ?
-                 Math.max(availableWidth / response.totalTime, 0) :
-                 0);
-
-    $("#timings-summary-blocked .requests-menu-timings-box")
-      .setAttribute("width", blocked * scale);
-    $("#timings-summary-blocked .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", blocked));
-
-    $("#timings-summary-dns .requests-menu-timings-box")
-      .setAttribute("width", dns * scale);
-    $("#timings-summary-dns .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", dns));
-
-    $("#timings-summary-connect .requests-menu-timings-box")
-      .setAttribute("width", connect * scale);
-    $("#timings-summary-connect .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect));
-
-    $("#timings-summary-send .requests-menu-timings-box")
-      .setAttribute("width", send * scale);
-    $("#timings-summary-send .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", send));
-
-    $("#timings-summary-wait .requests-menu-timings-box")
-      .setAttribute("width", wait * scale);
-    $("#timings-summary-wait .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", wait));
-
-    $("#timings-summary-receive .requests-menu-timings-box")
-      .setAttribute("width", receive * scale);
-    $("#timings-summary-receive .requests-menu-timings-total")
-      .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", receive));
-
-    $("#timings-summary-dns .requests-menu-timings-box")
-      .style.transform = "translateX(" + (scale * blocked) + "px)";
-    $("#timings-summary-connect .requests-menu-timings-box")
-      .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
-    $("#timings-summary-send .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect)) + "px)";
-    $("#timings-summary-wait .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
-    $("#timings-summary-receive .requests-menu-timings-box")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
-          "px)";
-
-    $("#timings-summary-dns .requests-menu-timings-total")
-      .style.transform = "translateX(" + (scale * blocked) + "px)";
-    $("#timings-summary-connect .requests-menu-timings-total")
-      .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
-    $("#timings-summary-send .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect)) + "px)";
-    $("#timings-summary-wait .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
-    $("#timings-summary-receive .requests-menu-timings-total")
-      .style.transform =
-        "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
-         "px)";
-  },
-
-  /**
-   * Sets the preview for HTML responses shown in this view.
-   *
-   * @param object response
-   *        The message received from the server.
-   * @return object
-   *        A promise that is resolved when the html preview is rendered.
-   */
-  _setHtmlPreview: Task.async(function* (response) {
-    if (!response) {
-      return promise.resolve();
-    }
-    let { text } = response.content;
-    let responseBody = yield gNetwork.getString(text);
-
-    // Always disable JS when previewing HTML responses.
-    let iframe = $("#response-preview");
-    iframe.contentDocument.docShell.allowJavascript = false;
-    iframe.contentDocument.documentElement.innerHTML = responseBody;
-
-    window.emit(EVENTS.RESPONSE_HTML_PREVIEW_DISPLAYED);
-    return undefined;
-  }),
-
-  /**
-   * Sets the security information shown in this view.
-   *
-   * @param object securityInfo
-   *        The data received from server
-   * @param string url
-   *        The URL of this request
-   * @return object
-   *        A promise that is resolved when the security info is rendered.
-   */
-  _setSecurityInfo: Task.async(function* (securityInfo, url) {
-    if (!securityInfo) {
-      // We don't have security info. This could mean one of two things:
-      // 1) This connection is not secure and this tab is not visible and thus
-      //    we shouldn't be here.
-      // 2) We have already received securityState and the tab is visible BUT
-      //    the rest of the information is still on its way. Once it arrives
-      //    this method is called again.
-      return;
-    }
-
-    /**
-     * A helper that sets value and tooltiptext attributes of an element to
-     * specified value.
-     *
-     * @param string selector
-     *        A selector for the element.
-     * @param string value
-     *        The value to set. If this evaluates to false a placeholder string
-     *        <Not Available> is used instead.
-     */
-    function setValue(selector, value) {
-      let label = $(selector);
-      if (!value) {
-        label.setAttribute("value", L10N.getStr(
-          "netmonitor.security.notAvailable"));
-        label.setAttribute("tooltiptext", label.getAttribute("value"));
-      } else {
-        label.setAttribute("value", value);
-        label.setAttribute("tooltiptext", value);
-      }
-    }
-
-    let errorbox = $("#security-error");
-    let infobox = $("#security-information");
-
-    if (securityInfo.state === "secure" || securityInfo.state === "weak") {
-      infobox.hidden = false;
-      errorbox.hidden = true;
-
-      // Warning icons
-      let cipher = $("#security-warning-cipher");
-
-      if (securityInfo.state === "weak") {
-        cipher.hidden = securityInfo.weaknessReasons.indexOf("cipher") === -1;
-      } else {
-        cipher.hidden = true;
-      }
-
-      let enabledLabel = L10N.getStr("netmonitor.security.enabled");
-      let disabledLabel = L10N.getStr("netmonitor.security.disabled");
-
-      // Connection parameters
-      setValue("#security-protocol-version-value",
-        securityInfo.protocolVersion);
-      setValue("#security-ciphersuite-value", securityInfo.cipherSuite);
-
-      // Host header
-      let domain = getUriHostPort(url);
-      let hostHeader = L10N.getFormatStr("netmonitor.security.hostHeader",
-        domain);
-      setValue("#security-info-host-header", hostHeader);
-
-      // Parameters related to the domain
-      setValue("#security-http-strict-transport-security-value",
-                securityInfo.hsts ? enabledLabel : disabledLabel);
-
-      setValue("#security-public-key-pinning-value",
-                securityInfo.hpkp ? enabledLabel : disabledLabel);
-
-      // Certificate parameters
-      let cert = securityInfo.cert;
-      setValue("#security-cert-subject-cn", cert.subject.commonName);
-      setValue("#security-cert-subject-o", cert.subject.organization);
-      setValue("#security-cert-subject-ou", cert.subject.organizationalUnit);
-
-      setValue("#security-cert-issuer-cn", cert.issuer.commonName);
-      setValue("#security-cert-issuer-o", cert.issuer.organization);
-      setValue("#security-cert-issuer-ou", cert.issuer.organizationalUnit);
-
-      setValue("#security-cert-validity-begins", cert.validity.start);
-      setValue("#security-cert-validity-expires", cert.validity.end);
-
-      setValue("#security-cert-sha1-fingerprint", cert.fingerprint.sha1);
-      setValue("#security-cert-sha256-fingerprint", cert.fingerprint.sha256);
-    } else {
-      infobox.hidden = true;
-      errorbox.hidden = false;
-
-      // Strip any HTML from the message.
-      let plain = new DOMParser().parseFromString(securityInfo.errorMessage,
-        "text/html");
-      setValue("#security-error-message", plain.body.textContent);
-    }
-  }),
-
-  _dataSrc: null,
-  _headers: null,
-  _cookies: null,
-  _params: null,
-  _json: null,
-  _paramsQueryString: "",
-  _paramsFormData: "",
-  _paramsPostPayload: "",
-  _requestHeaders: "",
-  _responseHeaders: "",
-  _requestCookies: "",
-  _responseCookies: ""
-};
-
-/**
  * Functions handling the performance statistics view.
  */
 function PerformanceStatisticsView() {
 }
 
 PerformanceStatisticsView.prototype = {
   /**
+   * Initialization function, called when the debugger is started.
+   */
+  initialize: function (store) {
+    this.store = store;
+  },
+
+  /**
    * Initializes and displays empty charts in this container.
    */
   displayPlaceholderCharts: function () {
     this._createChart({
       id: "#primed-cache-chart",
       title: "charts.cacheEnabled"
     });
     this._createChart({
@@ -1310,17 +139,17 @@ PerformanceStatisticsView.prototype = {
       data: data,
       strings: strings,
       totals: totals,
       sorted: sorted
     });
 
     chart.on("click", (_, item) => {
       // Reset FilterButtons and enable one filter exclusively
-      gStore.dispatch(Actions.enableFilterOnly(item.label));
+      this.store.dispatch(Actions.enableFilterOnly(item.label));
       NetMonitorView.showNetworkInspectorView();
     });
 
     container.appendChild(chart.node);
   },
 
   /**
    * Sanitizes the data source used for creating charts, to follow the
@@ -1388,22 +217,16 @@ PerformanceStatisticsView.prototype = {
       data[type].count++;
     }
 
     return data.filter(e => e.count > 0);
   },
 };
 
 /**
- * DOM query helper.
- */
-var $ = (selector, target = document) => target.querySelector(selector);
-var $all = (selector, target = document) => target.querySelectorAll(selector);
-
-/**
  * Checks if the "Expiration Calculations" defined in section 13.2.4 of the
  * "HTTP/1.1: Caching in HTTP" spec holds true for a collection of headers.
  *
  * @param object
  *        An object containing the { responseHeaders, status } properties.
  * @return boolean
  *         True if the response is fresh and loaded from cache.
  */
@@ -1434,49 +257,9 @@ function responseIsFresh({ responseHeade
   // Check the "Expires" header for a valid date.
   if (expires && Date.parse(expires.value)) {
     return true;
   }
 
   return false;
 }
 
-/**
- * Makes sure certain properties are available on all objects in a data store.
- *
- * @param array dataStore
- *        A list of objects for which to check the availability of properties.
- * @param array mandatoryFields
- *        A list of strings representing properties of objects in dataStore.
- * @return object
- *         A promise resolved when all objects in dataStore contain the
- *         properties defined in mandatoryFields.
- */
-function whenDataAvailable(dataStore, mandatoryFields) {
-  let deferred = promise.defer();
-
-  let interval = setInterval(() => {
-    if (dataStore.every(item => {
-      return mandatoryFields.every(field => field in item);
-    })) {
-      clearInterval(interval);
-      clearTimeout(timer);
-      deferred.resolve();
-    }
-  }, WDA_DEFAULT_VERIFY_INTERVAL);
-
-  let timer = setTimeout(() => {
-    clearInterval(interval);
-    deferred.reject(new Error("Timed out while waiting for data"));
-  }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
-
-  return deferred.promise;
-}
-
-/**
- * Preliminary setup for the NetMonitorView object.
- */
-NetMonitorView.Toolbar = new ToolbarView();
-NetMonitorView.RequestsMenu = new RequestsMenuView();
-NetMonitorView.Sidebar = new SidebarView();
-NetMonitorView.CustomRequest = new CustomRequestView();
-NetMonitorView.NetworkDetails = new NetworkDetailsView();
-NetMonitorView.PerformanceStatistics = new PerformanceStatisticsView();
+exports.PerformanceStatisticsView = PerformanceStatisticsView;