blob: ef79440164201cfe36bf5292ca7363a92f2b63d3 [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(
Wolfgang Beyerfd504ec2022-09-06 09:48:11102 headers: HeaderDescriptor[], headersWithIssues: HeaderDescriptor[]): HeaderDescriptor[] {
Wolfgang Beyerd2286472022-09-02 18:32:30103 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
Wolfgang Beyerfd504ec2022-09-06 09:48:11123 this.#headers = this.#request.sortedResponseHeaders.map(
124 header => ({name: header.name.toLowerCase(), value: header.value, headerNotSet: false}));
125 this.#headers = mergeHeadersWithIssues(this.#headers, headersWithIssues);
Wolfgang Beyerd2286472022-09-02 18:32:30126
127 const blockedResponseCookies = this.#request.blockedResponseCookies();
128 const blockedCookieLineToReasons = new Map<string, Protocol.Network.SetCookieBlockedReason[]>(
129 blockedResponseCookies?.map(c => [c.cookieLine, c.blockedReasons]));
130 for (const header of this.#headers) {
Wolfgang Beyerfd504ec2022-09-06 09:48:11131 if (header.name === 'set-cookie' && header.value) {
132 const matchingBlockedReasons = blockedCookieLineToReasons.get(header.value);
Wolfgang Beyerd2286472022-09-02 18:32:30133 if (matchingBlockedReasons) {
134 header.setCookieBlockedReasons = matchingBlockedReasons;
135 }
136 }
137 }
138
139 if (data.toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Response) {
Wolfgang Beyerfd504ec2022-09-06 09:48:11140 this.#headers.filter(header => header.name === data.toReveal?.header?.toLowerCase()).forEach(header => {
141 header.highlight = true;
142 });
Wolfgang Beyerd2286472022-09-02 18:32:30143 }
144
145 this.#render();
146 }
147
148 #render(): void {
149 if (!this.#request) {
150 return;
151 }
152
153 // Disabled until https://crbug.com/1079231 is fixed.
154 // clang-format off
155 render(html`
156 ${this.#headers.map(header => html`
157 <${HeaderSectionRow.litTagName} .data=${{
158 header: header,
159 } as HeaderSectionRowData}></${HeaderSectionRow.litTagName}>
160 `)}
161 `, this.#shadow, {host: this});
162 // clang-format on
163 }
164}
165
166ComponentHelpers.CustomElements.defineComponent('devtools-response-header-section', ResponseHeaderSection);
167
168declare global {
169 interface HTMLElementTagNameMap {
170 'devtools-response-header-section': ResponseHeaderSection;
171 }
172}
173
174const BlockedReasonDetails = new Map<Protocol.Network.BlockedReason, HeaderDescriptor>([
175 [
176 Protocol.Network.BlockedReason.CoepFrameResourceNeedsCoepHeader,
177 {
178 name: 'cross-origin-embedder-policy',
179 value: null,
180 headerValueIncorrect: null,
181 blockedDetails: {
182 explanation: i18nLazyString(UIStrings.toEmbedThisFrameInYourDocument),
183 examples: [{codeSnippet: 'Cross-Origin-Embedder-Policy: require-corp', comment: undefined}],
184 link: {url: 'https://web.dev/coop-coep/'},
185 },
186 headerNotSet: null,
187 },
188 ],
189 [
190 Protocol.Network.BlockedReason.CorpNotSameOriginAfterDefaultedToSameOriginByCoep,
191 {
192 name: 'cross-origin-resource-policy',
193 value: null,
194 headerValueIncorrect: null,
195 blockedDetails: {
196 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferent),
197 examples: [
198 {
199 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
200 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
201 },
202 {
203 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
204 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
205 },
206 ],
207 link: {url: 'https://web.dev/coop-coep/'},
208 },
209 headerNotSet: null,
210 },
211 ],
212 [
213 Protocol.Network.BlockedReason.CoopSandboxedIframeCannotNavigateToCoopPage,
214 {
215 name: 'cross-origin-opener-policy',
216 value: null,
217 headerValueIncorrect: false,
218 blockedDetails: {
219 explanation: i18nLazyString(UIStrings.thisDocumentWasBlockedFrom),
220 examples: [],
221 link: {url: 'https://web.dev/coop-coep/'},
222 },
223 headerNotSet: null,
224 },
225 ],
226 [
227 Protocol.Network.BlockedReason.CorpNotSameSite,
228 {
229 name: 'cross-origin-resource-policy',
230 value: null,
231 headerValueIncorrect: true,
232 blockedDetails: {
233 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentSite),
234 examples: [
235 {
236 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
237 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
238 },
239 ],
240 link: null,
241 },
242 headerNotSet: null,
243 },
244 ],
245 [
246 Protocol.Network.BlockedReason.CorpNotSameOrigin,
247 {
248 name: 'cross-origin-resource-policy',
249 value: null,
250 headerValueIncorrect: true,
251 blockedDetails: {
252 explanation: i18nLazyString(UIStrings.toUseThisResourceFromADifferentOrigin),
253 examples: [
254 {
255 codeSnippet: 'Cross-Origin-Resource-Policy: same-site',
256 comment: i18nLazyString(UIStrings.chooseThisOptionIfTheResourceAnd),
257 },
258 {
259 codeSnippet: 'Cross-Origin-Resource-Policy: cross-origin',
260 comment: i18nLazyString(UIStrings.onlyChooseThisOptionIfAn),
261 },
262 ],
263 link: null,
264 },
265 headerNotSet: null,
266 },
267 ],
268]);