Bug 1398524 - Sort Request-Cookies,Response-Cookies in 'Cookies' tab and Query-String,Form-Params,JSON in Params tab. r=Honza draft
authorabhinav <abhinav.koppula@gmail.com>
Sun, 17 Sep 2017 03:16:41 +0530
changeset 671311 219ba067da783e4d9bb000f14631929cb05b7478
parent 668548 ca7d18dbacbf103d74a3213d8d08a7c3e4def9a2
child 733498 e31a32e90982067ef2a9a94b2711c7515c75b27c
push id81918
push userbmo:abhinav.koppula@gmail.com
push dateWed, 27 Sep 2017 20:24:03 +0000
reviewersHonza
bugs1398524
milestone58.0a1
Bug 1398524 - Sort Request-Cookies,Response-Cookies in 'Cookies' tab and Query-String,Form-Params,JSON in Params tab. r=Honza MozReview-Commit-ID: KE2plx3j9Qr
devtools/client/netmonitor/src/components/cookies-panel.js
devtools/client/netmonitor/src/components/headers-panel.js
devtools/client/netmonitor/src/components/params-panel.js
devtools/client/netmonitor/src/utils/moz.build
devtools/client/netmonitor/src/utils/sort-utils.js
devtools/client/netmonitor/test/browser.ini
devtools/client/netmonitor/test/browser_net_cookies_sorted.js
devtools/client/netmonitor/test/browser_net_params_sorted.js
devtools/client/netmonitor/test/browser_net_post-data-01.js
devtools/client/netmonitor/test/browser_net_post-data-02.js
devtools/client/netmonitor/test/browser_net_post-data-03.js
devtools/client/netmonitor/test/head.js
devtools/client/netmonitor/test/sjs_simple-unsorted-cookies-test-server.sjs
--- a/devtools/client/netmonitor/src/components/cookies-panel.js
+++ b/devtools/client/netmonitor/src/components/cookies-panel.js
@@ -5,16 +5,17 @@
 "use strict";
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
+const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Component
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div } = DOM;
 
 const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
 const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
@@ -45,21 +46,21 @@ function CookiesPanel({
     return div({ className: "empty-notice" },
       COOKIES_EMPTY_TEXT
     );
   }
 
   let object = {};
 
   if (responseCookies.length) {
-    object[RESPONSE_COOKIES] = getProperties(responseCookies);
+    object[RESPONSE_COOKIES] = sortObjectKeys(getProperties(responseCookies));
   }
 
   if (requestCookies.length) {
-    object[REQUEST_COOKIES] = getProperties(requestCookies);
+    object[REQUEST_COOKIES] = sortObjectKeys(getProperties(requestCookies));
   }
 
   return (
     div({ className: "panel-container" },
       PropertiesView({
         object,
         filterPlaceHolder: COOKIES_FILTER_TEXT,
         sectionNames: SECTION_NAMES,
--- a/devtools/client/netmonitor/src/components/headers-panel.js
+++ b/devtools/client/netmonitor/src/components/headers-panel.js
@@ -15,16 +15,17 @@ const {
   getFormattedSize,
 } = require("../utils/format-utils");
 const { L10N } = require("../utils/l10n");
 const {
   getHeadersURL,
   getHTTPStatusCodeURL,
 } = require("../utils/mdn-utils");
 const { writeHeaderText } = require("../utils/request-utils");
+const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Components
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const MDNLink = createFactory(require("./mdn-link"));
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { Rep } = REPS;
 const { button, div, input, textarea, span } = DOM;
@@ -69,17 +70,17 @@ const HeadersPanel = createClass({
       let headerKey = `${title} (${getFormattedSize(headers.headersSize, 3)})`;
       let propertiesResult = {
         [headerKey]:
           headers.headers.reduce((acc, { name, value }) =>
             name ? Object.assign(acc, { [name]: value }) : acc
           , {})
       };
 
-      propertiesResult[headerKey] = this.sortByKey(propertiesResult[headerKey]);
+      propertiesResult[headerKey] = sortObjectKeys(propertiesResult[headerKey]);
       return propertiesResult;
     }
 
     return null;
   },
 
   toggleRawHeaders() {
     this.setState({
@@ -123,26 +124,16 @@ const HeadersPanel = createClass({
         })),
         headerDocURL ? MDNLink({
           url: headerDocURL,
         }) : null
       )
     );
   },
 
-  sortByKey: function (object) {
-    let result = {};
-    Object.keys(object).sort(function (left, right) {
-      return left.toLowerCase().localeCompare(right.toLowerCase());
-    }).forEach(function (key) {
-      result[key] = object[key];
-    });
-    return result;
-  },
-
   render() {
     const {
       openLink,
       cloneSelectedRequest,
       request: {
         fromCache,
         fromServiceWorker,
         httpVersion,
--- a/devtools/client/netmonitor/src/components/params-panel.js
+++ b/devtools/client/netmonitor/src/components/params-panel.js
@@ -6,16 +6,17 @@
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { getUrlQuery, parseQueryString, parseFormData } = require("../utils/request-utils");
+const { sortObjectKeys } = require("../utils/sort-utils");
 
 // Components
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div } = DOM;
 
 const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
 const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
@@ -71,17 +72,17 @@ function ParamsPanel({
   if (formDataSections && formDataSections.length === 0 && postData) {
     try {
       json = JSON.parse(postData);
     } catch (error) {
       // Continue regardless of parsing error
     }
 
     if (json) {
-      object[JSON_SCOPE_NAME] = json;
+      object[JSON_SCOPE_NAME] = sortObjectKeys(json);
     } else {
       object[PARAMS_POST_PAYLOAD] = {
         EDITOR_CONFIG: {
           text: postData,
           mode: mimeType.replace(/;.+/, ""),
         },
       };
     }
@@ -113,23 +114,23 @@ ParamsPanel.propTypes = {
  * Since TreeView only support Object(dict) format.
  * This function also deal with duplicate key case
  * (for multiple selection and query params with same keys)
  *
  * @param {Object[]} arr - key-value pair array like query or form params
  * @returns {Object} Rep compatible object
  */
 function getProperties(arr) {
-  return arr.reduce((map, obj) => {
+  return sortObjectKeys(arr.reduce((map, obj) => {
     let value = map[obj.name];
     if (value) {
       if (typeof value !== "object") {
         map[obj.name] = [value];
       }
       map[obj.name].push(obj.value);
     } else {
       map[obj.name] = obj.value;
     }
     return map;
-  }, {});
+  }, {}));
 }
 
 module.exports = ParamsPanel;
--- a/devtools/client/netmonitor/src/utils/moz.build
+++ b/devtools/client/netmonitor/src/utils/moz.build
@@ -10,9 +10,10 @@ DevToolsModules(
     'filter-text-utils.js',
     'format-utils.js',
     'l10n.js',
     'mdn-utils.js',
     'menu.js',
     'prefs.js',
     'request-utils.js',
     'sort-predicates.js',
+    'sort-utils.js'
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/utils/sort-utils.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Sorts object by keys in alphabetical order
+ * If object has nested children, it sorts the child-elements also by keys
+ * @param {object} which should be sorted by keys in alphabetical order
+ */
+function sortObjectKeys(object) {
+  if (object == null) {
+    return null;
+  }
+  return Object.keys(object).sort(function (left, right) {
+    return left.toLowerCase().localeCompare(right.toLowerCase());
+  }).reduce((acc, key) => {
+    if (typeof object[key] === "object") {
+      acc[key] = sortObjectKeys(object[key]);
+    } else {
+      acc[key] = object[key];
+    }
+    return acc;
+  }, {});
+}
+
+module.exports = {
+  sortObjectKeys
+};
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -39,16 +39,17 @@ support-files =
   html_copy-as-curl.html
   html_curl-utils.html
   sjs_content-type-test-server.sjs
   sjs_cors-test-server.sjs
   sjs_https-redirect-test-server.sjs
   sjs_hsts-test-server.sjs
   sjs_json-test-server.sjs
   sjs_simple-test-server.sjs
+  sjs_simple-unsorted-cookies-test-server.sjs
   sjs_sorting-test-server.sjs
   sjs_status-codes-test-server.sjs
   sjs_truncate-test-server.sjs
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   !/devtools/client/framework/test/shared-head.js
   xhr_bundle.js
@@ -94,16 +95,17 @@ skip-if = (os == 'linux' && bits == 32 &
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_response.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_copy_headers.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
+[browser_net_cookies_sorted.js]
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_frame.js]
 [browser_net_header-docs.js]
@@ -128,16 +130,17 @@ skip-if = (os == 'linux' && debug && bit
 [browser_net_jsonp.js]
 [browser_net_large-response.js]
 [browser_net_leak_on_tab_close.js]
 [browser_net_open_in_debugger.js]
 [browser_net_open_in_style_editor.js]
 [browser_net_open_request_in_tab.js]
 [browser_net_pane-collapse.js]
 [browser_net_pane-toggle.js]
+[browser_net_params_sorted.js]
 [browser_net_persistent_logs.js]
 [browser_net_post-data-01.js]
 [browser_net_post-data-02.js]
 [browser_net_post-data-03.js]
 [browser_net_post-data-04.js]
 [browser_net_prefs-and-l10n.js]
 [browser_net_prefs-reload.js]
 skip-if = os == 'win' # bug 1391264
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_cookies_sorted.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Request-Cookies and Response-Cookies are sorted in Cookies tab.
+ */
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(SIMPLE_UNSORTED_COOKIES_SJS);
+  info("Starting test... ");
+
+  let { document, store, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+
+  tab.linkedBrowser.reload();
+
+  let wait = waitForNetworkEvents(monitor, 1);
+  yield wait;
+
+  wait = waitForDOM(document, ".headers-overview");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  yield wait;
+
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#cookies-tab"));
+
+  info("Check if Request-Cookies and Response-Cookies are sorted");
+  let expectedLabelValues = ["Response cookies", "bob", "httpOnly", "value",
+                             "foo", "httpOnly", "value", "tom", "httpOnly", "value",
+                             "Request cookies", "bob", "foo", "tom"];
+  let labelCells = document.querySelectorAll(".treeLabelCell");
+  labelCells.forEach(function (val, index) {
+    is(val.innerText, expectedLabelValues[index],
+    "Actual label value " + val.innerText + " not equal to expected label value "
+    + expectedLabelValues[index]);
+  });
+  yield teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_params_sorted.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests whether keys in Params panel are sorted.
+ */
+
+add_task(function* () {
+  let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
+  info("Starting test... ");
+
+  let { document, store, windowRequire } = monitor.panelWin;
+  let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+
+  store.dispatch(Actions.batchEnable(false));
+
+  let wait = waitForNetworkEvents(monitor, 0, 2);
+  yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
+    content.wrappedJSObject.performRequests();
+  });
+  yield wait;
+
+  wait = waitForDOM(document, ".headers-overview");
+  EventUtils.sendMouseEvent({ type: "mousedown" },
+    document.querySelectorAll(".request-list-item")[0]);
+  yield wait;
+
+  EventUtils.sendMouseEvent({ type: "click" },
+    document.querySelector("#params-tab"));
+
+  let actualKeys = document.querySelectorAll(".treeLabel");
+  let expectedKeys = ["Query string", "baz", "foo", "type",
+                      "Form data", "baz", "foo"];
+
+  for (let i = 0; i < actualKeys.length; i++) {
+    is(actualKeys[i].innerText, expectedKeys[i],
+      "Actual value " + actualKeys[i].innerText + " is equal to the " +
+      "expected value " + expectedKeys[i]);
+  }
+});
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -107,30 +107,30 @@ add_task(function* () {
       L10N.getStr(type == "urlencoded" ? "paramsFormData" : "paramsPostPayload"),
       "The post section doesn't have the correct title.");
 
     let labels = tabpanel
       .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
     let values = tabpanel
       .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-    is(labels[0].textContent, "foo", "The first query param name was incorrect.");
-    is(values[0].textContent, "bar", "The first query param value was incorrect.");
-    is(labels[1].textContent, "baz", "The second query param name was incorrect.");
-    is(values[1].textContent, "42", "The second query param value was incorrect.");
+    is(labels[0].textContent, "baz", "The first query param name was incorrect.");
+    is(values[0].textContent, "42", "The first query param value was incorrect.");
+    is(labels[1].textContent, "foo", "The second query param name was incorrect.");
+    is(values[1].textContent, "bar", "The second query param value was incorrect.");
     is(labels[2].textContent, "type", "The third query param name was incorrect.");
     is(values[2].textContent, type, "The third query param value was incorrect.");
 
     if (type == "urlencoded") {
       checkVisibility("params");
       is(labels.length, 5, "There should be 5 param values displayed in this tabpanel.");
-      is(labels[3].textContent, "foo", "The first post param name was incorrect.");
-      is(values[3].textContent, "bar", "The first post param value was incorrect.");
-      is(labels[4].textContent, "baz", "The second post param name was incorrect.");
-      is(values[4].textContent, "123", "The second post param value was incorrect.");
+      is(labels[3].textContent, "baz", "The first post param name was incorrect.");
+      is(values[3].textContent, "123", "The first post param value was incorrect.");
+      is(labels[4].textContent, "foo", "The second post param name was incorrect.");
+      is(values[4].textContent, "bar", "The second post param value was incorrect.");
     } else {
       checkVisibility("params editor");
 
       is(labels.length, 3, "There should be 3 param values displayed in this tabpanel.");
 
       let text = document.querySelector(".CodeMirror-code").textContent;
 
       ok(text.includes("Content-Disposition: form-data; name=\"text\""),
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -49,15 +49,15 @@ add_task(function* () {
     L10N.getStr("paramsFormData"),
     "The post section doesn't have the correct title.");
 
   let labels = tabpanel
     .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
   let values = tabpanel
     .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-  is(labels[0].textContent, "foo", "The first query param name was incorrect.");
-  is(values[0].textContent, "bar", "The first query param value was incorrect.");
-  is(labels[1].textContent, "baz", "The second query param name was incorrect.");
-  is(values[1].textContent, "123", "The second query param value was incorrect.");
+  is(labels[0].textContent, "baz", "The first query param name was incorrect.");
+  is(values[0].textContent, "123", "The first query param value was incorrect.");
+  is(labels[1].textContent, "foo", "The second query param name was incorrect.");
+  is(values[1].textContent, "bar", "The second query param value was incorrect.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -74,15 +74,15 @@ add_task(function* () {
     L10N.getStr("paramsFormData"),
     "The form data section doesn't have the correct title.");
 
   labels = tabpanel
     .querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
   values = tabpanel
     .querySelectorAll("tr:not(.tree-section) .treeValueCell .objectBox");
 
-  is(labels[0].textContent, "foo", "The first payload param name was incorrect.");
-  is(values[0].textContent, "bar", "The first payload param value was incorrect.");
-  is(labels[1].textContent, "baz", "The second payload param name was incorrect.");
-  is(values[1].textContent, "123", "The second payload param value was incorrect.");
+  is(labels[0].textContent, "baz", "The first payload param name was incorrect.");
+  is(values[0].textContent, "123", "The first payload param value was incorrect.");
+  is(labels[1].textContent, "foo", "The second payload param name was incorrect.");
+  is(values[1].textContent, "bar", "The second payload param value was incorrect.");
 
   return teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -57,16 +57,17 @@ const CUSTOM_GET_URL = EXAMPLE_URL + "ht
 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";
 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 SIMPLE_UNSORTED_COOKIES_SJS = EXAMPLE_URL + "sjs_simple-unsorted-cookies-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";
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/sjs_simple-unsorted-cookies-test-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("Set-Cookie", "tom=cool; Max-Age=10; HttpOnly", true);
+  response.setHeader("Set-Cookie", "bob=true; Max-Age=10; HttpOnly", true);
+  response.setHeader("Set-Cookie", "foo=bar; Max-Age=10; HttpOnly", true);
+
+  response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
+  response.setHeader("Foo-Bar", "baz", false);
+  response.write("Hello world!");
+}