blob: e2dd874e8e2474ed75255572a3158976eae4ec86 [file] [log] [blame]
Wolfgang Beyerd2286472022-09-02 18:32:301// Copyright 2022 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
6import * as LitHtml from '../../../ui/lit-html/lit-html.js';
7
8import type * as SDK from '../../../core/sdk/sdk.js';
9import * as Protocol from '../../../generated/protocol.js';
10import * as i18n from '../../../core/i18n/i18n.js';
11import * as NetworkForward from '../../../panels/network/forward/forward.js';
12import * as Host from '../../../core/host/host.js';
13import * as IssuesManager from '../../../models/issues_manager/issues_manager.js';
14import {type HeaderDescriptor, HeaderSectionRow, type HeaderSectionRowData} from './HeaderSectionRow.js';
15
16import responseHeaderSectionStyles from './ResponseHeaderSection.css.js';
17
18const {render, html} = LitHtml;
19
20const UIStrings = {
21 /**
22 *@description Explanation text for which cross-origin policy to set.
23 */
24 chooseThisOptionIfTheResourceAnd:
25 'Choose this option if the resource and the document are served from the same site.',
26 /**
27 *@description Explanation text for which cross-origin policy to set.
28 */
29 onlyChooseThisOptionIfAn:
30 'Only choose this option if an arbitrary website including this resource does not impose a security risk.',
31 /**
32 *@description Message in the Headers View of the Network panel when a cross-origin opener policy blocked loading a sandbox iframe.
33 */
34 thisDocumentWasBlockedFrom:
35 'This document was blocked from loading in an `iframe` with a `sandbox` attribute because this document specified a cross-origin opener policy.',
36 /**
37 *@description Message in the Headers View of the Network panel when a cross-origin embedder policy header needs to be set.
38 */
39 toEmbedThisFrameInYourDocument:
40 'To embed this frame in your document, the response needs to enable the cross-origin embedder policy by specifying the following response header:',
41 /**
42 *@description Message in the Headers View of the Network panel when a cross-origin resource policy header needs to be set.
43 */
44 toUseThisResourceFromADifferent:
45 'To use this resource from a different origin, the server needs to specify a cross-origin resource policy in the response headers:',
46 /**
47 *@description Message in the Headers View of the Network panel when the cross-origin resource policy header is too strict.
48 */
49 toUseThisResourceFromADifferentOrigin:
50 'To use this resource from a different origin, the server may relax the cross-origin resource policy response header:',
51 /**
52 *@description Message in the Headers View of the Network panel when the cross-origin resource policy header is too strict.
53 */
54 toUseThisResourceFromADifferentSite:
55 'To use this resource from a different site, the server may relax the cross-origin resource policy response header:',
56};
57
58const str_ = i18n.i18n.registerUIStrings('panels/network/components/ResponseHeaderSection.ts', UIStrings);
59const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_);
60
61export interface ResponseHeaderSectionData {
62 request: SDK.NetworkRequest.NetworkRequest;
63 toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string};
64}
65
66export class ResponseHeaderSection extends HTMLElement {
67 static readonly litTagName = LitHtml.literal`devtools-response-header-section`;
68 readonly #shadow = this.attachShadow({mode: 'open'});
69 #request?: Readonly<SDK.NetworkRequest.NetworkRequest>;
70 #headers: HeaderDescriptor[] = [];
71
72 connectedCallback(): void {
73 this.#shadow.adoptedStyleSheets = [responseHeaderSectionStyles];
74 }
75
76 set data(data: ResponseHeaderSectionData) {
77 this.#request = data.request;
78
79 const headersWithIssues = [];
80 if (this.#request.wasBlocked()) {
81 const headerWithIssues =
82 BlockedReasonDetails.get((this.#request.blockedReason() as Protocol.Network.BlockedReason));
83 if (headerWithIssues) {
84 if (IssuesManager.RelatedIssue.hasIssueOfCategory(
85 this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy)) {
86 const followLink = (): void => {
87 Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.LearnMoreLinkCOEP);
88 if (this.#request) {
89 void IssuesManager.RelatedIssue.reveal(
90 this.#request, IssuesManager.Issue.IssueCategory.CrossOriginEmbedderPolicy);
91 }
92 };
93 if (headerWithIssues.blockedDetails) {
94 headerWithIssues.blockedDetails.reveal = followLink;
95 }
96 }
97 headersWithIssues.push(headerWithIssues);
98 }
99 }
100
101 function mergeHeadersWithIssues(
102 headers: SDK.NetworkRequest.NameValue[], headersWithIssues: HeaderDescriptor[]): HeaderDescriptor[] {
103 let i = 0, j = 0;
104 const result: HeaderDescriptor[] = [];
105 while (i < headers.length && j < headersWithIssues.length) {
106 if (headers[i].name < headersWithIssues[j].name) {
107 result.push({...headers[i++], headerNotSet: false});
108 } else if (headers[i].name > headersWithIssues[j].name) {
109 result.push({...headersWithIssues[j++], headerNotSet: true});
110 } else {
111 result.push({...headersWithIssues[j++], ...headers[i++], headerNotSet: false});
112 }
113 }
114 while (i < headers.length) {
115 result.push({...headers[i++], headerNotSet: false});
116 }
117 while (j < headersWithIssues.length) {
118 result.push({...headersWithIssues[j++], headerNotSet: true});
119 }
120 return result;
121 }
122
123 this.#headers = mergeHeadersWithIssues(this.#request.sortedResponseHeaders.slice(), headersWithIssues);
124
125 const blockedResponseCookies = this.#request.blockedResponseCookies();
126 const blockedCookieLineToReasons = new Map<string, Protocol.Network.SetCookieBlockedReason[]>(
127 blockedResponseCookies?.map(c => [c.cookieLine, c.blockedReasons]));
128 for (const header of this.#headers) {
129 if (header.name.toLowerCase() === 'set-cookie' && header.value) {
130 const matchingBlockedReasons = blockedCookieLineToReasons.get(header.value.toString());
131 if (matchingBlockedReasons) {
132 header.setCookieBlockedReasons = matchingBlockedReasons;
133 }
134 }
135 }
136
137 if (data.toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Response) {
138 this.#headers.filter(header => header.name.toUpperCase() === data.toReveal?.header?.toUpperCase())
139 .forEach(header => {
140 header.highlight = true;
141 });
142 }
143
144 this.#render();
145 }
146
147 #render(): void {
148 if (!this.#request) {
149 return;
150 }
151
152 // Disabled until https://crbug.com/1079231 is fixed.
153 // clang-format off
154 render(html`
155 ${this.#headers.map(header => html`
156 <${HeaderSectionRow.litTagName} .data=${{
157 header: header,
158 } as HeaderSectionRowData}></${HeaderSectionRow.litTagName}>
159 `)}
160 `, this.#shadow, {host: this});
161 // clang-format on
162 }
163}
164
165ComponentHelpers.CustomElements.defineComponent('devtools-response-header-section', ResponseHeaderSection);
166
167declare global {
168 interface HTMLElementTagNameMap {
169 'devtools-response-header-section': ResponseHeaderSection;
170 }
171}
172
173const BlockedReasonDetails = new Map<Protocol.Network.BlockedReason, HeaderDescriptor>([
174 [
175 Protocol.Network.BlockedReason.CoepFrameResourceNeedsCoepHeader,
176 {
177 name: 'cross-origin-embedder-policy',
178 value: null,
179 headerValueIncorrect: null,
180 blockedDetails: {
181 explanation: i18nLazyString(UIStrings.toEmbedThisFrameInYourDocument),
182 examples: [{codeSnippet: 'Cross-Origin-Embedder-Policy: require-corp', comment: undefined}],
183 link: {url: 'https://web.dev/coop-coep/'},
184 },
185 headerNotSet: null,
186 },
187 ],
188 [
189 Protocol.Network.BlockedReason.CorpNotSameOriginAfterDefaultedToSameOriginByCoep,
190 {
191 name: 'cross-origin-resource-policy',
192 value: null,
193 headerValueIncorrect: null,
194 blockedDetails: {
195 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferent),
196 examples: [
197 {
198 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
199 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
200 },
201 {
202 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
203 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
204 },
205 ],
206 link: {url: 'https://web.dev/coop-coep/'},
207 },
208 headerNotSet: null,
209 },
210 ],
211 [
212 Protocol.Network.BlockedReason.CoopSandboxedIframeCannotNavigateToCoopPage,
213 {
214 name: 'cross-origin-opener-policy',
215 value: null,
216 headerValueIncorrect: false,
217 blockedDetails: {
218 explanation: i18nLazyString(UIStrings.thisDocumentWasBlockedFrom),
219 examples: [],
220 link: {url: 'https://web.dev/coop-coep/'},
221 },
222 headerNotSet: null,
223 },
224 ],
225 [
226 Protocol.Network.BlockedReason.CorpNotSameSite,
227 {
228 name: 'cross-origin-resource-policy',
229 value: null,
230 headerValueIncorrect: true,
231 blockedDetails: {
232 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentSite),
233 examples: [
234 {
235 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
236 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
237 },
238 ],
239 link: null,
240 },
241 headerNotSet: null,
242 },
243 ],
244 [
245 Protocol.Network.BlockedReason.CorpNotSameOrigin,
246 {
247 name: 'cross-origin-resource-policy',
248 value: null,
249 headerValueIncorrect: true,
250 blockedDetails: {
251 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentOrigin),
252 examples: [
253 {
254 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
255 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
256 },
257 {
258 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
259 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
260 },
261 ],
262 link: null,
263 },
264 headerNotSet: null,
265 },
266 ],
267]);