--- 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!");
+}