Header Overrides Editor: show original header after deleting override
When deleting a header override in the UI and the response as received
over the wire before any overrides already contains a header with the
same name, show this original header in place of the deleted header
override
Screencast: https://i.imgur.com/CMJUtuW.mp4
Bug: 1297533
Change-Id: Ic929b50535184207af0f1fd8857e08bd959534b6
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3974770
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Wolfgang Beyer <wolfi@chromium.org>
diff --git a/front_end/panels/network/components/HeaderSectionRow.ts b/front_end/panels/network/components/HeaderSectionRow.ts
index 30e6e45..53df626 100644
--- a/front_end/panels/network/components/HeaderSectionRow.ts
+++ b/front_end/panels/network/components/HeaderSectionRow.ts
@@ -175,7 +175,7 @@
// value of the contenteditable element and not the potentially outdated
// value from the previous render.
// clang-format off
- return LitHtml.html`<span contenteditable="true" class="editable" tabindex="0" .innerText=${LitHtml.Directives.live(value)}></span>`;
+ return html`<span contenteditable="true" class="editable" tabindex="0" .innerText=${LitHtml.Directives.live(value)}></span>`;
// clang-format on
}
@@ -325,6 +325,10 @@
const headerValueElement = this.#shadow.querySelector('.header-value .editable') as HTMLElement;
const headerName = Platform.StringUtilities.toLowerCaseString(headerNameElement.innerText.slice(0, -1));
const headerValue = headerValueElement.innerText;
+ const row = this.shadowRoot?.querySelector<HTMLDivElement>('.row');
+ if (row) {
+ row.classList.remove('header-overridden');
+ }
this.dispatchEvent(new HeaderRemovedEvent(headerName, headerValue));
}
diff --git a/front_end/panels/network/components/ResponseHeaderSection.ts b/front_end/panels/network/components/ResponseHeaderSection.ts
index 955c75b..50aff69 100644
--- a/front_end/panels/network/components/ResponseHeaderSection.ts
+++ b/front_end/panels/network/components/ResponseHeaderSection.ts
@@ -89,7 +89,7 @@
export class ResponseHeaderSection extends HTMLElement {
static readonly litTagName = LitHtml.literal`devtools-response-header-section`;
readonly #shadow = this.attachShadow({mode: 'open'});
- #request?: Readonly<SDK.NetworkRequest.NetworkRequest>;
+ #request?: SDK.NetworkRequest.NetworkRequest;
#headerDetails: HeaderDetailsDescriptor[] = [];
#headerEditors: HeaderEditorDescriptor[] = [];
#uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null;
@@ -343,9 +343,27 @@
const rawFileName = this.#fileNameFromUrl(this.#request.url());
this.#removeEntryFromOverrides(rawFileName, event.headerName, event.headerValue);
this.#commitOverrides();
- this.#headerEditors.splice(index, 1);
if (index < this.#headerDetails.length) {
- this.#headerDetails.splice(index, 1);
+ const originalHeaders =
+ (this.#request?.originalResponseHeaders ||
+ []).filter(header => Platform.StringUtilities.toLowerCaseString(header.name) === event.headerName);
+ if (originalHeaders.length === 1) {
+ // Remove the header override and replace it with the original non-
+ // overridden header in the UI.
+ this.#headerDetails[index].value = originalHeaders[0].value;
+ this.#headerEditors[index].value = originalHeaders[0].value;
+ this.#headerEditors[index].originalValue = originalHeaders[0].value;
+ this.#headerEditors[index].isOverride = false;
+ } else {
+ // If there is no (or multiple) matching originalResonseHeader,
+ // remove the header from the UI.
+ this.#headerDetails.splice(index, 1);
+ this.#headerEditors.splice(index, 1);
+ }
+ } else {
+ // This is the branch for headers which were added via the UI after the
+ // response was received. They can simply be removed.
+ this.#headerEditors.splice(index, 1);
}
this.#render();
}
@@ -354,6 +372,13 @@
if (!this.#request) {
return;
}
+ // If 'originalResponseHeaders' are not populated (because there was no
+ // request interception), fill them with a copy of 'sortedResponseHeaders'.
+ // This ensures we have access to the original values when undoing edits.
+ if (this.#request.originalResponseHeaders.length === 0) {
+ this.#request.originalResponseHeaders =
+ this.#request.sortedResponseHeaders.map(headerEntry => ({...headerEntry}));
+ }
const previousName = this.#headerEditors[index].name;
const previousValue = this.#headerEditors[index].value;
diff --git a/test/unittests/front_end/panels/network/components/ResponseHeaderSection_test.ts b/test/unittests/front_end/panels/network/components/ResponseHeaderSection_test.ts
index 7ed2f59..c2419e0 100644
--- a/test/unittests/front_end/panels/network/components/ResponseHeaderSection_test.ts
+++ b/test/unittests/front_end/panels/network/components/ResponseHeaderSection_test.ts
@@ -59,14 +59,16 @@
dispatchFocusOutEvent(editable, {bubbles: true});
}
-function removeHeaderRow(component: NetworkComponents.ResponseHeaderSection.ResponseHeaderSection): void {
+async function removeHeaderRow(
+ component: NetworkComponents.ResponseHeaderSection.ResponseHeaderSection, index: number): Promise<void> {
assertShadowRoot(component.shadowRoot);
- const row = component.shadowRoot.querySelector('devtools-header-section-row');
+ const row = component.shadowRoot.querySelectorAll('devtools-header-section-row')[index];
assertElement(row, HTMLElement);
assertShadowRoot(row.shadowRoot);
const button = row.shadowRoot.querySelector('.remove-header');
assertElement(button, HTMLElement);
button.click();
+ await coordinator.done();
}
async function setupHeaderEditing(
@@ -516,6 +518,10 @@
{
"name": "cache-control",
"value": "max-age=9999"
+ },
+ {
+ "name": "added",
+ "value": "foo"
}
]
}
@@ -524,6 +530,7 @@
const actualHeaders = [
{name: 'server', value: 'overridden server'},
{name: 'cache-control', value: 'max-age=9999'},
+ {name: 'added', value: 'foo'},
];
const originalHeaders = [
@@ -532,9 +539,35 @@
];
const {component, spy} = await setupHeaderEditing(headerOverridesFileContent, actualHeaders, originalHeaders);
- removeHeaderRow(component);
+ await removeHeaderRow(component, 0);
- const expected = [{
+ let expected = [{
+ applyTo: 'index.html',
+ headers: [
+ {
+ name: 'cache-control',
+ value: 'max-age=9999',
+ },
+ {
+ name: 'added',
+ value: 'foo',
+ },
+ ],
+ }];
+ assert.strictEqual(spy.callCount, 1);
+ assert.isTrue(spy.calledOnceWith(JSON.stringify(expected, null, 2)));
+
+ assertShadowRoot(component.shadowRoot);
+ let rows = component.shadowRoot.querySelectorAll('devtools-header-section-row');
+ assert.strictEqual(rows.length, 3);
+ checkHeaderSectionRow(rows[0], 'server:', 'original server', false, false, true);
+ checkHeaderSectionRow(rows[1], 'cache-control:', 'max-age=9999', true, false, true);
+ checkHeaderSectionRow(rows[2], 'added:', 'foo', true, false, true);
+
+ spy.resetHistory();
+ await removeHeaderRow(component, 2);
+
+ expected = [{
applyTo: 'index.html',
headers: [
{
@@ -545,6 +578,10 @@
}];
assert.strictEqual(spy.callCount, 1);
assert.isTrue(spy.calledOnceWith(JSON.stringify(expected, null, 2)));
+ rows = component.shadowRoot.querySelectorAll('devtools-header-section-row');
+ assert.strictEqual(rows.length, 2);
+ checkHeaderSectionRow(rows[0], 'server:', 'original server', false, false, true);
+ checkHeaderSectionRow(rows[1], 'cache-control:', 'max-age=9999', true, false, true);
});
it('can remove the last header override', async () => {
@@ -569,7 +606,7 @@
];
const {component, spy} = await setupHeaderEditing(headerOverridesFileContent, actualHeaders, originalHeaders);
- removeHeaderRow(component);
+ await removeHeaderRow(component, 0);
const expected: Persistence.NetworkPersistenceManager.HeaderOverride[] = [];
assert.strictEqual(spy.callCount, 1);
@@ -669,6 +706,42 @@
assert.isTrue(spy.getCall(-1).calledWith(JSON.stringify(expected, null, 2)));
});
+ it('can remove a newly added header', async () => {
+ const actualHeaders = [
+ {name: 'server', value: 'original server'},
+ ];
+ const {component, spy} = await setupHeaderEditing('[]', actualHeaders, actualHeaders);
+ assertShadowRoot(component.shadowRoot);
+ const addHeaderButton = component.shadowRoot.querySelector('.add-header-button');
+ assertElement(addHeaderButton, HTMLElement);
+ addHeaderButton.click();
+ await coordinator.done();
+
+ const expected = [{
+ applyTo: 'index.html',
+ headers: [
+ {
+ name: 'header-name',
+ value: 'header value',
+ },
+ ],
+ }];
+ assert.isTrue(spy.getCall(-1).calledWith(JSON.stringify(expected, null, 2)));
+ let rows = component.shadowRoot.querySelectorAll('devtools-header-section-row');
+ assert.strictEqual(rows.length, 2);
+ checkHeaderSectionRow(rows[0], 'server:', 'original server', false, false, true);
+ checkHeaderSectionRow(rows[1], 'header-name:', 'header value', true, true, true);
+
+ spy.resetHistory();
+ await removeHeaderRow(component, 1);
+
+ assert.strictEqual(spy.callCount, 1);
+ assert.isTrue(spy.calledOnceWith(JSON.stringify([], null, 2)));
+ rows = component.shadowRoot.querySelectorAll('devtools-header-section-row');
+ assert.strictEqual(rows.length, 1);
+ checkHeaderSectionRow(rows[0], 'server:', 'original server', false, false, true);
+ });
+
it('can edit multiple headers', async () => {
const headerOverridesFileContent = `[
{