Bug 905226 - Identify custom JSON MIME-types as text. r=vporof
authorFelix Crux <felixc@felixcrux.com>
Fri, 20 Sep 2013 10:20:44 -0400
changeset 148071 07a9df4849b7e866fc6e172b3702c02e374559a4
parent 148070 1dde3e9a04c4dc89d6126b27a106235d76512f85
child 148072 4947135aebacb5637618e67731bf4f56cc5fbf50
push id2762
push userryanvm@gmail.com
push dateFri, 20 Sep 2013 14:21:59 +0000
treeherderfx-team@42d54d6f6d1c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvporof
bugs905226
milestone27.0a1
Bug 905226 - Identify custom JSON MIME-types as text. r=vporof MIME types that can't be identified as textual are base64 encoded before being shown in the Network Monitor tab of the developer tools. This text detection wasn't identifying "custom" JSON MIME-types like "application/x-bigcorp-api-json" as being JSON, and consequently they were shown in a rather unhelpful base64 form. This patch extends and liberalizes the isTextMimeType method to identify these forms correctly.
browser/devtools/netmonitor/netmonitor-view.js
browser/devtools/netmonitor/test/Makefile.in
browser/devtools/netmonitor/test/browser_net_json_custom_mime.js
browser/devtools/netmonitor/test/head.js
browser/devtools/netmonitor/test/html_json-custom-mime-test-page.html
browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs
toolkit/devtools/webconsole/network-helper.js
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -1859,18 +1859,21 @@ NetworkDetailsView.prototype = {
    */
   _setResponseBody: function(aUrl, aResponse) {
     if (!aResponse) {
       return;
     }
     let { mimeType, text, encoding } = aResponse.content;
 
     gNetwork.getString(text).then(aString => {
-      // Handle json.
-      if (mimeType.contains("/json")) {
+      // Handle json, which we tentatively identify by checking the MIME type
+      // for "json" after any word boundary. This works for the standard
+      // "application/json", and also for custom types like "x-bigcorp-json".
+      // This should be marginally more reliable than just looking for "json".
+      if (/\bjson/.test(mimeType)) {
         let jsonpRegex = /^[a-zA-Z0-9_$]+\(|\)$/g; // JSONP with callback.
         let sanitizedJSON = aString.replace(jsonpRegex, "");
         let callbackPadding = aString.match(jsonpRegex);
 
         // Make sure this is an valid JSON object first. If so, nicely display
         // the parsing results in a variables view. Otherwise, simply show
         // the contents as plain text.
         try {
--- a/browser/devtools/netmonitor/test/Makefile.in
+++ b/browser/devtools/netmonitor/test/Makefile.in
@@ -19,16 +19,17 @@ MOCHITEST_BROWSER_FILES = \
 	browser_net_cyrillic-02.js \
 	browser_net_large-response.js \
 	browser_net_status-codes.js \
 	browser_net_post-data-01.js \
 	browser_net_post-data-02.js \
 	browser_net_jsonp.js \
 	browser_net_json-long.js \
 	browser_net_json-malformed.js \
+	browser_net_json_custom_mime.js \
 	browser_net_timeline_ticks.js \
 	browser_net_sort-01.js \
 	browser_net_sort-02.js \
 	browser_net_sort-03.js \
 	browser_net_filter-01.js \
 	browser_net_filter-02.js \
 	browser_net_filter-03.js \
 	browser_net_accessibility-01.js \
@@ -45,16 +46,17 @@ MOCHITEST_BROWSER_FILES = \
 	html_content-type-test-page.html \
 	html_cyrillic-test-page.html \
 	html_status-codes-test-page.html \
 	html_post-data-test-page.html \
 	html_post-raw-test-page.html \
 	html_jsonp-test-page.html \
 	html_json-long-test-page.html \
 	html_json-malformed-test-page.html \
+	html_json-custom-mime-test-page.html \
 	html_sorting-test-page.html \
 	html_filter-test-page.html \
 	html_infinite-get-page.html \
 	html_custom-get-page.html \
 	sjs_simple-test-server.sjs \
 	sjs_content-type-test-server.sjs \
 	sjs_status-codes-test-server.sjs \
 	sjs_sorting-test-server.sjs \
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_json_custom_mime.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if JSON responses with unusal/custom MIME types are handled correctly.
+ */
+
+function test() {
+  initNetMonitor(JSON_CUSTOM_MIME_URL).then(([aTab, aDebuggee, aMonitor]) => {
+    info("Starting test... ");
+
+    let { document, L10N, SourceEditor, NetMonitorView } = aMonitor.panelWin;
+    let { RequestsMenu } = NetMonitorView;
+
+    RequestsMenu.lazyUpdate = false;
+
+    waitForNetworkEvents(aMonitor, 1).then(() => {
+      verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+        "GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
+          status: 200,
+          statusText: "OK",
+          type: "x-bigcorp-json",
+          fullMimeType: "text/x-bigcorp-json; charset=utf-8",
+          size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.04),
+          time: true
+        });
+
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+        document.getElementById("details-pane-toggle"));
+      EventUtils.sendMouseEvent({ type: "mousedown" },
+        document.querySelectorAll("#details-pane tab")[3]);
+
+      testResponseTab();
+      teardown(aMonitor).then(finish);
+
+      function testResponseTab() {
+        let tab = document.querySelectorAll("#details-pane tab")[3];
+        let tabpanel = document.querySelectorAll("#details-pane tabpanel")[3];
+
+        is(tab.getAttribute("selected"), "true",
+          "The response tab in the network details pane should be selected.");
+
+        is(tabpanel.querySelector("#response-content-info-header")
+          .hasAttribute("hidden"), true,
+          "The response info header doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-json-box")
+          .hasAttribute("hidden"), false,
+          "The response content json box doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-textarea-box")
+          .hasAttribute("hidden"), true,
+          "The response content textarea box doesn't have the intended visibility.");
+        is(tabpanel.querySelector("#response-content-image-box")
+          .hasAttribute("hidden"), true,
+          "The response content image box doesn't have the intended visibility.");
+
+        is(tabpanel.querySelectorAll(".variables-view-scope").length, 1,
+          "There should be 1 json scope displayed in this tabpanel.");
+        is(tabpanel.querySelectorAll(".variables-view-property").length, 2,
+          "There should be 2 json properties displayed in this tabpanel.");
+        is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+          "The empty notice should not be displayed in this tabpanel.");
+
+        let jsonScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+        is(jsonScope.querySelectorAll(".variables-view-property .name")[0].getAttribute("value"),
+          "greeting", "The first json property name was incorrect.");
+        is(jsonScope.querySelectorAll(".variables-view-property .value")[0].getAttribute("value"),
+          "\"Hello oddly-named JSON!\"", "The first json property value was incorrect.");
+
+        is(jsonScope.querySelectorAll(".variables-view-property .name")[1].getAttribute("value"),
+          "__proto__", "The second json property name was incorrect.");
+        is(jsonScope.querySelectorAll(".variables-view-property .value")[1].getAttribute("value"),
+          "Object", "The second json property value was incorrect.");
+      }
+    });
+
+    aDebuggee.performRequests();
+  });
+}
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -18,16 +18,17 @@ const NAVIGATE_URL = EXAMPLE_URL + "html
 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-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_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html";
 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html";
 const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html";
 const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html";
+const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html";
 const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html";
 const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html";
 const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html";
 const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-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 STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs";
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/html_json-custom-mime-test-page.html
@@ -0,0 +1,35 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Network Monitor test page</title>
+  </head>
+
+  <body>
+    <p>JSONP test</p>
+
+    <script type="text/javascript">
+      function get(aAddress, aCallback) {
+        var xhr = new XMLHttpRequest();
+        xhr.open("GET", aAddress, true);
+
+        xhr.onreadystatechange = function() {
+          if (this.readyState == this.DONE) {
+            aCallback();
+          }
+        };
+        xhr.send(null);
+      }
+
+      function performRequests() {
+        get("sjs_content-type-test-server.sjs?fmt=json-custom-mime", function() {
+          // Done.
+        });
+      }
+    </script>
+  </body>
+
+</html>
--- a/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs
+++ b/browser/devtools/netmonitor/test/sjs_content-type-test-server.sjs
@@ -80,16 +80,23 @@ function handleRequest(request, response
       }
       case "json-malformed": {
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.setHeader("Content-Type", "text/json; charset=utf-8", false);
         response.write("{ \"greeting\": \"Hello malformed JSON!\" },");
         response.finish();
         break;
       }
+      case "json-custom-mime": {
+        response.setStatusLine(request.httpVersion, 200, "OK");
+        response.setHeader("Content-Type", "text/x-bigcorp-json; charset=utf-8", false);
+        response.write("{ \"greeting\": \"Hello oddly-named JSON!\" }");
+        response.finish();
+        break;
+      }
       case "font": {
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.setHeader("Content-Type", "font/woff", false);
         response.finish();
         break;
       }
       case "image": {
         response.setStatusLine(request.httpVersion, 200, "OK");
--- a/toolkit/devtools/webconsole/network-helper.js
+++ b/toolkit/devtools/webconsole/network-helper.js
@@ -415,17 +415,21 @@ let NetworkHelper = {
    * @return boolean
    */
   isTextMimeType: function NH_isTextMimeType(aMimeType)
   {
     if (aMimeType.indexOf("text/") == 0) {
       return true;
     }
 
-    if (/^application\/[a-z-]+\+xml$/.test(aMimeType)) {
+    // XML and JSON often come with custom MIME types, so in addition to the
+    // standard "application/xml" and "application/json", we also look for
+    // variants like "application/x-bigcorp-xml" by checking for either string
+    // after any word boundary.
+    if (/^application\/[a-z-]+\b(xml|json)/.test(aMimeType)) {
       return true;
     }
 
     switch (this.mimeCategoryMap[aMimeType]) {
       case "txt":
       case "js":
       case "json":
       case "css":