blob: e2dd874e8e2474ed75255572a3158976eae4ec86 [file] [log] [blame]
// 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,
},
],
]);