Bug 1426809 - Prevent fetching network update packet again after packet arrived r=Honza
authorRicky Chien <ricky060709@gmail.com>
Sat, 23 Dec 2017 14:03:24 +0800
changeset 449509 6b76f6a5ca637af6cd6c32aeed183bbf4f01ae0e
parent 449508 849d216e3f9afe08b2896c0b793119efa6c966d1
child 449510 d64cd605dc2f942f8b8efbf6ad21101f5f53544f
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)
reviewersHonza
bugs1426809
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 1426809 - Prevent fetching network update packet again after packet arrived r=Honza MozReview-Commit-ID: 5Ifgj1opsNW
devtools/client/netmonitor/src/connector/firefox-data-provider.js
devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
--- a/devtools/client/netmonitor/src/connector/firefox-data-provider.js
+++ b/devtools/client/netmonitor/src/connector/firefox-data-provider.js
@@ -129,50 +129,38 @@ class FirefoxDataProvider {
 
   async fetchResponseContent(responseContent) {
     let payload = {};
     if (responseContent && responseContent.content) {
       let { text } = responseContent.content;
       let response = await this.getLongString(text);
       responseContent.content.text = response;
       payload.responseContent = responseContent;
-
-      // Lock down responseContentAvailable once we fetch data from back-end.
-      // Using this as flag to prevent fetching arrived data again.
-      payload.responseContentAvailable = false;
     }
     return payload;
   }
 
   async fetchRequestHeaders(requestHeaders) {
     let payload = {};
     if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
       let headers = await fetchHeaders(requestHeaders, this.getLongString);
       if (headers) {
         payload.requestHeaders = headers;
       }
-
-      // Lock down requestHeadersAvailable once we fetch data from back-end.
-      // Using this as flag to prevent fetching arrived data again.
-      payload.requestHeadersAvailable = false;
     }
     return payload;
   }
 
   async fetchResponseHeaders(responseHeaders) {
     let payload = {};
     if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
       let headers = await fetchHeaders(responseHeaders, this.getLongString);
       if (headers) {
         payload.responseHeaders = headers;
       }
-
-      // Lock down responseHeadersAvailable once we fetch data from back-end.
-      // Using this as flag to prevent fetching arrived data again.
-      payload.responseHeadersAvailable = false;
     }
     return payload;
   }
 
   async fetchPostData(requestPostData) {
     let payload = {};
     if (requestPostData && requestPostData.postData) {
       let { text } = requestPostData.postData;
@@ -183,20 +171,16 @@ 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 fetchRequestCookies(requestCookies) {
     let payload = {};
     if (requestCookies) {
       let reqCookies = [];
@@ -209,20 +193,16 @@ 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;
   }
 
   async fetchResponseCookies(responseCookies) {
     let payload = {};
     if (responseCookies) {
       let resCookies = [];
@@ -235,20 +215,16 @@ 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;
   }
 
   /**
    * Public API used by the Toolbox: Tells if there is still any pending request.
    *
    * @return {boolean} returns true if the payload queue is empty
@@ -429,17 +405,22 @@ class FirefoxDataProvider {
     }
     // Fetch the data
     promise = this._requestData(actor, method).then(async (payload) => {
       // Remove the request from the cache, any new call to requestData will fetch the
       // data again.
       this.lazyRequestData.delete(key);
 
       if (this.actions.updateRequest) {
-        await this.actions.updateRequest(actor, payload, true);
+        await this.actions.updateRequest(actor, {
+          ...payload,
+          // Lockdown *Available property once we fetch data from back-end.
+          // Using this as a flag to prevent fetching arrived data again.
+          [`${method}Available`]: false,
+        }, true);
       }
 
       return payload;
     });
 
     this.lazyRequestData.set(key, promise);
 
     return promise;
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -35,16 +35,17 @@ class RequestListContextMenu {
       mimeType,
       httpVersion,
       requestHeaders,
       requestHeadersAvailable,
       requestPostData,
       requestPostDataAvailable,
       responseHeaders,
       responseHeadersAvailable,
+      responseContent,
       responseContentAvailable,
       url,
     } = selectedRequest;
     let {
       cloneSelectedRequest,
       openStatistics,
     } = this.props;
     let menu = [];
@@ -68,27 +69,30 @@ class RequestListContextMenu {
 
     copySubmenu.push({
       id: "request-list-context-copy-post-data",
       label: L10N.getStr("netmonitor.context.copyPostData"),
       accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
       visible: !!(selectedRequest && (requestPostDataAvailable || requestPostData)),
-      click: () => this.copyPostData(id, formDataSections),
+      click: () => this.copyPostData(id, formDataSections, requestPostData),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-as-curl",
       label: L10N.getStr("netmonitor.context.copyAsCurl"),
       accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && (requestHeadersAvailable || requestHeaders)),
-      click: () => this.copyAsCurl(id, url, method, requestHeaders, httpVersion),
+      visible: !!(selectedRequest &&
+        (requestHeadersAvailable || requestHeaders) &&
+        (responseContentAvailable || responseContent)),
+      click: () =>
+        this.copyAsCurl(id, url, method, httpVersion, requestHeaders, responseContent),
     });
 
     copySubmenu.push({
       type: "separator",
       visible: copySubmenu.slice(0, 4).some((subMenu) => subMenu.visible),
     });
 
     copySubmenu.push({
@@ -112,26 +116,27 @@ class RequestListContextMenu {
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-response",
       label: L10N.getStr("netmonitor.context.copyResponse"),
       accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
-      visible: !!(selectedRequest && responseContentAvailable),
-      click: () => this.copyResponse(id),
+      visible: !!(selectedRequest && (responseContentAvailable || responseContent)),
+      click: () => this.copyResponse(id, responseContent),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-image-as-data-uri",
       label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
       accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
-      visible: !!(selectedRequest && mimeType && mimeType.includes("image/")),
-      click: () => this.copyImageAsDataUri(id, mimeType),
+      visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+        mimeType && mimeType.includes("image/")),
+      click: () => this.copyImageAsDataUri(id, mimeType, responseContent),
     });
 
     copySubmenu.push({
       type: "separator",
       visible: copySubmenu.slice(5, 9).some((subMenu) => subMenu.visible),
     });
 
     copySubmenu.push({
@@ -156,18 +161,19 @@ class RequestListContextMenu {
       visible: sortedRequests.size > 0,
       click: () => this.saveAllAsHar(sortedRequests),
     });
 
     menu.push({
       id: "request-list-context-save-image-as",
       label: L10N.getStr("netmonitor.context.saveImageAs"),
       accesskey: L10N.getStr("netmonitor.context.saveImageAs.accesskey"),
-      visible: !!(selectedRequest && mimeType && mimeType.includes("image/")),
-      click: () => this.saveImageAs(id, url),
+      visible: !!(selectedRequest && (responseContentAvailable || responseContent) &&
+        mimeType && mimeType.includes("image/")),
+      click: () => this.saveImageAs(id, url, responseContent),
     });
 
     menu.push({
       type: "separator",
       visible: copySubmenu.slice(10, 14).some((subMenu) => subMenu.visible),
     });
 
     menu.push({
@@ -258,106 +264,112 @@ class RequestListContextMenu {
     let params = getUrlQuery(url).split("&");
     copyString(params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n"));
   }
 
   /**
    * Copy the request form data parameters (or raw payload) from
    * the currently selected item.
    */
-  async copyPostData(id, formDataSections) {
+  async copyPostData(id, formDataSections, requestPostData) {
     let params = [];
     // Try to extract any form data parameters.
     formDataSections.forEach(section => {
       let paramsArray = parseQueryString(section);
       if (paramsArray) {
         params = [...params, ...paramsArray];
       }
     });
 
     let string = params
       .map(param => param.name + (param.value ? "=" + param.value : ""))
       .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
 
     // Fall back to raw payload.
     if (!string) {
-      let { requestPostData } = await this.props.connector
-        .requestData(id, "requestPostData");
+      requestPostData = requestPostData ||
+        await this.props.connector.requestData(id, "requestPostData").requestPostData;
+
       string = requestPostData.postData.text;
       if (Services.appinfo.OS !== "WINNT") {
         string = string.replace(/\r/g, "");
       }
     }
     copyString(string);
   }
 
   /**
    * Copy a cURL command from the currently selected item.
    */
-  async copyAsCurl(id, url, method, requestHeaders, httpVersion) {
-    if (!requestHeaders) {
-      requestHeaders = await this.props.connector.requestData(id, "requestHeaders");
-    }
-    let { requestPostData } = await this.props.connector
-      .requestData(id, "requestPostData");
+  async copyAsCurl(id, url, method, httpVersion, requestHeaders, requestPostData) {
+    requestHeaders = requestHeaders ||
+      await this.props.connector.requestData(id, "requestHeaders");
+
+    requestPostData = requestPostData ||
+      await this.props.connector.requestData(id, "requestPostData").requestPostData;
+
     // Create a sanitized object for the Curl command generator.
     let data = {
       url,
       method,
       headers: requestHeaders.headers,
-      httpVersion: httpVersion,
+      httpVersion,
       postDataText: requestPostData ? requestPostData.postData.text : "",
     };
     copyString(Curl.generateCommand(data));
   }
 
   /**
    * Copy the raw request headers from the currently selected item.
    */
   async copyRequestHeaders(id, requestHeaders) {
-    if (!requestHeaders) {
-      requestHeaders = await this.props.connector.requestData(id, "requestHeaders");
-    }
+    requestHeaders = requestHeaders ||
+      await this.props.connector.requestData(id, "requestHeaders");
+
     let rawHeaders = requestHeaders.rawHeaders.trim();
 
     if (Services.appinfo.OS !== "WINNT") {
       rawHeaders = rawHeaders.replace(/\r/g, "");
     }
     copyString(rawHeaders);
   }
 
   /**
    * Copy the raw response headers from the currently selected item.
    */
   async copyResponseHeaders(id, responseHeaders) {
-    if (!responseHeaders) {
-      responseHeaders = await this.props.connector.requestData(id, "responseHeaders");
-    }
+    responseHeaders = responseHeaders ||
+      await this.props.connector.requestData(id, "responseHeaders");
+
     let rawHeaders = responseHeaders.rawHeaders.trim();
 
     if (Services.appinfo.OS !== "WINNT") {
       rawHeaders = rawHeaders.replace(/\r/g, "");
     }
     copyString(rawHeaders);
   }
 
   /**
    * Copy image as data uri.
    */
-  async copyImageAsDataUri(id, mimeType) {
-    let responseContent = await this.props.connector.requestData(id, "responseContent");
+  async copyImageAsDataUri(id, mimeType, responseContent) {
+    responseContent = responseContent ||
+      await this.props.connector.requestData(id, "responseContent");
+
     let { encoding, text } = responseContent.content;
     copyString(formDataURI(mimeType, encoding, text));
   }
 
   /**
    * Save image as.
    */
-  async saveImageAs(id, url) {
-    let responseContent = await this.props.connector.requestData(id, "responseContent");
+  async saveImageAs(id, url, responseContent) {
+    responseContent = responseContent ||
+      await this.props.connector.requestData(id, "responseContent");
+
     let { encoding, text } = responseContent.content;
     let fileName = getUrlBaseName(url);
     let data;
     if (encoding === "base64") {
       let decoded = atob(text);
       data = new Uint8Array(decoded.length);
       for (let i = 0; i < decoded.length; ++i) {
         data[i] = decoded.charCodeAt(i);
@@ -366,18 +378,20 @@ class RequestListContextMenu {
       data = text;
     }
     saveAs(new Blob([data]), fileName, document);
   }
 
   /**
    * Copy response data as a string.
    */
-  async copyResponse(id) {
-    let responseContent = await this.props.connector.requestData(id, "responseContent");
+  async copyResponse(id, responseContent) {
+    responseContent = responseContent ||
+      await this.props.connector.requestData(id, "responseContent");
+
     copyString(responseContent.content.text);
   }
 
   /**
    * Copy HAR from the network panel content to the clipboard.
    */
   copyAllAsHar(sortedRequests) {
     return HarExporter.copy(this.getDefaultHarOptions(sortedRequests));