Bug 1431758 - do not use netmonitor data to fetch stylesheets over 1MB;r=jryans draft
authorJulian Descottes <jdescottes@mozilla.com>
Sat, 20 Jan 2018 00:24:41 +0100
changeset 723194 dc25347c977b7890231b7434622d171d217f7bc6
parent 718801 59a75c1808ec67d20fd1eade0d3b4fc0e930454f
child 746795 3a7e4714f6d887d8cc4c7e66c680feb21fa65856
push id96357
push userjdescottes@mozilla.com
push dateMon, 22 Jan 2018 18:09:32 +0000
reviewersjryans
bugs1431758
milestone59.0a1
Bug 1431758 - do not use netmonitor data to fetch stylesheets over 1MB;r=jryans MozReview-Commit-ID: Gz6dRLiLREm
devtools/client/styleeditor/test/browser.ini
devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js
devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html
devtools/client/styleeditor/test/sjs_huge-css-server.sjs
devtools/server/actors/stylesheets.js
devtools/server/actors/webconsole.js
devtools/shared/webconsole/network-monitor.js
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -28,16 +28,17 @@ support-files =
   resources_inpage1.css
   resources_inpage2.css
   selector-highlighter.html
   simple.css
   simple.css.gz
   simple.css.gz^headers^
   simple.gz.html
   simple.html
+  sjs_huge-css-server.sjs
   sourcemap-css/contained.css
   sourcemap-css/sourcemaps.css
   sourcemap-css/sourcemaps.css.map
   sourcemap-css/media-rules.css
   sourcemap-css/media-rules.css.map
   sourcemap-css/test-bootstrap-scss.css
   sourcemap-css/test-stylus.css
   sourcemap-sass/sourcemaps.scss
--- a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-netmonitor.js
@@ -32,22 +32,31 @@ add_task(function* () {
   info("Waiting for the sources to be loaded.");
   yield ui.editors[0].getSourceEditor();
   yield ui.selectStyleSheet(ui.editors[1].styleSheet);
   yield ui.editors[1].getSourceEditor();
 
   info("Checking Netmonitor contents.");
   let shortRequests = [];
   let longRequests = [];
+  let hugeRequests = [];
   for (let item of getSortedRequests(store.getState())) {
     if (item.url.endsWith("doc_short_string.css")) {
       shortRequests.push(item);
     }
     if (item.url.endsWith("doc_long_string.css")) {
       longRequests.push(item);
     }
+    if (item.url.endsWith("sjs_huge-css-server.sjs")) {
+      hugeRequests.push(item);
+    }
   }
 
   is(shortRequests.length, 1,
      "Got one request for doc_short_string.css after Style Editor was loaded.");
   is(longRequests.length, 1,
      "Got one request for doc_long_string.css after Style Editor was loaded.");
+
+  // Requests with a response body size greater than 1MB cannot be fetched from the
+  // netmonitor, the style editor should perform a separate request.
+  is(hugeRequests.length, 2,
+     "Got two requests for sjs_huge-css-server.sjs after Style Editor was loaded.");
 });
--- a/devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html
+++ b/devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html
@@ -1,11 +1,13 @@
 <!doctype html>
 <html>
 <head>
   <title>Fetch from netmonitor testcase</title>
   <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="doc_short_string.css"/>
   <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="doc_long_string.css"/>
+  <!-- This last CSS is generated by a SJS server to avoid adding a 300,000 lines stylesheet to the codebase. -->
+  <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="sjs_huge-css-server.sjs"/>
 </head>
 <body>
 	<div>Fetch from netmonitor</div>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/styleeditor/test/sjs_huge-css-server.sjs
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function handleRequest(request, response) {
+  response.setStatusLine(request.httpVersion, 200, "Och Aye");
+
+  response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
+  response.setHeader("Pragma", "no-cache");
+  response.setHeader("Expires", "0");
+  response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+
+  // Taken from devtools/shared/webconsole/network-monitor
+  const NETMONITOR_LIMIT = 1048576;
+
+  // 2 * NETMONITOR_LIMIT reaches the exact limit for the netmonitor
+  // 3 * NETMONITOR_LIMIT makes sure we go past it.
+  response.write("x".repeat(3 * NETMONITOR_LIMIT));
+}
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -486,17 +486,17 @@ var StyleSheetActor = protocol.ActorClas
     if (!consoleActor) {
       return null;
     }
     let request = consoleActor.getNetworkEventActorForURL(href);
     if (!request) {
       return null;
     }
     let content = request._response.content;
-    if (request._discardResponseBody || !content) {
+    if (request._discardResponseBody || request._truncated || !content) {
       return null;
     }
     if (content.text.type != "longString") {
       // For short strings, the text is available directly.
       return {
         content: content.text,
         contentType: content.mimeType,
       };
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -2057,16 +2057,17 @@ NetworkEventActor.prototype =
       !!(this._stackTrace && this._stackTrace.length);
 
     for (let prop of ["method", "url", "httpVersion", "headersSize"]) {
       this._request[prop] = networkEvent[prop];
     }
 
     this._discardRequestBody = networkEvent.discardRequestBody;
     this._discardResponseBody = networkEvent.discardResponseBody;
+    this._truncated = false;
     this._private = networkEvent.private;
   },
 
   /**
    * The "getRequestHeaders" packet type handler.
    *
    * @return object
    *         The response packet - network request headers.
@@ -2358,35 +2359,39 @@ NetworkEventActor.prototype =
     this.conn.send(packet);
   },
 
   /**
    * Add network response content.
    *
    * @param object content
    *        The response content.
-   * @param boolean discardedResponseBody
-   *        Tells if the response content was recorded or not.
+   * @param object
+   *        - boolean discardedResponseBody
+   *          Tells if the response content was recorded or not.
+   *        - boolean truncated
+   *          Tells if the some of the response content is missing.
    */
-  addResponseContent: function (content, discardedResponseBody) {
+  addResponseContent: function (content, {discardResponseBody, truncated}) {
+    this._truncated = truncated;
     this._response.content = content;
     content.text = this.parent._createStringGrip(content.text);
     if (typeof content.text == "object") {
       this._longStringActors.add(content.text);
     }
 
     let packet = {
       from: this.actorID,
       type: "networkEventUpdate",
       updateType: "responseContent",
       mimeType: content.mimeType,
       contentSize: content.size,
       encoding: content.encoding,
       transferredSize: content.transferredSize,
-      discardResponseBody: discardedResponseBody,
+      discardResponseBody,
     };
 
     this.conn.send(packet);
   },
 
   /**
    * Add network event timing information.
    *
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -274,16 +274,18 @@ exports.StackTraceCollector = StackTrace
  *        HttpActivity object associated with this request. See NetworkMonitor
  *        for more information.
  */
 function NetworkResponseListener(owner, httpActivity) {
   this.owner = owner;
   this.receivedData = "";
   this.httpActivity = httpActivity;
   this.bodySize = 0;
+  // Indicates if the response had a size greater than RESPONSE_BODY_LIMIT.
+  this.truncated = false;
   // Note that this is really only needed for the non-e10s case.
   // See bug 1309523.
   let channel = this.httpActivity.channel;
   this._wrappedNotificationCallbacks = channel.notificationCallbacks;
   channel.notificationCallbacks = this;
 }
 
 NetworkResponseListener.prototype = {
@@ -407,20 +409,23 @@ NetworkResponseListener.prototype = {
    * @param unsigned long count
    */
   onDataAvailable: function (request, context, inputStream, offset, count) {
     this._findOpenResponse();
     let data = NetUtil.readInputStreamToString(inputStream, count);
 
     this.bodySize += count;
 
-    if (!this.httpActivity.discardResponseBody &&
-        this.receivedData.length < RESPONSE_BODY_LIMIT) {
-      this.receivedData +=
-        NetworkHelper.convertToUnicode(data, request.contentCharset);
+    if (!this.httpActivity.discardResponseBody) {
+      if (this.receivedData.length < RESPONSE_BODY_LIMIT) {
+        this.receivedData +=
+          NetworkHelper.convertToUnicode(data, request.contentCharset);
+      } else {
+        this.truncated = true;
+      }
     }
   },
 
   /**
    * See documentation at
    * https://developer.mozilla.org/En/NsIRequestObserver
    *
    * @param nsIRequest request
@@ -635,17 +640,20 @@ NetworkResponseListener.prototype = {
     if (response.mimeType && this.request.contentCharset) {
       response.mimeType += "; charset=" + this.request.contentCharset;
     }
 
     this.receivedData = "";
 
     this.httpActivity.owner.addResponseContent(
       response,
-      this.httpActivity.discardResponseBody
+      {
+        discardResponseBody: this.httpActivity.discardResponseBody,
+        truncated: this.truncated
+      }
     );
 
     this._wrappedNotificationCallbacks = null;
     this.httpActivity = null;
     this.sink = null;
     this.inputStream = null;
     this.converter = null;
     this.request = null;