Bug 895747 - DevTools Net Panel: cannot inspect contents of some POST messages, r=rcampbell
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -50,18 +50,17 @@ const DEFAULT_EDITOR_CONFIG = {
const GENERIC_VARIABLES_VIEW_SETTINGS = {
lazyEmpty: true,
lazyEmptyDelay: 10, // ms
searchEnabled: true,
editableValueTooltip: "",
editableNameTooltip: "",
preventDisableOnChange: true,
preventDescriptorModifiers: true,
- eval: () => {},
- switch: () => {}
+ eval: () => {}
};
const NETWORK_ANALYSIS_PIE_CHART_DIAMETER = 200; // px
/**
* Object defining the network monitor view components.
*/
let NetMonitorView = {
/**
@@ -2096,42 +2095,55 @@ NetworkDetailsView.prototype = {
* The "requestPostData" message received from the server.
* @return object
* A promise that is resolved when the request post params are set.
*/
_setRequestPostParams: function(aHeadersResponse, aPostDataResponse) {
if (!aHeadersResponse || !aPostDataResponse) {
return promise.resolve();
}
- return gNetwork.getString(aPostDataResponse.postData.text).then(aString => {
- // Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
- let cType = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
- let cString = cType ? cType.value : "";
- if (cString.contains("x-www-form-urlencoded") ||
- aString.contains("x-www-form-urlencoded")) {
- let formDataGroups = aString.split(/\r\n|\n|\r/);
- for (let group of formDataGroups) {
- this._addParams(this._paramsFormData, group);
+ return gNetwork.getString(aPostDataResponse.postData.text).then(aPostData => {
+ let contentTypeHeader = aHeadersResponse.headers.filter(({ name }) => name == "Content-Type")[0];
+ let contentTypeLongString = contentTypeHeader ? contentTypeHeader.value : "";
+
+ return gNetwork.getString(contentTypeLongString).then(aContentType => {
+ let urlencoded = "x-www-form-urlencoded";
+
+ // Handle query strings (poor man's forms, e.g. "?foo=bar&baz=42").
+ if (aContentType.contains(urlencoded)) {
+ let formDataGroups = aPostData.split(/\r\n|\r|\n/);
+ for (let group of formDataGroups) {
+ this._addParams(this._paramsFormData, group);
+ }
}
- }
- // Handle actual forms ("multipart/form-data" content type).
- else {
- // This is really awkward, but hey, it works. Let's show an empty
- // scope in the params view and place the source editor containing
- // the raw post data directly underneath.
- $("#request-params-box").removeAttribute("flex");
- let paramsScope = this._params.addScope(this._paramsPostPayload);
- paramsScope.expanded = true;
- paramsScope.locked = true;
+ // Handle actual forms ("multipart/form-data" content type).
+ else {
+ // This is really awkward, but hey, it works. Let's show an empty
+ // scope in the params view and place the source editor containing
+ // the raw post data directly underneath.
+ $("#request-params-box").removeAttribute("flex");
+ let paramsScope = this._params.addScope(this._paramsPostPayload);
+ paramsScope.expanded = true;
+ paramsScope.locked = true;
- $("#request-post-data-textarea-box").hidden = false;
- return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
- aEditor.setText(aString);
- });
- }
+ $("#request-post-data-textarea-box").hidden = false;
+ return NetMonitorView.editor("#request-post-data-textarea").then(aEditor => {
+ // Most POST bodies are usually JSON, so they can be neatly
+ // syntax highlighted as JS. Otheriwse, fall back to plain text.
+ try {
+ JSON.parse(aPostData);
+ aEditor.setMode(Editor.modes.js);
+ } catch (e) {
+ aEditor.setMode(Editor.modes.text);
+ } finally {
+ aEditor.setText(aPostData);
+ }
+ });
+ }
+ });
}).then(() => window.emit(EVENTS.REQUEST_POST_PARAMS_DISPLAYED));
},
/**
* Populates the params container in this view with the specified data.
*
* @param string aName
* The type of params to populate (get or post).
@@ -2142,18 +2154,18 @@ NetworkDetailsView.prototype = {
let paramsArray = parseQueryString(aQueryString);
if (!paramsArray) {
return;
}
let paramsScope = this._params.addScope(aName);
paramsScope.expanded = true;
for (let param of paramsArray) {
- let headerVar = paramsScope.addItem(param.name, {}, true);
- headerVar.setGrip(param.value);
+ let paramVar = paramsScope.addItem(param.name, {}, true);
+ paramVar.setGrip(param.value);
}
},
/**
* Sets the network response body shown in this view.
*
* @param string aUrl
* The request's url.
@@ -2586,24 +2598,26 @@ nsIURL.store = new Map();
*
* @param string aQueryString
* The query part of a url
* @return array
* Array of query params {name, value}
*/
function parseQueryString(aQueryString) {
// Make sure there's at least one param available.
- if (!aQueryString || !aQueryString.contains("=")) {
+ // Be careful here, params don't necessarily need to have values, so
+ // no need to verify the existence of a "=".
+ if (!aQueryString) {
return;
}
// Turn the params string into an array containing { name: value } tuples.
let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e =>
let (param = e.split("=")) {
- name: NetworkHelper.convertToUnicode(unescape(param[0])),
- value: NetworkHelper.convertToUnicode(unescape(param[1]))
+ name: param[0] ? NetworkHelper.convertToUnicode(unescape(param[0])) : "",
+ value: param[1] ? NetworkHelper.convertToUnicode(unescape(param[1])) : ""
});
return paramsArray;
}
/**
* Parse text representation of HTTP headers.
*
* @param string aText
--- a/browser/devtools/netmonitor/test/browser.ini
+++ b/browser/devtools/netmonitor/test/browser.ini
@@ -8,16 +8,17 @@ support-files =
html_filter-test-page.html
html_infinite-get-page.html
html_json-custom-mime-test-page.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_navigate-test-page.html
+ html_params-test-page.html
html_post-data-test-page.html
html_post-raw-test-page.html
html_simple-test-page.html
html_sorting-test-page.html
html_statistics-test-page.html
html_status-codes-test-page.html
sjs_content-type-test-server.sjs
sjs_simple-test-server.sjs
@@ -30,19 +31,20 @@ support-files =
[browser_net_accessibility-02.js]
[browser_net_autoscroll.js]
[browser_net_charts-01.js]
[browser_net_charts-02.js]
[browser_net_charts-03.js]
[browser_net_charts-04.js]
[browser_net_charts-05.js]
[browser_net_clear.js]
+[browser_net_complex-params.js]
[browser_net_content-type.js]
+[browser_net_copy_image_as_data_uri.js]
[browser_net_copy_url.js]
-[browser_net_copy_image_as_data_uri.js]
[browser_net_cyrillic-01.js]
[browser_net_cyrillic-02.js]
[browser_net_filter-01.js]
[browser_net_filter-02.js]
[browser_net_filter-03.js]
[browser_net_footer-summary.js]
[browser_net_html-preview.js]
[browser_net_json-long.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/browser_net_complex-params.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests whether complex request params and payload sent via POST are
+ * displayed correctly.
+ */
+
+function test() {
+ initNetMonitor(PARAMS_URL).then(([aTab, aDebuggee, aMonitor]) => {
+ info("Starting test... ");
+
+ let { document, L10N, EVENTS, Editor, NetMonitorView } = aMonitor.panelWin;
+ let { RequestsMenu, NetworkDetails } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+ NetworkDetails._params.lazyEmpty = false;
+
+ Task.spawn(function () {
+ yield waitForNetworkEvents(aMonitor, 0, 6);
+
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.getElementById("details-pane-toggle"));
+ EventUtils.sendMouseEvent({ type: "mousedown" },
+ document.querySelectorAll("#details-pane tab")[2]);
+
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab1('a', '""', '{ "foo": "bar" }', '""');
+
+ RequestsMenu.selectedIndex = 1;
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab1('a', '"b"', '{ "foo": "bar" }', '""');
+
+ RequestsMenu.selectedIndex = 2;
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab1('a', '"b"', 'foo', '"bar"');
+
+ RequestsMenu.selectedIndex = 3;
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab2('a', '""', '{ "foo": "bar" }', "js");
+
+ RequestsMenu.selectedIndex = 4;
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab2('a', '"b"', '{ "foo": "bar" }', "js");
+
+ RequestsMenu.selectedIndex = 5;
+ yield waitFor(aMonitor.panelWin, EVENTS.REQUEST_POST_PARAMS_DISPLAYED);
+ yield testParamsTab2('a', '"b"', '?foo=bar', "text");
+
+ yield teardown(aMonitor);
+ finish();
+ });
+
+ function testParamsTab1(
+ aQueryStringParamName, aQueryStringParamValue, aFormDataParamName, aFormDataParamValue)
+ {
+ let tab = document.querySelectorAll("#details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "The number of param scopes displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 2,
+ "The number of param values displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), true,
+ "The request post data textarea box should be hidden.");
+
+ let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let formDataScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(paramsScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The params scope doesn't have the correct title.");
+ is(formDataScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsFormData"),
+ "The form data scope doesn't have the correct title.");
+
+ is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
+ aQueryStringParamName,
+ "The first query string param name was incorrect.");
+ is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
+ aQueryStringParamValue,
+ "The first query string param value was incorrect.");
+
+ is(formDataScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
+ aFormDataParamName,
+ "The first form data param name was incorrect.");
+ is(formDataScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
+ aFormDataParamValue,
+ "The first form data param value was incorrect.");
+ }
+
+ function testParamsTab2(
+ aQueryStringParamName, aQueryStringParamValue, aRequestPayload, aEditorMode)
+ {
+ let tab = document.querySelectorAll("#details-pane tab")[2];
+ let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
+
+ is(tabpanel.querySelectorAll(".variables-view-scope").length, 2,
+ "The number of param scopes displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variable-or-property").length, 1,
+ "The number of param values displayed in this tabpanel is incorrect.");
+ is(tabpanel.querySelectorAll(".variables-view-empty-notice").length, 0,
+ "The empty notice should not be displayed in this tabpanel.");
+
+ is(tabpanel.querySelector("#request-params-box")
+ .hasAttribute("hidden"), false,
+ "The request params box should not be hidden.");
+ is(tabpanel.querySelector("#request-post-data-textarea-box")
+ .hasAttribute("hidden"), false,
+ "The request post data textarea box should not be hidden.");
+
+ let paramsScope = tabpanel.querySelectorAll(".variables-view-scope")[0];
+ let payloadScope = tabpanel.querySelectorAll(".variables-view-scope")[1];
+
+ is(paramsScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsQueryString"),
+ "The params scope doesn't have the correct title.");
+ is(payloadScope.querySelector(".name").getAttribute("value"),
+ L10N.getStr("paramsPostPayload"),
+ "The request payload scope doesn't have the correct title.");
+
+ is(paramsScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
+ aQueryStringParamName,
+ "The first query string param name was incorrect.");
+ is(paramsScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
+ aQueryStringParamValue,
+ "The first query string param value was incorrect.");
+
+ return NetMonitorView.editor("#request-post-data-textarea").then((aEditor) => {
+ is(aEditor.getText(), aRequestPayload,
+ "The text shown in the source editor is incorrect.");
+ is(aEditor.getMode(), Editor.modes[aEditorMode],
+ "The mode active in the source editor is incorrect.");
+
+ teardown(aMonitor).then(finish);
+ });
+ }
+
+ aDebuggee.performRequests();
+ });
+}
--- a/browser/devtools/netmonitor/test/browser_net_post-data-02.js
+++ b/browser/devtools/netmonitor/test/browser_net_post-data-02.js
@@ -50,15 +50,16 @@ function test() {
is(postScope.querySelectorAll(".variables-view-variable .name")[0].getAttribute("value"),
"foo", "The first query param name was incorrect.");
is(postScope.querySelectorAll(".variables-view-variable .value")[0].getAttribute("value"),
"\"bar\"", "The first query param value was incorrect.");
is(postScope.querySelectorAll(".variables-view-variable .name")[1].getAttribute("value"),
"baz", "The second query param name was incorrect.");
is(postScope.querySelectorAll(".variables-view-variable .value")[1].getAttribute("value"),
"\"123\"", "The second query param value was incorrect.");
+
teardown(aMonitor).then(finish);
});
});
aDebuggee.performRequests();
});
}
--- a/browser/devtools/netmonitor/test/head.js
+++ b/browser/devtools/netmonitor/test/head.js
@@ -17,16 +17,17 @@ const EXAMPLE_URL = "http://example.com/
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 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 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 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";
new file mode 100644
--- /dev/null
+++ b/browser/devtools/netmonitor/test/html_params-test-page.html
@@ -0,0 +1,54 @@
+<!-- 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>Request params type test</p>
+
+ <script type="text/javascript">
+ function post(aAddress, aQuery, aContentType, aPostBody) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("POST", aAddress + aQuery, true);
+ xhr.setRequestHeader("content-type", aContentType);
+ xhr.send(aPostBody);
+ }
+
+ function performRequests() {
+ var urlencoded = "application/x-www-form-urlencoded";
+
+ setTimeout(function() {
+ post("baz", "?a", urlencoded, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", urlencoded, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", urlencoded, '?foo=bar');
+
+ setTimeout(function() {
+ post("baz", "?a", undefined, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", undefined, '{ "foo": "bar" }');
+
+ setTimeout(function() {
+ post("baz", "?a=b", undefined, '?foo=bar');
+
+ // Done.
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }, 10);
+ }
+ </script>
+ </body>
+
+</html>
--- a/browser/devtools/netmonitor/test/html_post-raw-test-page.html
+++ b/browser/devtools/netmonitor/test/html_post-raw-test-page.html
@@ -10,27 +10,28 @@
<body>
<p>POST raw test</p>
<script type="text/javascript">
function post(aAddress, aMessage, aCallback) {
var xhr = new XMLHttpRequest();
xhr.open("POST", aAddress, true);
+ xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.onreadystatechange = function() {
if (this.readyState == this.DONE) {
aCallback();
}
};
xhr.send(aMessage);
}
function performRequests() {
- var rawData = "Content-Type: application/x-www-form-urlencoded\r\n\r\nfoo=bar&baz=123";
+ var rawData = "foo=bar&baz=123";
post("sjs_simple-test-server.sjs", rawData, function() {
// Done.
});
}
</script>
</body>
</html>