Bug 1495797 - change Raw headers buttons, plus styling changes. r=honza

Additional work for display of raw upload headers, plus corrected tests

--HG--
extra : histedit_source : c613b4e72b474e53b835bf51fc00f178b0d5cb3a
This commit is contained in:
lenka 2019-01-10 13:59:17 +01:00
Родитель 0f8a834501
Коммит 1d8b259661
5 изменённых файлов: 321 добавлений и 97 удалений

Просмотреть файл

@ -108,7 +108,8 @@
}
.network-monitor .properties-view .devtools-searchbox,
.network-monitor .tree-container .treeTable .tree-section {
.network-monitor .tree-container .treeTable .tree-section,
.network-monitor .properties-view .raw-headers-container {
width: 100%;
background-color: var(--theme-toolbar-background);
}
@ -207,6 +208,19 @@
color: var(--theme-selection-color);
}
.theme-dark .network-monitor .edit-and-resend-button {
background-color: var(--toolbarbutton-background);
color: var(--theme-selection-color);
}
.summary-edit-and-resend {
display: flex;
align-items: center;
flex-wrap: wrap;
justify-content: space-between;
padding-inline-end: 3px;
}
/* Headers tabpanel */
.network-monitor .headers-overview {
@ -219,29 +233,79 @@
align-items: center;
}
.network-monitor .headers-summary .devtools-button {
margin-inline-end: 6px;
.theme-light .network-monitor .edit-and-resend-button {
background-color: var(--grey-20);
}
.network-monitor .headers-summary .raw-headers-container {
.network-monitor .edit-and-resend-button {
align-self: flex-end;
height: 24px;
padding-left: 8px;
padding-right: 8px;
width: auto;
border: 1px solid var(--theme-splitter-color);
}
.network-monitor .raw-headers-toggle {
display: flex;
width: 100%;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-end;
align-items: center;
}
.network-monitor .headers-summary .raw-headers {
width: 50%;
padding: 0 4px;
.network-monitor .raw-headers-toggle .headers-summary-label {
color: var(--theme-toolbar-color);
}
.network-monitor .headers-summary .raw-headers textarea {
.network-monitor .raw-headers-toggle-input > input {
display: inline-block;
align-items: center;
background: var(--toggle-track-color);
width: 2em ;
vertical-align: bottom;
}
.network-monitor .properties-view .tree-container .treeTable .treeValueCell .devtools-checkbox-toggle {
margin-top: 2px;
margin-bottom: 2px;
}
.network-monitor .properties-view .raw-headers-container .raw-headers {
display: block;
overflow: hidden;
width: 100%;
height: 50vh;
font: message-box;
padding: 2px 12px;
white-space: pre;
overflow-wrap: normal;
overflow-x: auto;
border: none;
}
.network-monitor .properties-view .raw-headers-container td {
display: block;
}
.network-monitor .properties-view .raw-headers-container textarea {
width: 100%;
font-family: var(--monospace-font-family);
font-size: var(--theme-body-font-size);
resize: none;
}
.network-monitor .headers-summary .raw-headers .tabpanel-summary-label {
.theme-light .network-monitor .properties-view textarea {
background-color: white;
border: 1px solid var(--grey-25);
color: var(--grey-90);
}
.theme-dark .network-monitor .properties-view textarea {
background-color: var(--grey-70);
border: 1px solid var(--grey-85);
color: white;
}
.network-monitor .properties-view .raw-headers .tabpanel-summary-label {
padding: 0 0 4px 0;
}

Просмотреть файл

@ -38,13 +38,14 @@ loader.lazyGetter(this, "Rep", function() {
loader.lazyGetter(this, "MODE", function() {
return require("devtools/client/shared/components/reps/reps").MODE;
});
loader.lazyGetter(this, "TreeRow", function() {
return createFactory(require("devtools/client/shared/components/tree/TreeRow"));
});
const { button, div, input, textarea, span } = dom;
const { button, div, input, label, span, textarea, tr, td } = 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");
@ -67,6 +68,7 @@ class HeadersPanel extends Component {
return {
connector: PropTypes.object.isRequired,
cloneSelectedRequest: PropTypes.func.isRequired,
member: PropTypes.object.isRequired,
request: PropTypes.object.isRequired,
renderValue: PropTypes.func,
openLink: PropTypes.func,
@ -77,12 +79,17 @@ class HeadersPanel extends Component {
super(props);
this.state = {
rawHeadersOpened: false,
rawRequestHeadersOpened: false,
rawResponseHeadersOpened: false,
rawUploadHeadersOpened: false,
};
this.getProperties = this.getProperties.bind(this);
this.toggleRawHeaders = this.toggleRawHeaders.bind(this);
this.toggleRawResponseHeaders = this.toggleRawResponseHeaders.bind(this);
this.toggleRawRequestHeaders = this.toggleRawRequestHeaders.bind(this);
this.toggleRawUploadHeaders = this.toggleRawUploadHeaders.bind(this);
this.renderSummary = this.renderSummary.bind(this);
this.renderRow = this.renderRow.bind(this);
this.renderValue = this.renderValue.bind(this);
}
@ -105,29 +112,67 @@ class HeadersPanel extends Component {
}
getProperties(headers, title) {
let propertiesResult;
if (headers && headers.headers.length) {
const headerKey = `${title} (${getFormattedSize(headers.headersSize, 3)})`;
const propertiesResult = {
propertiesResult = {
[headerKey]: new HeaderList(headers.headers),
};
return propertiesResult;
}
return null;
if ((title === RESPONSE_HEADERS && this.state.rawResponseHeadersOpened) ||
(title === REQUEST_HEADERS && this.state.rawRequestHeadersOpened) ||
(title === REQUEST_HEADERS_FROM_UPLOAD && this.state.rawUploadHeadersOpened)) {
propertiesResult = {
[headerKey]: { RAW_HEADERS_ID: headers.rawHeaders },
};
}
}
return propertiesResult;
}
toggleRawHeaders() {
toggleRawResponseHeaders() {
this.setState({
rawHeadersOpened: !this.state.rawHeadersOpened,
rawResponseHeadersOpened: !this.state.rawResponseHeadersOpened,
});
}
renderSummary(label, value) {
toggleRawRequestHeaders() {
this.setState({
rawRequestHeadersOpened: !this.state.rawRequestHeadersOpened,
});
}
toggleRawUploadHeaders() {
this.setState({
rawUploadHeadersOpened: !this.state.rawUploadHeadersOpened,
});
}
/**
* Helper method to identify what kind of raw header this is.
* Information is in the path variable
*/
getRawHeaderType(path) {
if (path.includes(RESPONSE_HEADERS)) {
return "RESPONSE";
}
if (path.includes(REQUEST_HEADERS_FROM_UPLOAD)) {
return "UPLOAD";
}
return "REQUEST";
}
/**
* Renders the top part of the headers detail panel - Summary.
*/
renderSummary(summaryLabel, value) {
return (
div({ className: "tabpanel-summary-container headers-summary" },
div({ className: "tabpanel-summary-labelvalue"},
span({ className: "tabpanel-summary-label headers-summary-label"},
label
summaryLabel
),
span({ className: "tabpanel-summary-value textbox-input devtools-monospace"},
value
@ -137,9 +182,140 @@ class HeadersPanel extends Component {
);
}
/**
* Custom rendering method passed to PropertiesView. It's responsible
* for rendering <textarea> element with raw headers data.
*/
renderRow(props) {
const {
level,
path,
} = props.member;
const {
request: {
httpVersion,
requestHeaders,
requestHeadersFromUploadStream: uploadHeaders,
responseHeaders,
status,
statusText,
},
} = this.props;
let value;
if (level === 1 && path.includes("RAW_HEADERS_ID")) {
const rawHeaderType = this.getRawHeaderType(path);
switch (rawHeaderType) {
case "REQUEST":
value = writeHeaderText(requestHeaders.headers);
break;
case "RESPONSE":
// display Status-Line above other response headers
const statusLine = `${httpVersion} ${status} ${statusText}\n`;
value = statusLine + writeHeaderText(responseHeaders.headers);
break;
case "UPLOAD":
value = writeHeaderText(uploadHeaders.headers);
break;
}
let rows;
if (value) {
// Need to add 1 for the horizontal scrollbar
// not to cover the last row of raw data
rows = value.match(/\n/g).length + 1;
}
return (
tr({
key: path,
role: "treeitem",
className: "raw-headers-container",
},
td({
colSpan: 2,
},
textarea({
className: "raw-headers",
rows: rows,
value: value,
readOnly: true,
})
)
)
);
}
return TreeRow(props);
}
/**
* Rendering toggle buttons for switching between formated and raw
* headers data.
*/
renderInput(onChange, checked) {
return (
input({
checked,
className: "devtools-checkbox-toggle",
onChange,
type: "checkbox",
})
);
}
renderToggleRawHeadersBtn(path) {
let inputElement;
const rawHeaderType = this.getRawHeaderType(path);
switch (rawHeaderType) {
case "REQUEST":
// Render toggle button for REQUEST header
inputElement =
this.renderInput(
this.toggleRawRequestHeaders, this.state.rawRequestHeadersOpened);
break;
case "RESPONSE":
// Render toggle button for RESPONSE header
inputElement = this.renderInput(
this.toggleRawResponseHeaders, this.state.rawResponseHeadersOpened);
break;
case "UPLOAD":
// Render toggle button for UPLOAD header
inputElement =
this.renderInput(
this.toggleRawUploadHeaders, this.state.rawUploadHeadersOpened);
break;
}
return (
label({ className: "raw-headers-toggle" },
span({ className: "headers-summary-label"},
RAW_HEADERS
),
div({ className: "raw-headers-toggle-input" },
inputElement
)
)
);
}
renderValue(props) {
const member = props.member;
const value = props.value;
const path = member.path;
let toggleRawHeadersBtn;
// When member.level === 0, it is a section label
// Request/Response header
if (member.level === 0) {
toggleRawHeadersBtn = this.renderToggleRawHeadersBtn(path);
// Return label and toggle button
return toggleRawHeadersBtn;
}
if (typeof value !== "string") {
return null;
@ -216,10 +392,7 @@ class HeadersPanel extends Component {
if (status) {
const statusCodeDocURL = getHTTPStatusCodeURL(status.toString());
const inputWidth = statusText.length + 1;
const toggleRawHeadersClassList = ["devtools-button", "raw-headers-button"];
if (this.state.rawHeadersOpened) {
toggleRawHeadersClassList.push("checked");
}
summaryStatus = (
div({ className: "tabpanel-summary-container headers-summary" },
div({
@ -239,15 +412,6 @@ class HeadersPanel extends Component {
}) : span({
className: "headers-summary learn-more-link",
}),
button({
className: "devtools-button edit-and-resend-button",
onClick: cloneSelectedRequest,
}, EDIT_AND_RESEND),
button({
"aria-pressed": this.state.rawHeadersOpened,
className: toggleRawHeadersClassList.join(" "),
onClick: this.toggleRawHeaders,
}, RAW_HEADERS),
)
);
}
@ -258,34 +422,16 @@ class HeadersPanel extends Component {
const summaryReferrerPolicy = referrerPolicy ?
this.renderSummary(SUMMARY_REFERRER_POLICY, referrerPolicy) : null;
// display Status-Line above other response headers
const statusLine = `${httpVersion} ${status} ${statusText}\n`;
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({
className: "raw-request-headers-textarea",
value: writeHeaderText(requestHeaders.headers),
readOnly: true,
}),
),
div({ className: "raw-headers" },
div({ className: "tabpanel-summary-label" }, RAW_HEADERS_RESPONSE),
textarea({
className: "raw-response-headers-textarea",
value: statusLine + writeHeaderText(responseHeaders.headers),
readOnly: true,
}),
),
)
)
);
}
const summaryEditAndResendBtn = (
div({
className: "summary-edit-and-resend" },
summaryReferrerPolicy,
button({
className: "edit-and-resend-button devtools-button",
onClick: cloneSelectedRequest,
}, EDIT_AND_RESEND)
)
);
return (
div({ className: "panel-container" },
@ -295,14 +441,14 @@ class HeadersPanel extends Component {
summaryAddress,
summaryStatus,
summaryVersion,
summaryReferrerPolicy,
summaryRawHeaders,
summaryEditAndResendBtn,
),
PropertiesView({
object,
provider: HeadersProvider,
filterPlaceHolder: HEADERS_FILTER_TEXT,
sectionNames: Object.keys(object),
renderRow: this.renderRow,
renderValue: this.renderValue,
openLink,
}),

Просмотреть файл

@ -89,20 +89,25 @@ async function verifyRawHeaders(monitor) {
"Upgrade-Insecure-Requests", "Pragma",
"Cache-Control"];
// Click the 'Raw headers' button to show original headers source.
const rawHeadersBtn = document.querySelector(".raw-headers-button");
rawHeadersBtn.click();
// Click the 'Raw headers' toggle to show original headers source.
for (const rawToggleInput of document.querySelectorAll(".devtools-checkbox-toggle")) {
rawToggleInput.click();
}
// Wait till raw headers are available.
let rawArr;
await waitUntil(() => {
return document.querySelector(".raw-request-headers-textarea") &&
document.querySelector(".raw-response-headers-textarea");
rawArr = document.querySelectorAll("textarea.raw-headers");
// Both raw headers must be present
return (rawArr.length > 1);
});
// Request headers are rendered first, so it is element with index 1
const requestHeadersText =
document.querySelector(".raw-request-headers-textarea").textContent;
rawArr[1].textContent;
// Response headers are rendered first, so it is element with index 0
const responseHeadersText =
document.querySelector(".raw-response-headers-textarea").textContent;
rawArr[0].textContent;
const rawRequestHeadersArray = requestHeadersText.split("\n");
for (let i = 0; i < rawRequestHeadersArray.length; i++) {

Просмотреть файл

@ -27,41 +27,44 @@ add_task(async function() {
document.querySelectorAll(".request-list-item")[0]);
await wait;
wait = waitForDOM(document, ".raw-headers-container textarea", 2);
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersButton());
wait = waitForDOM(document, "textarea.raw-headers", 2);
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersToggle("RESPONSE"));
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersToggle("REQUEST"));
await wait;
testRawHeaderButtonStyle(true);
testRawHeaderToggleStyle(true);
testShowRawHeaders(getSortedRequests(store.getState()).get(0));
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersButton());
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersToggle("RESPONSE"));
EventUtils.sendMouseEvent({ type: "click" }, getRawHeadersToggle("REQUEST"));
testRawHeaderButtonStyle(false);
testRawHeaderToggleStyle(false);
testHideRawHeaders(document);
return teardown(monitor);
/**
* Tests that checked, aria-pressed style is applied correctly
* Tests that checked is applied correctly
*
* @param checked
* flag indicating whether button is pressed or not
* flag indicating whether toggle is checked or not
*/
function testRawHeaderButtonStyle(checked) {
const rawHeadersButton = getRawHeadersButton();
function testRawHeaderToggleStyle(checked) {
const rawHeadersRequestToggle = getRawHeadersToggle("REQUEST");
const rawHeadersResponseToggle = getRawHeadersToggle("RESPONSE");
if (checked) {
is(rawHeadersButton.classList.contains("checked"), true,
"The 'Raw Headers' button should have a 'checked' class.");
is(rawHeadersButton.getAttribute("aria-pressed"), "true",
"The 'Raw Headers' button should have the 'aria-pressed' attribute set to true");
is(rawHeadersRequestToggle.checked, true,
"The 'Raw Request Headers' toggle should be 'checked'");
is(rawHeadersResponseToggle.checked, true,
"The 'Raw Response Headers' toggle should be 'checked'");
} else {
is(rawHeadersButton.classList.contains("checked"), false,
"The 'Raw Headers' button should not have a 'checked' class.");
is(rawHeadersButton.getAttribute("aria-pressed"), "false",
"The 'Raw Headers' button should have the 'aria-pressed' attribute set to false");
is(rawHeadersRequestToggle.checked, false,
"The 'Raw Request Headers' toggle should NOT be 'checked'");
is(rawHeadersResponseToggle.checked, false,
"The 'Raw Response Headers' toggle should NOT be 'checked'");
}
}
@ -69,14 +72,16 @@ add_task(async function() {
* Tests that raw headers were displayed correctly
*/
function testShowRawHeaders(data) {
// Request headers are rendered first, so it is element with index 1
const requestHeaders = document
.querySelectorAll(".raw-headers-container textarea")[0].value;
.querySelectorAll("textarea.raw-headers")[1].value;
for (const header of data.requestHeaders.headers) {
ok(requestHeaders.includes(header.name + ": " + header.value),
"textarea contains request headers");
}
// Response headers are rendered first, so it is element with index 0
const responseHeaders = document
.querySelectorAll(".raw-headers-container textarea")[1].value;
.querySelectorAll("textarea.raw-headers")[0].value;
for (const header of data.responseHeaders.headers) {
ok(responseHeaders.includes(header.name + ": " + header.value),
"textarea contains response headers");
@ -94,7 +99,11 @@ add_task(async function() {
/**
* Returns the 'Raw Headers' button
*/
function getRawHeadersButton() {
return document.querySelectorAll(".headers-summary .devtools-button")[2];
function getRawHeadersToggle(rawHeaderType) {
if (rawHeaderType === "RESPONSE") {
// Response header is first displayed
return document.querySelectorAll(".devtools-checkbox-toggle")[0];
}
return document.querySelectorAll(".devtools-checkbox-toggle")[1];
}
});

Просмотреть файл

@ -17,5 +17,5 @@
}
.network-monitor .tree-container .treeTable tr:not(:hover) .learn-more-link {
opacity: 0.1;
opacity: 0.4;
}