--- a/devtools/client/framework/sidebar.js
+++ b/devtools/client/framework/sidebar.js
@@ -426,17 +426,18 @@ ToolSidebar.prototype = {
},
/**
* Return the tab based on the provided id, if one was registered with this id.
* @param {String} id
* @return {DOMNode}
*/
getTab: function (id) {
- return this._tabs.get(id);
+ // FIXME: A workaround for broken browser_net_raw_headers.js failure only in non-e10s mode
+ return this._tabs && this._tabs.get(id);
},
/**
* Event handler.
*/
handleEvent: function (event) {
if (event.type !== "select" || this._destroyed) {
return;
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -11,29 +11,26 @@ const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
const { Task } = require("devtools/shared/task");
const { ToolSidebar } = require("devtools/client/framework/sidebar");
const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
const { EVENTS } = require("./events");
const { L10N } = require("./l10n");
const { Filters } = require("./filter-predicates");
-const {
- decodeUnicodeUrl,
-} = require("./request-utils");
const { createFactory } = require("devtools/client/shared/vendor/react");
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const HeadersPanel = createFactory(require("./shared/components/headers-panel"));
const ParamsPanel = createFactory(require("./shared/components/params-panel"));
const PreviewPanel = createFactory(require("./shared/components/preview-panel"));
const ResponsePanel = createFactory(require("./shared/components/response-panel"));
const SecurityPanel = createFactory(require("./shared/components/security-panel"));
const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
-const HEADERS_SIZE_DECIMALS = 3;
const GENERIC_VARIABLES_VIEW_SETTINGS = {
lazyEmpty: true,
// ms
lazyEmptyDelay: 10,
searchEnabled: true,
editableValueTooltip: "",
editableNameTooltip: "",
preventDisableOnChange: true,
@@ -68,16 +65,23 @@ DetailsView.prototype = {
},
/**
* Initialization function, called when the network monitor is started.
*/
initialize: function (store) {
dumpn("Initializing the DetailsView");
+ this._headersPanelNode = $("#react-headers-tabpanel-hook");
+
+ ReactDOM.render(Provider(
+ { store },
+ HeadersPanel()
+ ), this._headersPanelNode);
+
this._paramsPanelNode = $("#react-params-tabpanel-hook");
ReactDOM.render(Provider(
{ store },
ParamsPanel()
), this._paramsPanelNode);
this._previewPanelNode = $("#react-preview-tabpanel-hook");
@@ -109,42 +113,35 @@ DetailsView.prototype = {
), this._timingsPanelNode);
this.widget = $("#event-details-pane");
this.sidebar = new ToolSidebar(this.widget, this, "netmonitor", {
disableTelemetry: true,
showAllTabsMenu: true
});
- this._headers = new VariablesView($("#all-headers"),
- Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
- emptyText: L10N.getStr("headersEmptyText"),
- searchPlaceholder: L10N.getStr("headersFilterText")
- }));
this._cookies = new VariablesView($("#all-cookies"),
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
emptyText: L10N.getStr("cookiesEmptyText"),
searchPlaceholder: L10N.getStr("cookiesFilterText")
}));
- this._requestHeaders = L10N.getStr("requestHeaders");
- this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
- this._responseHeaders = L10N.getStr("responseHeaders");
this._requestCookies = L10N.getStr("requestCookies");
this._responseCookies = L10N.getStr("responseCookies");
$("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
},
/**
* Destruction function, called when the network monitor is closed.
*/
destroy: function () {
dumpn("Destroying the DetailsView");
ReactDOM.unmountComponentAtNode(this._paramsPanelNode);
+ ReactDOM.unmountComponentAtNode(this._headersPanelNode);
ReactDOM.unmountComponentAtNode(this._previewPanelNode);
ReactDOM.unmountComponentAtNode(this._responsePanelNode);
ReactDOM.unmountComponentAtNode(this._securityPanelNode);
ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
this.sidebar.destroy();
$("tabpanels", this.widget).removeEventListener("select",
this._onTabSelect);
},
@@ -153,18 +150,16 @@ DetailsView.prototype = {
* Populates this view with the specified data.
*
* @param object data
* The data source (this should be the attachment of a request item).
* @return object
* Returns a promise that resolves upon population the view.
*/
populate: function (data) {
- $("#raw-headers").hidden = true;
-
let isHtml = Filters.html(data);
// Show the "Preview" tabpanel only for plain HTML responses.
this.sidebar.toggleTab(isHtml, "preview-tab");
// Show the "Security" tab only for requests that
// 1) are https (state != insecure)
// 2) come from a target that provides security information.
@@ -177,17 +172,16 @@ DetailsView.prototype = {
// request has no security information.
if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
!hasSecurityInfo && this.widget.selectedPanel ===
$("#security-tabpanel")) {
this.widget.selectedIndex = 0;
}
- this._headers.empty();
this._cookies.empty();
this._dataSrc = { src: data, populated: [] };
this._onTabSelect();
window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
return promise.resolve();
},
@@ -214,24 +208,16 @@ DetailsView.prototype = {
viewState.dirty[tab] = true;
viewState.latestData = src;
return;
}
Task.spawn(function* () {
viewState.updating[tab] = true;
switch (tab) {
- // "Headers"
- case 0:
- yield view._setSummary(src);
- yield view._setResponseHeaders(src.responseHeaders);
- yield view._setRequestHeaders(
- src.requestHeaders,
- src.requestHeadersFromUploadStream);
- break;
// "Cookies"
case 1:
yield view._setResponseCookies(src.responseCookies);
yield view._setRequestCookies(src.requestCookies);
break;
}
viewState.updating[tab] = false;
}).then(() => {
@@ -249,140 +235,16 @@ DetailsView.prototype = {
// Tab is dirty but no longer selected. Don't refresh it now, it'll be
// done if the tab is shown again.
viewState.dirty[tab] = false;
}
}, e => console.error(e));
},
/**
- * Sets the network request summary shown in this view.
- *
- * @param object data
- * The data source (this should be the attachment of a request item).
- */
- _setSummary: function (data) {
- if (data.url) {
- let unicodeUrl = decodeUnicodeUrl(data.url);
- $("#headers-summary-url-value").setAttribute("value", unicodeUrl);
- $("#headers-summary-url-value").setAttribute("tooltiptext", unicodeUrl);
- $("#headers-summary-url").removeAttribute("hidden");
- } else {
- $("#headers-summary-url").setAttribute("hidden", "true");
- }
-
- if (data.method) {
- $("#headers-summary-method-value").setAttribute("value", data.method);
- $("#headers-summary-method").removeAttribute("hidden");
- } else {
- $("#headers-summary-method").setAttribute("hidden", "true");
- }
-
- if (data.remoteAddress) {
- let address = data.remoteAddress;
- if (address.indexOf(":") != -1) {
- address = `[${address}]`;
- }
- if (data.remotePort) {
- address += `:${data.remotePort}`;
- }
- $("#headers-summary-address-value").setAttribute("value", address);
- $("#headers-summary-address-value").setAttribute("tooltiptext", address);
- $("#headers-summary-address").removeAttribute("hidden");
- } else {
- $("#headers-summary-address").setAttribute("hidden", "true");
- }
-
- if (data.status) {
- // "code" attribute is only used by css to determine the icon color
- let code;
- if (data.fromCache) {
- code = "cached";
- } else if (data.fromServiceWorker) {
- code = "service worker";
- } else {
- code = data.status;
- }
- $("#headers-summary-status-circle").setAttribute("data-code", code);
- $("#headers-summary-status-value").setAttribute("value",
- data.status + " " + data.statusText);
- $("#headers-summary-status").removeAttribute("hidden");
- } else {
- $("#headers-summary-status").setAttribute("hidden", "true");
- }
-
- if (data.httpVersion) {
- $("#headers-summary-version-value").setAttribute("value",
- data.httpVersion);
- $("#headers-summary-version").removeAttribute("hidden");
- } else {
- $("#headers-summary-version").setAttribute("hidden", "true");
- }
- },
-
- /**
- * Sets the network request headers shown in this view.
- *
- * @param object headers
- * The "requestHeaders" message received from the server.
- * @param object uploadHeaders
- * The "requestHeadersFromUploadStream" inferred from the POST payload.
- * @return object
- * A promise that resolves when request headers are set.
- */
- _setRequestHeaders: Task.async(function* (headers, uploadHeaders) {
- if (headers && headers.headers.length) {
- yield this._addHeaders(this._requestHeaders, headers);
- }
- if (uploadHeaders && uploadHeaders.headers.length) {
- yield this._addHeaders(this._requestHeadersFromUpload, uploadHeaders);
- }
- }),
-
- /**
- * Sets the network response headers shown in this view.
- *
- * @param object response
- * The message received from the server.
- * @return object
- * A promise that resolves when response headers are set.
- */
- _setResponseHeaders: Task.async(function* (response) {
- if (response && response.headers.length) {
- response.headers.sort((a, b) => a.name > b.name);
- yield this._addHeaders(this._responseHeaders, response);
- }
- }),
-
- /**
- * Populates the headers container in this view with the specified data.
- *
- * @param string name
- * The type of headers to populate (request or response).
- * @param object response
- * The message received from the server.
- * @return object
- * A promise that resolves when headers are added.
- */
- _addHeaders: Task.async(function* (name, response) {
- let kb = response.headersSize / 1024;
- let size = L10N.numberWithDecimals(kb, HEADERS_SIZE_DECIMALS);
- let text = L10N.getFormatStr("networkMenu.sizeKB", size);
-
- let headersScope = this._headers.addScope(name + " (" + text + ")");
- headersScope.expanded = true;
-
- for (let header of response.headers) {
- let headerVar = headersScope.addItem(header.name, {}, {relaxed: true});
- let headerValue = yield gNetwork.getString(header.value);
- headerVar.setGrip(headerValue);
- }
- }),
-
- /**
* Sets the network request cookies shown in this view.
*
* @param object response
* The message received from the server.
* @return object
* A promise that is resolved when the request cookies are set.
*/
_setRequestCookies: Task.async(function* (response) {
@@ -441,17 +303,14 @@ DetailsView.prototype = {
}
cookieVar.populate(rawObject);
cookieVar.twisty = true;
cookieVar.expanded = true;
}
}),
_dataSrc: null,
- _headers: null,
_cookies: null,
- _requestHeaders: "",
- _responseHeaders: "",
_requestCookies: "",
_responseCookies: ""
};
exports.DetailsView = DetailsView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -118,96 +118,18 @@
data-localization="label=netmonitor.tab.security"/>
<tab id="preview-tab"
crop="end"
data-localization="label=netmonitor.tab.preview"/>
</tabs>
<tabpanels flex="1">
<tabpanel id="headers-tabpanel"
class="tabpanel-content">
- <vbox flex="1">
- <hbox id="headers-summary-url"
- class="tabpanel-summary-container"
- align="center">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.url"/>
- <textbox id="headers-summary-url-value"
- class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
- flex="1"
- readonly="true"/>
- </hbox>
- <hbox id="headers-summary-method"
- class="tabpanel-summary-container"
- align="center">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.method"/>
- <label id="headers-summary-method-value"
- class="plain tabpanel-summary-value devtools-monospace"
- crop="end"
- flex="1"/>
- </hbox>
- <hbox id="headers-summary-address"
- class="tabpanel-summary-container"
- align="center">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.address"/>
- <textbox id="headers-summary-address-value"
- class="plain tabpanel-summary-value devtools-monospace cropped-textbox"
- flex="1"
- readonly="true"/>
- </hbox>
- <hbox id="headers-summary-status"
- class="tabpanel-summary-container"
- align="center">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.status"/>
- <box id="headers-summary-status-circle"
- class="requests-menu-status-icon"/>
- <label id="headers-summary-status-value"
- class="plain tabpanel-summary-value devtools-monospace"
- crop="end"
- flex="1"/>
- <button id="headers-summary-resend"
- class="devtools-toolbarbutton"
- data-localization="label=netmonitor.summary.editAndResend"/>
- <button id="toggle-raw-headers"
- class="devtools-toolbarbutton"
- data-localization="label=netmonitor.summary.rawHeaders"/>
- </hbox>
- <hbox id="headers-summary-version"
- class="tabpanel-summary-container"
- align="center">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.version"/>
- <label id="headers-summary-version-value"
- class="plain tabpanel-summary-value devtools-monospace"
- crop="end"
- flex="1"/>
- </hbox>
- <hbox id="raw-headers"
- class="tabpanel-summary-container"
- align="center"
- hidden="true">
- <vbox id="raw-request-headers-textarea-box" flex="1" hidden="false">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.rawHeaders.requestHeaders"/>
- <textbox id="raw-request-headers-textarea"
- class="raw-response-textarea"
- flex="1" multiline="true" readonly="true"/>
- </vbox>
- <vbox id="raw-response-headers-textarea-box" flex="1" hidden="false">
- <label class="plain tabpanel-summary-label"
- data-localization="content=netmonitor.summary.rawHeaders.responseHeaders"/>
- <textbox id="raw-response-headers-textarea"
- class="raw-response-textarea"
- flex="1" multiline="true" readonly="true"/>
- </vbox>
- </hbox>
- <vbox id="all-headers" flex="1"/>
- </vbox>
+ <html:div xmlns="http://www.w3.org/1999/xhtml"
+ id="react-headers-tabpanel-hook"/>
</tabpanel>
<tabpanel id="cookies-tabpanel"
class="tabpanel-content">
<vbox flex="1">
<vbox id="all-cookies" flex="1"/>
</vbox>
</tabpanel>
<tabpanel id="params-tabpanel"
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -73,16 +73,31 @@ const getFormDataSections = Task.async(f
}
}
}
return formDataSections;
});
/**
+ * Fetch headers full content from actor server
+ *
+ * @param {object} headers - a object presents headers data
+ * @param {function} getString - callback to retrieve a string from a LongStringGrip
+ * @return {object} a headers object with updated content payload
+ */
+const fetchHeaders = Task.async(function* (headers, getString) {
+ for (let { value } of headers.headers) {
+ headers.headers.value = yield getString(value);
+ }
+
+ return headers;
+});
+
+/**
* Form a data: URI given a mime type, encoding, and some text.
*
* @param {string} mimeType - mime type
* @param {string} encoding - encoding to use; if not set, the
* text will be base64-encoded.
* @param {string} text - text of the URI.
* @return {string} a data URI
*/
@@ -236,16 +251,17 @@ function parseQueryString(query) {
value: param[1] ? decodeUnicodeUrl(param[1]) : "",
};
});
}
module.exports = {
getKeyWithEvent,
getFormDataSections,
+ fetchHeaders,
formDataURI,
writeHeaderText,
decodeUnicodeUrl,
getAbbreviatedMimeType,
getUrlBaseName,
getUrlQuery,
getUrlBaseNameWithQuery,
getUrlHostName,
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -16,18 +16,18 @@ const { createElement, createFactory } =
const ReactDOM = require("devtools/client/shared/vendor/react-dom");
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const RequestList = createFactory(require("./components/request-list"));
const RequestListContextMenu = require("./request-list-context-menu");
const Actions = require("./actions/index");
const { Prefs } = require("./prefs");
const {
+ fetchHeaders,
formDataURI,
- writeHeaderText,
getFormDataSections,
} = require("./request-utils");
const {
getActiveFilters,
getSortedRequests,
getDisplayedRequests,
getRequestById,
@@ -136,21 +136,16 @@ RequestsMenuView.prototype = {
));
});
}
},
));
this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
- this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
- this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
-
- $("#toggle-raw-headers")
- .addEventListener("click", this.toggleRawHeadersEvent, false);
this._summary = $("#requests-menu-network-summary-button");
this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
this.onResize = this.onResize.bind(this);
this._splitter = $("#network-inspector-view-splitter");
this._splitter.addEventListener("mouseup", this.onResize, false);
window.addEventListener("resize", this.onResize, false);
@@ -167,20 +162,16 @@ RequestsMenuView.prototype = {
},
_onConnect() {
if (NetMonitorController.supportsCustomRequest) {
$("#custom-request-send-button")
.addEventListener("click", this.sendCustomRequestEvent, false);
$("#custom-request-close-button")
.addEventListener("click", this.closeCustomRequestEvent, false);
- $("#headers-summary-resend")
- .addEventListener("click", this.cloneSelectedRequestEvent, false);
- } else {
- $("#headers-summary-resend").hidden = true;
}
},
/**
* Destruction function, called when the network monitor is closed.
*/
destroy() {
dumpn("Destroying the RequestsMenuView");
@@ -188,20 +179,16 @@ RequestsMenuView.prototype = {
Prefs.filters = getActiveFilters(this.store.getState());
// this.flushRequestsTask.disarm();
$("#custom-request-send-button")
.removeEventListener("click", this.sendCustomRequestEvent, false);
$("#custom-request-close-button")
.removeEventListener("click", this.closeCustomRequestEvent, false);
- $("#headers-summary-resend")
- .removeEventListener("click", this.cloneSelectedRequestEvent, false);
- $("#toggle-raw-headers")
- .removeEventListener("click", this.toggleRawHeadersEvent, false);
this._splitter.removeEventListener("mouseup", this.onResize, false);
window.removeEventListener("resize", this.onResize, false);
this.tooltip.destroy();
ReactDOM.unmountComponentAtNode(this.mountPoint);
},
@@ -244,17 +231,46 @@ RequestsMenuView.prototype = {
this.store.dispatch(action).then(() => window.emit(EVENTS.REQUEST_ADDED, action.id));
},
updateRequest: Task.async(function* (id, data) {
const action = Actions.updateRequest(id, data, true);
yield this.store.dispatch(action);
- let { responseContent, requestPostData } = action.data;
+ let {
+ requestHeaders,
+ requestPostData,
+ responseContent,
+ responseHeaders,
+ } = action.data;
+
+ if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
+ let headers = yield fetchHeaders(
+ requestHeaders, gNetwork.getString.bind(gNetwork));
+ if (headers) {
+ yield this.store.dispatch(Actions.updateRequest(
+ action.id,
+ { requestHeaders: headers },
+ true,
+ ));
+ }
+ }
+
+ if (responseHeaders && responseHeaders.headers && responseHeaders.headers.length) {
+ let headers = yield fetchHeaders(
+ responseHeaders, gNetwork.getString.bind(gNetwork));
+ if (headers) {
+ yield this.store.dispatch(Actions.updateRequest(
+ action.id,
+ { responseHeaders: headers },
+ true,
+ ));
+ }
+ }
if (responseContent && responseContent.content) {
let request = getRequestById(this.store.getState(), action.id);
if (request) {
let { mimeType } = request;
let { text, encoding } = responseContent.content;
let response = yield gNetwork.getString(text);
let payload = {};
@@ -375,38 +391,16 @@ RequestsMenuView.prototype = {
* Create a new custom request form populated with the data from
* the currently selected request.
*/
cloneSelectedRequest() {
this.store.dispatch(Actions.cloneSelectedRequest());
},
/**
- * Shows raw request/response headers in textboxes.
- */
- toggleRawHeaders: function () {
- let requestTextarea = $("#raw-request-headers-textarea");
- let responseTextarea = $("#raw-response-headers-textarea");
- let rawHeadersHidden = $("#raw-headers").getAttribute("hidden");
-
- if (rawHeadersHidden) {
- let selected = getSelectedRequest(this.store.getState());
- let selectedRequestHeaders = selected.requestHeaders.headers;
- let selectedResponseHeaders = selected.responseHeaders.headers;
- requestTextarea.value = writeHeaderText(selectedRequestHeaders);
- responseTextarea.value = writeHeaderText(selectedResponseHeaders);
- $("#raw-headers").hidden = false;
- } else {
- requestTextarea.value = null;
- responseTextarea.value = null;
- $("#raw-headers").hidden = true;
- }
- },
-
- /**
* Send a new HTTP request using the data in the custom request form.
*/
sendCustomRequest: function () {
let selected = getSelectedRequest(this.store.getState());
let data = {
url: selected.url,
method: selected.method,
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/headers-panel.js
@@ -0,0 +1,231 @@
+/* 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/. */
+
+/* globals NetMonitorController */
+
+"use strict";
+
+const {
+ createClass,
+ createFactory,
+ DOM,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const Actions = require("../../actions/index");
+const { getSelectedRequest } = require("../../selectors/index");
+const { writeHeaderText } = require("../../request-utils");
+const { getFormattedSize } = require("../../utils/format-utils");
+
+// Components
+const PropertiesView = createFactory(require("./properties-view"));
+
+const { div, input, textarea } = DOM;
+const EDIT_AND_RESEND = L10N.getStr("netmonitor.summary.editAndResend");
+const RAW_HEADERS = L10N.getStr("netmonitor.summary.rawHeaders");
+const RAW_HEADERS_REQUEST = L10N.getStr("netmonitor.summary.rawHeaders.requestHeaders");
+const RAW_HEADERS_RESPONSE = L10N.getStr("netmonitor.summary.rawHeaders.responseHeaders");
+const HEADERS_EMPTY_TEXT = L10N.getStr("headersEmptyText");
+const HEADERS_FILTER_TEXT = L10N.getStr("headersFilterText");
+const REQUEST_HEADERS = L10N.getStr("requestHeaders");
+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
+ */
+const HeadersPanel = createClass({
+ displayName: "HeadersPanel",
+
+ propTypes: {
+ cloneSelectedRequest: PropTypes.func.isRequired,
+ request: PropTypes.object,
+ },
+
+ getInitialState() {
+ return {
+ rawHeadersOpened: false,
+ };
+ },
+
+ getProperties(headers, title) {
+ if (headers && headers.headers.length) {
+ return {
+ [`${title} (${getFormattedSize(headers.headersSize, 3)})`]:
+ headers.headers.reduce((acc, { name, value }) =>
+ name ? Object.assign(acc, { [name]: value }) : acc
+ , {})
+ };
+ }
+
+ return null;
+ },
+
+ toggleRawHeaders() {
+ this.setState({
+ rawHeadersOpened: !this.state.rawHeadersOpened,
+ });
+ },
+
+ renderSummary(label, value) {
+ return (
+ div({ className: "tabpanel-summary-container headers-summary" },
+ div({
+ className: "tabpanel-summary-label headers-summary-label",
+ }, label),
+ input({
+ className: "tabpanel-summary-value textbox-input devtools-monospace",
+ readOnly: true,
+ value,
+ }),
+ )
+ );
+ },
+
+ render() {
+ const {
+ cloneSelectedRequest,
+ request: {
+ fromCache,
+ fromServiceWorker,
+ httpVersion,
+ method,
+ remoteAddress,
+ remotePort,
+ requestHeaders,
+ requestHeadersFromUploadStream: uploadHeaders,
+ responseHeaders,
+ status,
+ statusText,
+ urlDetails,
+ },
+ } = this.props;
+
+ if ((!requestHeaders || !requestHeaders.headers.length) &&
+ (!uploadHeaders || !uploadHeaders.headers.length) &&
+ (!responseHeaders || !responseHeaders.headers.length)) {
+ return div({ className: "empty-notice" },
+ HEADERS_EMPTY_TEXT
+ );
+ }
+
+ let object = Object.assign({},
+ this.getProperties(responseHeaders, RESPONSE_HEADERS),
+ this.getProperties(requestHeaders, REQUEST_HEADERS),
+ this.getProperties(uploadHeaders, REQUEST_HEADERS_FROM_UPLOAD),
+ );
+
+ let summaryUrl = urlDetails.unicodeUrl ?
+ this.renderSummary(SUMMARY_URL, urlDetails.unicodeUrl) : null;
+
+ let summaryMethod = method ?
+ this.renderSummary(SUMMARY_METHOD, method) : null;
+
+ let summaryAddress = remoteAddress ?
+ this.renderSummary(SUMMARY_ADDRESS,
+ remotePort ? `${remoteAddress}:${remotePort}` : remoteAddress) : null;
+
+ let summaryStatus;
+ if (status) {
+ let code;
+ if (fromCache) {
+ code = "cached";
+ } else if (fromServiceWorker) {
+ code = "service worker";
+ } else {
+ code = status;
+ }
+
+ summaryStatus = (
+ div({ className: "tabpanel-summary-container headers-summary" },
+ div({
+ className: "tabpanel-summary-label headers-summary-label",
+ }, SUMMARY_STATUS),
+ div({
+ className: "requests-menu-status-icon",
+ "data-code": code,
+ }),
+ input({
+ className: "tabpanel-summary-value textbox-input devtools-monospace",
+ readOnly: true,
+ value: `${status} ${statusText}`,
+ }),
+ NetMonitorController.supportsCustomRequest && input({
+ className: "tool-button",
+ onClick: cloneSelectedRequest,
+ type: "button",
+ value: EDIT_AND_RESEND,
+ }),
+ input({
+ className: "tool-button",
+ onClick: this.toggleRawHeaders,
+ type: "button",
+ value: RAW_HEADERS,
+ }),
+ )
+ );
+ }
+
+ let summaryVersion = httpVersion ?
+ this.renderSummary(SUMMARY_VERSION, httpVersion) : null;
+
+ let summaryRawHeaders;
+ if (this.state.rawHeadersOpened) {
+ summaryRawHeaders = (
+ div({ className: "tabpanel-summary-container headers-summary" },
+ div({ className: "raw-headers-container" },
+ div({ className: "raw-headers" },
+ div({ className: "tabpanel-summary-label" }, RAW_HEADERS_REQUEST),
+ textarea({
+ value: writeHeaderText(requestHeaders.headers),
+ readOnly: true,
+ }),
+ ),
+ div({ className: "raw-headers" },
+ div({ className: "tabpanel-summary-label" }, RAW_HEADERS_RESPONSE),
+ textarea({
+ value: writeHeaderText(responseHeaders.headers),
+ readOnly: true,
+ }),
+ ),
+ )
+ )
+ );
+ }
+
+ return (
+ div({},
+ div({ className: "summary" },
+ summaryUrl,
+ summaryMethod,
+ summaryAddress,
+ summaryStatus,
+ summaryVersion,
+ summaryRawHeaders,
+ ),
+ PropertiesView({
+ object,
+ filterPlaceHolder: HEADERS_FILTER_TEXT,
+ sectionNames: Object.keys(object),
+ }),
+ )
+ );
+ }
+});
+
+module.exports = connect(
+ (state) => ({
+ request: getSelectedRequest(state) || {},
+ }),
+ (dispatch) => ({
+ cloneSelectedRequest: () => dispatch(Actions.cloneSelectedRequest()),
+ }),
+)(HeadersPanel);
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,13 +1,14 @@
# 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/.
DevToolsModules(
'editor.js',
+ 'headers-panel.js',
'params-panel.js',
'preview-panel.js',
'properties-view.js',
'response-panel.js',
'security-panel.js',
'timings-panel.js',
)
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -21,52 +21,46 @@ add_task(function* () {
let wait = waitForNetworkEvents(monitor, 0, 1);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
yield wait;
// Wait for all tree view updated by react
- wait = waitForDOM(document, "#headers-tabpanel .variables-view-scope", 3);
+ wait = waitForDOM(document, ".properties-view .treeTable");
EventUtils.sendMouseEvent({ type: "mousedown" },
document.getElementById("details-pane-toggle"));
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll("#details-pane tab")[0]);
yield wait;
- let tabEl = document.querySelectorAll("#details-pane tab")[0];
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
- let requestFromUploadScope = tabpanel.querySelectorAll(".variables-view-scope")[2];
+
+ is(tabpanel.querySelectorAll(".tree-section .treeLabel").length, 3,
+ "There should be 3 header sections displayed in this tabpanel.");
- is(tabEl.getAttribute("selected"), "true",
- "The headers tab in the network details pane should be selected.");
- is(tabpanel.querySelectorAll(".variables-view-scope").length, 3,
- "There should be 3 header scopes displayed in this tabpanel.");
-
- is(requestFromUploadScope.querySelector(".name").getAttribute("value"),
+ is(tabpanel.querySelectorAll(".tree-section .treeLabel")[2].textContent,
L10N.getStr("requestHeadersFromUpload") + " (" +
- L10N.getFormatStr("networkMenu.sizeKB", L10N.numberWithDecimals(74 / 1024, 3)) + ")",
- "The request headers from upload scope doesn't have the correct title.");
+ L10N.getFormatStr("networkMenu.sizeB", 74) + ")",
+ "The request headers from upload section doesn't have the correct title.");
- is(requestFromUploadScope.querySelectorAll(".variables-view-variable").length, 2,
- "There should be 2 headers displayed in the request headers from upload scope.");
+ let labels = tabpanel
+ .querySelectorAll(".properties-view tr:not(.tree-section) .treeLabelCell .treeLabel");
+ let values = tabpanel
+ .querySelectorAll(".properties-view tr:not(.tree-section) .treeValueCell .objectBox");
- is(requestFromUploadScope.querySelectorAll(".variables-view-variable .name")[0]
- .getAttribute("value"),
- "content-type", "The first request header name was incorrect.");
- is(requestFromUploadScope.querySelectorAll(".variables-view-variable .value")[0]
- .getAttribute("value"), "\"application/x-www-form-urlencoded\"",
+ is(labels[labels.length - 2].textContent, "content-type",
+ "The first request header name was incorrect.");
+ is(values[values.length - 2].textContent, "\"application/x-www-form-urlencoded\"",
"The first request header value was incorrect.");
- is(requestFromUploadScope.querySelectorAll(".variables-view-variable .name")[1]
- .getAttribute("value"),
- "custom-header", "The second request header name was incorrect.");
- is(requestFromUploadScope.querySelectorAll(".variables-view-variable .value")[1]
- .getAttribute("value"),
- "\"hello world!\"", "The second request header value was incorrect.");
+ is(labels[labels.length - 1].textContent, "custom-header",
+ "The second request header name was incorrect.");
+ is(values[values.length - 1].textContent, "\"hello world!\"",
+ "The second request header value was incorrect.");
// Wait for all tree sections updated by react
wait = waitForDOM(document, "#params-tabpanel .tree-section");
EventUtils.sendMouseEvent({ type: "mousedown" },
document.querySelectorAll("#details-pane tab")[2]);
yield wait;
tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
@@ -75,19 +69,19 @@ add_task(function* () {
"The params tree view should be displayed.");
ok(tabpanel.querySelector(".editor-mount") === null,
"The post data shouldn't be displayed.");
is(tabpanel.querySelector(".tree-section .treeLabel").textContent,
L10N.getStr("paramsFormData"),
"The form data section doesn't have the correct title.");
- let labels = tabpanel
+ labels = tabpanel
.querySelectorAll("tr:not(.tree-section) .treeLabelCell .treeLabel");
- let values = tabpanel
+ 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.");
return teardown(monitor);
--- a/devtools/client/netmonitor/test/browser_net_raw_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -6,65 +6,65 @@
/**
* Tests if showing raw headers works.
*/
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
info("Starting test... ");
- let { document, EVENTS, NetMonitorView } = monitor.panelWin;
+ let { document, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
let wait = waitForNetworkEvents(monitor, 0, 2);
yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
content.wrappedJSObject.performRequests();
});
yield wait;
let origItem = RequestsMenu.getItemAtIndex(0);
- let onTabEvent = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ wait = waitForDOM(document, "#headers-tabpanel .summary");
RequestsMenu.selectedItem = origItem;
- yield onTabEvent;
+ yield wait;
+ wait = waitForDOM(document, ".raw-headers-container textarea", 2);
EventUtils.sendMouseEvent({ type: "click" },
- document.getElementById("toggle-raw-headers"));
+ document.querySelectorAll(".tool-button")[1]);
+ yield wait;
testShowRawHeaders(origItem);
EventUtils.sendMouseEvent({ type: "click" },
- document.getElementById("toggle-raw-headers"));
+ document.querySelectorAll(".tool-button")[1]);
testHideRawHeaders(document);
return teardown(monitor);
/*
* Tests that raw headers were displayed correctly
*/
function testShowRawHeaders(data) {
- let requestHeaders = document.getElementById("raw-request-headers-textarea").value;
+ let requestHeaders = document
+ .querySelectorAll(".raw-headers-container textarea")[0].value;
for (let header of data.requestHeaders.headers) {
ok(requestHeaders.includes(header.name + ": " + header.value),
"textarea contains request headers");
}
- let responseHeaders = document.getElementById("raw-response-headers-textarea").value;
+ let responseHeaders = document
+ .querySelectorAll(".raw-headers-container textarea")[1].value;
for (let header of data.responseHeaders.headers) {
ok(responseHeaders.includes(header.name + ": " + header.value),
"textarea contains response headers");
}
}
/*
- * Tests that raw headers textareas are hidden and empty
+ * Tests that raw headers textareas are hidden
*/
function testHideRawHeaders() {
- let rawHeadersHidden = document.getElementById("raw-headers").getAttribute("hidden");
- let requestTextarea = document.getElementById("raw-request-headers-textarea");
- let responseTextarea = document.getElementById("raw-response-headers-textarea");
- ok(rawHeadersHidden, "raw headers textareas are hidden");
- ok(requestTextarea.value == "", "raw request headers textarea is empty");
- ok(responseTextarea.value == "", "raw response headers textarea is empty");
+ ok(!document.querySelector(".raw-headers-container"),
+ "raw request headers textarea is empty");
}
});
--- a/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-deselect.js
@@ -28,19 +28,20 @@ add_task(function* () {
yield wait;
info("Selecting secure request.");
RequestsMenu.selectedIndex = 0;
info("Selecting security tab.");
NetworkDetails.widget.selectedIndex = 5;
+ wait = monitor.panelWin.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
info("Selecting insecure request.");
RequestsMenu.selectedIndex = 1;
info("Waiting for security tab to be updated.");
- yield monitor.panelWin.once(EVENTS.NETWORKDETAILSVIEW_POPULATED);
+ yield wait;
is(NetworkDetails.widget.selectedIndex, 0,
"Selected tab was reset when selected security tab was hidden.");
return teardown(monitor);
});
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -150,24 +150,23 @@ add_task(function* () {
/**
* A function that tests "Summary" contains correct information.
*/
function* testSummary(data) {
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[0];
let { method, uri, details: { status, statusText } } = data;
- is(tabpanel.querySelector("#headers-summary-url-value").getAttribute("value"),
- uri, "The url summary value is incorrect.");
- is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
- method, "The method summary value is incorrect.");
- is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("data-code"),
- status, "The status summary code is incorrect.");
- is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
- status + " " + statusText, "The status summary value is incorrect.");
+ let summaryValues = tabpanel.querySelectorAll(".tabpanel-summary-value.textbox-input");
+ is(summaryValues[0].value, uri, "The url summary value is incorrect.");
+ is(summaryValues[1].value, method, "The method summary value is incorrect.");
+ is(tabpanel.querySelector(".requests-menu-status-icon").dataset.code, status,
+ "The status summary code is incorrect.");
+ is(summaryValues[3].value, status + " " + statusText,
+ "The status summary value is incorrect.");
}
/**
* A function that tests "Params" tab contains correct information.
*/
function* testParams(data) {
let tabpanel = document.querySelectorAll("#details-pane tabpanel")[2];
let statusParamValue = data.uri.split("=").pop();
--- a/devtools/client/netmonitor/utils/format-utils.js
+++ b/devtools/client/netmonitor/utils/format-utils.js
@@ -9,36 +9,35 @@ const { L10N } = require("../l10n");
// Constants for formatting bytes.
const BYTES_IN_KB = 1024;
const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
const MAX_BYTES_SIZE = 1000;
const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
-const CONTENT_SIZE_DECIMALS = 2;
-
/**
* Get a human-readable string from a number of bytes, with the B, KB, MB, or
* GB value. Note that the transition between abbreviations is by 1000 rather
* than 1024 in order to keep the displayed digits smaller as "1016 KB" is
* more awkward than 0.99 MB"
*/
-function getFormattedSize(bytes) {
+function getFormattedSize(bytes, decimals = 2) {
if (bytes < MAX_BYTES_SIZE) {
return L10N.getFormatStr("networkMenu.sizeB", bytes);
} else if (bytes < MAX_KB_SIZE) {
let kb = bytes / BYTES_IN_KB;
- let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
+ let size = L10N.numberWithDecimals(kb, decimals);
return L10N.getFormatStr("networkMenu.sizeKB", size);
} else if (bytes < MAX_MB_SIZE) {
let mb = bytes / BYTES_IN_MB;
- let size = L10N.numberWithDecimals(mb, CONTENT_SIZE_DECIMALS);
+ let size = L10N.numberWithDecimals(mb, decimals);
return L10N.getFormatStr("networkMenu.sizeMB", size);
}
let gb = bytes / BYTES_IN_GB;
- let size = L10N.numberWithDecimals(gb, CONTENT_SIZE_DECIMALS);
+ let size = L10N.numberWithDecimals(gb, decimals);
return L10N.getFormatStr("networkMenu.sizeGB", size);
}
module.exports = {
- getFormattedSize
+ getFormattedSize,
};
+
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -655,20 +655,16 @@
.tabpanel-content {
background-color: var(--theme-sidebar-background);
}
.theme-dark .tabpanel-content {
color: var(--theme-selection-color);
}
-#headers-tabpanel {
- background-color: var(--theme-toolbar-background);
-}
-
.theme-firebug .variables-view-scope:focus > .title {
color: var(--theme-body-color);
}
/* Summary tabpanel */
.tabpanel-summary-container {
padding: 1px;
@@ -687,37 +683,18 @@
}
.theme-dark .tabpanel-summary-value {
color: var(--theme-selection-color);
}
/* Headers tabpanel */
-#headers-summary-status,
-#headers-summary-version {
- padding-bottom: 2px;
-}
-
-#headers-summary-size {
- padding-top: 2px;
-}
-
-#headers-summary-resend {
- margin-top: -10px;
- margin-inline-end: 6px;
-}
-
-#toggle-raw-headers {
- margin-top: -10px;
- margin-inline-end: 6px;
-}
-
-.raw-response-textarea {
- height: 50vh;
+#headers-tabpanel .summary {
+ background-color: var(--theme-toolbar-background);
}
/* Response tabpanel */
.response-error-header {
margin: 0;
padding: 3px 8px;
background-color: var(--theme-highlight-red);
@@ -1105,17 +1082,17 @@
content: "";
}
/* Layout additional warning icon in tree value cell */
.security-info-value {
display: flex;
}
-.treeTable .textbox-input {
+.textbox-input {
text-overflow: ellipsis;
border: none;
background: none;
color: inherit;
width: 100%;
}
.treeTable .textbox-input:focus {
@@ -1126,16 +1103,22 @@
.properties-view {
/* FIXME: Minus 24px * 2 for toolbox height + panel height
* Give a fixed panel container height in order to force tree view scrollable */
height: calc(100vh - 48px);
display: flex;
flex-direction: column;
}
+#headers-tabpanel .properties-view {
+ /* FIXME: Minus 24px * 2 + 87.5 for toolbox height + panel height + headers summary
+ * Give a fixed panel container height in order to force tree view scrollable */
+ height: calc(100vh - 135.5px);
+}
+
.properties-view .searchbox-section {
flex: 0 1 auto;
}
.properties-view .devtools-searchbox {
padding: 0;
}
@@ -1180,20 +1163,99 @@
}
.tree-container .treeTable .treeValueCell {
/* FIXME: Make value cell can be reduced to shorter width */
max-width: 0;
padding-inline-end: 5px;
}
+.headers-summary input:not([type="button"]) {
+ width: 100%;
+ background: none;
+ border: none;
+ color: inherit;
+ margin-inline-end: 2px;
+}
+
+.headers-summary input:not([type="button"]):focus {
+ outline: none;
+ box-shadow: var(--theme-focus-box-shadow-textbox);
+ transition: all 0.2s ease-in-out;
+}
+
+.headers-summary-label,
.tree-container .objectBox {
white-space: nowrap;
}
+.headers-summary,
+.response-summary {
+ display: flex;
+ align-items: center;
+}
+
+.headers-summary .tool-button {
+ border: 1px solid transparent;
+ color: var(--theme-body-color);
+ transition: background 0.05s ease-in-out;
+ margin-inline-end: 6px;
+ padding: 0 5px;
+}
+
+.theme-light .headers-summary .tool-button {
+ background-color: var(--toolbar-tab-hover);
+}
+
+.theme-light .headers-summary .tool-button:hover {
+ background-color: rgba(170, 170, 170, 0.3);
+}
+
+.theme-light .headers-summary .tool-button:hover:active {
+ background-color: var(--toolbar-tab-hover-active);
+}
+
+.theme-dark .headers-summary .tool-button {
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+.theme-dark .headers-summary .tool-button:hover {
+ background-color: rgba(0, 0, 0, 0.3);
+}
+
+.theme-dark .headers-summary .tool-button:hover:active {
+ background-color: rgba(0, 0, 0, 0.4);
+}
+
+.headers-summary .requests-menu-status-icon {
+ min-width: 10px;
+}
+
+.headers-summary .raw-headers-container {
+ display: flex;
+ width: 100%;
+}
+
+.headers-summary .raw-headers {
+ width: 50%;
+ padding: 0px 4px;
+}
+
+.headers-summary .raw-headers textarea {
+ width: 100%;
+ height: 50vh;
+ font: message-box;
+ resize: none;
+ box-sizing: border-box;
+}
+
+.headers-summary .raw-headers .tabpanel-summary-label {
+ padding: 0 0 4px 0;
+}
+
.empty-notice {
color: var(--theme-body-color-alt);
padding: 3px 8px;
}
.response-summary {
display: flex;
}
@@ -1205,29 +1267,31 @@
width: 100%;
height: 100%;
}
/*
* FIXME: normal html block element cannot fill outer XUL element
* This workaround should be removed after netmonitor is migrated to react
*/
+#react-headers-tabpanel-hook,
#react-params-tabpanel-hook,
#react-preview-tabpanel-hook,
#react-response-tabpanel-hook,
#react-security-tabpanel-hook,
#react-timings-tabpanel-hook,
#network-statistics-charts,
#primed-cache-chart,
#empty-cache-chart {
display: -moz-box;
-moz-box-flex: 1;
}
/* For vbox */
+#react-headers-tabpanel-hook,
#react-params-tabpanel-hook,
#react-preview-tabpanel-hook,
#react-response-tabpanel-hook,
#react-security-tabpanel-hook,
#react-timings-tabpanel-hook,
#primed-cache-chart,
#empty-cache-chart {
-moz-box-orient: vertical;