Bug 1437435 - Added isBase64, isEmpty method, cleaned up typos and format of comments. r=Honza
authortanhengyeow <E0032242@u.nus.edu>
Tue, 09 Oct 2018 15:56:18 +0000
changeset 496157 c9d9dd203994cd2251869d7b470564281d5d995f
parent 496156 a79effc68816dc508ca25b07cfb61bcb2282e230
child 496158 91b4c3687d7563244fbba0f58075779eb89259fb
child 496160 9a70e53741767b0c2e149e16f5a33594bebc89ba
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza
bugs1437435
milestone64.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 1437435 - Added isBase64, isEmpty method, cleaned up typos and format of comments. r=Honza Differential Revision: https://phabricator.services.mozilla.com/D6148
devtools/client/netmonitor/src/components/ResponsePanel.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_json-empty.js
devtools/client/netmonitor/test/browser_net_json-null.js
devtools/client/netmonitor/test/head.js
devtools/client/netmonitor/test/html_json-empty.html
devtools/client/netmonitor/test/sjs_json-test-server.sjs
--- a/devtools/client/netmonitor/src/components/ResponsePanel.js
+++ b/devtools/client/netmonitor/src/components/ResponsePanel.js
@@ -25,17 +25,17 @@ const JSON_FILTER_TEXT = L10N.getStr("js
 const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name");
 const RESPONSE_IMG_DIMENSIONS = L10N.getStr("netmonitor.response.dimensions");
 const RESPONSE_IMG_MIMETYPE = L10N.getStr("netmonitor.response.mime");
 const RESPONSE_PAYLOAD = L10N.getStr("responsePayload");
 const RESPONSE_PREVIEW = L10N.getStr("responsePreview");
 
 const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
 
-/*
+/**
  * Response panel component
  * Displays the GET parameters and POST data of a request
  */
 class ResponsePanel extends Component {
   static get propTypes() {
     return {
       request: PropTypes.object.isRequired,
       openLink: PropTypes.func,
@@ -48,53 +48,77 @@ class ResponsePanel extends Component {
 
     this.state = {
       imageDimensions: {
         width: 0,
         height: 0,
       },
     };
 
-    this.updateImageDimemsions = this.updateImageDimemsions.bind(this);
-    this.isJSON = this.isJSON.bind(this);
+    this.updateImageDimensions = this.updateImageDimensions.bind(this);
   }
 
   componentDidMount() {
     const { request, connector } = this.props;
     fetchNetworkUpdatePacket(connector.requestData, request, ["responseContent"]);
   }
 
   componentWillReceiveProps(nextProps) {
     const { request, connector } = nextProps;
     fetchNetworkUpdatePacket(connector.requestData, request, ["responseContent"]);
   }
 
-  updateImageDimemsions({ target }) {
+  updateImageDimensions({ target }) {
     this.setState({
       imageDimensions: {
         width: target.naturalWidth,
         height: target.naturalHeight,
       },
     });
   }
 
-  // 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".
-  // Additionally, we also directly parse the response text content to
-  // verify whether it's json or not, to handle responses incorrectly
-  // labeled as text/plain instead.
+  /**
+   * This method checks that the response is base64 encoded by
+   * comparing these 2 values:
+   * 1. The original response
+   * 2. The value of doing a base64 decode on the
+   * response and then base64 encoding the result.
+   * If the values are different or an error is thrown,
+   * the method will return false.
+   */
+  isBase64(response) {
+    try {
+      return btoa(atob(response)) == response;
+    } catch (err) {
+      return false;
+    }
+  }
+
+  /**
+   * 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". Additionally, we also
+   * directly parse the response text content to verify whether
+   * it's json or not, to handle responses incorrectly labeled
+   * as text/plain instead.
+   */
   isJSON(mimeType, response) {
     let json, error;
+
     try {
       json = JSON.parse(response);
     } catch (err) {
-      try {
-        json = JSON.parse(atob(response));
-      } catch (err64) {
+      if (this.isBase64(response)) {
+        try {
+          json = JSON.parse(atob(response));
+        } catch (err64) {
+          error = err;
+        }
+      } else {
         error = err;
       }
     }
 
     if (/\bjson/.test(mimeType) || json) {
       // Extract the actual json substring in case this might be a "JSONP".
       // This regex basically parses a function call and captures the
       // function name and arguments in two separate groups.
@@ -145,17 +169,17 @@ class ResponsePanel extends Component {
     if (mimeType.includes("image/")) {
       const { width, height } = this.state.imageDimensions;
 
       return (
         div({ className: "panel-container response-image-box devtools-monospace" },
           img({
             className: "response-image",
             src: formDataURI(mimeType, encoding, text),
-            onLoad: this.updateImageDimemsions,
+            onLoad: this.updateImageDimensions,
           }),
           div({ className: "response-summary" },
             div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_NAME),
             div({ className: "tabpanel-summary-value" }, getUrlBaseName(url)),
           ),
           div({ className: "response-summary" },
             div({ className: "tabpanel-summary-label" }, RESPONSE_IMG_DIMENSIONS),
             div({ className: "tabpanel-summary-value" }, `${width} × ${height}`),
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -13,16 +13,17 @@ support-files =
   html_cyrillic-test-page.html
   html_frame-test-page.html
   html_frame-subdocument.html
   html_filter-test-page.html
   html_infinite-get-page.html
   html_json-b64.html
   html_json-basic.html
   html_json-custom-mime-test-page.html
+  html_json-empty.html
   html_json-long-test-page.html
   html_json-malformed-test-page.html
   html_json-text-mime-test-page.html
   html_jsonp-test-page.html
   html_maps-test-page.html
   html_navigate-test-page.html
   html_params-test-page.html
   html_pause-test-page.html
@@ -128,16 +129,17 @@ skip-if = (os == 'mac') # Bug 1479782
 [browser_net_filter-autocomplete.js]
 [browser_net_filter-flags.js]
 [browser_net_footer-summary.js]
 [browser_net_headers-alignment.js]
 [browser_net_headers_filter.js]
 [browser_net_headers_sorted.js]
 [browser_net_image-tooltip.js]
 [browser_net_json-b64.js]
+[browser_net_json-empty.js]
 [browser_net_json-null.js]
 [browser_net_json-long.js]
 [browser_net_json-malformed.js]
 [browser_net_json-nogrip.js]
 [browser_net_json_custom_mime.js]
 [browser_net_json_text_mime.js]
 [browser_net_jsonp.js]
 [browser_net_large-response.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_json-empty.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if empty JSON responses are properly displayed.
+ */
+
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(JSON_EMPTY_URL + "?name=empty");
+  info("Starting test... ");
+
+  const { document, store, windowRequire } = monitor.panelWin;
+  const { L10N } = windowRequire("devtools/client/netmonitor/src/utils/l10n");
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+
+  // Execute requests.
+  await performRequests(monitor, tab, 1);
+
+  const onResponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
+  store.dispatch(Actions.toggleNetworkDetails());
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#response-tab"));
+  await onResponsePanelReady;
+
+  const tabpanel = document.querySelector("#response-panel");
+  is(tabpanel.querySelectorAll(".tree-section").length, 2,
+    "There should be 2 tree sections displayed in this tabpanel.");
+  is(tabpanel.querySelectorAll(".empty-notice").length, 0,
+    "The empty notice should not be displayed in this tabpanel.");
+
+  is(tabpanel.querySelector(".response-error-header") === null, true,
+    "The response error header doesn't have the intended visibility.");
+  is(tabpanel.querySelector(".CodeMirror-code") === null, false,
+    "The response editor has the intended visibility.");
+  is(tabpanel.querySelector(".response-image-box") === null, true,
+    "The response image box doesn't have the intended visibility.");
+
+  const jsonView = tabpanel.querySelector(".tree-section .treeLabel") || {};
+  is(jsonView.textContent === L10N.getStr("jsonScopeName"), true,
+    "The response json view has the intended visibility.");
+
+  await teardown(monitor);
+});
--- a/devtools/client/netmonitor/test/browser_net_json-null.js
+++ b/devtools/client/netmonitor/test/browser_net_json-null.js
@@ -15,21 +15,21 @@ add_task(async function() {
   const { L10N } = windowRequire("devtools/client/netmonitor/src/utils/l10n");
   const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
 
   store.dispatch(Actions.batchEnable(false));
 
   // Execute requests.
   await performRequests(monitor, tab, 1);
 
-  const onReponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
+  const onResponsePanelReady = waitForDOM(document, "#response-panel .CodeMirror-code");
   store.dispatch(Actions.toggleNetworkDetails());
   EventUtils.sendMouseEvent({ type: "click" },
     document.querySelector("#response-tab"));
-  await onReponsePanelReady;
+  await onResponsePanelReady;
 
   checkResponsePanelDisplaysJSON();
 
   const tabpanel = document.querySelector("#response-panel");
   is(tabpanel.querySelectorAll(".tree-section").length, 2,
     "There should be 2 tree sections displayed in this tabpanel.");
   is(tabpanel.querySelectorAll(".treeRow:not(.tree-section)").length, 1,
     "There should be 1 json properties displayed in this tabpanel.");
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -56,16 +56,17 @@ const POST_RAW_WITH_HEADERS_URL = EXAMPL
 const PARAMS_URL = EXAMPLE_URL + "html_params-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 JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html";
 const JSON_B64_URL = EXAMPLE_URL + "html_json-b64.html";
 const JSON_BASIC_URL = EXAMPLE_URL + "html_json-basic.html";
+const JSON_EMPTY_URL = EXAMPLE_URL + "html_json-empty.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 SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html";
 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";
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_json-empty.html
@@ -0,0 +1,42 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Network Monitor test page</title>
+  </head>
+
+  <body>
+    <p>Empty JSON test page</p>
+
+    <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
+      function get(address, callback) {
+        const xhr = new XMLHttpRequest();
+        xhr.open("GET", address, true);
+
+        xhr.onreadystatechange = function() {
+          if (this.readyState == this.DONE) {
+            callback();
+          }
+        };
+        xhr.send(null);
+      }
+
+      function performRequests() {
+        // Forward the query parameter for this page to sjs_json-test-server
+        get("sjs_json-test-server.sjs" + window.location.search, function() {
+          // Done.
+        });
+      }
+    </script>
+  </body>
+
+</html>
--- a/devtools/client/netmonitor/test/sjs_json-test-server.sjs
+++ b/devtools/client/netmonitor/test/sjs_json-test-server.sjs
@@ -16,10 +16,13 @@ function handleRequest(request, response
   let name = (params.filter((s) => s.includes("name="))[0] || "").split("=")[1];
   switch (name) {
     case "null":
       response.write("{ \"greeting\": null }");
       break;
     case "nogrip":
       response.write("{\"obj\": {\"type\": \"string\" }}");
       break;
+    case "empty":
+      response.write("{}");
+      break;
   }
 }