Wolfgang Beyer | d228647 | 2022-09-02 18:32:30 | [diff] [blame] | 1 | // 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 | |
| 5 | import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js'; |
| 6 | import * as LitHtml from '../../../ui/lit-html/lit-html.js'; |
| 7 | |
| 8 | import type * as SDK from '../../../core/sdk/sdk.js'; |
| 9 | import * as Protocol from '../../../generated/protocol.js'; |
| 10 | import * as i18n from '../../../core/i18n/i18n.js'; |
| 11 | import * as NetworkForward from '../../../panels/network/forward/forward.js'; |
| 12 | import * as Host from '../../../core/host/host.js'; |
| 13 | import * as IssuesManager from '../../../models/issues_manager/issues_manager.js'; |
| 14 | import {type HeaderDescriptor, HeaderSectionRow, type HeaderSectionRowData} from './HeaderSectionRow.js'; |
| 15 | |
| 16 | import responseHeaderSectionStyles from './ResponseHeaderSection.css.js'; |
| 17 | |
| 18 | const {render, html} = LitHtml; |
| 19 | |
| 20 | const 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 | |
| 58 | const str_ = i18n.i18n.registerUIStrings('panels/network/components/ResponseHeaderSection.ts', UIStrings); |
| 59 | const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); |
| 60 | |
| 61 | export interface ResponseHeaderSectionData { |
| 62 | request: SDK.NetworkRequest.NetworkRequest; |
| 63 | toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string}; |
| 64 | } |
| 65 | |
| 66 | export 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 Beyer | fd504ec | 2022-09-06 09:48:11 | [diff] [blame^] | 102 | headers: HeaderDescriptor[], headersWithIssues: HeaderDescriptor[]): HeaderDescriptor[] { |
Wolfgang Beyer | d228647 | 2022-09-02 18:32:30 | [diff] [blame] | 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 | |
Wolfgang Beyer | fd504ec | 2022-09-06 09:48:11 | [diff] [blame^] | 123 | 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 Beyer | d228647 | 2022-09-02 18:32:30 | [diff] [blame] | 126 | |
| 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 Beyer | fd504ec | 2022-09-06 09:48:11 | [diff] [blame^] | 131 | if (header.name === 'set-cookie' && header.value) { |
| 132 | const matchingBlockedReasons = blockedCookieLineToReasons.get(header.value); |
Wolfgang Beyer | d228647 | 2022-09-02 18:32:30 | [diff] [blame] | 133 | if (matchingBlockedReasons) { |
| 134 | header.setCookieBlockedReasons = matchingBlockedReasons; |
| 135 | } |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if (data.toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.Response) { |
Wolfgang Beyer | fd504ec | 2022-09-06 09:48:11 | [diff] [blame^] | 140 | this.#headers.filter(header => header.name === data.toReveal?.header?.toLowerCase()).forEach(header => { |
| 141 | header.highlight = true; |
| 142 | }); |
Wolfgang Beyer | d228647 | 2022-09-02 18:32:30 | [diff] [blame] | 143 | } |
| 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 | |
| 166 | ComponentHelpers.CustomElements.defineComponent('devtools-response-header-section', ResponseHeaderSection); |
| 167 | |
| 168 | declare global { |
| 169 | interface HTMLElementTagNameMap { |
| 170 | 'devtools-response-header-section': ResponseHeaderSection; |
| 171 | } |
| 172 | } |
| 173 | |
| 174 | const 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 | ]); |