Bug 1418928 - requestCookies and responseCookies should be loaded lazily r?honza draft
authorRicky Chien <ricky060709@gmail.com>
Fri, 24 Nov 2017 14:57:10 +0800
changeset 706359 8f69082809ea411078ec618898d69a408880a352
parent 706025 a21f4e2ce5186e2dc9ee411b07e9348866b4ef30
child 742637 ee4ba59dd3c8d87ac1ef441b217e6d1431dc275e
push id91779
push userbmo:rchien@mozilla.com
push dateFri, 01 Dec 2017 18:45:13 +0000
reviewershonza
bugs1418928
milestone59.0a1
Bug 1418928 - requestCookies and responseCookies should be loaded lazily r?honza MozReview-Commit-ID: bywCQWGqWI
devtools/client/netmonitor/src/components/CookiesPanel.js
devtools/client/netmonitor/src/components/MonitorPanel.js
devtools/client/netmonitor/src/components/RequestListColumnCookies.js
devtools/client/netmonitor/src/components/RequestListColumnSetCookies.js
devtools/client/netmonitor/src/components/RequestListContent.js
devtools/client/netmonitor/src/components/RequestListItem.js
devtools/client/netmonitor/src/components/TabboxPanel.js
devtools/client/netmonitor/src/components/Toolbar.js
devtools/client/netmonitor/src/connector/firefox-connector.js
devtools/client/netmonitor/src/connector/firefox-data-provider.js
devtools/client/netmonitor/src/constants.js
devtools/client/netmonitor/src/har/har-builder.js
devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
devtools/client/netmonitor/src/request-list-context-menu.js
devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js
devtools/client/netmonitor/test/browser_net_autoscroll.js
devtools/client/netmonitor/test/browser_net_cause.js
devtools/client/netmonitor/test/browser_net_charts-01.js
devtools/client/netmonitor/test/browser_net_charts-02.js
devtools/client/netmonitor/test/browser_net_charts-03.js
devtools/client/netmonitor/test/browser_net_charts-04.js
devtools/client/netmonitor/test/browser_net_charts-05.js
devtools/client/netmonitor/test/browser_net_charts-06.js
devtools/client/netmonitor/test/browser_net_charts-07.js
devtools/client/netmonitor/test/browser_net_complex-params.js
devtools/client/netmonitor/test/browser_net_copy_params.js
devtools/client/netmonitor/test/browser_net_cors_requests.js
devtools/client/netmonitor/test/browser_net_curl-utils.js
devtools/client/netmonitor/test/browser_net_header-docs.js
devtools/client/netmonitor/test/browser_net_headers-alignment.js
devtools/client/netmonitor/test/browser_net_params_sorted.js
devtools/client/netmonitor/test/browser_net_post-data-01.js
devtools/client/netmonitor/test/browser_net_post-data-02.js
devtools/client/netmonitor/test/browser_net_post-data-03.js
devtools/client/netmonitor/test/browser_net_post-data-04.js
devtools/client/netmonitor/test/browser_net_raw_headers.js
devtools/client/netmonitor/test/browser_net_resend.js
devtools/client/netmonitor/test/browser_net_resend_cors.js
devtools/client/netmonitor/test/browser_net_resend_headers.js
devtools/client/netmonitor/test/browser_net_throttle.js
devtools/client/netmonitor/test/browser_net_timing-division.js
devtools/client/netmonitor/test/browser_net_view-source-debugger.js
devtools/client/netmonitor/test/head.js
devtools/client/netmonitor/test/shared-head.js
devtools/client/shared/components/SearchBox.js
--- a/devtools/client/netmonitor/src/components/CookiesPanel.js
+++ b/devtools/client/netmonitor/src/components/CookiesPanel.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { createFactory } = require("devtools/client/shared/vendor/react");
+const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const { L10N } = require("../utils/l10n");
 const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Component
 const PropertiesView = createFactory(require("./PropertiesView"));
 
@@ -23,79 +23,103 @@ 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,
-  openLink,
-}) {
-  let {
-    requestCookies = { cookies: [] },
-    responseCookies = { cookies: [] },
-  } = request;
+class CookiesPanel extends Component {
+  static get propTypes() {
+    return {
+      connector: PropTypes.object.isRequired,
+      openLink: PropTypes.func,
+      request: PropTypes.object.isRequired,
+    };
+  }
 
-  requestCookies = requestCookies.cookies || requestCookies;
-  responseCookies = responseCookies.cookies || responseCookies;
-
-  if (!requestCookies.length && !responseCookies.length) {
-    return div({ className: "empty-notice" },
-      COOKIES_EMPTY_TEXT
-    );
+  componentDidMount() {
+    this.maybeFetchCookies(this.props);
   }
 
-  let object = {};
-
-  if (responseCookies.length) {
-    object[RESPONSE_COOKIES] = sortObjectKeys(getProperties(responseCookies));
+  componentWillReceiveProps(nextProps) {
+    this.maybeFetchCookies(nextProps);
   }
 
-  if (requestCookies.length) {
-    object[REQUEST_COOKIES] = sortObjectKeys(getProperties(requestCookies));
+  /**
+   * When switching to another request, lazily fetch request cookies
+   * from the backend. The panel will first be empty and then display the content.
+   */
+  maybeFetchCookies(props) {
+    if (props.request.requestCookiesAvailable && !props.request.requestCookies) {
+      props.connector.requestData(props.request.id, "requestCookies");
+    }
+    if (props.request.responseCookiesAvailable && !props.request.responseCookies) {
+      props.connector.requestData(props.request.id, "responseCookies");
+    }
   }
 
-  return (
-    div({ className: "panel-container" },
-      PropertiesView({
-        object,
-        filterPlaceHolder: COOKIES_FILTER_TEXT,
-        sectionNames: SECTION_NAMES,
-        openLink,
-      })
-    )
-  );
-}
-
-CookiesPanel.displayName = "CookiesPanel";
-
-CookiesPanel.propTypes = {
-  request: PropTypes.object.isRequired,
-  openLink: PropTypes.func,
-};
+  /**
+   * 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}
+   */
+  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;
+    }, {});
+  }
 
-/**
- * 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;
+  render() {
+    let {
+      request: {
+        requestCookies = { cookies: [] },
+        responseCookies = { cookies: [] },
+      },
+      openLink,
+    } = this.props;
+
+    requestCookies = requestCookies.cookies || requestCookies;
+    responseCookies = responseCookies.cookies || responseCookies;
+
+    if (!requestCookies.length && !responseCookies.length) {
+      return div({ className: "empty-notice" },
+        COOKIES_EMPTY_TEXT
+      );
     }
-    return map;
-  }, {});
+
+    let object = {};
+
+    if (responseCookies.length) {
+      object[RESPONSE_COOKIES] = sortObjectKeys(this.getProperties(responseCookies));
+    }
+
+    if (requestCookies.length) {
+      object[REQUEST_COOKIES] = sortObjectKeys(this.getProperties(requestCookies));
+    }
+
+    return (
+      div({ className: "panel-container" },
+        PropertiesView({
+          object,
+          filterPlaceHolder: COOKIES_FILTER_TEXT,
+          sectionNames: SECTION_NAMES,
+          openLink,
+        })
+      )
+    );
+  }
 }
 
 module.exports = CookiesPanel;
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ b/devtools/client/netmonitor/src/components/MonitorPanel.js
@@ -90,17 +90,17 @@ class MonitorPanel extends Component {
 
     let initialWidth = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-height");
 
     return (
       div({ className: "monitor-panel" },
-        Toolbar(),
+        Toolbar({ connector }),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${initialWidth}px`,
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: "1px",
           startPanel: RequestList({ isEmpty, connector }),
--- a/devtools/client/netmonitor/src/components/RequestListColumnCookies.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnCookies.js
@@ -8,28 +8,46 @@ const { Component } = require("devtools/
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const { div } = dom;
 
 class RequestListColumnCookies extends Component {
   static get propTypes() {
     return {
+      connector: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
     };
   }
 
+  componentDidMount() {
+    this.maybeFetchRequestCookies(this.props);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.maybeFetchRequestCookies(nextProps);
+  }
+
   shouldComponentUpdate(nextProps) {
     let { requestCookies: currRequestCookies = { cookies: [] } } = this.props.item;
     let { requestCookies: nextRequestCookies = { cookies: [] } } = nextProps.item;
     currRequestCookies = currRequestCookies.cookies || currRequestCookies;
     nextRequestCookies = nextRequestCookies.cookies || nextRequestCookies;
     return currRequestCookies !== nextRequestCookies;
   }
 
+  /**
+   * Lazily fetch request cookies from the backend.
+   */
+  maybeFetchRequestCookies(props) {
+    if (props.item.requestCookiesAvailable && !props.requestCookies) {
+      props.connector.requestData(props.item.id, "requestCookies");
+    }
+  }
+
   render() {
     let { requestCookies = { cookies: [] } } = this.props.item;
     requestCookies = requestCookies.cookies || requestCookies;
     let requestCookiesLength = requestCookies.length > 0 ? requestCookies.length : "";
     return (
       div({
         className: "requests-list-column requests-list-cookies",
         title: requestCookiesLength
--- a/devtools/client/netmonitor/src/components/RequestListColumnSetCookies.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnSetCookies.js
@@ -8,28 +8,46 @@ const { Component } = require("devtools/
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const { div } = dom;
 
 class RequestListColumnSetCookies extends Component {
   static get propTypes() {
     return {
+      connector: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
     };
   }
 
+  componentDidMount() {
+    this.maybeFetchResponseCookies(this.props);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    this.maybeFetchResponseCookies(nextProps);
+  }
+
   shouldComponentUpdate(nextProps) {
     let { responseCookies: currResponseCookies = { cookies: [] } } = this.props.item;
     let { responseCookies: nextResponseCookies = { cookies: [] } } = nextProps.item;
     currResponseCookies = currResponseCookies.cookies || currResponseCookies;
     nextResponseCookies = nextResponseCookies.cookies || nextResponseCookies;
     return currResponseCookies !== nextResponseCookies;
   }
 
+  /**
+   * Lazily fetch response cookies from the backend.
+   */
+  maybeFetchResponseCookies(props) {
+    if (props.item.responseCookiesAvailable && !props.responseCookies) {
+      props.connector.requestData(props.item.id, "responseCookies");
+    }
+  }
+
   render() {
     let { responseCookies = { cookies: [] } } = this.props.item;
     responseCookies = responseCookies.cookies || responseCookies;
     let responseCookiesLength = responseCookies.length > 0 ? responseCookies.length : "";
     return (
       div({
         className: "requests-list-column requests-list-set-cookies",
         title: responseCookiesLength
--- a/devtools/client/netmonitor/src/components/RequestListContent.js
+++ b/devtools/client/netmonitor/src/components/RequestListContent.js
@@ -206,16 +206,17 @@ class RequestListContent extends Compone
    * scrolled to bottom, but allow scrolling up with the selection.
    */
   onFocusedNodeChange() {
     this.shouldScrollBottom = false;
   }
 
   render() {
     const {
+      connector,
       columns,
       displayedRequests,
       firstRequestStartedMillis,
       onCauseBadgeMouseDown,
       onItemMouseDown,
       onSecurityIconMouseDown,
       onWaterfallMouseDown,
       scale,
@@ -231,16 +232,17 @@ class RequestListContent extends Compone
             tabIndex: 0,
             onKeyDown: this.onKeyDown,
             style: { "--timings-scale": scale, "--timings-rev-scale": 1 / scale }
           },
             RequestListHeader(),
             displayedRequests.map((item, index) => RequestListItem({
               firstRequestStartedMillis,
               fromCache: item.status === "304" || item.fromCache,
+              connector,
               columns,
               item,
               index,
               isSelected: item.id === (selectedRequest && selectedRequest.id),
               key: item.id,
               onContextMenu: this.onContextMenu,
               onFocusedNodeChange: this.onFocusedNodeChange,
               onMouseDown: () => onItemMouseDown(item.id),
--- a/devtools/client/netmonitor/src/components/RequestListItem.js
+++ b/devtools/client/netmonitor/src/components/RequestListItem.js
@@ -52,31 +52,34 @@ const UPDATED_REQ_ITEM_PROPS = [
   "method",
   "url",
   "remoteAddress",
   "cause",
   "contentSize",
   "transferredSize",
   "startedMillis",
   "totalTime",
+  "requestCookies",
+  "responseCookies",
 ];
 
 const UPDATED_REQ_PROPS = [
   "firstRequestStartedMillis",
   "index",
   "isSelected",
   "waterfallWidth",
 ];
 
 /**
  * Render one row in the request list.
  */
 class RequestListItem extends Component {
   static get propTypes() {
     return {
+      connector: PropTypes.object.isRequired,
       columns: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
       index: PropTypes.number.isRequired,
       isSelected: PropTypes.bool.isRequired,
       firstRequestStartedMillis: PropTypes.number.isRequired,
       fromCache: PropTypes.bool,
       onCauseBadgeMouseDown: PropTypes.func.isRequired,
       onContextMenu: PropTypes.func.isRequired,
@@ -106,16 +109,17 @@ class RequestListItem extends Component 
       if (this.props.onFocusedNodeChange) {
         this.props.onFocusedNodeChange();
       }
     }
   }
 
   render() {
     let {
+      connector,
       columns,
       item,
       index,
       isSelected,
       firstRequestStartedMillis,
       fromCache,
       onContextMenu,
       onMouseDown,
@@ -142,18 +146,18 @@ class RequestListItem extends Component 
         columns.get("file") && RequestListColumnFile({ item }),
         columns.get("protocol") && RequestListColumnProtocol({ item }),
         columns.get("scheme") && RequestListColumnScheme({ item }),
         columns.get("domain") && RequestListColumnDomain({ item,
                                                            onSecurityIconMouseDown }),
         columns.get("remoteip") && RequestListColumnRemoteIP({ item }),
         columns.get("cause") && RequestListColumnCause({ item, onCauseBadgeMouseDown }),
         columns.get("type") && RequestListColumnType({ item }),
-        columns.get("cookies") && RequestListColumnCookies({ item }),
-        columns.get("setCookies") && RequestListColumnSetCookies({ item }),
+        columns.get("cookies") && RequestListColumnCookies({ connector, item }),
+        columns.get("setCookies") && RequestListColumnSetCookies({ connector, item }),
         columns.get("transferred") && RequestListColumnTransferredSize({ item }),
         columns.get("contentSize") && RequestListColumnContentSize({ item }),
         columns.get("startTime") &&
           RequestListColumnStartTime({ item, firstRequestStartedMillis }),
         columns.get("endTime") &&
           RequestListColumnEndTime({ item, firstRequestStartedMillis }),
         columns.get("responseTime") &&
           RequestListColumnResponseTime({ item, firstRequestStartedMillis }),
--- a/devtools/client/netmonitor/src/components/TabboxPanel.js
+++ b/devtools/client/netmonitor/src/components/TabboxPanel.js
@@ -63,17 +63,21 @@ function TabboxPanel({
           openLink,
           request,
         }),
       ),
       TabPanel({
         id: PANELS.COOKIES,
         title: COOKIES_TITLE,
       },
-        CookiesPanel({ request, openLink }),
+        CookiesPanel({
+          connector,
+          openLink,
+          request,
+        }),
       ),
       TabPanel({
         id: PANELS.PARAMS,
         title: PARAMS_TITLE,
       },
         ParamsPanel({ connector, openLink, request }),
       ),
       TabPanel({
--- a/devtools/client/netmonitor/src/components/Toolbar.js
+++ b/devtools/client/netmonitor/src/components/Toolbar.js
@@ -51,16 +51,17 @@ const DISABLE_CACHE_LABEL = L10N.getStr(
  * Network monitor toolbar component.
  *
  * Toolbar contains a set of useful tools to control network requests
  * as well as set of filters for filtering the content.
  */
 class Toolbar extends Component {
   static get propTypes() {
     return {
+      connector: PropTypes.object.isRequired,
       toggleRecording: PropTypes.func.isRequired,
       recording: PropTypes.bool.isRequired,
       clearRequests: PropTypes.func.isRequired,
       requestFilterTypes: PropTypes.object.isRequired,
       setRequestFilterText: PropTypes.func.isRequired,
       networkDetailsToggleDisabled: PropTypes.bool.isRequired,
       networkDetailsOpen: PropTypes.bool.isRequired,
       toggleNetworkDetails: PropTypes.func.isRequired,
@@ -73,16 +74,17 @@ class Toolbar extends Component {
       toggleRequestFilterType: PropTypes.func.isRequired,
       filteredRequests: PropTypes.array.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.autocompleteProvider = this.autocompleteProvider.bind(this);
+    this.onSearchBoxFocus = this.onSearchBoxFocus.bind(this);
     this.toggleRequestFilterType = this.toggleRequestFilterType.bind(this);
     this.updatePersistentLogsEnabled = this.updatePersistentLogsEnabled.bind(this);
     this.updateBrowserCacheDisabled = this.updateBrowserCacheDisabled.bind(this);
   }
 
   componentDidMount() {
     Services.prefs.addObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
                                this.updatePersistentLogsEnabled);
@@ -125,16 +127,25 @@ class Toolbar extends Component {
     this.props.disableBrowserCache(
       Services.prefs.getBoolPref(DEVTOOLS_DISABLE_CACHE_PREF));
   }
 
   autocompleteProvider(filter) {
     return autocompleteProvider(filter, this.props.filteredRequests);
   }
 
+  onSearchBoxFocus() {
+    let { connector, filteredRequests } = this.props;
+
+    // Fetch responseCookies for building autocomplete list
+    filteredRequests.forEach((request) => {
+      connector.requestData(request.id, "responseCookies");
+    });
+  }
+
   render() {
     let {
       toggleRecording,
       clearRequests,
       requestFilterTypes,
       setRequestFilterText,
       networkDetailsToggleDisabled,
       networkDetailsOpen,
@@ -233,16 +244,17 @@ class Toolbar extends Component {
         span({ className: "devtools-toolbar-group" },
           SearchBox({
             delay: FILTER_SEARCH_DELAY,
             keyShortcut: SEARCH_KEY_SHORTCUT,
             placeholder: SEARCH_PLACE_HOLDER,
             type: "filter",
             ref: "searchbox",
             onChange: setRequestFilterText,
+            onFocus: this.onSearchBoxFocus,
             autocompleteProvider: this.autocompleteProvider,
           }),
           button({
             className: toggleDetailButtonClass,
             title: toggleDetailButtonTitle,
             disabled: networkDetailsToggleDisabled,
             tabIndex: "0",
             onClick: toggleNetworkDetails,
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -63,28 +63,29 @@ class FirefoxConnector {
     }
 
     this.displayCachedEvents();
   }
 
   async disconnect() {
     this.actions.batchReset();
 
-    // The timeline front wasn't initialized and started if the server wasn't
-    // recent enough to emit the markers we were interested in.
-    if (this.tabTarget.getTrait("documentLoadingMarkers") && this.timelineFront) {
-      this.timelineFront.off("doc-loading", this.onDocLoadingMarker);
-      await this.timelineFront.destroy();
-    }
-
     this.removeListeners();
 
-    this.tabTarget.off("will-navigate");
+    if (this.tabTarget) {
+      // The timeline front wasn't initialized and started if the server wasn't
+      // recent enough to emit the markers we were interested in.
+      if (this.tabTarget.getTrait("documentLoadingMarkers") && this.timelineFront) {
+        this.timelineFront.off("doc-loading", this.onDocLoadingMarker);
+        await this.timelineFront.destroy();
+      }
 
-    this.tabTarget = null;
+      this.tabTarget.off("will-navigate");
+      this.tabTarget = null;
+    }
     this.webConsoleClient = null;
     this.timelineFront = null;
     this.dataProvider = null;
     this.panel = null;
   }
 
   pause() {
     this.removeListeners();
@@ -98,19 +99,23 @@ class FirefoxConnector {
     this.tabTarget.on("close", this.disconnect);
     this.webConsoleClient.on("networkEvent",
       this.dataProvider.onNetworkEvent);
     this.webConsoleClient.on("networkEventUpdate",
       this.dataProvider.onNetworkEventUpdate);
   }
 
   removeListeners() {
-    this.tabTarget.off("close");
-    this.webConsoleClient.off("networkEvent");
-    this.webConsoleClient.off("networkEventUpdate");
+    if (this.tabTarget) {
+      this.tabTarget.off("close");
+    }
+    if (this.webConsoleClient) {
+      this.webConsoleClient.off("networkEvent");
+      this.webConsoleClient.off("networkEventUpdate");
+    }
   }
 
   willNavigate() {
     if (!Services.prefs.getBoolPref("devtools.netmonitor.persistlog")) {
       this.actions.batchReset();
       this.actions.clearRequests();
     } else {
       // If the log is persistent, just clear all accumulated timing markers.
@@ -311,14 +316,20 @@ class FirefoxConnector {
    * @param {number} sourceLine source line number
    */
   viewSourceInDebugger(sourceURL, sourceLine) {
     if (this.toolbox) {
       this.toolbox.viewSourceInDebugger(sourceURL, sourceLine);
     }
   }
 
+  /**
+   * Fetch networkEventUpdate websocket message from back-end when
+   * data provider is connected.
+   * @param {object} request network request instance
+   * @param {string} type NetworkEventUpdate type
+   */
   requestData(request, type) {
     return this.dataProvider.requestData(request, type);
   }
 }
 
 module.exports = new FirefoxConnector();
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -2,19 +2,17 @@
  * 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 block-scoped-var */
 
 "use strict";
 
 const { EVENTS } = require("../constants");
 const { CurlUtils } = require("devtools/client/shared/curl");
-const {
-  fetchHeaders,
-} = require("../utils/request-utils");
+const { fetchHeaders } = require("../utils/request-utils");
 
 /**
  * This object is responsible for fetching additional HTTP
  * data from the backend over RDP protocol.
  *
  * The object also keeps track of RDP requests in-progress,
  * so it's possible to determine whether all has been fetched
  * or not.
@@ -175,16 +173,20 @@ class FirefoxDataProvider {
       // two new-line characters at the end.
       const headersSize = headers.reduce((acc, { name, value }) => {
         return acc + name.length + value.length + 2;
       }, 0);
 
       requestPostData.postData.text = postData;
       payload.requestPostData = Object.assign({}, requestPostData);
       payload.requestHeadersFromUploadStream = { headers, headersSize };
+
+      // Lock down requestPostDataAvailable once we fetch data from back-end.
+      // Using this as flag to prevent fetching arrived data again.
+      payload.requestPostDataAvailable = false;
     }
     return payload;
   }
 
   async fetchResponseCookies(responseCookies) {
     let payload = {};
     if (responseCookies) {
       let resCookies = [];
@@ -197,16 +199,20 @@ class FirefoxDataProvider {
           resCookies.push(Object.assign({}, cookie, {
             value: await this.getLongString(cookie.value),
           }));
         }
         if (resCookies.length) {
           payload.responseCookies = resCookies;
         }
       }
+
+      // Lock down responseCookiesAvailable once we fetch data from back-end.
+      // Using this as flag to prevent fetching arrived data again.
+      payload.responseCookiesAvailable = false;
     }
     return payload;
   }
 
   async fetchRequestCookies(requestCookies) {
     let payload = {};
     if (requestCookies) {
       let reqCookies = [];
@@ -219,16 +225,20 @@ class FirefoxDataProvider {
           reqCookies.push(Object.assign({}, cookie, {
             value: await this.getLongString(cookie.value),
           }));
         }
         if (reqCookies.length) {
           payload.requestCookies = reqCookies;
         }
       }
+
+      // Lock down requestCookiesAvailable once we fetch data from back-end.
+      // Using this as flag to prevent fetching arrived data again.
+      payload.requestCookiesAvailable = false;
     }
     return payload;
   }
 
   /**
    * Access a payload item from payload queue.
    *
    * @param {string} id request id
@@ -260,22 +270,19 @@ class FirefoxDataProvider {
     }
 
     let { payload } = this.getRequestFromQueue(id);
 
     // The payload is ready when all values in the record are true.
     // Note that we never fetch response header/cookies for request with security issues.
     // Bug 1404917 should simplify this heuristic by making all these field be lazily
     // fetched, only on-demand.
-    return record.requestHeaders && record.requestCookies && record.eventTimings &&
-      (
-        (record.responseHeaders && record.responseCookies) ||
-        payload.securityState === "broken" ||
-        (!payload.status && payload.responseContentAvailable)
-      );
+    return record.requestHeaders && record.eventTimings &&
+      (record.responseHeaders || payload.securityState === "broken" ||
+        (!payload.status && payload.responseContentAvailable));
   }
 
   /**
    * Merge upcoming networkEventUpdate payload into existing one.
    *
    * @param {string} id request id
    * @param {object} payload request data payload
    */
@@ -337,19 +344,17 @@ class FirefoxDataProvider {
         url,
       },
       startedDateTime,
     } = networkInfo;
 
     // Create tracking record for this request.
     this.rdpRequestMap.set(actor, {
       requestHeaders: false,
-      requestCookies: false,
       responseHeaders: false,
-      responseCookies: false,
       eventTimings: false,
     });
 
     this.addRequest(actor, {
       cause,
       fromCache,
       fromServiceWorker,
       isXHR,
@@ -376,27 +381,25 @@ class FirefoxDataProvider {
     // When we pause and resume, we may receive `networkEventUpdate` for a request
     // that started during the pause and we missed its `networkEvent`.
     if (!this.rdpRequestMap.has(actor)) {
       return;
     }
 
     switch (updateType) {
       case "requestHeaders":
-      case "requestCookies":
       case "responseHeaders":
-      case "responseCookies":
         this.requestPayloadData(actor, updateType);
         break;
+      case "requestCookies":
+      case "responseCookies":
       case "requestPostData":
-        this.updateRequest(actor, {
-          // This field helps knowing when/if requestPostData property is available
-          // and can be requested via `requestData`
-          requestPostDataAvailable: true
-        });
+        // This field helps knowing when/if updateType property is available
+        // and can be requested via `requestData`
+        this.updateRequest(actor, { [`${updateType}Available`]: true });
         break;
       case "securityInfo":
         this.updateRequest(actor, { securityState: networkInfo.securityInfo });
         break;
       case "responseStart":
         this.updateRequest(actor, {
           httpVersion: networkInfo.response.httpVersion,
           remoteAddress: networkInfo.response.remoteAddress,
@@ -549,17 +552,17 @@ class FirefoxDataProvider {
 
     let response = await new Promise((resolve, reject) => {
       // Do a RDP request to fetch data from the actor.
       if (typeof this.webConsoleClient[clientMethodName] === "function") {
         // Make sure we fetch the real actor data instead of cloned actor
         // e.g. CustomRequestPanel will clone a request with additional '-clone' actor id
         this.webConsoleClient[clientMethodName](actor.replace("-clone", ""), (res) => {
           if (res.error) {
-            console.error(res.error);
+            console.error(res.message);
           }
           resolve(res);
         });
       } else {
         reject(new Error(`Error: No such client method '${clientMethodName}'!`));
       }
     });
 
@@ -586,22 +589,22 @@ class FirefoxDataProvider {
     });
   }
 
   /**
    * Handles additional information received for a "requestCookies" packet.
    *
    * @param {object} response the message received from the server.
    */
-  onRequestCookies(response) {
-    return this.updateRequest(response.from, {
+  async onRequestCookies(response) {
+    let payload = await this.updateRequest(response.from, {
       requestCookies: response
-    }).then(() => {
-      emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
     });
+    emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
+    return payload.requestCookies;
   }
 
   /**
    * Handles additional information received for a "requestPostData" packet.
    *
    * @param {object} response the message received from the server.
    */
   async onRequestPostData(response) {
@@ -638,22 +641,22 @@ class FirefoxDataProvider {
     });
   }
 
   /**
    * Handles additional information received for a "responseCookies" packet.
    *
    * @param {object} response the message received from the server.
    */
-  onResponseCookies(response) {
-    return this.updateRequest(response.from, {
+  async onResponseCookies(response) {
+    let payload = await this.updateRequest(response.from, {
       responseCookies: response
-    }).then(() => {
-      emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
     });
+    emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
+    return payload.responseCookies;
   }
 
   /**
    * Handles additional information received via "getResponseContent" request.
    *
    * @param {object} response the message received from the server.
    */
   async onResponseContent(response) {
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -118,20 +118,22 @@ const UPDATE_PROPS = [
   "transferredSize",
   "totalTime",
   "eventTimings",
   "headersSize",
   "customQueryValue",
   "requestHeaders",
   "requestHeadersFromUploadStream",
   "requestCookies",
+  "requestCookiesAvailable",
   "requestPostData",
   "requestPostDataAvailable",
   "responseHeaders",
   "responseCookies",
+  "responseCookiesAvailable",
   "responseContent",
   "responseContentAvailable",
   "formDataSections",
   "stacktrace",
 ];
 
 const PANELS = {
   COOKIES: "cookies",
--- a/devtools/client/netmonitor/src/har/har-builder.js
+++ b/devtools/client/netmonitor/src/har/har-builder.js
@@ -263,17 +263,16 @@ HarBuilder.prototype = {
     }
 
     // If we are dealing with URL encoded body, parse parameters.
     if (CurlUtils.isUrlEncodedRequest({
       headers: requestHeaders.headers,
       postDataText: postData.text,
     })) {
       postData.mimeType = "application/x-www-form-urlencoded";
-
       // Extract form parameters and produce nice HAR array.
       let formDataSections = await getFormDataSections(
         requestHeaders,
         requestHeadersFromUploadStream,
         requestPostData,
         this._options.getString,
       );
 
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_post_data.js
@@ -17,17 +17,17 @@ add_task(function* () {
   let RequestListContextMenu = windowRequire(
     "devtools/client/netmonitor/src/request-list-context-menu");
   let { getSortedRequests } = windowRequire(
     "devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   // Execute one POST request on the page and wait till its done.
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.executeTest();
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
   let contextMenu = new RequestListContextMenu({ connector });
   let jsonString = yield contextMenu.copyAllAsHar(getSortedRequests(store.getState()));
--- a/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/src/har/test/browser_net_har_throttle_upload.js
@@ -42,17 +42,17 @@ function* throttleUploadTest(actuallyThr
   info("sending throttle request");
   yield new Promise((resolve) => {
     connector.setPreferences(request, (response) => {
       resolve(response);
     });
   });
 
   // Execute one POST request on the page and wait till its done.
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) {
     content.wrappedJSObject.executeTest2(args.size);
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
   let contextMenu = new RequestListContextMenu({ connector });
   let jsonString = yield contextMenu.copyAllAsHar(getSortedRequests(store.getState()));
--- a/devtools/client/netmonitor/src/request-list-context-menu.js
+++ b/devtools/client/netmonitor/src/request-list-context-menu.js
@@ -38,16 +38,17 @@ class RequestListContextMenu {
     let copySubmenu = [];
     let {
       id,
       isCustom,
       method,
       mimeType,
       httpVersion,
       requestHeaders,
+      requestPostData,
       requestPostDataAvailable,
       responseHeaders,
       responseContentAvailable,
       url,
     } = selectedRequest || {};
     let {
       cloneSelectedRequest,
       openStatistics,
@@ -68,17 +69,17 @@ class RequestListContextMenu {
       visible: !!(selectedRequest && getUrlQuery(url)),
       click: () => this.copyUrlParams(url),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-post-data",
       label: L10N.getStr("netmonitor.context.copyPostData"),
       accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
-      visible: !!(selectedRequest && requestPostDataAvailable),
+      visible: !!(selectedRequest && (requestPostDataAvailable || requestPostData)),
       click: () => this.copyPostData(id),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-as-curl",
       label: L10N.getStr("netmonitor.context.copyAsCurl"),
       accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
       visible: !!selectedRequest,
--- a/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js
+++ b/devtools/client/netmonitor/src/utils/filter-autocomplete-provider.js
@@ -1,17 +1,17 @@
 /* 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 { FILTER_FLAGS } = require("../constants");
 
-/*
+/**
  * Generates a value for the given filter
  * ie. if flag = status-code, will generate "200" from the given request item.
  * For flags related to cookies, it might generate an array based on the request
  * ie. ["cookie-name-1", "cookie-name-2", ...]
  *
  * @param {string} flag - flag specified in filter, ie. "status-code"
  * @param {object} request - Network request item
  * @return {string|Array} - The output is a string or an array based on the request
@@ -65,17 +65,17 @@ function getAutocompleteValuesForFlag(fl
     case "method":
     default:
       values.push(request[flag]);
   }
 
   return values;
 }
 
-/*
+/**
  * For a given lastToken passed ie. "is:", returns an array of populated flag
  * values for consumption in autocompleteProvider
  * ie. ["is:cached", "is:running", "is:from-cache"]
  *
  * @param {string} lastToken - lastToken parsed from filter input, ie "is:"
  * @param {object} requests - List of requests from which values are generated
  * @return {Array} - array of autocomplete values
  */
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -70,18 +70,16 @@ add_task(function* () {
     return waitUntil(() => !!document.querySelector(".requests-list-contents"));
   }
 
   function* waitForRequestsToOverflowContainer() {
     info("Waiting for enough requests to overflow the container");
     while (true) {
       info("Waiting for one network request");
       yield waitForNetworkEvents(monitor, 1);
-      console.log(requestsContainer.scrollHeight);
-      console.log(requestsContainer.clientHeight);
       if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
         info("The list is long enough, returning");
         return;
       }
     }
   }
 
   function scrolledToBottom(element) {
--- a/devtools/client/netmonitor/test/browser_net_cause.js
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -97,41 +97,41 @@ add_task(function* () {
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
   tab.linkedBrowser.loadURI(CAUSE_URL);
   yield wait;
 
-  // Fetch stack-trace data from the backend and wait till
-  // all packets are received.
   let requests = getSortedRequests(store.getState());
   yield Promise.all(requests.map(requestItem =>
     connector.requestData(requestItem.id, "stackTrace")));
 
   is(store.getState().requests.requests.size, EXPECTED_REQUESTS.length,
     "All the page events should be recorded.");
 
-  EXPECTED_REQUESTS.forEach((spec, i) => {
+  EXPECTED_REQUESTS.forEach(async (spec, i) => {
     let { method, url, causeType, causeUri, stack } = spec;
 
     let requestItem = getSortedRequests(store.getState()).get(i);
     verifyRequestItemTarget(
       document,
       getDisplayedRequests(store.getState()),
       requestItem,
       method,
       url,
       { cause: { type: causeType, loadingDocumentUri: causeUri } }
     );
 
     let stacktrace = requestItem.stacktrace;
     let stackLen = stacktrace ? stacktrace.length : 0;
 
+    await waitUntil(() => !!requestItem.stacktrace);
+
     if (stack) {
       ok(stacktrace, `Request #${i} has a stacktrace`);
       ok(stackLen > 0,
         `Request #${i} (${causeType}) has a stacktrace with ${stackLen} items`);
 
       // if "stack" is array, check the details about the top stack frames
       if (Array.isArray(stack)) {
         stack.forEach((frame, j) => {
--- a/devtools/client/netmonitor/test/browser_net_charts-01.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-01.js
@@ -3,22 +3,27 @@
 
 "use strict";
 
 /**
  * Makes sure Pie Charts have the right internal structure.
  */
 
 add_task(function* () {
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
+
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let pie = Chart.Pie(document, {
     width: 100,
     height: 100,
     data: [{
       size: 1,
       label: "foo"
     }, {
       size: 2,
--- a/devtools/client/netmonitor/test/browser_net_charts-02.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-02.js
@@ -6,22 +6,26 @@
 /**
  * Makes sure Pie Charts have the right internal structure when
  * initialized with empty data.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let pie = Chart.Pie(document, {
     data: null,
     width: 100,
     height: 100
   });
 
   let node = pie.node;
   let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
--- a/devtools/client/netmonitor/test/browser_net_charts-03.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -5,22 +5,26 @@
 
 /**
  * Makes sure Table Charts have the right internal structure.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let table = Chart.Table(document, {
     title: "Table title",
     data: [{
       label1: 1,
       label2: 11.1
     }, {
       label1: 2,
       label2: 12.2
--- a/devtools/client/netmonitor/test/browser_net_charts-04.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-04.js
@@ -6,22 +6,26 @@
 /**
  * Makes sure Pie Charts have the right internal structure when
  * initialized with empty data.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let table = Chart.Table(document, {
     title: "Table title",
     data: null,
     totals: {
       label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
       label2: value => "World " + L10N.numberWithDecimals(value, 2)
     },
     header: {
--- a/devtools/client/netmonitor/test/browser_net_charts-05.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-05.js
@@ -5,22 +5,26 @@
 
 /**
  * Makes sure Pie+Table Charts have the right internal structure.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let chart = Chart.PieTable(document, {
     title: "Table title",
     data: [{
       size: 1,
       label: 11.1
     }, {
       size: 2,
       label: 12.2
--- a/devtools/client/netmonitor/test/browser_net_charts-06.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-06.js
@@ -5,22 +5,26 @@
 
 /**
  * Makes sure Pie Charts correctly handle empty source data.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let pie = Chart.Pie(document, {
     data: [],
     width: 100,
     height: 100
   });
 
   let node = pie.node;
   let slices = node.querySelectorAll(".pie-chart-slice.chart-colored-blob");
--- a/devtools/client/netmonitor/test/browser_net_charts-07.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-07.js
@@ -5,22 +5,26 @@
 
 /**
  * Makes sure Table Charts correctly handle empty source data.
  */
 
 add_task(function* () {
   let { L10N } = require("devtools/client/netmonitor/src/utils/l10n");
 
-  let { monitor } = yield initNetMonitor(SIMPLE_URL);
+  let { monitor, tab } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
+  let wait = waitForNetworkEvents(monitor, 1);
+  tab.linkedBrowser.loadURI(SIMPLE_URL);
+  yield wait;
+
   let table = Chart.Table(document, {
     data: [],
     totals: {
       label1: value => "Hello " + L10N.numberWithDecimals(value, 2),
       label2: value => "World " + L10N.numberWithDecimals(value, 2)
     },
     header: {
       label1: "",
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -13,17 +13,17 @@ add_task(function* () {
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let { L10N } = windowRequire("devtools/client/netmonitor/src/utils/l10n");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 1, 6);
+  let wait = waitForNetworkEvents(monitor, 7);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   wait = waitForDOM(document, "#params-panel .tree-section", 2);
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
--- a/devtools/client/netmonitor/test/browser_net_copy_params.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_params.js
@@ -11,17 +11,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 1, 6);
+  let wait = waitForNetworkEvents(monitor, 7);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   yield testCopyUrlParamsHidden(0, false);
   yield testCopyUrlParams(0, "a");
   yield testCopyPostDataHidden(0, false);
--- a/devtools/client/netmonitor/test/browser_net_cors_requests.js
+++ b/devtools/client/netmonitor/test/browser_net_cors_requests.js
@@ -14,17 +14,17 @@ add_task(function* () {
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 1, 1);
+  let wait = waitForNetworkEvents(monitor, 2);
 
   info("Performing a CORS request");
   let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
   yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
     content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
   });
 
   info("Waiting until the requests appear in netmonitor");
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -20,17 +20,17 @@ add_task(function* () {
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
   let {
     getLongString,
     requestData,
   } = connector;
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 1, 3);
+  let wait = waitForNetworkEvents(monitor, 4);
   yield ContentTask.spawn(tab.linkedBrowser, SIMPLE_SJS, function* (url) {
     content.wrappedJSObject.performRequests(url);
   });
   yield wait;
 
   let requests = {
     get: getSortedRequests(store.getState()).get(0),
     post: getSortedRequests(store.getState()).get(1),
--- a/devtools/client/netmonitor/test/browser_net_header-docs.js
+++ b/devtools/client/netmonitor/test/browser_net_header-docs.js
@@ -15,17 +15,17 @@ add_task(function* () {
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
   let { getHeadersURL } = require("devtools/client/netmonitor/src/utils/mdn-utils");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 2);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelectorAll(".request-list-item")[0]);
 
--- a/devtools/client/netmonitor/test/browser_net_headers-alignment.js
+++ b/devtools/client/netmonitor/test/browser_net_headers-alignment.js
@@ -53,17 +53,15 @@ add_task(function* () {
     return waitUntil(() => !!document.querySelector(".requests-list-contents"));
   }
 
   function* waitForRequestsToOverflowContainer() {
     info("Waiting for enough requests to overflow the container");
     while (true) {
       info("Waiting for one network request");
       yield waitForNetworkEvents(monitor, 1);
-      console.log(requestsContainer.scrollHeight);
-      console.log(requestsContainer.clientHeight);
       if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
         info("The list is long enough, returning");
         return;
       }
     }
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_params_sorted.js
+++ b/devtools/client/netmonitor/test/browser_net_params_sorted.js
@@ -11,17 +11,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 2);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   wait = waitForDOM(document, ".headers-overview");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -20,17 +20,17 @@ add_task(function* () {
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 2);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItems = document.querySelectorAll(".request-list-item");
   for (let requestItem of requestItems) {
     requestItem.scrollIntoView();
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -14,17 +14,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "mousedown" },
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -14,17 +14,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#headers-panel .tree-section .treeLabel", 3);
   EventUtils.sendMouseEvent({ type: "click" },
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -14,17 +14,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(POST_JSON_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   // Wait for all tree view updated by react
   wait = waitForDOM(document, "#params-panel .tree-section");
   EventUtils.sendMouseEvent({ type: "click" },
--- a/devtools/client/netmonitor/test/browser_net_raw_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -14,32 +14,30 @@ add_task(function* () {
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 2);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   wait = waitForDOM(document, ".headers-overview");
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll(".request-list-item")[0]);
   yield wait;
 
-  let onRequestPostData = monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_POST_DATA);
   wait = waitForDOM(document, ".raw-headers-container textarea", 2);
   EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersButton());
   yield wait;
-  yield onRequestPostData;
 
   testRawHeaderButtonStyle(true);
 
   testShowRawHeaders(getSortedRequests(store.getState()).get(0));
 
   EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersButton());
 
   testRawHeaderButtonStyle(false);
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -21,17 +21,17 @@ add_task(function* () {
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
-  let wait = waitForNetworkEvents(monitor, 0, 2);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let origItem = getSortedRequests(store.getState()).get(0);
 
   store.dispatch(Actions.selectRequest(origItem.id));
@@ -47,17 +47,17 @@ add_task(function* () {
   // edit the custom request
   yield editCustomForm();
 
   // FIXME: reread the customItem, it's been replaced by a new object (immutable!)
   customItem = getSelectedRequest(store.getState());
   testCustomItemChanged(customItem, origItem);
 
   // send the new request
-  wait = waitForNetworkEvents(monitor, 0, 1);
+  wait = waitForNetworkEvents(monitor, 1);
   store.dispatch(Actions.sendCustomRequest(connector));
   yield wait;
 
   let sentItem = getSelectedRequest(store.getState());
 
   yield testSentRequest(sentItem, origItem);
 
   // Ensure the UI shows the new request, selected, and that the detail panel was closed.
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -18,34 +18,34 @@ add_task(function* () {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
 
   info("Waiting for OPTIONS, then POST");
-  let wait = waitForNetworkEvents(monitor, 1, 1);
+  let wait = waitForNetworkEvents(monitor, 2);
   yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
     content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
   });
   yield wait;
 
   const METHODS = ["OPTIONS", "POST"];
   const ITEMS = METHODS.map((val, i) => getSortedRequests(store.getState()).get(i));
 
   // Check the requests that were sent
   ITEMS.forEach((item, i) => {
     is(item.method, METHODS[i], `The ${item.method} request has the right method`);
     is(item.url, requestUrl, `The ${item.method} request has the right URL`);
   });
 
   // Resend both requests without modification. Wait for resent OPTIONS, then POST.
   // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
-  let onRequests = waitForNetworkEvents(monitor, 1, 0);
+  let onRequests = waitForNetworkEvents(monitor, 1);
   ITEMS.forEach((item) => {
     info(`Selecting the ${item.method} request`);
     store.dispatch(Actions.selectRequest(item.id));
 
     info("Cloning the selected request into a custom clone");
     store.dispatch(Actions.cloneSelectedRequest());
 
     info("Sending the cloned request (without change)");
--- a/devtools/client/netmonitor/test/browser_net_resend_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -25,17 +25,17 @@ add_task(function* () {
     { name: "Host", value: "fakehost.example.com" },
     { name: "User-Agent", value: "Testzilla" },
     { name: "Referer", value: "http://example.com/referrer" },
     { name: "Accept", value: "application/jarda"},
     { name: "Accept-Encoding", value: "compress, identity, funcoding" },
     { name: "Accept-Language", value: "cs-CZ" }
   ];
 
-  let wait = waitForNetworkEvents(monitor, 0, 1);
+  let wait = waitForNetworkEvents(monitor, 1);
   sendHTTPRequest({
     url: requestUrl,
     method: "POST",
     headers: requestHeaders,
     body: "Hello"
   });
   yield wait;
 
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -11,17 +11,16 @@ add_task(function* () {
 });
 
 function* throttleTest(actuallyThrottle) {
   requestLongerTimeout(2);
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   let { store, windowRequire, connector } = monitor.panelWin;
   let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/src/constants");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/src/constants");
   let { setPreferences, triggerActivity } = connector;
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
   // When throttling, must be smaller than the length of the content
@@ -41,19 +40,19 @@ function* throttleTest(actuallyThrottle)
 
   info("sending throttle request");
   yield new Promise((resolve) => {
     setPreferences(request, response => {
       resolve(response);
     });
   });
 
-  let eventPromise = monitor.panelWin.once(EVENTS.RECEIVED_EVENT_TIMINGS);
+  let wait = waitForNetworkEvents(monitor, 1);
   yield triggerActivity(ACTIVITY_TYPE.RELOAD.WITH_CACHE_DISABLED);
-  yield eventPromise;
+  yield wait;
 
   yield waitUntil(() => {
     let requestItem = getSortedRequests(store.getState()).get(0);
     return requestItem && requestItem.eventTimings;
   });
 
   let requestItem = getSortedRequests(store.getState()).get(0);
   const reportedOneSecond = requestItem.eventTimings.timings.receive > 1000;
--- a/devtools/client/netmonitor/test/browser_net_timing-division.js
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -3,22 +3,16 @@
 
 "use strict";
 
 /**
  * Tests if timing intervals are divided againts seconds when appropriate.
  */
 
 add_task(function* () {
-  // Make sure timing division can render properly
-  Services.prefs.setCharPref(
-    "devtools.netmonitor.visibleColumns",
-    "[\"waterfall\"]"
-  );
-
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/src/selectors/index");
--- a/devtools/client/netmonitor/test/browser_net_view-source-debugger.js
+++ b/devtools/client/netmonitor/test/browser_net_view-source-debugger.js
@@ -12,17 +12,17 @@ add_task(async function () {
 
   let { tab, monitor, toolbox } = await initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
   let { document, store, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
   store.dispatch(Actions.batchEnable(false));
 
-  let waitForContentRequests = waitForNetworkEvents(monitor, 0, 2);
+  let waitForContentRequests = waitForNetworkEvents(monitor, 2);
   await ContentTask.spawn(tab.linkedBrowser, {},
     () => content.wrappedJSObject.performRequests());
   await waitForContentRequests;
 
   info("Clicking stack-trace tab and waiting for stack-trace panel to open");
   let wait = waitForDOM(document, "#stack-trace-panel .frame-link", 4);
   // Click on the first request
   EventUtils.sendMouseEvent({ type: "mousedown" },
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -216,16 +216,55 @@ function waitForAllRequestsFinished(moni
       resolve();
     }
 
     window.on(EVENTS.NETWORK_EVENT, onRequest);
     window.on(EVENTS.PAYLOAD_READY, onTimings);
   });
 }
 
+let finishedQueue = {};
+let updatingTypes = [
+  "NetMonitor:NetworkEventUpdating:RequestCookies",
+  "NetMonitor:NetworkEventUpdating:ResponseCookies",
+];
+let updatedTypes = [
+  "NetMonitor:NetworkEventUpdated:RequestCookies",
+  "NetMonitor:NetworkEventUpdated:ResponseCookies",
+];
+
+// Start collecting all networkEventUpdate event when panel is opened.
+// removeTab() should be called once all corresponded RECEIVED_* events finished.
+function startNetworkEventUpdateObserver(panelWin) {
+  updatingTypes.forEach((type) => panelWin.on(type, (event, actor) => {
+    let key = actor + "-" + event.replace("NetMonitor:NetworkEventUpdating:", "");
+    finishedQueue[key] = finishedQueue[key] ? finishedQueue[key] + 1 : 1;
+  }));
+
+  updatedTypes.forEach((type) => panelWin.on(type, (event, actor) => {
+    let key = actor + "-" + event.replace("NetMonitor:NetworkEventUpdated:", "");
+    finishedQueue[key]--;
+  }));
+}
+
+function* waitForAllNetworkUpdateEvents() {
+  function checkNetworkEventUpdateState() {
+    for (let key in finishedQueue) {
+      if (finishedQueue[key] > 0) {
+        return false;
+      }
+    }
+
+    return true;
+  }
+  info("Wait for completion of all NetworkUpdateEvents packets...");
+  yield waitUntil(() => checkNetworkEventUpdateState());
+  finishedQueue = {};
+}
+
 function initNetMonitor(url, enableCache) {
   info("Initializing a network monitor pane.");
 
   return Task.spawn(function* () {
     let tab = yield addTab(url);
     info("Net tab added successfully: " + url);
 
     let target = TargetFactory.forTab(tab);
@@ -233,16 +272,18 @@ function initNetMonitor(url, enableCache
     yield target.makeRemote();
     info("Target remoted.");
 
     let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
     info("Network monitor pane shown successfully.");
 
     let monitor = toolbox.getCurrentPanel();
 
+    startNetworkEventUpdateObserver(monitor.panelWin);
+
     if (!enableCache) {
       let panel = monitor.panelWin;
       let { store, windowRequire } = panel;
       let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
       info("Disabling cache and reloading page.");
       let requestsDone = waitForAllRequestsFinished(monitor);
       let markersDone = waitForTimelineMarkers(monitor);
@@ -266,16 +307,19 @@ function initNetMonitor(url, enableCache
 
 function restartNetMonitor(monitor, newUrl) {
   info("Restarting the specified network monitor.");
 
   return Task.spawn(function* () {
     let tab = monitor.toolbox.target.tab;
     let url = newUrl || tab.linkedBrowser.currentURI.spec;
 
+    yield waitForAllNetworkUpdateEvents();
+    info("All pending requests finished.");
+
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
 
     return initNetMonitor(url);
   });
 }
 
@@ -284,40 +328,37 @@ function teardown(monitor) {
 
   return Task.spawn(function* () {
     let tab = monitor.toolbox.target.tab;
 
     // Ensure that there is no pending RDP requests related to payload request
     // done from FirefoxDataProvider.
     info("Wait for completion of all pending RDP requests...");
     yield waitForExistingRequests(monitor);
+    yield waitForAllNetworkUpdateEvents();
     info("All pending requests finished.");
 
     let onDestroyed = monitor.once("destroyed");
     yield removeTab(tab);
     yield onDestroyed;
   });
 }
 
-function waitForNetworkEvents(monitor, getRequests, postRequests = 0) {
+function waitForNetworkEvents(monitor, getRequests) {
   return new Promise((resolve) => {
     let panel = monitor.panelWin;
     let { getNetworkRequest } = panel.connector;
     let progress = {};
     let genericEvents = 0;
     let payloadReady = 0;
     let awaitedEventsToListeners = [
       ["UPDATING_REQUEST_HEADERS", onGenericEvent],
       ["RECEIVED_REQUEST_HEADERS", onGenericEvent],
-      ["UPDATING_REQUEST_COOKIES", onGenericEvent],
-      ["RECEIVED_REQUEST_COOKIES", onGenericEvent],
       ["UPDATING_RESPONSE_HEADERS", onGenericEvent],
       ["RECEIVED_RESPONSE_HEADERS", onGenericEvent],
-      ["UPDATING_RESPONSE_COOKIES", onGenericEvent],
-      ["RECEIVED_RESPONSE_COOKIES", onGenericEvent],
       ["UPDATING_EVENT_TIMINGS", onGenericEvent],
       ["RECEIVED_EVENT_TIMINGS", onGenericEvent],
       ["PAYLOAD_READY", onPayloadReady]
     ];
     let expectedGenericEvents = awaitedEventsToListeners
       .filter(([, listener]) => listener == onGenericEvent).length;
 
     function initProgressForURL(url) {
@@ -354,33 +395,32 @@ function waitForNetworkEvents(monitor, g
         return;
       }
 
       payloadReady++;
       maybeResolve(event, actor, networkInfo);
     }
 
     function maybeResolve(event, actor, networkInfo) {
-      info("> Network events progress: " +
-        "Payload: " + payloadReady + "/" + (getRequests + postRequests) + ", " +
-        "Generic: " + genericEvents + "/" +
-          ((getRequests + postRequests) * expectedGenericEvents) + ", " +
+      info("> Network event progress: " +
+        "Payload: " + payloadReady + "/" + getRequests + ", " +
+        "Generic: " + genericEvents + "/" + (getRequests * expectedGenericEvents) + ", " +
         "got " + event + " for " + actor);
 
       let url = networkInfo.request.url;
       updateProgressForURL(url, event);
 
       // Uncomment this to get a detailed progress logging (when debugging a test)
       // info("> Current state: " + JSON.stringify(progress, null, 2));
 
       // There are `expectedGenericEvents` updates which need to be fired for a request
       // to be considered finished. The "requestPostData" packet isn't fired for non-POST
       // requests.
-      if (payloadReady >= (getRequests + postRequests) &&
-        genericEvents >= (getRequests + postRequests) * expectedGenericEvents) {
+      if (payloadReady >= getRequests &&
+        genericEvents >= getRequests * expectedGenericEvents) {
         awaitedEventsToListeners.forEach(([e, l]) => panel.off(EVENTS[e], l));
         executeSoon(resolve);
       }
     }
 
     awaitedEventsToListeners.forEach(([e, l]) => panel.on(EVENTS[e], l));
   });
 }
--- a/devtools/client/netmonitor/test/shared-head.js
+++ b/devtools/client/netmonitor/test/shared-head.js
@@ -17,21 +17,19 @@ async function waitForExistingRequests(m
     for (let request of requests) {
       // Ignore cloned request as we don't lazily fetch data for them
       // and have arbitrary number of field set.
       if (request.id.includes("-clone")) {
         continue;
       }
       // Do same check than FirefoxDataProvider.isRequestPayloadReady,
       // in order to ensure there is no more pending payload requests to be done.
-      if (!request.requestHeaders || !request.requestCookies ||
-          !request.eventTimings ||
-          ((!request.responseHeaders || !request.responseCookies) &&
-            request.securityState != "broken" &&
-            (!request.responseContentAvailable || request.status))) {
+      if (!request.requestHeaders || !request.eventTimings ||
+          (!request.responseHeaders && request.securityState !== "broken" &&
+          (!request.responseContentAvailable || request.status))) {
         return false;
       }
     }
     return true;
   }
   // If there is no request, we are good to go.
   if (getRequests().size == 0) {
     return;
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -13,16 +13,19 @@ const KeyShortcuts = require("devtools/c
 const AutocompletePopup = createFactory(require("devtools/client/shared/components/AutoCompletePopup"));
 
 class SearchBox extends Component {
   static get propTypes() {
     return {
       delay: PropTypes.number,
       keyShortcut: PropTypes.string,
       onChange: PropTypes.func,
+      onFocus: PropTypes.func,
+      onBlur: PropTypes.func,
+      onKeyDown: PropTypes.func,
       placeholder: PropTypes.string,
       type: PropTypes.string,
       autocompleteProvider: PropTypes.func,
     };
   }
 
   constructor(props) {
     super(props);
@@ -91,24 +94,36 @@ class SearchBox extends Component {
   }
 
   onClearButtonClick() {
     this.refs.input.value = "";
     this.onChange();
   }
 
   onFocus() {
+    if (this.props.onFocus) {
+      this.props.onFocus();
+    }
+
     this.setState({ focused: true });
   }
 
   onBlur() {
+    if (this.props.onBlur) {
+      this.props.onBlur();
+    }
+
     this.setState({ focused: false });
   }
 
   onKeyDown(e) {
+    if (this.props.onKeyDown) {
+      this.props.onKeyDown();
+    }
+
     let { autocomplete } = this.refs;
     if (!autocomplete || autocomplete.state.list.length <= 0) {
       return;
     }
 
     switch (e.key) {
       case "ArrowDown":
         autocomplete.jumpBy(1);