Bug 1418927 - Introduce fetchNetworkUpdatePacket helper in request-utils r=ochameau
authorRicky Chien <ricky060709@gmail.com>
Tue, 12 Dec 2017 10:30:04 -0600
changeset 448231 9a512e69ff1bc71dafb838410fe413a6a5a4527a
parent 448230 e72a70119dfb5856fd8d73279c3dfe6b9866e60d
child 448232 acc656fd0c97962779b46e11b3500cc3dc7d69f4
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau
bugs1418927
milestone59.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1418927 - Introduce fetchNetworkUpdatePacket helper in request-utils r=ochameau MozReview-Commit-ID: VRu3k3dxtV
devtools/client/netmonitor/src/components/CookiesPanel.js
devtools/client/netmonitor/src/components/CustomRequestPanel.js
devtools/client/netmonitor/src/components/HeadersPanel.js
devtools/client/netmonitor/src/components/ParamsPanel.js
devtools/client/netmonitor/src/components/RequestListColumnCookies.js
devtools/client/netmonitor/src/components/RequestListColumnSetCookies.js
devtools/client/netmonitor/src/components/ResponsePanel.js
devtools/client/netmonitor/src/components/SecurityPanel.js
devtools/client/netmonitor/src/components/StackTracePanel.js
devtools/client/netmonitor/src/components/StatisticsPanel.js
devtools/client/netmonitor/src/utils/request-utils.js
--- a/devtools/client/netmonitor/src/components/CookiesPanel.js
+++ b/devtools/client/netmonitor/src/components/CookiesPanel.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 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 { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Component
 const PropertiesView = createFactory(require("./PropertiesView"));
 
 const { div } = dom;
 
 const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
@@ -33,34 +34,29 @@ class CookiesPanel extends Component {
     return {
       connector: PropTypes.object.isRequired,
       openLink: PropTypes.func,
       request: PropTypes.object.isRequired,
     };
   }
 
   componentDidMount() {
-    this.maybeFetchCookies(this.props);
+    let { connector, request } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestCookies",
+      "responseCookies",
+    ]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchCookies(nextProps);
-  }
-
-  /**
-   * 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");
-    }
+    let { connector, request } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestCookies",
+      "responseCookies",
+    ]);
   }
 
   /**
    * 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}
--- a/devtools/client/netmonitor/src/components/CustomRequestPanel.js
+++ b/devtools/client/netmonitor/src/components/CustomRequestPanel.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 const { Component } = 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 { connect } = require("devtools/client/shared/vendor/react-redux");
 const { L10N } = require("../utils/l10n");
+const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 const Actions = require("../actions/index");
 const { getSelectedRequest } = require("../selectors/index");
 const {
   getUrlQuery,
   parseQueryString,
   writeHeaderText,
 } = require("../utils/request-utils");
 
@@ -43,35 +44,31 @@ class CustomRequestPanel extends Compone
       removeSelectedCustomRequest: PropTypes.func.isRequired,
       request: PropTypes.object,
       sendCustomRequest: PropTypes.func.isRequired,
       updateRequest: PropTypes.func.isRequired,
     };
   }
 
   componentDidMount() {
-    this.maybeFetchPostData(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestHeaders",
+      "responseHeaders",
+      "requestPostData",
+    ]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchPostData(nextProps);
-  }
-
-  /**
-   * When switching to another request, lazily fetch request post data
-   * from the backend. The panel will first be empty and then display the content.
-   */
-  maybeFetchPostData(props) {
-    if (props.request.requestPostDataAvailable &&
-      (!props.request.requestPostData ||
-        !props.request.requestPostData.postData.text)) {
-      // This method will set `props.request.requestPostData`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "requestPostData");
-    }
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestHeaders",
+      "responseHeaders",
+      "requestPostData",
+    ]);
   }
 
   /**
    * Parse a text representation of a name[divider]value list with
    * the given name regex and divider character.
    *
    * @param {string} text - Text of list
    * @return {array} array of headers info {name, value}
--- a/devtools/client/netmonitor/src/components/HeadersPanel.js
+++ b/devtools/client/netmonitor/src/components/HeadersPanel.js
@@ -11,17 +11,20 @@ const {
   getFormattedIPAndPort,
   getFormattedSize,
 } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const {
   getHeadersURL,
   getHTTPStatusCodeURL,
 } = require("../utils/mdn-utils");
-const { writeHeaderText } = require("../utils/request-utils");
+const {
+  fetchNetworkUpdatePacket,
+  writeHeaderText,
+} = require("../utils/request-utils");
 const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Components
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const MDNLink = createFactory(require("./MdnLink"));
 const PropertiesView = createFactory(require("./PropertiesView"));
 
 const { Rep } = REPS;
@@ -63,40 +66,34 @@ class HeadersPanel extends Component {
     this.state = {
       rawHeadersOpened: false,
     };
 
     this.getProperties = this.getProperties.bind(this);
     this.toggleRawHeaders = this.toggleRawHeaders.bind(this);
     this.renderSummary = this.renderSummary.bind(this);
     this.renderValue = this.renderValue.bind(this);
-    this.maybeFetchPostData = this.maybeFetchPostData.bind(this);
   }
 
   componentDidMount() {
-    this.maybeFetchPostData(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestHeaders",
+      "responseHeaders",
+      "requestPostData",
+    ]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchPostData(nextProps);
-  }
-
-  /**
-   * When switching to another request, lazily fetch request post data
-   * from the backend. The panel will first be empty and then display the content.
-   * Fetching post data is used for updating requestHeadersFromUploadStream section,
-   */
-  maybeFetchPostData(props) {
-    if (props.request.requestPostDataAvailable &&
-        (!props.request.requestPostData ||
-        !props.request.requestPostData.postData.text)) {
-      // This method will set `props.request.requestPostData`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "requestPostData");
-    }
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, [
+      "requestHeaders",
+      "responseHeaders",
+      "requestPostData",
+    ]);
   }
 
   getProperties(headers, title) {
     if (headers && headers.headers.length) {
       let headerKey = `${title} (${getFormattedSize(headers.headersSize, 3)})`;
       let propertiesResult = {
         [headerKey]:
           headers.headers.reduce((acc, { name, value }) =>
--- a/devtools/client/netmonitor/src/components/ParamsPanel.js
+++ b/devtools/client/netmonitor/src/components/ParamsPanel.js
@@ -4,17 +4,22 @@
 
 "use strict";
 
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { L10N } = require("../utils/l10n");
-const { getUrlQuery, parseQueryString, parseFormData } = require("../utils/request-utils");
+const {
+  fetchNetworkUpdatePacket,
+  getUrlQuery,
+  parseQueryString,
+  parseFormData,
+} = require("../utils/request-utils");
 const { sortObjectKeys } = require("../utils/sort-utils");
 const { updateFormDataSections } = require("../utils/request-utils");
 const Actions = require("../actions/index");
 
 // Components
 const PropertiesView = createFactory(require("./PropertiesView"));
 
 const { div } = dom;
@@ -46,40 +51,28 @@ class ParamsPanel extends Component {
     };
   }
 
   constructor(props) {
     super(props);
   }
 
   componentDidMount() {
-    this.maybeFetchPostData(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["requestPostData"]);
     updateFormDataSections(this.props);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchPostData(nextProps);
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["requestPostData"]);
     updateFormDataSections(nextProps);
   }
 
   /**
-   * When switching to another request, lazily fetch request post data
-   * from the backend. The panel will first be empty and then display the content.
-   */
-  maybeFetchPostData(props) {
-    if (props.request.requestPostDataAvailable &&
-        (!props.request.requestPostData ||
-        !props.request.requestPostData.postData.text)) {
-      // This method will set `props.request.requestPostData`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "requestPostData");
-    }
-  }
-
-  /**
    * Mapping array to dict for TreeView usage.
    * Since TreeView only support Object(dict) format.
    * This function also deal with duplicate key case
    * (for multiple selection and query params with same keys)
    *
    * @param {Object[]} arr - key-value pair array like query or form params
    * @returns {Object} Rep compatible object
    */
--- a/devtools/client/netmonitor/src/components/RequestListColumnCookies.js
+++ b/devtools/client/netmonitor/src/components/RequestListColumnCookies.js
@@ -2,52 +2,46 @@
  * 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 { Component } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 
 const { div } = dom;
 
 class RequestListColumnCookies extends Component {
   static get propTypes() {
     return {
       connector: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
     };
   }
 
   componentDidMount() {
-    this.maybeFetchRequestCookies(this.props);
+    let { item, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, item, ["requestCookies"]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchRequestCookies(nextProps);
+    let { item, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, item, ["requestCookies"]);
   }
 
   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
@@ -2,52 +2,46 @@
  * 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 { Component } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 
 const { div } = dom;
 
 class RequestListColumnSetCookies extends Component {
   static get propTypes() {
     return {
       connector: PropTypes.object.isRequired,
       item: PropTypes.object.isRequired,
     };
   }
 
   componentDidMount() {
-    this.maybeFetchResponseCookies(this.props);
+    let { item, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, item, ["responseCookies"]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchResponseCookies(nextProps);
+    let { item, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, item, ["responseCookies"]);
   }
 
   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/ResponsePanel.js
+++ b/devtools/client/netmonitor/src/components/ResponsePanel.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { L10N } = require("../utils/l10n");
 const {
   decodeUnicodeBase64,
+  fetchNetworkUpdatePacket,
   formDataURI,
   getUrlBaseName,
 } = require("../utils/request-utils");
 const { Filters } = require("../utils/filter-predicates");
 
 // Components
 const PropertiesView = createFactory(require("./PropertiesView"));
 
@@ -52,36 +53,23 @@ class ResponsePanel extends Component {
       },
     };
 
     this.updateImageDimemsions = this.updateImageDimemsions.bind(this);
     this.isJSON = this.isJSON.bind(this);
   }
 
   componentDidMount() {
-    this.maybeFetchResponseContent(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["responseContent"]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchResponseContent(nextProps);
-  }
-
-  /**
-   * When switching to another request, lazily fetch response content
-   * from the backend. The Response Panel will first be empty and then
-   * display the content.
-   */
-  maybeFetchResponseContent(props) {
-    if (props.request.responseContentAvailable &&
-        (!props.request.responseContent ||
-         !props.request.responseContent.content)) {
-      // This method will set `props.request.responseContent.content`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "responseContent");
-    }
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["responseContent"]);
   }
 
   updateImageDimemsions({ target }) {
     this.setState({
       imageDimensions: {
         width: target.naturalWidth,
         height: target.naturalHeight,
       },
--- a/devtools/client/netmonitor/src/components/SecurityPanel.js
+++ b/devtools/client/netmonitor/src/components/SecurityPanel.js
@@ -6,17 +6,20 @@
 
 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 { getUrlHost } = require("../utils/request-utils");
+const {
+  fetchNetworkUpdatePacket,
+  getUrlHost,
+} = require("../utils/request-utils");
 
 // Components
 const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
 const PropertiesView = createFactory(require("./PropertiesView"));
 
 const { div, input, span } = dom;
 const NOT_AVAILABLE = L10N.getStr("netmonitor.security.notAvailable");
 const ERROR_LABEL = L10N.getStr("netmonitor.security.error");
@@ -55,34 +58,23 @@ class SecurityPanel extends Component {
     return {
       connector: PropTypes.object.isRequired,
       openLink: PropTypes.func,
       request: PropTypes.object.isRequired,
     };
   }
 
   componentDidMount() {
-    this.maybeFetchSecurityInfo(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]);
   }
 
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchSecurityInfo(nextProps);
-  }
-
-  /**
-   * When switching to another request, lazily fetch securityInfo
-   * from the backend. The Security Panel will first be empty and then
-   * display the content.
-   */
-  maybeFetchSecurityInfo(props) {
-    if (!props.request.securityInfo) {
-      // This method will set `props.request.securityInfo`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "securityInfo");
-    }
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["securityInfo"]);
   }
 
   renderValue(props, weaknessReasons = []) {
     const { member, value } = props;
 
     // Hide object summary
     if (typeof member.value === "object") {
       return null;
--- a/devtools/client/netmonitor/src/components/StackTracePanel.js
+++ b/devtools/client/netmonitor/src/components/StackTracePanel.js
@@ -2,16 +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/. */
 
 "use strict";
 
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 
 const { div } = dom;
 
 // Components
 const StackTrace = createFactory(require("devtools/client/shared/components/StackTrace"));
 
 /**
  * This component represents a side panel responsible for
@@ -27,41 +28,27 @@ class StackTracePanel extends Component 
     };
   }
 
   /**
    * `componentDidMount` is called when opening the StackTracePanel
    * for the first time
    */
   componentDidMount() {
-    this.maybeFetchStackTrace(this.props);
+    let { request, connector } = this.props;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["stackTrace"]);
   }
 
   /**
    * `componentWillReceiveProps` is the only method called when
    * switching between two requests while this panel is displayed.
    */
   componentWillReceiveProps(nextProps) {
-    this.maybeFetchStackTrace(nextProps);
-  }
-
-  /**
-   * When switching to another request, lazily fetch stack-trace
-   * from the backend. This Panel will first be empty and then
-   * display the content.
-   */
-  maybeFetchStackTrace(props) {
-    // Fetch stack trace only if it's available and not yet
-    // on the client.
-    if (!props.request.stacktrace &&
-      props.request.cause.stacktraceAvailable) {
-      // This method will set `props.request.stacktrace`
-      // asynchronously and force another render.
-      props.connector.requestData(props.request.id, "stackTrace");
-    }
+    let { request, connector } = nextProps;
+    fetchNetworkUpdatePacket(connector.requestData, request, ["stackTrace"]);
   }
 
   // Rendering
 
   render() {
     let {
       connector,
       openLink,
--- a/devtools/client/netmonitor/src/components/StatisticsPanel.js
+++ b/devtools/client/netmonitor/src/components/StatisticsPanel.js
@@ -12,16 +12,17 @@ const PropTypes = require("devtools/clie
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { Chart } = require("devtools/client/shared/widgets/Chart");
 const { PluralForm } = require("devtools/shared/plural-form");
 const Actions = require("../actions/index");
 const { Filters } = require("../utils/filter-predicates");
 const { getSizeWithDecimals, getTimeWithDecimals } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const { getPerformanceAnalysisURL } = require("../utils/mdn-utils");
+const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
 
 // Components
 const MDNLink = createFactory(require("./MdnLink"));
 
 const { button, div } = dom;
 const MediaQueryList = window.matchMedia("(min-width: 700px)");
 
 const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200;
@@ -58,16 +59,30 @@ class StatisticsPanel extends Component 
     this.responseIsFresh = this.responseIsFresh.bind(this);
     this.onLayoutChange = this.onLayoutChange.bind(this);
   }
 
   componentWillMount() {
     this.mdnLinkContainerNodes = new Map();
   }
 
+  componentDidMount() {
+    let { requests, connector } = this.props;
+    requests.forEach((request) => {
+      fetchNetworkUpdatePacket(connector.requestData, request, ["responseHeaders"]);
+    });
+  }
+
+  componentWillReceiveProps(nextProps) {
+    let { requests, connector } = nextProps;
+    requests.forEach((request) => {
+      fetchNetworkUpdatePacket(connector.requestData, request, ["responseHeaders"]);
+    });
+  }
+
   componentDidUpdate(prevProps) {
     MediaQueryList.addListener(this.onLayoutChange);
 
     const { requests } = this.props;
     let ready = requests && requests.length && requests.every((req) =>
       req.contentSize !== undefined && req.mimeType && req.responseHeaders &&
       req.status !== undefined && req.totalTime !== undefined
     );
--- a/devtools/client/netmonitor/src/utils/request-utils.js
+++ b/devtools/client/netmonitor/src/utils/request-utils.js
@@ -65,16 +65,40 @@ async function getFormDataSections(heade
 async function fetchHeaders(headers, getLongString) {
   for (let { value } of headers.headers) {
     headers.headers.value = await getLongString(value);
   }
   return headers;
 }
 
 /**
+ * Fetch network event update packets from actor server
+ * Expect to fetch a couple of network update packets from a given request.
+ *
+ * @param {function} requestData - requestData function for lazily fetch data
+ * @param {object} request - request object
+ * @param {array} updateTypes - a list of network event update types
+ */
+function fetchNetworkUpdatePacket(requestData, request, updateTypes) {
+  updateTypes.forEach((updateType) => {
+    // Only stackTrace will be handled differently
+    if (updateType === "stackTrace") {
+      if (request.cause.stacktraceAvailable && !request.stacktrace) {
+        requestData(request.id, updateType);
+      }
+      return;
+    }
+
+    if (request[`${updateType}Available`] && !request[updateType]) {
+      requestData(request.id, updateType);
+    }
+  });
+}
+
+/**
  * Form a data: URI given a mime type, encoding, and some text.
  *
  * @param {string} mimeType - mime type
  * @param {string} encoding - encoding to use; if not set, the
  *                            text will be base64-encoded.
  * @param {string} text - text of the URI.
  * @return {string} a data URI
  */
@@ -455,16 +479,17 @@ function processNetworkUpdates(request) 
   }
   return result;
 }
 
 module.exports = {
   decodeUnicodeBase64,
   getFormDataSections,
   fetchHeaders,
+  fetchNetworkUpdatePacket,
   formDataURI,
   writeHeaderText,
   decodeUnicodeUrl,
   getAbbreviatedMimeType,
   getEndTime,
   getFormattedProtocol,
   getResponseHeader,
   getResponseTime,