| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| /* eslint-disable @devtools/no-lit-render-outside-of-view */ |
| |
| import * as Common from '../../../core/common/common.js'; |
| import * as Host from '../../../core/host/host.js'; |
| import * as i18n from '../../../core/i18n/i18n.js'; |
| import * as Platform from '../../../core/platform/platform.js'; |
| import type {NameValue} from '../../../core/sdk/NetworkRequest.js'; |
| import type * as SDK from '../../../core/sdk/sdk.js'; |
| import * as Protocol from '../../../generated/protocol.js'; |
| import * as IssuesManager from '../../../models/issues_manager/issues_manager.js'; |
| import * as Persistence from '../../../models/persistence/persistence.js'; |
| import * as TextUtils from '../../../models/text_utils/text_utils.js'; |
| import type * as Workspace from '../../../models/workspace/workspace.js'; |
| import * as NetworkForward from '../../../panels/network/forward/forward.js'; |
| import * as Sources from '../../../panels/sources/sources.js'; |
| import * as Buttons from '../../../ui/components/buttons/buttons.js'; |
| import * as UI from '../../../ui/legacy/legacy.js'; |
| import {html, nothing, render} from '../../../ui/lit/lit.js'; |
| import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; |
| |
| import { |
| compareHeaders, |
| EditingAllowedStatus, |
| type HeaderDescriptor, |
| type HeaderDetailsDescriptor, |
| type HeaderEditedEvent, |
| type HeaderEditorDescriptor, |
| type HeaderRemovedEvent, |
| type HeaderSectionRowData, |
| isValidHeaderName, |
| } from './HeaderSectionRow.js'; |
| import responseHeaderSectionStyles from './ResponseHeaderSection.css.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Label for a button which allows adding an HTTP header. |
| */ |
| addHeader: 'Add header', |
| /** |
| * @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: |
| 'The document was blocked from loading in a popup opened by a sandboxed iframe 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:', |
| } as const; |
| |
| const str_ = i18n.i18n.registerUIStrings('panels/network/components/ResponseHeaderSection.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined, str_); |
| |
| export const RESPONSE_HEADER_SECTION_DATA_KEY = 'ResponseHeaderSection'; |
| |
| export interface ResponseHeaderSectionData { |
| request: SDK.NetworkRequest.NetworkRequest; |
| toReveal?: {section: NetworkForward.UIRequestLocation.UIHeaderSection, header?: string}; |
| } |
| |
| class ResponseHeaderSectionBase extends HTMLElement { |
| protected readonly shadow = this.attachShadow({mode: 'open'}); |
| protected headerDetails: HeaderDetailsDescriptor[] = []; |
| |
| protected setHeaders(headers: NameValue[]): void { |
| headers.sort(function(a, b) { |
| return Platform.StringUtilities.compare(a.name.toLowerCase(), b.name.toLowerCase()); |
| }); |
| |
| this.headerDetails = headers.map(header => ({ |
| name: Platform.StringUtilities.toLowerCaseString(header.name), |
| value: header.value.replace(/\s/g, ' '), |
| })); |
| } |
| |
| protected highlightHeaders(data: ResponseHeaderSectionData): void { |
| if (data.toReveal?.section === NetworkForward.UIRequestLocation.UIHeaderSection.RESPONSE) { |
| this.headerDetails.filter(header => compareHeaders(header.name, data.toReveal?.header?.toLowerCase())) |
| .forEach(header => { |
| header.highlight = true; |
| }); |
| } |
| } |
| } |
| |
| export class EarlyHintsHeaderSection extends ResponseHeaderSectionBase { |
| #request?: SDK.NetworkRequest.NetworkRequest; |
| |
| set data(data: ResponseHeaderSectionData) { |
| this.#request = data.request; |
| |
| this.setHeaders(this.#request.earlyHintsHeaders); |
| this.highlightHeaders(data); |
| |
| this.#render(); |
| } |
| |
| #render(): void { |
| if (!this.#request) { |
| return; |
| } |
| |
| // Disabled until https://crbug.com/1079231 is fixed. |
| // clang-format off |
| render(html` |
| <style>${responseHeaderSectionStyles}</style> |
| ${this.headerDetails.map(header => html` |
| <devtools-header-section-row .data=${{ |
| header, |
| } as HeaderSectionRowData}></devtools-header-section-row> |
| `)} |
| `, this.shadow, { host: this }); |
| // clang-format on |
| } |
| } |
| |
| customElements.define('devtools-early-hints-header-section', EarlyHintsHeaderSection); |
| |
| export class ResponseHeaderSection extends ResponseHeaderSectionBase { |
| #request?: SDK.NetworkRequest.NetworkRequest; |
| #headerEditors: HeaderEditorDescriptor[] = []; |
| #uiSourceCode: Workspace.UISourceCode.UISourceCode|null = null; |
| #overrides: Persistence.NetworkPersistenceManager.HeaderOverride[] = []; |
| #isEditingAllowed = EditingAllowedStatus.DISABLED; |
| |
| set data(data: ResponseHeaderSectionData) { |
| this.#request = data.request; |
| this.#isEditingAllowed = |
| Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(this.#request.url()) ? |
| EditingAllowedStatus.FORBIDDEN : |
| EditingAllowedStatus.DISABLED; |
| // If the request has been locally overridden, its 'sortedResponseHeaders' |
| // contains no 'set-cookie' headers, because they have been filtered out by |
| // the Chromium backend. DevTools therefore uses previously stored values. |
| const headers = this.#request.sortedResponseHeaders.concat(this.#request.setCookieHeaders); |
| this.setHeaders(headers); |
| |
| 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.CROSS_ORIGIN_EMBEDDER_POLICY)) { |
| const followLink = (): void => { |
| Host.userMetrics.issuesPanelOpenedFrom(Host.UserMetrics.IssueOpener.LEARN_MORE_LINK_COEP); |
| if (this.#request) { |
| void IssuesManager.RelatedIssue.reveal( |
| this.#request, IssuesManager.Issue.IssueCategory.CROSS_ORIGIN_EMBEDDER_POLICY); |
| } |
| }; |
| if (headerWithIssues.blockedDetails) { |
| headerWithIssues.blockedDetails.reveal = followLink; |
| } |
| } |
| headersWithIssues.push(headerWithIssues); |
| } |
| } |
| |
| function mergeHeadersWithIssues( |
| headers: HeaderDetailsDescriptor[], headersWithIssues: HeaderDetailsDescriptor[]): HeaderDetailsDescriptor[] { |
| let i = 0, j = 0; |
| const result: HeaderDetailsDescriptor[] = []; |
| 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.headerDetails = mergeHeadersWithIssues(this.headerDetails, headersWithIssues); |
| |
| const blockedResponseCookies = this.#request.blockedResponseCookies(); |
| const blockedCookieLineToReasons = new Map<string, Protocol.Network.SetCookieBlockedReason[]>( |
| blockedResponseCookies?.map(c => [c.cookieLine.replace(/\s/g, ' '), c.blockedReasons])); |
| for (const header of this.headerDetails) { |
| if (header.name === 'set-cookie' && header.value) { |
| const matchingBlockedReasons = blockedCookieLineToReasons.get(header.value); |
| if (matchingBlockedReasons) { |
| header.setCookieBlockedReasons = matchingBlockedReasons; |
| } |
| } |
| } |
| |
| this.highlightHeaders(data); |
| |
| const dataAssociatedWithRequest = this.#request.getAssociatedData(RESPONSE_HEADER_SECTION_DATA_KEY); |
| if (dataAssociatedWithRequest) { |
| this.#headerEditors = dataAssociatedWithRequest as HeaderEditorDescriptor[]; |
| } else { |
| this.#headerEditors = this.headerDetails.map(header => ({ |
| name: header.name, |
| value: header.value, |
| originalValue: header.value, |
| valueEditable: this.#isEditingAllowed, |
| })); |
| this.#markOverrides(); |
| } |
| |
| void this.#loadOverridesFileInfo(); |
| this.#request.setAssociatedData(RESPONSE_HEADER_SECTION_DATA_KEY, this.#headerEditors); |
| this.#render(); |
| } |
| |
| #resetEditorState(): void { |
| if (!this.#request) { |
| return; |
| } |
| this.#isEditingAllowed = |
| Persistence.NetworkPersistenceManager.NetworkPersistenceManager.isForbiddenNetworkUrl(this.#request.url()) ? |
| EditingAllowedStatus.FORBIDDEN : |
| EditingAllowedStatus.DISABLED; |
| this.#headerEditors = this.headerDetails.map(header => ({ |
| name: header.name, |
| value: header.value, |
| originalValue: header.value, |
| valueEditable: this.#isEditingAllowed, |
| })); |
| this.#markOverrides(); |
| this.#request.setAssociatedData(RESPONSE_HEADER_SECTION_DATA_KEY, this.#headerEditors); |
| } |
| |
| async #loadOverridesFileInfo(): Promise<void> { |
| if (!this.#request) { |
| return; |
| } |
| this.#uiSourceCode = |
| Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().getHeadersUISourceCodeFromUrl( |
| this.#request.url()); |
| if (!this.#uiSourceCode) { |
| this.#resetEditorState(); |
| this.#render(); |
| return; |
| } |
| try { |
| const contentData = |
| await this.#uiSourceCode.requestContentData().then(TextUtils.ContentData.ContentData.contentDataOrEmpty); |
| this.#overrides = JSON.parse(contentData.text || '[]') as Persistence.NetworkPersistenceManager.HeaderOverride[]; |
| if (!this.#overrides.every(Persistence.NetworkPersistenceManager.isHeaderOverride)) { |
| throw new Error('Type mismatch after parsing'); |
| } |
| if (Common.Settings.Settings.instance().moduleSetting('persistence-network-overrides-enabled').get() && |
| this.#isEditingAllowed === EditingAllowedStatus.DISABLED) { |
| this.#isEditingAllowed = EditingAllowedStatus.ENABLED; |
| } |
| for (const header of this.#headerEditors) { |
| header.valueEditable = this.#isEditingAllowed; |
| } |
| } catch { |
| console.error( |
| 'Failed to parse', this.#uiSourceCode?.url() || 'source code file', 'for locally overriding headers.'); |
| this.#resetEditorState(); |
| } finally { |
| this.#render(); |
| } |
| } |
| |
| #markOverrides(): void { |
| if (!this.#request || this.#request.originalResponseHeaders.length === 0) { |
| return; |
| } |
| const originalHeaders = |
| this.#request.originalResponseHeaders.map(header => ({ |
| name: Platform.StringUtilities.toLowerCaseString(header.name), |
| value: header.value.replace(/\s/g, ' '), |
| })); |
| originalHeaders.sort(function(a, b) { |
| return Platform.StringUtilities.compare(a.name, b.name); |
| }); |
| |
| // Loop over actual headers and original headers simultaneously and mark each actual header as |
| // overridden if there is no identical original header. |
| // If there are multiple headers with the same name, concatenate their values first before |
| // comparing them. |
| let indexActual = 0; |
| let indexOriginal = 0; |
| while (indexActual < this.headerDetails.length) { |
| const currentName = this.headerDetails[indexActual].name; |
| let actualValue = this.headerDetails[indexActual].value || ''; |
| const headerNotSet = this.headerDetails[indexActual].headerNotSet; |
| while (indexActual < this.headerDetails.length - 1 && this.headerDetails[indexActual + 1].name === currentName) { |
| indexActual++; |
| actualValue += `, ${this.headerDetails[indexActual].value}`; |
| } |
| |
| while (indexOriginal < originalHeaders.length && originalHeaders[indexOriginal].name < currentName) { |
| indexOriginal++; |
| } |
| if (indexOriginal < originalHeaders.length && originalHeaders[indexOriginal].name === currentName) { |
| let originalValue = originalHeaders[indexOriginal].value; |
| while (indexOriginal < originalHeaders.length - 1 && originalHeaders[indexOriginal + 1].name === currentName) { |
| indexOriginal++; |
| originalValue += `, ${originalHeaders[indexOriginal].value}`; |
| } |
| indexOriginal++; |
| if (currentName !== 'set-cookie' && !headerNotSet && !compareHeaders(actualValue, originalValue)) { |
| this.#headerEditors.filter(header => compareHeaders(header.name, currentName)).forEach(header => { |
| header.isOverride = true; |
| }); |
| } |
| } else if (currentName !== 'set-cookie' && !headerNotSet) { |
| this.#headerEditors.filter(header => compareHeaders(header.name, currentName)).forEach(header => { |
| header.isOverride = true; |
| }); |
| } |
| indexActual++; |
| } |
| |
| // Special case for 'set-cookie' headers: compare each header individually |
| // and don't treat all 'set-cookie' headers as a single unit. |
| this.#headerEditors.filter(header => header.name === 'set-cookie').forEach(header => { |
| if (this.#request?.originalResponseHeaders.find( |
| originalHeader => Platform.StringUtilities.toLowerCaseString(originalHeader.name) === 'set-cookie' && |
| compareHeaders(originalHeader.value, header.value)) === undefined) { |
| header.isOverride = true; |
| } |
| }); |
| } |
| |
| #onHeaderEdited(event: HeaderEditedEvent): void { |
| const target = event.target as HTMLElement; |
| if (target.dataset.index === undefined) { |
| return; |
| } |
| const index = Number(target.dataset.index); |
| if (isValidHeaderName(event.headerName)) { |
| this.#updateOverrides(event.headerName, event.headerValue, index); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideHeaderEdited); |
| } |
| } |
| |
| #fileNameFromUrl(url: Platform.DevToolsPath.UrlString): Platform.DevToolsPath.RawPathString { |
| const rawPath = |
| Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance().rawPathFromUrl(url, true); |
| const lastIndexOfSlash = rawPath.lastIndexOf('/'); |
| return Common.ParsedURL.ParsedURL.substring(rawPath, lastIndexOfSlash + 1); |
| } |
| |
| #commitOverrides(): void { |
| this.#uiSourceCode?.setWorkingCopy(JSON.stringify(this.#overrides, null, 2)); |
| this.#uiSourceCode?.commitWorkingCopy(); |
| } |
| |
| #removeEntryFromOverrides( |
| rawFileName: Platform.DevToolsPath.RawPathString, headerName: Platform.StringUtilities.LowerCaseString, |
| headerValue: string): void { |
| for (let blockIndex = this.#overrides.length - 1; blockIndex >= 0; blockIndex--) { |
| const block = this.#overrides[blockIndex]; |
| if (block.applyTo !== rawFileName) { |
| continue; |
| } |
| const foundIndex = block.headers.findIndex( |
| header => compareHeaders(header.name, headerName) && compareHeaders(header.value, headerValue)); |
| if (foundIndex < 0) { |
| continue; |
| } |
| block.headers.splice(foundIndex, 1); |
| if (block.headers.length === 0) { |
| this.#overrides.splice(blockIndex, 1); |
| } |
| return; |
| } |
| } |
| |
| #onHeaderRemoved(event: HeaderRemovedEvent): void { |
| const target = event.target as HTMLElement; |
| if (target.dataset.index === undefined || !this.#request) { |
| return; |
| } |
| const index = Number(target.dataset.index); |
| const rawFileName = this.#fileNameFromUrl(this.#request.url()); |
| this.#removeEntryFromOverrides(rawFileName, event.headerName, event.headerValue); |
| this.#commitOverrides(); |
| this.#headerEditors[index].isDeleted = true; |
| this.#render(); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideHeaderRemoved); |
| } |
| |
| #updateOverrides(headerName: Platform.StringUtilities.LowerCaseString, headerValue: string, index: number): void { |
| 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; |
| this.#headerEditors[index].name = headerName; |
| this.#headerEditors[index].value = headerValue; |
| |
| let headersToUpdate: HeaderEditorDescriptor[] = []; |
| if (headerName === 'set-cookie') { |
| // Special case for 'set-cookie' headers: each such header is treated |
| // separately without looking at other 'set-cookie' headers. |
| headersToUpdate.push({name: headerName, value: headerValue, valueEditable: this.#isEditingAllowed}); |
| } else { |
| // If multiple headers have the same name 'foo', we treat them as a unit. |
| // If there are overrides for 'foo', all original 'foo' headers are removed |
| // and replaced with the override(s) for 'foo'. |
| headersToUpdate = this.#headerEditors.filter( |
| header => compareHeaders(header.name, headerName) && |
| (!compareHeaders(header.value, header.originalValue) || header.isOverride)); |
| } |
| |
| const rawFileName = this.#fileNameFromUrl(this.#request.url()); |
| |
| // If the last override-block matches 'rawFileName', use this last block. |
| // Otherwise just append a new block at the end. We are not using earlier |
| // blocks, because they could be overruled by later blocks, which contain |
| // wildcards in the filenames they apply to. |
| let block: Persistence.NetworkPersistenceManager.HeaderOverride|null = null; |
| const [lastOverride] = this.#overrides.slice(-1); |
| if (lastOverride?.applyTo === rawFileName) { |
| block = lastOverride; |
| } else { |
| block = { |
| applyTo: rawFileName, |
| headers: [], |
| }; |
| this.#overrides.push(block); |
| } |
| |
| if (headerName === 'set-cookie') { |
| // Special case for 'set-cookie' headers: only remove the one specific |
| // header which is currently being modified, keep all other headers |
| // (including other 'set-cookie' headers). |
| const foundIndex = block.headers.findIndex( |
| header => compareHeaders(header.name, previousName) && compareHeaders(header.value, previousValue)); |
| if (foundIndex >= 0) { |
| block.headers.splice(foundIndex, 1); |
| } |
| } else { |
| // Keep header overrides for all headers with a different name. |
| block.headers = block.headers.filter(header => !compareHeaders(header.name, headerName)); |
| } |
| |
| // If a header name has been edited (only possible when adding headers), |
| // remove the previous override entry. |
| if (!compareHeaders(this.#headerEditors[index].name, previousName)) { |
| for (let i = 0; i < block.headers.length; ++i) { |
| if (compareHeaders(block.headers[i].name, previousName) && |
| compareHeaders(block.headers[i].value, previousValue)) { |
| block.headers.splice(i, 1); |
| break; |
| } |
| } |
| } |
| |
| // Append freshly edited header overrides. |
| for (const header of headersToUpdate) { |
| block.headers.push({name: header.name, value: header.value || ''}); |
| } |
| |
| if (block.headers.length === 0) { |
| this.#overrides.pop(); |
| } |
| this.#commitOverrides(); |
| } |
| |
| #onAddHeaderClick(): void { |
| this.#headerEditors.push({ |
| name: Platform.StringUtilities.toLowerCaseString(i18n.i18n.lockedString('header-name')), |
| value: i18n.i18n.lockedString('header value'), |
| isOverride: true, |
| nameEditable: true, |
| valueEditable: EditingAllowedStatus.ENABLED, |
| }); |
| const index = this.#headerEditors.length - 1; |
| this.#updateOverrides(this.#headerEditors[index].name, this.#headerEditors[index].value || '', index); |
| this.#render(); |
| |
| const rows = this.shadow.querySelectorAll('devtools-header-section-row'); |
| const [lastRow] = Array.from(rows).slice(-1); |
| lastRow?.focus(); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideHeaderAdded); |
| } |
| |
| #render(): void { |
| if (!this.#request) { |
| return; |
| } |
| |
| const headerDescriptors: HeaderDescriptor[] = this.#headerEditors.map( |
| (headerEditor, index) => ({...this.headerDetails[index], ...headerEditor, isResponseHeader: true})); |
| |
| // Disabled until https://crbug.com/1079231 is fixed. |
| // clang-format off |
| render(html` |
| <style>${responseHeaderSectionStyles}</style> |
| ${headerDescriptors.map((header, index) => html` |
| <devtools-header-section-row |
| .data=${{header} as HeaderSectionRowData} |
| @headeredited=${this.#onHeaderEdited} |
| @headerremoved=${this.#onHeaderRemoved} |
| @enableheaderediting=${this.#onEnableHeaderEditingClick} |
| data-index=${index} |
| jslog=${VisualLogging.item('response-header')} |
| ></devtools-header-section-row> |
| `)} |
| ${this.#isEditingAllowed === EditingAllowedStatus.ENABLED ? html` |
| <devtools-button |
| class="add-header-button" |
| .variant=${Buttons.Button.Variant.OUTLINED} |
| .iconName=${'plus'} |
| @click=${this.#onAddHeaderClick} |
| jslog=${VisualLogging.action('add-header').track({click: true})}> |
| ${i18nString(UIStrings.addHeader)} |
| </devtools-button> |
| ` : nothing} |
| `, this.shadow, {host: this}); |
| // clang-format on |
| } |
| |
| async #onEnableHeaderEditingClick(): Promise<void> { |
| if (!this.#request) { |
| return; |
| } |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideEnableEditingClicked); |
| const requestUrl = this.#request.url(); |
| const networkPersistenceManager = Persistence.NetworkPersistenceManager.NetworkPersistenceManager.instance(); |
| if (networkPersistenceManager.project()) { |
| Common.Settings.Settings.instance().moduleSetting('persistence-network-overrides-enabled').set(true); |
| await networkPersistenceManager.getOrCreateHeadersUISourceCodeFromUrl(requestUrl); |
| } else { // If folder for local overrides has not been provided yet |
| UI.InspectorView.InspectorView.instance().displaySelectOverrideFolderInfobar(async () => { |
| await Sources.SourcesNavigator.OverridesNavigatorView.instance().setupNewWorkspace(); |
| await networkPersistenceManager.getOrCreateHeadersUISourceCodeFromUrl(requestUrl); |
| }); |
| } |
| } |
| } |
| |
| customElements.define('devtools-response-header-section', ResponseHeaderSection); |
| |
| declare global { |
| interface HTMLElementTagNameMap { |
| 'devtools-response-header-section': ResponseHeaderSection; |
| 'devtools-early-hints-header-section': EarlyHintsHeaderSection; |
| } |
| } |
| |
| const BlockedReasonDetails = new Map<Protocol.Network.BlockedReason, HeaderDetailsDescriptor>([ |
| [ |
| Protocol.Network.BlockedReason.CoepFrameResourceNeedsCoepHeader, |
| { |
| name: Platform.StringUtilities.toLowerCaseString('cross-origin-embedder-policy'), |
| value: null, |
| blockedDetails: { |
| explanation: i18nLazyString(UIStrings.toEmbedThisFrameInYourDocument), |
| examples: [{codeSnippet: 'Cross-Origin-Embedder-Policy: require-corp', comment: undefined}], |
| link: {url: 'https://web.dev/coop-coep/'}, |
| }, |
| }, |
| ], |
| [ |
| Protocol.Network.BlockedReason.CorpNotSameOriginAfterDefaultedToSameOriginByCoep, |
| { |
| name: Platform.StringUtilities.toLowerCaseString('cross-origin-resource-policy'), |
| value: 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/'}, |
| }, |
| }, |
| ], |
| [ |
| Protocol.Network.BlockedReason.CoopSandboxedIframeCannotNavigateToCoopPage, |
| { |
| name: Platform.StringUtilities.toLowerCaseString('cross-origin-opener-policy'), |
| value: null, |
| headerValueIncorrect: false, |
| blockedDetails: { |
| explanation: i18nLazyString(UIStrings.thisDocumentWasBlockedFrom), |
| examples: [], |
| link: {url: 'https://web.dev/coop-coep/'}, |
| }, |
| }, |
| ], |
| [ |
| Protocol.Network.BlockedReason.CorpNotSameSite, |
| { |
| name: Platform.StringUtilities.toLowerCaseString('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, |
| }, |
| }, |
| ], |
| [ |
| Protocol.Network.BlockedReason.CorpNotSameOrigin, |
| { |
| name: Platform.StringUtilities.toLowerCaseString('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, |
| }, |
| }, |
| ], |
| ]); |