Bug 1260403 - Support brotli encoding in Developer Tools Netmonitor. r=ochameau
authorTooru Fujisawa <arai_a@mac.com>
Tue, 08 Nov 2016 04:18:04 +0900
changeset 321476 cceb2a87580748749fb0932fa4f553b1e60ee8c4
parent 321475 e2acfe066887bb24724f0ee3ba3a532e2491a5ff
child 321477 131019a5ba42fa6a492e44a20f90c37dd8e5427b
push id21
push usermaklebus@msu.edu
push dateThu, 01 Dec 2016 06:22:08 +0000
reviewersochameau
bugs1260403
milestone52.0a1
Bug 1260403 - Support brotli encoding in Developer Tools Netmonitor. r=ochameau
devtools/client/netmonitor/test/browser_net_content-type.js
devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
devtools/client/netmonitor/test/browser_net_copy_response.js
devtools/client/netmonitor/test/browser_net_icon-preview.js
devtools/client/netmonitor/test/browser_net_image-tooltip.js
devtools/client/netmonitor/test/head.js
devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
devtools/shared/webconsole/network-monitor.js
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -2,98 +2,122 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /**
  * Tests if different response content types are handled correctly.
  */
 
-add_task(function* () {
+function* content_type_test(isHTTPS) {
   let { L10N } = require("devtools/client/netmonitor/l10n");
 
-  let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
+  let pageURL = isHTTPS ? HTTPS_CONTENT_TYPE_WITHOUT_CACHE_URL
+                        : CONTENT_TYPE_WITHOUT_CACHE_URL;
+  let imageURL = isHTTPS ? HTTPS_TEST_IMAGE
+                         : TEST_IMAGE;
+  let sjsURL = isHTTPS ? HTTPS_CONTENT_TYPE_SJS
+                       : CONTENT_TYPE_SJS;
+  let { tab, monitor } = yield initNetMonitor(pageURL);
   info("Starting test... ");
 
   let { document, Editor, NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 7);
+  let wait = waitForNetworkEvents(monitor, 8);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
+  let okStatus = isHTTPS ? "Connected" : "OK";
+
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
-    "GET", CONTENT_TYPE_SJS + "?fmt=xml", {
+    "GET", sjsURL + "?fmt=xml", {
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "xml",
       fullMimeType: "text/xml; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
       time: true
     });
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
-    "GET", CONTENT_TYPE_SJS + "?fmt=css", {
+    "GET", sjsURL + "?fmt=css", {
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "css",
       fullMimeType: "text/css; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
     });
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
-    "GET", CONTENT_TYPE_SJS + "?fmt=js", {
+    "GET", sjsURL + "?fmt=js", {
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "js",
       fullMimeType: "application/javascript; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
       time: true
     });
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
-    "GET", CONTENT_TYPE_SJS + "?fmt=json", {
+    "GET", sjsURL + "?fmt=json", {
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "json",
       fullMimeType: "application/json; charset=utf-8",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
       time: true
     });
-  verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
-    "GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
-      status: 404,
-      statusText: "Not Found",
-      type: "html",
-      fullMimeType: "text/html; charset=utf-8",
-      size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
-      time: true
-    });
+  if (!isHTTPS) {
+    // 404 doesn't work on HTTPS test harness.
+    verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
+      "GET", sjsURL + "?fmt=bogus", {
+        status: 404,
+        statusText: "Not Found",
+        type: "html",
+        fullMimeType: "text/html; charset=utf-8",
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
+        time: true
+      });
+  }
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
-    "GET", TEST_IMAGE, {
+    "GET", imageURL, {
       fuzzyUrl: true,
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "png",
       fullMimeType: "image/png",
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
       time: true
     });
   verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
-    "GET", CONTENT_TYPE_SJS + "?fmt=gzip", {
+    "GET", sjsURL + "?fmt=gzip", {
       status: 200,
-      statusText: "OK",
+      statusText: okStatus,
       type: "plain",
       fullMimeType: "text/plain",
       transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
       size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
       time: true
     });
+  if (isHTTPS) {
+    // Brotli is enabled only on HTTPS.
+    verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
+      "GET", sjsURL + "?fmt=gzip", {
+        status: 200,
+        statusText: okStatus,
+        type: "plain",
+        fullMimeType: "text/plain",
+        transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 73),
+        size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 10.73),
+        time: true
+      });
+  }
 
   let onEvent = waitForResponseBodyDisplayed();
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.getElementById("details-pane-toggle"));
   EventUtils.sendMouseEvent({ type: "mousedown" },
     document.querySelectorAll("#details-pane tab")[3]);
   yield onEvent;
   yield testResponseTab("xml");
@@ -111,16 +135,21 @@ add_task(function* () {
   yield testResponseTab("html");
 
   yield selectIndexAndWaitForTabUpdated(5);
   yield testResponseTab("png");
 
   yield selectIndexAndWaitForTabUpdated(6);
   yield testResponseTab("gzip");
 
+  if (isHTTPS) {
+    yield selectIndexAndWaitForTabUpdated(7);
+    yield testResponseTab("br");
+  }
+
   yield teardown(monitor);
 
   function* testResponseTab(type) {
     let tabEl = document.querySelectorAll("#details-pane tab")[3];
     let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
 
     is(tabEl.getAttribute("selected"), "true",
       "The response tab in the network details pane should be selected.");
@@ -235,21 +264,40 @@ add_task(function* () {
         let expected = new Array(1000).join("Hello gzip!");
         let editor = yield NetMonitorView.editor("#response-content-textarea");
         is(editor.getText(), expected,
           "The text shown in the source editor is incorrect for the gzip request.");
         is(editor.getMode(), Editor.modes.text,
           "The mode active in the source editor is incorrect for the gzip request.");
         break;
       }
+      case "br": {
+        checkVisibility("textarea");
+
+        let expected = "X".repeat(64);
+        let editor = yield NetMonitorView.editor("#response-content-textarea");
+        is(editor.getText(), expected,
+          "The text shown in the source editor is incorrect for the brotli request.");
+        is(editor.getMode(), Editor.modes.text,
+          "The mode active in the source editor is incorrect for the brotli request.");
+        break;
+      }
     }
   }
 
   function selectIndexAndWaitForTabUpdated(index) {
     let onTabUpdated = monitor.panelWin.once(monitor.panelWin.EVENTS.TAB_UPDATED);
     RequestsMenu.selectedIndex = index;
     return onTabUpdated;
   }
 
   function waitForResponseBodyDisplayed() {
     return monitor.panelWin.once(monitor.panelWin.EVENTS.RESPONSE_BODY_DISPLAYED);
   }
+}
+
+add_task(function* () {
+    yield* content_type_test(false);
 });
+
+add_task(function* () {
+    yield* content_type_test(true);
+});
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -11,17 +11,17 @@ add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 7);
+  let wait = waitForNetworkEvents(monitor, 8);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(5);
   RequestsMenu.selectedItem = requestItem;
 
--- a/devtools/client/netmonitor/test/browser_net_copy_response.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -13,17 +13,17 @@ add_task(function* () {
 
   const EXPECTED_RESULT = '{ "greeting": "Hello JSON!" }';
 
   let { NetMonitorView } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
-  let wait = waitForNetworkEvents(monitor, 7);
+  let wait = waitForNetworkEvents(monitor, 8);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(3);
   RequestsMenu.selectedItem = requestItem;
 
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -39,17 +39,17 @@ add_task(function* () {
 
   info("Checking the image thumbnail after a reload.");
   checkImageThumbnail();
 
   yield teardown(monitor);
 
   function waitForEvents() {
     return promise.all([
-      waitForNetworkEvents(monitor, 7),
+      waitForNetworkEvents(monitor, 8),
       monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED)
     ]);
   }
 
   function performRequests() {
     return ContentTask.spawn(tab.linkedBrowser, {}, function* () {
       content.wrappedJSObject.performRequests();
     });
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -10,17 +10,17 @@ add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { $, EVENTS, ACTIVITY_TYPE, NetMonitorView, NetMonitorController } =
     monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
   RequestsMenu.lazyUpdate = true;
 
-  let onEvents = waitForNetworkEvents(monitor, 7);
+  let onEvents = waitForNetworkEvents(monitor, 8);
   let onThumbnail = monitor.panelWin.once(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
 
   yield performRequests();
   yield onEvents;
   yield onThumbnail;
 
   info("Checking the image thumbnail after a few requests were made...");
   yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[5]);
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -8,22 +8,24 @@
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 var NetworkHelper = require("devtools/shared/webconsole/network-helper");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
+const HTTPS_EXAMPLE_URL = "https://example.com/browser/devtools/client/netmonitor/test/";
 
 const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
+const HTTPS_CONTENT_TYPE_WITHOUT_CACHE_URL = HTTPS_EXAMPLE_URL + "html_content-type-without-cache-test-page.html";
 const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html";
 const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html";
 const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html";
 const POST_JSON_URL = EXAMPLE_URL + "html_post-json-test-page.html";
 const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
 const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html";
 const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html";
 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
@@ -39,26 +41,28 @@ const SINGLE_GET_URL = EXAMPLE_URL + "ht
 const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
 const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
 const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
 const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html";
 const CORS_URL = EXAMPLE_URL + "html_cors-test-page.html";
 
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
+const HTTPS_CONTENT_TYPE_SJS = HTTPS_EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs";
 const HTTPS_REDIRECT_SJS = EXAMPLE_URL + "sjs_https-redirect-test-server.sjs";
 const CORS_SJS_PATH = "/browser/devtools/client/netmonitor/test/sjs_cors-test-server.sjs";
 const HSTS_SJS = EXAMPLE_URL + "sjs_hsts-test-server.sjs";
 
 const HSTS_BASE_URL = EXAMPLE_URL;
 const HSTS_PAGE_URL = CUSTOM_GET_URL;
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
+const HTTPS_TEST_IMAGE = HTTPS_EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
--- a/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
+++ b/devtools/client/netmonitor/test/html_content-type-without-cache-test-page.html
@@ -30,17 +30,19 @@
       function performRequests() {
         get("sjs_content-type-test-server.sjs?fmt=xml", function() {
           get("sjs_content-type-test-server.sjs?fmt=css", function() {
             get("sjs_content-type-test-server.sjs?fmt=js", function() {
               get("sjs_content-type-test-server.sjs?fmt=json", function() {
                 get("sjs_content-type-test-server.sjs?fmt=bogus", function() {
                   get("test-image.png?v=" + Math.random(), function() {
                     get("sjs_content-type-test-server.sjs?fmt=gzip", function() {
-                      // Done.
+                      get("sjs_content-type-test-server.sjs?fmt=br", function() {
+                        // Done.
+                      });
                     });
                   });
                 });
               });
             });
           });
         });
       }
--- a/devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
+++ b/devtools/client/netmonitor/test/sjs_content-type-test-server.sjs
@@ -227,16 +227,27 @@ function handleRequest(request, response
             response.write(buffer);
             response.finish();
           }
         };
         let data = new Array(1000).join("Hello gzip!");
         doubleGzipCompressString(data, observer);
         break;
       }
+      case "br": {
+        response.setStatusLine(request.httpVersion, status, "Connected");
+        response.setHeader("Content-Type", "text/plain", false);
+        response.setHeader("Content-Encoding", "br", false);
+        setCacheHeaders();
+        response.setHeader("Content-Length", "10", false);
+        // Use static data since we cannot encode brotli.
+        response.write("\x1b\x3f\x00\x00\x24\xb0\xe2\x99\x80\x12");
+        response.finish();
+        break;
+      }
       case "hls-m3u8": {
         response.setStatusLine(request.httpVersion, status, "OK");
         response.setHeader("Content-Type", "application/x-mpegurl", false);
         setCacheHeaders();
         response.write("#EXTM3U\n");
         response.finish();
         break;
       }
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -441,17 +441,17 @@ NetworkResponseListener.prototype = {
         channel instanceof Ci.nsIEncodedChannel &&
         channel.contentEncodings &&
         !channel.applyConversion) {
       let encodingHeader = channel.getResponseHeader("Content-Encoding");
       let scs = Cc["@mozilla.org/streamConverters;1"]
         .getService(Ci.nsIStreamConverterService);
       let encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
       let nextListener = this;
-      let acceptedEncodings = ["gzip", "deflate", "x-gzip", "x-deflate"];
+      let acceptedEncodings = ["gzip", "deflate", "br", "x-gzip", "x-deflate"];
       for (let i in encodings) {
         // There can be multiple conversions applied
         let enc = encodings[i].toLowerCase();
         if (acceptedEncodings.indexOf(enc) > -1) {
           this.converter = scs.asyncConvertData(enc, "uncompressed",
                                                 nextListener, null);
           nextListener = this.converter;
         }