--- a/devtools/client/netmonitor/src/components/HeadersPanel.js
+++ b/devtools/client/netmonitor/src/components/HeadersPanel.js
@@ -15,17 +15,20 @@ const { L10N } = require("../utils/l10n"
const {
getHeadersURL,
getHTTPStatusCodeURL,
} = require("../utils/mdn-utils");
const {
fetchNetworkUpdatePacket,
writeHeaderText,
} = require("../utils/request-utils");
-const { sortObjectKeys } = require("../utils/sort-utils");
+const {
+ HeadersProvider,
+ HeaderList,
+} = require("../utils/headers-provider");
// Components
const PropertiesView = createFactory(require("./PropertiesView"));
loader.lazyGetter(this, "MDNLink", function () {
return createFactory(require("./MdnLink"));
});
@@ -48,17 +51,17 @@ const REQUEST_HEADERS = L10N.getStr("req
const REQUEST_HEADERS_FROM_UPLOAD = L10N.getStr("requestHeadersFromUpload");
const RESPONSE_HEADERS = L10N.getStr("responseHeaders");
const SUMMARY_ADDRESS = L10N.getStr("netmonitor.summary.address");
const SUMMARY_METHOD = L10N.getStr("netmonitor.summary.method");
const SUMMARY_URL = L10N.getStr("netmonitor.summary.url");
const SUMMARY_STATUS = L10N.getStr("netmonitor.summary.status");
const SUMMARY_VERSION = L10N.getStr("netmonitor.summary.version");
-/*
+/**
* Headers panel component
* Lists basic information about the request
*/
class HeadersPanel extends Component {
static get propTypes() {
return {
connector: PropTypes.object.isRequired,
cloneSelectedRequest: PropTypes.func.isRequired,
@@ -98,23 +101,18 @@ class HeadersPanel extends Component {
"requestPostData",
]);
}
getProperties(headers, title) {
if (headers && headers.headers.length) {
let headerKey = `${title} (${getFormattedSize(headers.headersSize, 3)})`;
let propertiesResult = {
- [headerKey]:
- headers.headers.reduce((acc, { name, value }) =>
- name ? Object.assign(acc, { [name]: value }) : acc
- , {})
+ [headerKey]: new HeaderList(headers.headers)
};
-
- propertiesResult[headerKey] = sortObjectKeys(propertiesResult[headerKey]);
return propertiesResult;
}
return null;
}
toggleRawHeaders() {
this.setState({
@@ -297,16 +295,17 @@ class HeadersPanel extends Component {
summaryMethod,
summaryAddress,
summaryStatus,
summaryVersion,
summaryRawHeaders,
),
PropertiesView({
object,
+ provider: HeadersProvider,
filterPlaceHolder: HEADERS_FILTER_TEXT,
sectionNames: Object.keys(object),
renderValue: this.renderValue,
openLink,
}),
)
);
}
--- a/devtools/client/netmonitor/src/components/PropertiesView.js
+++ b/devtools/client/netmonitor/src/components/PropertiesView.js
@@ -37,31 +37,32 @@ loader.lazyGetter(this, "MODE", function
});
const { div, tr, td } = dom;
const AUTO_EXPAND_MAX_LEVEL = 7;
const AUTO_EXPAND_MAX_NODES = 50;
const EDITOR_CONFIG_ID = "EDITOR_CONFIG";
const HTML_PREVIEW_ID = "HTML_PREVIEW";
-/*
+/**
* Properties View component
* A scrollable tree view component which provides some useful features for
* representing object properties.
*
* Search filter - Set enableFilter to enable / disable SearchBox feature.
* Tree view - Default enabled.
* Source editor - Enable by specifying object level 1 property name to EDITOR_CONFIG_ID.
* HTML preview - Enable by specifying object level 1 property name to HTML_PREVIEW_ID.
* Rep - Default enabled.
*/
class PropertiesView extends Component {
static get propTypes() {
return {
object: PropTypes.object,
+ provider: PropTypes.object,
enableInput: PropTypes.bool,
expandableStrings: PropTypes.bool,
filterPlaceHolder: PropTypes.string,
sectionNames: PropTypes.array,
openLink: PropTypes.func,
cropLimit: PropTypes.number
};
}
@@ -185,32 +186,34 @@ class PropertiesView extends Component {
enableInput,
expandableStrings,
filterPlaceHolder,
object,
renderRow,
renderValue,
sectionNames,
openLink,
+ provider,
} = this.props;
return (
div({ className: "properties-view" },
this.shouldRenderSearchBox(object) &&
div({ className: "searchbox-section" },
SearchBox({
delay: FILTER_SEARCH_DELAY,
type: "filter",
onChange: this.updateFilterText,
placeholder: filterPlaceHolder,
}),
),
div({ className: "tree-container" },
TreeView({
object,
+ provider,
columns: [{
id: "value",
width: "100%",
}],
decorator: decorator || {
getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames),
},
enableInput,
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/src/utils/headers-provider.js
@@ -0,0 +1,86 @@
+/* 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";
+
+const { ObjectProvider } = require("devtools/client/shared/components/tree/ObjectProvider");
+
+/**
+ * Custom tree provider.
+ *
+ * This provider is used to provide set of headers and is
+ * utilized by the HeadersPanel.
+ * The default ObjectProvider can't be used since it doesn't
+ * allow duplicities by design and so it can't support duplicity
+ * headers (more headers with the same name).
+ */
+var HeadersProvider = {
+ ...ObjectProvider,
+
+ getChildren(object) {
+ if (object.value instanceof HeaderList) {
+ return object.value.headers.map((header, index) =>
+ new Header(header.name, header.value, index));
+ }
+ return ObjectProvider.getChildren(object);
+ },
+
+ hasChildren: function (object) {
+ if (object.value instanceof HeaderList) {
+ return object.value.headers.length > 0;
+ } else if (object instanceof Header) {
+ return false;
+ }
+ return ObjectProvider.hasChildren(object);
+ },
+
+ getLabel: function (object) {
+ if (object instanceof Header) {
+ return object.name;
+ }
+ return ObjectProvider.getLabel(object);
+ },
+
+ getValue: function (object) {
+ if (object instanceof Header) {
+ return object.value;
+ }
+ return ObjectProvider.getValue(object);
+ },
+
+ getKey(object) {
+ if (object instanceof Header) {
+ return object.key;
+ }
+ return ObjectProvider.getKey(object);
+ },
+
+ getType: function (object) {
+ if (object instanceof Header) {
+ return "string";
+ }
+ return ObjectProvider.getType(object);
+ }
+};
+
+/**
+ * Helper data structures for list of headers.
+ */
+function HeaderList(headers) {
+ this.headers = headers;
+ this.headers.sort((a, b) => {
+ return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
+ });
+}
+
+function Header(name, value, key) {
+ this.name = name;
+ this.value = value;
+ this.key = key;
+}
+
+module.exports = {
+ HeadersProvider,
+ HeaderList
+};
--- a/devtools/client/netmonitor/src/utils/moz.build
+++ b/devtools/client/netmonitor/src/utils/moz.build
@@ -8,16 +8,17 @@ DIRS += [
]
DevToolsModules(
'create-store.js',
'filter-autocomplete-provider.js',
'filter-predicates.js',
'filter-text-utils.js',
'format-utils.js',
+ 'headers-provider.js',
'l10n.js',
'mdn-utils.js',
'menu.js',
'open-request-in-tab.js',
'prefs.js',
'request-utils.js',
'sort-predicates.js',
'sort-utils.js'
--- a/devtools/client/netmonitor/test/browser_net_headers_sorted.js
+++ b/devtools/client/netmonitor/test/browser_net_headers_sorted.js
@@ -1,15 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests if Request-Headers and Response-Headers are sorted in Headers tab.
+ * The test also verifies that headers with the same name and headers
+ * with an empty value are also displayed.
*/
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
info("Starting test... ");
let { document, store, windowRequire } = monitor.panelWin;
let Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
let {
@@ -30,17 +32,18 @@ add_task(function* () {
yield waitUntil(() => {
let request = getSortedRequests(store.getState()).get(0);
return request.requestHeaders && request.responseHeaders;
});
info("Check if Request-Headers and Response-Headers are sorted");
let expectedResponseHeaders = ["cache-control", "connection", "content-length",
"content-type", "date", "expires", "foo-bar",
- "pragma", "server", "set-cookie"];
+ "foo-bar", "foo-bar", "pragma", "server", "set-cookie",
+ "set-cookie"];
let expectedRequestHeaders = ["Accept", "Accept-Encoding", "Accept-Language",
"Cache-Control", "Connection", "Cookie", "Host",
"Pragma", "Upgrade-Insecure-Requests", "User-Agent"];
let labelCells = document.querySelectorAll(".treeLabelCell");
let actualResponseHeaders = [];
let actualRequestHeaders = [];
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -169,19 +169,19 @@ function test() {
let requestItem = getSortedRequests(store.getState()).get(0);
return requestItem && requestItem.responseHeaders;
});
let requestItem = getSortedRequests(store.getState()).get(0);
ok(requestItem.responseHeaders,
"There should be a responseHeaders data available.");
- is(requestItem.responseHeaders.headers.length, 10,
+ is(requestItem.responseHeaders.headers.length, 13,
"The responseHeaders data has an incorrect |headers| property.");
- is(requestItem.responseHeaders.headersSize, 330,
+ is(requestItem.responseHeaders.headersSize, 335,
"The responseHeaders data has an incorrect |headersSize| property.");
verifyRequestItemTarget(
document,
getDisplayedRequests(store.getState()),
requestItem,
"GET",
SIMPLE_SJS
@@ -223,17 +223,17 @@ function test() {
let requestItem = getSortedRequests(store.getState()).get(0);
is(requestItem.httpVersion, "HTTP/1.1",
"The httpVersion data has an incorrect value.");
is(requestItem.status, "200",
"The status data has an incorrect value.");
is(requestItem.statusText, "Och Aye",
"The statusText data has an incorrect value.");
- is(requestItem.headersSize, 330,
+ is(requestItem.headersSize, 335,
"The headersSize data has an incorrect value.");
let requestListItem = document.querySelector(".request-list-item");
requestListItem.scrollIntoView();
let requestsListStatus = requestListItem.querySelector(".requests-list-status");
EventUtils.sendMouseEvent({ type: "mouseover" }, requestsListStatus);
await waitUntil(() => requestsListStatus.title);
@@ -256,17 +256,17 @@ function test() {
return requestItem &&
requestItem.transferredSize &&
requestItem.contentSize &&
requestItem.mimeType;
});
let requestItem = getSortedRequests(store.getState()).get(0);
- is(requestItem.transferredSize, "342",
+ is(requestItem.transferredSize, "347",
"The transferredSize data has an incorrect value.");
is(requestItem.contentSize, "12",
"The contentSize data has an incorrect value.");
is(requestItem.mimeType, "text/plain; charset=utf-8",
"The mimeType data has an incorrect value.");
verifyRequestItemTarget(
document,
--- a/devtools/client/netmonitor/test/sjs_simple-test-server.sjs
+++ b/devtools/client/netmonitor/test/sjs_simple-test-server.sjs
@@ -3,15 +3,19 @@
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", "bob=true; Max-Age=10; HttpOnly", true);
- response.setHeader("Set-Cookie", "tom=cool; Max-Age=10; HttpOnly", true);
+ response.setHeaderNoCheck("Set-Cookie", "bob=true; Max-Age=10; HttpOnly");
+ response.setHeaderNoCheck("Set-Cookie", "tom=cool; Max-Age=10; HttpOnly");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
- response.setHeader("Foo-Bar", "baz", false);
+
+ response.setHeaderNoCheck("Foo-Bar", "baz");
+ response.setHeaderNoCheck("Foo-Bar", "baz");
+ response.setHeaderNoCheck("Foo-Bar", "");
+
response.write("Hello world!");
}
--- a/devtools/client/shared/components/tree/TreeView.js
+++ b/devtools/client/shared/components/tree/TreeView.js
@@ -58,17 +58,17 @@ define(function (require, exports, modul
* renderValue: function(object, colId);
* renderRow: function(object);
* renderCell: function(object, colId);
* renderLabelCell: function(object);
* }
*/
class TreeView extends Component {
// The only required property (not set by default) is the input data
- // object that is used to puputate the tree.
+ // object that is used to populate the tree.
static get propTypes() {
return {
// The input data object.
object: PropTypes.any,
className: PropTypes.string,
label: PropTypes.string,
// Data provider (see also the interface above)
provider: PropTypes.shape({
@@ -88,17 +88,17 @@ define(function (require, exports, modul
renderRow: PropTypes.func,
renderCell: PropTypes.func,
renderLabelCell: PropTypes.func,
}),
// Custom tree row (node) renderer
renderRow: PropTypes.func,
// Custom cell renderer
renderCell: PropTypes.func,
- // Custom value renderef
+ // Custom value renderer
renderValue: PropTypes.func,
// Custom tree label (including a toggle button) renderer
renderLabelCell: PropTypes.func,
// Set of expanded nodes
expandedNodes: PropTypes.object,
// Custom filtering callback
onFilter: PropTypes.func,
// Custom sorting callback
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -874,35 +874,38 @@ NetworkMonitor.prototype = {
let response = {
id: gSequenceId(),
channel: channel,
headers: [],
cookies: [],
};
- let setCookieHeader = null;
+ let setCookieHeaders = [];
- channel.visitResponseHeaders({
+ channel.visitOriginalResponseHeaders({
visitHeader: function (name, value) {
let lowerName = name.toLowerCase();
if (lowerName == "set-cookie") {
- setCookieHeader = value;
+ setCookieHeaders.push(value);
}
response.headers.push({ name: name, value: value });
}
});
if (!response.headers.length) {
// No need to continue.
return;
}
- if (setCookieHeader) {
- response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
+ if (setCookieHeaders.length) {
+ response.cookies = setCookieHeaders.reduce((result, header) => {
+ let cookies = NetworkHelper.parseSetCookieHeader(header);
+ return result.concat(cookies);
+ }, []);
}
// Determine the HTTP version.
let httpVersionMaj = {};
let httpVersionMin = {};
channel.QueryInterface(Ci.nsIHttpChannelInternal);
channel.getResponseVersion(httpVersionMaj, httpVersionMin);