Split RequestHeadersView into multiple components. [3/3]
This stack of CLs extracts multiple separate components from the
`RequestHeadersView`. The existing logic and UI stay almost unchanged.
Part 3/3:
ResponseHeaderSection: contains the logic for rendering response
headers; renders multiple HeaderSectionRows; additional logic for displaying and editing overridden headers will be added to this
component in follow-up CLs.
Bug: 1297533
Change-Id: I965bd150356de9a7791bb17850c1789f81fbfd71
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3870305
Commit-Queue: Wolfgang Beyer <[email protected]>
Reviewed-by: Simon Zünd <[email protected]>
diff --git a/front_end/panels/network/components/ResponseHeaderSection.ts b/front_end/panels/network/components/ResponseHeaderSection.ts
new file mode 100644
index 0000000..e2dd874
--- /dev/null
+++ b/front_end/panels/network/components/ResponseHeaderSection.ts
@@ -0,0 +1,267 @@
+// Copyright 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
+import * as LitHtml from '../../../ui/lit-html/lit-html.js';
+
+import type * as SDK from '../../../core/sdk/sdk.js';
+import * as Protocol from '../../../generated/protocol.js';
+import * as i18n from '../../../core/i18n/i18n.js';
+import * as NetworkForward from '../../../panels/network/forward/forward.js';
+import * as Host from '../../../core/host/host.js';
+import * as IssuesManager from '../../../models/issues_manager/issues_manager.js';
+import {type HeaderDescriptor, HeaderSectionRow, type HeaderSectionRowData} from './HeaderSectionRow.js';
+
+import responseHeaderSectionStyles from './ResponseHeaderSection.css.js';
+
+const {render, html} = LitHtml;
+
+const UIStrings = {
+ /**
+ *@description Explanation text for which cross-origin policy to set.
+ */
+ chooseThisOptionIfTheResourceAnd:
+ 'Choose this option if the resource and the document are served from the same site.',
+ /**
+ *@description Explanation text for which cross-origin policy to set.
+ */
+ onlyChooseThisOptionIfAn:
+ 'Only choose this option if an arbitrary website including this resource does not impose a security risk.',
+ /**
+ *@description Message in the Headers View of the Network panel when a cross-origin opener policy blocked loading a sandbox iframe.
+ */
+ thisDocumentWasBlockedFrom:
+ 'This document was blocked from loading in an `iframe` with a `sandbox` attribute because this document specified a cross-origin opener policy.',
+ /**
+ *@description Message in the Headers View of the Network panel when a cross-origin embedder policy header needs to be set.
+ */
+ toEmbedThisFrameInYourDocument:
+ 'To embed this frame in your document, the response needs to enable the cross-origin embedder policy by specifying the following response header:',
+ /**
+ *@description Message in the Headers View of the Network panel when a cross-origin resource policy header needs to be set.
+ */
+ toUseThisResourceFromADifferent:
+ 'To use this resource from a different origin, the server needs to specify a cross-origin resource policy in the response headers:',
+ /**
+ *@description Message in the Headers View of the Network panel when the cross-origin resource policy header is too strict.
+ */
+ toUseThisResourceFromADifferentOrigin:
+ 'To use this resource from a different origin, the server may relax the cross-origin resource policy response header:',
+ /**
+ *@description Message in the Headers View of the Network panel when the cross-origin resource policy header is too strict.
+ */
+ toUseThisResourceFromADifferentSite:
+ 'To use this resource from a different site, the server may relax the cross-origin resource policy response header:',
+};
+
+const str_ = i18n.i18n.registerUIStrings('panels/network/components/ResponseHeaderSection.ts', UIStrings);
+const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
+
+export interface ResponseHeaderSectionData {
+ request: SDK.NetworkRequest.NetworkRequest;
+ toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string};
+}
+
+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>;
+ #headers: HeaderDescriptor[] = [];
+
+ connectedCallback(): void {
+ this.#shadow.adoptedStyleSheets = [responseHeaderSectionStyles];
+ }
+
+ set data(data: ResponseHeaderSectionData) {
+ this.#request = data.request;
+
+ const headersWithIssues = [];
+ if (this.#request.wasBlocked()) {
+ const headerWithIssues =
+ BlockedReasonDetails.get((this.#request.blockedReason() as Protocol.Network.BlockedReason));
+ if (headerWithIssues) {
+ if (IssuesManager.RelatedIssue.hasIssueOfCategory(
+ this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy)) {
+ const followLink = (): void => {
+ Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.LearnMoreLinkCOEP);
+ if (this.#request) {
+ void IssuesManager.RelatedIssue.reveal(
+ this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy);
+ }
+ };
+ if (headerWithIssues.blockedDetails) {
+ headerWithIssues.blockedDetails.reveal = followLink;
+ }
+ }
+ headersWithIssues.push(headerWithIssues);
+ }
+ }
+
+ function mergeHeadersWithIssues(
+ headers: SDK.NetworkRequest.NameValue[], headersWithIssues: HeaderDescriptor[]): HeaderDescriptor[] {
+ let i = 0, j = 0;
+ const result: HeaderDescriptor[] = [];
+ while (i < headers.length && j < headersWithIssues.length) {
+ if (headers[i].name < headersWithIssues[j].name) {
+ result.push({...headers[i++], headerNotSet: false});
+ } else if (headers[i].name > headersWithIssues[j].name) {
+ result.push({...headersWithIssues[j++], headerNotSet: true});
+ } else {
+ result.push({...headersWithIssues[j++], ...headers[i++], headerNotSet: false});
+ }
+ }
+ while (i < headers.length) {
+ result.push({...headers[i++], headerNotSet: false});
+ }
+ while (j < headersWithIssues.length) {
+ result.push({...headersWithIssues[j++], headerNotSet: true});
+ }
+ return result;
+ }
+
+ this.#headers = mergeHeadersWithIssues(this.#request.sortedResponseHeaders.slice(), headersWithIssues);
+
+ const blockedResponseCookies = this.#request.blockedResponseCookies();
+ const blockedCookieLineToReasons = new Map<string, Protocol.Network.SetCookieBlockedReason[]>(
+ blockedResponseCookies?.map(c => [c.cookieLine, c.blockedReasons]));
+ for (const header of this.#headers) {
+ if (header.name.toLowerCase() === 'set-cookie' && header.value) {
+ const matchingBlockedReasons = blockedCookieLineToReasons.get(header.value.toString());
+ if (matchingBlockedReasons) {
+ header.setCookieBlockedReasons = matchingBlockedReasons;
+ }
+ }
+ }
+
+ if (data.toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Response) {
+ this.#headers.filter(header => header.name.toUpperCase() === data.toReveal?.header?.toUpperCase())
+ .forEach(header => {
+ header.highlight = true;
+ });
+ }
+
+ this.#render();
+ }
+
+ #render(): void {
+ if (!this.#request) {
+ return;
+ }
+
+ // Disabled until https://crbug.com/1079231 is fixed.
+ // clang-format off
+ render(html`
+ ${this.#headers.map(header => html`
+ <${HeaderSectionRow.litTagName} .data=${{
+ header: header,
+ } as HeaderSectionRowData}></${HeaderSectionRow.litTagName}>
+ `)}
+ `, this.#shadow, {host: this});
+ // clang-format on
+ }
+}
+
+ComponentHelpers.CustomElements.defineComponent('devtools-response-header-section', ResponseHeaderSection);
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'devtools-response-header-section': ResponseHeaderSection;
+ }
+}
+
+const BlockedReasonDetails = new Map<Protocol.Network.BlockedReason, HeaderDescriptor>([
+ [
+ Protocol.Network.BlockedReason.CoepFrameResourceNeedsCoepHeader,
+ {
+ name: 'cross-origin-embedder-policy',
+ value: null,
+ headerValueIncorrect: null,
+ blockedDetails: {
+ explanation: i18nLazyString(UIStrings.toEmbedThisFrameInYourDocument),
+ examples: [{codeSnippet: 'Cross-Origin-Embedder-Policy: require-corp', comment: undefined}],
+ link: {url: 'https://web.dev/coop-coep/'},
+ },
+ headerNotSet: null,
+ },
+ ],
+ [
+ Protocol.Network.BlockedReason.CorpNotSameOriginAfterDefaultedToSameOriginByCoep,
+ {
+ name: 'cross-origin-resource-policy',
+ value: null,
+ headerValueIncorrect: null,
+ blockedDetails: {
+ explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferent),
+ examples: [
+ {
+ codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
+ comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
+ },
+ {
+ codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
+ comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
+ },
+ ],
+ link: {url: 'https://web.dev/coop-coep/'},
+ },
+ headerNotSet: null,
+ },
+ ],
+ [
+ Protocol.Network.BlockedReason.CoopSandboxedIframeCannotNavigateToCoopPage,
+ {
+ name: 'cross-origin-opener-policy',
+ value: null,
+ headerValueIncorrect: false,
+ blockedDetails: {
+ explanation: i18nLazyString(UIStrings.thisDocumentWasBlockedFrom),
+ examples: [],
+ link: {url: 'https://web.dev/coop-coep/'},
+ },
+ headerNotSet: null,
+ },
+ ],
+ [
+ Protocol.Network.BlockedReason.CorpNotSameSite,
+ {
+ name: 'cross-origin-resource-policy',
+ value: null,
+ headerValueIncorrect: true,
+ blockedDetails: {
+ explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentSite),
+ examples: [
+ {
+ codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
+ comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
+ },
+ ],
+ link: null,
+ },
+ headerNotSet: null,
+ },
+ ],
+ [
+ Protocol.Network.BlockedReason.CorpNotSameOrigin,
+ {
+ name: 'cross-origin-resource-policy',
+ value: null,
+ headerValueIncorrect: true,
+ blockedDetails: {
+ explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentOrigin),
+ examples: [
+ {
+ codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
+ comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
+ },
+ {
+ codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
+ comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
+ },
+ ],
+ link: null,
+ },
+ headerNotSet: null,
+ },
+ ],
+]);