Bug 1418927 - Introduce fetchNetworkUpdatePacket helper in request-utils r?ochameau draft
authorRicky Chien <ricky060709@gmail.com>
Tue, 12 Dec 2017 10:30:04 -0600
changeset 711268 4add05ca4e0e33631efb0143f376f99930c3cfa7
parent 711178 defccba824aa91e8d4d820b1defaadfdca34bac7
child 711269 48e4a6dbc95eaf73a3d67f50fd9fe8409592ac9f
child 711271 7b15fac001fc4175b5312895111e4f72e7e4a299
push id93040
push userbmo:rchien@mozilla.com
push dateWed, 13 Dec 2017 17:51:50 +0000
reviewersochameau
bugs1418927
milestone59.0a1
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,