chrome-devtools-frontend
Version:
Chrome DevTools UI
963 lines (907 loc) • 40.6 kB
text/typescript
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../ui/components/expandable_list/expandable_list.js';
import '../../ui/components/report_view/report_view.js';
import * as Common from '../../core/common/common.js';
import * as i18n from '../../core/i18n/i18n.js';
import type * as Platform from '../../core/platform/platform.js';
import * as Root from '../../core/root/root.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import * as Bindings from '../../models/bindings/bindings.js';
import type * as StackTrace from '../../models/stack_trace/stack_trace.js';
import * as Workspace from '../../models/workspace/workspace.js';
import * as PanelCommon from '../../panels/common/common.js';
import * as NetworkForward from '../../panels/network/forward/forward.js';
import * as CspEvaluator from '../../third_party/csp_evaluator/csp_evaluator.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import type * as ExpandableList from '../../ui/components/expandable_list/expandable_list.js';
import type * as ReportView from '../../ui/components/report_view/report_view.js';
import * as Components from '../../ui/legacy/components/utils/utils.js';
import * as UI from '../../ui/legacy/legacy.js';
import {html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import * as ApplicationComponents from './components/components.js';
import frameDetailsReportViewStyles from './frameDetailsReportView.css.js';
import {OriginTrialTreeView} from './OriginTrialTreeView.js';
const {widgetConfig} = UI.Widget;
const UIStrings = {
/**
* @description Section header in the Frame Details view
*/
additionalInformation: 'Additional Information',
/**
* @description Explanation for why the additional information section is being shown
*/
thisAdditionalDebugging:
'This additional (debugging) information is shown because the \'Protocol Monitor\' experiment is enabled.',
/**
* @description Label for subtitle of frame details view
*/
frameId: 'Frame ID',
/**
* @description Name of a network resource type
*/
document: 'Document',
/**
* @description A web URL (for a lot of languages this does not need to be translated, please translate only where necessary)
*/
url: 'URL',
/**
* /**
* @description Title for a link to the Sources panel
*/
clickToOpenInSourcesPanel: 'Click to open in Sources panel',
/**
* @description Title for a link to the Network panel
*/
clickToOpenInNetworkPanel: 'Click to open in Network panel',
/**
* @description Title for unreachable URL field
*/
unreachableUrl: 'Unreachable URL',
/**
* @description Title for a link that applies a filter to the network panel
*/
clickToOpenInNetworkPanelMight: 'Click to open in Network panel (might require page reload)',
/**
* @description The origin of a URL (https://web.dev/same-site-same-origin/#origin)
*(for a lot of languages this does not need to be translated, please translate only where necessary)
*/
origin: 'Origin',
/**
* /**
* @description Related node label in Timeline UIUtils of the Performance panel
*/
ownerElement: 'Owner Element',
/**
* @description Title for ad frame type field
*/
adStatus: 'Ad Status',
/**
* @description Description for ad frame type
*/
rootDescription: 'This frame has been identified as the root frame of an ad',
/**
* @description Value for ad frame type
*/
root: 'root',
/**
* @description Description for ad frame type
*/
childDescription: 'This frame has been identified as a child frame of an ad',
/**
* @description Value for ad frame type
*/
child: 'child',
/**
* @description Section header in the Frame Details view
*/
securityIsolation: 'Security & Isolation',
/**
* @description Section header in the Frame Details view
*/
contentSecurityPolicy: 'Content Security Policy (CSP)',
/**
* @description Row title for in the Frame Details view
*/
secureContext: 'Secure Context',
/**
* @description Text in Timeline indicating that input has happened recently
*/
yes: 'Yes',
/**
* @description Text in Timeline indicating that input has not happened recently
*/
no: 'No',
/**
* @description Label for whether a frame is cross-origin isolated
*(https://developer.chrome.com/docs/extensions/mv3/cross-origin-isolation/)
*(for a lot of languages this does not need to be translated, please translate only where necessary)
*/
crossoriginIsolated: 'Cross-Origin Isolated',
/**
* @description Explanatory text in the Frame Details view
*/
localhostIsAlwaysASecureContext: '`Localhost` is always a secure context',
/**
* @description Explanatory text in the Frame Details view
*/
aFrameAncestorIsAnInsecure: 'A frame ancestor is an insecure context',
/**
* @description Explanatory text in the Frame Details view
*/
theFramesSchemeIsInsecure: 'The frame\'s scheme is insecure',
/**
* @description This label specifies the server endpoints to which the server is reporting errors
*and warnings through the Report-to API. Following this label will be the URL of the server.
*/
reportingTo: 'reporting to',
/**
* @description Section header in the Frame Details view
*/
apiAvailability: 'API availability',
/**
* @description Explanation of why cross-origin isolation is important
*(https://web.dev/why-coop-coep/)
*(for a lot of languages 'cross-origin isolation' does not need to be translated, please translate only where necessary)
*/
availabilityOfCertainApisDepends: 'Availability of certain APIs depends on the document being cross-origin isolated.',
/**
* @description Description of the SharedArrayBuffer status
*/
availableTransferable: 'available, transferable',
/**
* @description Description of the SharedArrayBuffer status
*/
availableNotTransferable: 'available, not transferable',
/**
* @description Explanation for the SharedArrayBuffer availability status
*/
unavailable: 'unavailable',
/**
* @description Tooltip for the SharedArrayBuffer availability status
*/
sharedarraybufferConstructorIs:
'`SharedArrayBuffer` constructor is available and `SABs` can be transferred via `postMessage`',
/**
* @description Tooltip for the SharedArrayBuffer availability status
*/
sharedarraybufferConstructorIsAvailable:
'`SharedArrayBuffer` constructor is available but `SABs` cannot be transferred via `postMessage`',
/**
* @description Explanation why SharedArrayBuffer will not be available in the future
*(https://developer.chrome.com/docs/extensions/mv3/cross-origin-isolation/)
*(for a lot of languages 'cross-origin isolation' does not need to be translated, please translate only where necessary)
*/
willRequireCrossoriginIsolated: '⚠️ will require cross-origin isolated context in the future',
/**
* @description Explanation why SharedArrayBuffer is not available
*(https://developer.chrome.com/docs/extensions/mv3/cross-origin-isolation/)
*(for a lot of languages 'cross-origin isolation' does not need to be translated, please translate only where necessary).
*/
requiresCrossoriginIsolated: 'requires cross-origin isolated context',
/**
* @description Explanation for the SharedArrayBuffer availability status in case the transfer of a SAB requires the
* permission policy `cross-origin-isolated` to be enabled (e.g. because the message refers to the situation in an iframe).
*/
transferRequiresCrossoriginIsolatedPermission:
'`SharedArrayBuffer` transfer requires enabling the permission policy:',
/**
* @description Explanation for the Measure Memory availability status
*/
available: 'available',
/**
* @description Tooltip for the Measure Memory availability status
*/
thePerformanceAPI: 'The `performance.measureUserAgentSpecificMemory()` API is available',
/**
* @description Tooltip for the Measure Memory availability status
*/
thePerformancemeasureuseragentspecificmemory:
'The `performance.measureUserAgentSpecificMemory()` API is not available',
/**
* @description Entry in the API availability section of the frame details view
*/
measureMemory: 'Measure Memory',
/**
* @description Text that is usually a hyperlink to more documentation
*/
learnMore: 'Learn more',
/**
* @description Label for a stack trace. If a frame is created programmatically (i.e. via JavaScript), there is a
* stack trace for the line of code which caused the creation of the iframe. This is the stack trace we are showing here.
*/
creationStackTrace: 'Frame Creation `Stack Trace`',
/**
* @description Tooltip for 'Frame Creation Stack Trace' explaining that the stack
*trace shows where in the code the frame has been created programmatically
*/
creationStackTraceExplanation:
'This frame was created programmatically. The `stack trace` shows where this happened.',
/**
* @description Text descripting why a frame has been indentified as an advertisement.
*/
parentIsAdExplanation: 'This frame is considered an ad frame because its parent frame is an ad frame.',
/**
* @description Text descripting why a frame has been indentified as an advertisement.
*/
matchedBlockingRuleExplanation:
'This frame is considered an ad frame because its current (or previous) main document is an ad resource.',
/**
* @description Text descripting why a frame has been indentified as an advertisement.
*/
createdByAdScriptExplanation:
'There was an ad script in the `(async) stack` when this frame was created. Examining the creation `stack trace` of this frame might provide more insight.',
/**
* @description Label for the link(s) to the ad script(s) that led to this frame's creation.
*/
creatorAdScriptAncestry: 'Creator Ad Script Ancestry',
/**
* @description Label for the filterlist rule that identified the root script in 'Creator Ad Script Ancestry' as an ad.
*/
rootScriptFilterlistRule: 'Root Script Filterlist Rule',
/**
* @description Text describing the absence of a value.
*/
none: 'None',
/**
* @description Explanation of what origin trials are
*(https://developer.chrome.com/docs/web-platform/origin-trials/)
*(please don't translate 'origin trials').
*/
originTrialsExplanation: 'Origin trials give you access to a new or experimental feature.',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/FrameDetailsView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export interface FrameDetailsReportViewData {
frame: SDK.ResourceTreeModel.ResourceTreeFrame;
target?: SDK.Target.Target;
adScriptAncestry: Protocol.Page.AdScriptAncestry|null;
}
interface FrameDetailsViewInput {
frame: SDK.ResourceTreeModel.ResourceTreeFrame;
target: SDK.Target.Target|null;
creationStackTrace: StackTrace.StackTrace.StackTrace|null;
creationTarget: SDK.Target.Target|null;
adScriptAncestry: Protocol.Page.AdScriptAncestry|null;
linkTargetDOMNode: SDK.DOMModel.DOMNode|null;
permissionsPolicies: Protocol.Page.PermissionsPolicyFeatureState[]|null;
protocolMonitorExperimentEnabled: boolean;
trials: Protocol.Page.OriginTrial[]|null;
securityIsolationInfo: Protocol.Network.SecurityIsolationStatus|null;
onRevealInNetwork?: () => void;
onRevealInSources: () => void;
}
type View = (input: FrameDetailsViewInput, output: undefined, target: HTMLElement) => void;
const DEFAULT_VIEW: View = (input, _output, target) => {
if (!input.frame) {
return;
}
// clang-format off
render(html`
<style>${frameDetailsReportViewStyles}</style>
<devtools-report .data=${{reportTitle: input.frame.displayName()} as ReportView.ReportView.ReportData}
jslog=${VisualLogging.pane('frames')}>
${renderDocumentSection(input)}
${renderIsolationSection(input)}
${renderApiAvailabilitySection(input.frame)}
${renderOriginTrial(input.trials)}
${input.permissionsPolicies ? html`
<devtools-widget .widgetConfig=${widgetConfig(ApplicationComponents.PermissionsPolicySection.PermissionsPolicySection, {
policies: input.permissionsPolicies,
showDetails: false})}>
</devtools-widget>` : nothing}
${input.protocolMonitorExperimentEnabled ? renderAdditionalInfoSection(input.frame) : nothing}
</devtools-report>
`, target);
// clang-format on
};
function renderOriginTrial(trials: Protocol.Page.OriginTrial[]|null): LitTemplate {
if (!trials) {
return nothing;
}
const data = {trials};
// clang-format off
return html`
<devtools-report-section-header>
${i18n.i18n.lockedString('Origin trials')}
</devtools-report-section-header>
<devtools-report-section>
<span class="report-section">
${i18nString(UIStrings.originTrialsExplanation)}
<x-link href="https://developer.chrome.com/docs/web-platform/origin-trials/" class="link"
jslog=${VisualLogging.link('learn-more.origin-trials').track({click: true})}>
${i18nString(UIStrings.learnMore)}
</x-link>
</span>
</devtools-report-section>
<devtools-widget class="span-cols" .widgetConfig=${widgetConfig(OriginTrialTreeView, {data})}>
</devtools-widget>
<devtools-report-divider></devtools-report-divider>`;
// clang-format on
}
function renderDocumentSection(input: FrameDetailsViewInput): LitTemplate {
if (!input.frame) {
return nothing;
}
// clang-format off
return html`
<devtools-report-section-header>${i18nString(UIStrings.document)}</devtools-report-section-header>
<devtools-report-key>${i18nString(UIStrings.url)}</devtools-report-key>
<devtools-report-value>
<div class="inline-items">
${!input.frame?.unreachableUrl() ? renderSourcesLinkForURL(input.onRevealInSources) : nothing}
${input.onRevealInNetwork ? renderNetworkLinkForURL(input.onRevealInNetwork) : nothing}
<div class="text-ellipsis" title=${input.frame.url}>${input.frame.url}</div>
</div>
</devtools-report-value>
${maybeRenderUnreachableURL(input.frame?.unreachableUrl())}
${maybeRenderOrigin(input.frame?.securityOrigin)}
${renderOwnerElement(input.linkTargetDOMNode)}
${maybeRenderCreationStacktrace(input.creationStackTrace, input.creationTarget)}
${maybeRenderAdStatus(input.frame?.adFrameType(), input.frame?.adFrameStatus())}
${maybeRenderCreatorAdScriptAncestry(input.frame?.adFrameType(), input.target, input.adScriptAncestry)}
<devtools-report-divider></devtools-report-divider>`;
// clang-format on
}
function renderSourcesLinkForURL(onRevealInSources: () => void): LitTemplate {
return ApplicationComponents.PermissionsPolicySection.renderIconLink(
'label',
i18nString(UIStrings.clickToOpenInSourcesPanel),
onRevealInSources,
'reveal-in-sources',
);
}
function renderNetworkLinkForURL(onRevealInNetwork: () => void): LitTemplate {
return ApplicationComponents.PermissionsPolicySection.renderIconLink(
'arrow-up-down-circle', i18nString(UIStrings.clickToOpenInNetworkPanel), onRevealInNetwork, 'reveal-in-network');
}
function maybeRenderUnreachableURL(unreachableUrl: Platform.DevToolsPath.UrlString): LitTemplate {
if (!unreachableUrl) {
return nothing;
}
return html`
<devtools-report-key>${i18nString(UIStrings.unreachableUrl)}</devtools-report-key>
<devtools-report-value>
<div class="inline-items">
${renderNetworkLinkForUnreachableURL(unreachableUrl)}
<div class="text-ellipsis" title=${unreachableUrl}>${unreachableUrl}</div>
</div>
</devtools-report-value>
`;
}
function renderNetworkLinkForUnreachableURL(unreachableUrlString: Platform.DevToolsPath.UrlString): LitTemplate {
const unreachableUrl = Common.ParsedURL.ParsedURL.fromString(unreachableUrlString);
if (unreachableUrl) {
return ApplicationComponents.PermissionsPolicySection.renderIconLink(
'arrow-up-down-circle',
i18nString(UIStrings.clickToOpenInNetworkPanelMight),
():
void => {
void Common.Revealer.reveal(NetworkForward.UIFilter.UIRequestFilter.filters([
{
filterType: NetworkForward.UIFilter.FilterType.Domain,
filterValue: unreachableUrl.domain(),
},
{
filterType: null,
filterValue: unreachableUrl.path,
},
]));
},
'unreachable-url.reveal-in-network',
);
}
return nothing;
}
function maybeRenderOrigin(securityOrigin: string|null): LitTemplate {
if (securityOrigin && securityOrigin !== '://') {
return html`
<devtools-report-key>${i18nString(UIStrings.origin)}</devtools-report-key>
<devtools-report-value>
<div class="text-ellipsis" title=${securityOrigin}>${securityOrigin}</div>
</devtools-report-value>
`;
}
return nothing;
}
function renderOwnerElement(linkTargetDOMNode: SDK.DOMModel.DOMNode|null): LitTemplate {
if (linkTargetDOMNode) {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-key>${i18nString(UIStrings.ownerElement)}</devtools-report-key>
<devtools-report-value class="without-min-width">
<div class="inline-items">
<devtools-widget .widgetConfig=${widgetConfig(PanelCommon.DOMLinkifier.DOMNodeLink, {
node: linkTargetDOMNode
})}>
</devtools-widget>
</div>
</devtools-report-value>
`;
// clang-format on
}
return nothing;
}
function maybeRenderCreationStacktrace(
stackTrace: StackTrace.StackTrace.StackTrace|null, target: SDK.Target.Target|null): LitTemplate {
if (stackTrace && target) {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-key title=${i18nString(UIStrings.creationStackTraceExplanation)}>${
i18nString(UIStrings.creationStackTrace)}</devtools-report-key>
<devtools-report-value jslog=${VisualLogging.section('frame-creation-stack-trace')}>
<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(
Components.JSPresentationUtils.StackTracePreviewContent, {target, stackTrace, options: {expandable: true}})}>
</devtools-widget>
</devtools-report-value>
`;
// clang-format on
}
return nothing;
}
function getAdFrameTypeStrings(type: Protocol.Page.AdFrameType.Child|Protocol.Page.AdFrameType.Root):
{value: Platform.UIString.LocalizedString, description: Platform.UIString.LocalizedString} {
switch (type) {
case Protocol.Page.AdFrameType.Child:
return {value: i18nString(UIStrings.child), description: i18nString(UIStrings.childDescription)};
case Protocol.Page.AdFrameType.Root:
return {value: i18nString(UIStrings.root), description: i18nString(UIStrings.rootDescription)};
}
}
function getAdFrameExplanationString(explanation: Protocol.Page.AdFrameExplanation): Platform.UIString.LocalizedString {
switch (explanation) {
case Protocol.Page.AdFrameExplanation.CreatedByAdScript:
return i18nString(UIStrings.createdByAdScriptExplanation);
case Protocol.Page.AdFrameExplanation.MatchedBlockingRule:
return i18nString(UIStrings.matchedBlockingRuleExplanation);
case Protocol.Page.AdFrameExplanation.ParentIsAd:
return i18nString(UIStrings.parentIsAdExplanation);
}
}
function maybeRenderAdStatus(
adFrameType: Protocol.Page.AdFrameType|undefined,
adFrameStatus: Protocol.Page.AdFrameStatus|undefined): LitTemplate {
if (adFrameType === undefined || adFrameType === Protocol.Page.AdFrameType.None) {
return nothing;
}
const typeStrings = getAdFrameTypeStrings(adFrameType);
const rows = [html`<div title=${typeStrings.description}>${typeStrings.value}</div>`];
for (const explanation of adFrameStatus?.explanations || []) {
rows.push(html`<div>${getAdFrameExplanationString(explanation)}</div>`);
}
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-key>${i18nString(UIStrings.adStatus)}</devtools-report-key>
<devtools-report-value class="ad-status-list" jslog=${VisualLogging.section('ad-status')}>
<devtools-expandable-list .data=${
{rows, title: i18nString(UIStrings.adStatus)} as ExpandableList.ExpandableList.ExpandableListData}>
</devtools-expandable-list>
</devtools-report-value>`;
// clang-format on
}
function maybeRenderCreatorAdScriptAncestry(
adFrameType: Protocol.Page.AdFrameType|null, target: SDK.Target.Target|null,
adScriptAncestry: Protocol.Page.AdScriptAncestry|null): LitTemplate {
if (adFrameType === Protocol.Page.AdFrameType.None) {
return nothing;
}
if (!target || !adScriptAncestry || adScriptAncestry.ancestryChain.length === 0) {
return nothing;
}
const rows = adScriptAncestry.ancestryChain.map(adScriptId => {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`<div>
<devtools-widget .widgetConfig=${widgetConfig(Components.Linkifier.ScriptLocationLink, {
target, scriptId: adScriptId.scriptId, options: {jslogContext: 'ad-script'}})}>
</devtools-widget>
</div>`;
// clang-format on
});
const shouldRenderFilterlistRule = (adScriptAncestry.rootScriptFilterlistRule !== undefined);
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-key>${i18nString(UIStrings.creatorAdScriptAncestry)}</devtools-report-key>
<devtools-report-value class="creator-ad-script-ancestry-list" jslog=${VisualLogging.section('creator-ad-script-ancestry')}>
<devtools-expandable-list .data=${
{rows, title: i18nString(UIStrings.creatorAdScriptAncestry)} as ExpandableList.ExpandableList.ExpandableListData}>
</devtools-expandable-list>
</devtools-report-value>
${shouldRenderFilterlistRule ? html`
<devtools-report-key>${i18nString(UIStrings.rootScriptFilterlistRule)}</devtools-report-key>
<devtools-report-value jslog=${VisualLogging.section('root-script-filterlist-rule')}>${adScriptAncestry.rootScriptFilterlistRule}</devtools-report-value>
` : nothing}
`;
// clang-format on
}
function renderIsolationSection(input: FrameDetailsViewInput): LitTemplate {
if (!input.frame) {
return nothing;
}
return html`
<devtools-report-section-header>${i18nString(UIStrings.securityIsolation)}</devtools-report-section-header>
<devtools-report-key>${i18nString(UIStrings.secureContext)}</devtools-report-key>
<devtools-report-value>
${input.frame.isSecureContext() ? i18nString(UIStrings.yes) : i18nString(UIStrings.no)}\xA0${
maybeRenderSecureContextExplanation(input.frame)}
</devtools-report-value>
<devtools-report-key>${i18nString(UIStrings.crossoriginIsolated)}</devtools-report-key>
<devtools-report-value>
${input.frame.isCrossOriginIsolated() ? i18nString(UIStrings.yes) : i18nString(UIStrings.no)}
</devtools-report-value>
${maybeRenderCoopCoepCSPStatus(input.securityIsolationInfo)}
<devtools-report-divider></devtools-report-divider>
`;
}
function maybeRenderSecureContextExplanation(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null): LitTemplate {
const explanation = getSecureContextExplanation(frame);
if (explanation) {
return html`<span class="inline-comment">${explanation}</span>`;
}
return nothing;
}
function getSecureContextExplanation(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null):
Platform.UIString.LocalizedString|null {
switch (frame?.getSecureContextType()) {
case Protocol.Page.SecureContextType.Secure:
return null;
case Protocol.Page.SecureContextType.SecureLocalhost:
return i18nString(UIStrings.localhostIsAlwaysASecureContext);
case Protocol.Page.SecureContextType.InsecureAncestor:
return i18nString(UIStrings.aFrameAncestorIsAnInsecure);
case Protocol.Page.SecureContextType.InsecureScheme:
return i18nString(UIStrings.theFramesSchemeIsInsecure);
}
return null;
}
function maybeRenderCoopCoepCSPStatus(info: Protocol.Network.SecurityIsolationStatus|null): LitTemplate {
if (info) {
return html`
${
maybeRenderCrossOriginStatus(
info.coep, i18n.i18n.lockedString('Cross-Origin Embedder Policy (COEP)'),
Protocol.Network.CrossOriginEmbedderPolicyValue.None)}
${
maybeRenderCrossOriginStatus(
info.coop, i18n.i18n.lockedString('Cross-Origin Opener Policy (COOP)'),
Protocol.Network.CrossOriginOpenerPolicyValue.UnsafeNone)}
${renderCSPSection(info.csp)}
`;
}
return nothing;
}
function maybeRenderCrossOriginStatus(
info: Protocol.Network.CrossOriginEmbedderPolicyStatus|Protocol.Network.CrossOriginOpenerPolicyStatus|undefined,
policyName: string,
noneValue: Protocol.Network.CrossOriginEmbedderPolicyValue|
Protocol.Network.CrossOriginOpenerPolicyValue): LitTemplate {
if (!info) {
return nothing;
}
function crossOriginValueToString(
value: Protocol.Network.CrossOriginEmbedderPolicyValue|Protocol.Network.CrossOriginOpenerPolicyValue): string {
switch (value) {
case Protocol.Network.CrossOriginEmbedderPolicyValue.Credentialless:
return 'credentialless';
case Protocol.Network.CrossOriginEmbedderPolicyValue.None:
return 'none';
case Protocol.Network.CrossOriginEmbedderPolicyValue.RequireCorp:
return 'require-corp';
case Protocol.Network.CrossOriginOpenerPolicyValue.NoopenerAllowPopups:
return 'noopenener-allow-popups';
case Protocol.Network.CrossOriginOpenerPolicyValue.SameOrigin:
return 'same-origin';
case Protocol.Network.CrossOriginOpenerPolicyValue.SameOriginAllowPopups:
return 'same-origin-allow-popups';
case Protocol.Network.CrossOriginOpenerPolicyValue.SameOriginPlusCoep:
return 'same-origin-plus-coep';
case Protocol.Network.CrossOriginOpenerPolicyValue.RestrictProperties:
return 'restrict-properties';
case Protocol.Network.CrossOriginOpenerPolicyValue.RestrictPropertiesPlusCoep:
return 'restrict-properties-plus-coep';
case Protocol.Network.CrossOriginOpenerPolicyValue.UnsafeNone:
return 'unsafe-none';
}
}
const isEnabled = info.value !== noneValue;
const isReportOnly = (!isEnabled && info.reportOnlyValue !== noneValue);
const endpoint = isEnabled ? info.reportingEndpoint : info.reportOnlyReportingEndpoint;
return html`
<devtools-report-key>${policyName}</devtools-report-key>
<devtools-report-value>
${crossOriginValueToString(isEnabled ? info.value : info.reportOnlyValue)}
${isReportOnly ? html`<span class="inline-comment">report-only</span>` : nothing}
${endpoint ? html`<span class="inline-name">${i18nString(UIStrings.reportingTo)}</span>${endpoint}` : nothing}
</devtools-report-value>
`;
}
function renderEffectiveDirectives(directives: string): LitTemplate[] {
const parsedDirectives = new CspEvaluator.CspParser.CspParser(directives).csp.directives;
const result = [];
for (const directive in parsedDirectives) {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
result.push(html`
<div>
<span class="bold">${directive}</span>
${': ' + parsedDirectives[directive]?.join(', ')}
</div>`);
// clang-format on
}
return result;
}
function renderSingleCSP(cspInfo: Protocol.Network.ContentSecurityPolicyStatus, divider: boolean): LitTemplate {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-key>
${cspInfo.isEnforced ? i18n.i18n.lockedString('Content-Security-Policy') : html`
${i18n.i18n.lockedString('Content-Security-Policy-Report-Only')}
<devtools-button
.iconName=${'help'}
class='help-button'
.variant=${Buttons.Button.Variant.ICON}
.size=${Buttons.Button.Size.SMALL}
@click=${()=> {window.location.href = 'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only';}}
jslog=${VisualLogging.link('learn-more.csp-report-only').track({click: true})}
></devtools-button>`}
</devtools-report-key>
<devtools-report-value>
${cspInfo.source === Protocol.Network.ContentSecurityPolicySource.HTTP ?
i18n.i18n.lockedString('HTTP header') : i18n.i18n.lockedString('Meta tag')}
${renderEffectiveDirectives(cspInfo.effectiveDirectives)}
</devtools-report-value>
${divider ? html`<devtools-report-divider class="subsection-divider"></devtools-report-divider>` : nothing}
`;
// clang-format on
}
function renderCSPSection(cspInfos: Protocol.Network.ContentSecurityPolicyStatus[]|undefined): LitTemplate {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-divider></devtools-report-divider>
<devtools-report-section-header>
${i18nString(UIStrings.contentSecurityPolicy)}
</devtools-report-section-header>
${(cspInfos?.length) ? cspInfos.map((cspInfo, index) => renderSingleCSP(cspInfo, index < cspInfos?.length - 1)) : html`
<devtools-report-key>
${i18n.i18n.lockedString('Content-Security-Policy')}
</devtools-report-key>
<devtools-report-value>
${i18nString(UIStrings.none)}
</devtools-report-value>
`}
`;
// clang-format on
}
function renderApiAvailabilitySection(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null): LitTemplate {
if (!frame) {
return nothing;
}
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
return html`
<devtools-report-section-header>
${i18nString(UIStrings.apiAvailability)}
</devtools-report-section-header>
<devtools-report-section>
<span class="report-section">
${i18nString(UIStrings.availabilityOfCertainApisDepends)}
<x-link
href="https://web.dev/why-coop-coep/" class="link"
jslog=${VisualLogging.link('learn-more.coop-coep').track({click: true})}>
${i18nString(UIStrings.learnMore)}
</x-link>
</span>
</devtools-report-section>
${renderSharedArrayBufferAvailability(frame)}
${renderMeasureMemoryAvailability(frame)}
<devtools-report-divider></devtools-report-divider>`;
// clang-format on
}
function renderSharedArrayBufferAvailability(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null): LitTemplate {
if (frame) {
const features = frame.getGatedAPIFeatures();
if (features) {
const sabAvailable = features.includes(Protocol.Page.GatedAPIFeatures.SharedArrayBuffers);
const sabTransferAvailable =
sabAvailable && features.includes(Protocol.Page.GatedAPIFeatures.SharedArrayBuffersTransferAllowed);
const availabilityText = sabTransferAvailable ?
i18nString(UIStrings.availableTransferable) :
(sabAvailable ? i18nString(UIStrings.availableNotTransferable) : i18nString(UIStrings.unavailable));
const tooltipText = sabTransferAvailable ?
i18nString(UIStrings.sharedarraybufferConstructorIs) :
(sabAvailable ? i18nString(UIStrings.sharedarraybufferConstructorIsAvailable) : '');
function renderHint(frame: SDK.ResourceTreeModel.ResourceTreeFrame): LitTemplate {
switch (frame.getCrossOriginIsolatedContextType()) {
case Protocol.Page.CrossOriginIsolatedContextType.Isolated:
return nothing;
case Protocol.Page.CrossOriginIsolatedContextType.NotIsolated:
if (sabAvailable) {
// clang-format off
return html`
<span class="inline-comment">
${i18nString(UIStrings.willRequireCrossoriginIsolated)}
</span>`;
// clang-format on
}
return html`<span class="inline-comment">${i18nString(UIStrings.requiresCrossoriginIsolated)}</span>`;
case Protocol.Page.CrossOriginIsolatedContextType.NotIsolatedFeatureDisabled:
if (!sabTransferAvailable) {
// clang-format off
return html`
<span class="inline-comment">
${i18nString(UIStrings.transferRequiresCrossoriginIsolatedPermission)}
<code> cross-origin-isolated</code>
</span>`;
// clang-format on
}
break;
}
return nothing;
}
// SharedArrayBuffer is an API name, so we don't translate it.
return html`
<devtools-report-key>SharedArrayBuffers</devtools-report-key>
<devtools-report-value title=${tooltipText}>
${availabilityText}\xA0${renderHint(frame)}
</devtools-report-value>
`;
}
}
return nothing;
}
function renderMeasureMemoryAvailability(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null): LitTemplate {
if (frame) {
const measureMemoryAvailable = frame.isCrossOriginIsolated();
const availabilityText =
measureMemoryAvailable ? i18nString(UIStrings.available) : i18nString(UIStrings.unavailable);
const tooltipText = measureMemoryAvailable ? i18nString(UIStrings.thePerformanceAPI) :
i18nString(UIStrings.thePerformancemeasureuseragentspecificmemory);
return html`
<devtools-report-key>${i18nString(UIStrings.measureMemory)}</devtools-report-key>
<devtools-report-value>
<span title=${tooltipText}>${
availabilityText}</span>\xA0<x-link class="link" href="https://web.dev/monitor-total-page-memory-usage/" jslog=${
VisualLogging.link('learn-more.monitor-memory-usage').track({click: true})}>${
i18nString(UIStrings.learnMore)}</x-link>
</devtools-report-value>
`;
}
return nothing;
}
function renderAdditionalInfoSection(frame: SDK.ResourceTreeModel.ResourceTreeFrame|null): LitTemplate {
if (!frame) {
return nothing;
}
return html`
<devtools-report-section-header
title=${i18nString(UIStrings.thisAdditionalDebugging)}
>${i18nString(UIStrings.additionalInformation)}</devtools-report-section-header>
<devtools-report-key>${i18nString(UIStrings.frameId)}</devtools-report-key>
<devtools-report-value>
<div class="text-ellipsis" title=${frame.id}>${frame.id}</div>
</devtools-report-value>
<devtools-report-divider></devtools-report-divider>
`;
}
export class FrameDetailsReportView extends UI.Widget.Widget {
#frame?: SDK.ResourceTreeModel.ResourceTreeFrame;
#target: SDK.Target.Target|null = null;
#creationStackTrace: StackTrace.StackTrace.StackTrace|null = null;
#creationTarget: SDK.Target.Target|null = null;
#securityIsolationInfo: Protocol.Network.SecurityIsolationStatus|null = null;
#linkTargetDOMNode: SDK.DOMModel.DOMNode|null = null;
#trials: Protocol.Page.OriginTrial[]|null = null;
#protocolMonitorExperimentEnabled = false;
#permissionsPolicies: Protocol.Page.PermissionsPolicyFeatureState[]|null = null;
#linkifier = new Components.Linkifier.Linkifier();
#adScriptAncestry: Protocol.Page.AdScriptAncestry|null = null;
#view: View;
constructor(element?: HTMLElement, view = DEFAULT_VIEW) {
super(element, {useShadowDom: true});
this.#protocolMonitorExperimentEnabled = Root.Runtime.experiments.isEnabled('protocol-monitor');
this.#view = view;
}
set frame(frame: SDK.ResourceTreeModel.ResourceTreeFrame) {
this.#frame = frame;
void this.#frame.getPermissionsPolicyState().then(permissionsPolicies => {
this.#permissionsPolicies = permissionsPolicies;
this.requestUpdate();
});
const {creationStackTrace: rawCreationStackTrace, creationStackTraceTarget: creationTarget} =
frame.getCreationStackTraceData();
this.#creationTarget = creationTarget;
if (rawCreationStackTrace) {
void Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()
.createStackTraceFromProtocolRuntime(rawCreationStackTrace, creationTarget)
.then(creationStackTrace => {
this.#creationStackTrace = creationStackTrace;
this.requestUpdate();
});
}
const networkManager = frame.resourceTreeModel().target().model(SDK.NetworkManager.NetworkManager);
void networkManager?.getSecurityIsolationStatus(frame.id).then(securityIsolationInfo => {
this.#securityIsolationInfo = securityIsolationInfo;
this.requestUpdate();
});
void frame.getOwnerDOMNodeOrDocument().then(linkTargetDOMNode => {
this.#linkTargetDOMNode = linkTargetDOMNode;
this.requestUpdate();
});
void frame.getOriginTrials().then(trials => {
this.#trials = trials;
this.requestUpdate();
});
this.requestUpdate();
}
get frame(): SDK.ResourceTreeModel.ResourceTreeFrame|undefined {
return this.#frame;
}
override async performUpdate(): Promise<void> {
const result = await this.#frame?.parentFrame()?.getAdScriptAncestry(this.#frame?.id);
if (result && result.ancestryChain.length > 0) {
this.#adScriptAncestry = result;
// Obtain the Target associated with the first ad script, because in most scenarios all
// scripts share the same debuggerId. However, discrepancies might arise when content scripts
// from browser extensions are involved. We will monitor the debugging experiences and revisit
// this approach if it proves problematic.
const firstScript = this.#adScriptAncestry.ancestryChain[0];
const debuggerModel = firstScript?.debuggerId ?
await SDK.DebuggerModel.DebuggerModel.modelForDebuggerId(firstScript.debuggerId) :
null;
this.#target = debuggerModel?.target() ?? null;
}
const frame = this.#frame;
if (!frame) {
return;
}
const frameRequest = frame.resourceForURL(frame.url)?.request;
const input = {
frame,
target: this.#target,
creationStackTrace: this.#creationStackTrace,
creationTarget: this.#creationTarget,
protocolMonitorExperimentEnabled: this.#protocolMonitorExperimentEnabled,
permissionsPolicies: this.#permissionsPolicies,
adScriptAncestry: this.#adScriptAncestry,
linkifier: this.#linkifier,
linkTargetDOMNode: this.#linkTargetDOMNode,
trials: this.#trials,
securityIsolationInfo: this.#securityIsolationInfo,
onRevealInNetwork: frameRequest ?
() => {
const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.tab(
frameRequest, NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT);
return Common.Revealer.reveal(requestLocation);
} :
undefined,
onRevealInSources: async () => {
const sourceCode = this.#uiSourceCodeForFrame(frame);
if (sourceCode) {
await Common.Revealer.reveal(sourceCode);
}
},
};
this.#view(input, undefined, this.contentElement);
}
#uiSourceCodeForFrame(frame: SDK.ResourceTreeModel.ResourceTreeFrame): Workspace.UISourceCode.UISourceCode|null {
for (const project of Workspace.Workspace.WorkspaceImpl.instance().projects()) {
const projectTarget = Bindings.NetworkProject.NetworkProject.getTargetForProject(project);
if (projectTarget && projectTarget === frame.resourceTreeModel().target()) {
const uiSourceCode = project.uiSourceCodeForURL(frame.url);
if (uiSourceCode) {
return uiSourceCode;
}
}
}
return null;
}
}