blob: f46263f6421d2f2dad846a702ace6b2ff10e2b23 [file] [log] [blame]
// Copyright 2020 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 Bindings from '../bindings/bindings.js';
import * as Common from '../common/common.js';
import * as Network from '../network/network.js';
import * as Root from '../root/root.js';
import * as SDK from '../sdk/sdk.js'; // eslint-disable-line no-unused-vars
import * as UI from '../ui/ui.js';
import * as Workspace from '../workspace/workspace.js';
/**
* @param {boolean} b
*/
const booleanToYesNo = b => b ? ls`Yes` : ls`No`;
export class FrameDetailsView extends UI.ThrottledWidget.ThrottledWidget {
/**
* @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame
*/
constructor(frame) {
super();
this._protocolMonitorExperimentEnabled = Root.Runtime.experiments.isEnabled('protocolMonitor');
this.registerRequiredCSS('resources/frameDetailsReportView.css');
this._frame = frame;
this.contentElement.classList.add('frame-details-container');
this._reportView = new UI.ReportView.ReportView(frame.displayName());
this._reportView.registerRequiredCSS('resources/frameDetailsReportView.css');
this._reportView.show(this.contentElement);
this._reportView.element.classList.add('frame-details-report-container');
this._generalSection = this._reportView.appendSection(ls`Document`);
this._urlFieldValue = this._generalSection.appendField(ls`URL`);
this._urlStringElement = this._urlFieldValue.createChild('div', 'text-ellipsis');
this._unreachableURL = this._generalSection.appendField(ls`Unreachable URL`);
const originFieldValue = this._generalSection.appendField(ls`Origin`);
this._originStringElement = originFieldValue.createChild('div', 'text-ellipsis');
this._ownerFieldValue = this._generalSection.appendField(ls`Owner Element`);
this._adStatus = this._generalSection.appendField(ls`Ad Status`);
this._isolationSection = this._reportView.appendSection(ls`Security & Isolation`);
this._secureContext = this._isolationSection.appendField(ls`Secure Context`);
this._crossOriginIsolatedContext = this._isolationSection.appendField(ls`Cross-Origin Isolated`);
this._coepPolicy = this._isolationSection.appendField(ls`Cross-Origin Embedder Policy`);
this._coopPolicy = this._isolationSection.appendField(ls`Cross-Origin Opener Policy`);
this._apiAvailability = this._reportView.appendSection(ls`API availablity`);
const summaryRow = this._apiAvailability.appendRow();
const summaryText = ls`Availability of certain APIs depends on the document being cross-origin isolated.`;
const link = 'https://web.dev/why-coop-coep/';
summaryRow.appendChild(UI.Fragment.html`<div>${summaryText} ${UI.XLink.XLink.create(link, ls`Learn more`)}</div>`);
if (this._protocolMonitorExperimentEnabled) {
this._additionalInfo = this._reportView.appendSection(ls`Additional Information`);
this._additionalInfo.setTitle(
ls`Additional Information`,
ls`This additional (debugging) information is shown because the 'Protocol Monitor' experiment is enabled.`);
const frameIDField = this._additionalInfo.appendField(ls`Frame ID`);
frameIDField.textContent = frame.id;
}
this.update();
}
/**
* @override
* @return {!Promise<?>}
*/
async doUpdate() {
this._urlFieldValue.removeChildren();
this._urlStringElement.textContent = this._frame.url;
this._urlStringElement.title = this._frame.url;
this._urlFieldValue.appendChild(this._urlStringElement);
if (!this._frame.unreachableUrl()) {
const sourceCode = this.uiSourceCodeForFrame(this._frame);
const revealSource = linkifyIcon(
'mediumicon-sources-panel', ls`Click to reveal in Sources panel`, () => Common.Revealer.reveal(sourceCode));
this._urlFieldValue.appendChild(revealSource);
}
FrameDetailsView.maybeAppendLinkToRequest(this._urlFieldValue, this._frame.resourceForURL(this._frame.url));
this._maybeAppendLinkForUnreachableUrl();
if (this._frame.securityOrigin && this._frame.securityOrigin !== '://') {
this._originStringElement.textContent = this._frame.securityOrigin;
this._originStringElement.title = this._frame.securityOrigin;
this._generalSection.setFieldVisible(ls`Origin`, true);
} else {
this._generalSection.setFieldVisible(ls`Origin`, false);
}
this._updateAdStatus();
this._ownerFieldValue.removeChildren();
const linkElement = await maybeCreateLinkToElementsPanel(this._frame);
if (linkElement) {
this._ownerFieldValue.appendChild(linkElement);
}
await this._updateCoopCoepStatus();
this._updateContextStatus();
}
/**
* @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame
* @return {?Workspace.UISourceCode.UISourceCode}
*/
uiSourceCodeForFrame(frame) {
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;
}
/**
*
* @param {!HTMLElement} field
* @param {function((!Protocol.Network.CrossOriginEmbedderPolicyValue|!Protocol.Network.CrossOriginOpenerPolicyValue)):boolean} isEnabled
* @param {!Protocol.Network.CrossOriginEmbedderPolicyStatus|!Protocol.Network.CrossOriginOpenerPolicyStatus} info
*/
static fillCrossOriginPolicy(field, isEnabled, info) {
const enabled = isEnabled(info.value);
field.textContent = enabled ? info.value : info.reportOnlyValue;
if (!enabled && isEnabled(info.reportOnlyValue)) {
const reportOnly = document.createElement('span');
reportOnly.classList.add('inline-comment');
reportOnly.textContent = 'report-only';
field.appendChild(reportOnly);
}
const endpoint = enabled ? info.reportingEndpoint : info.reportOnlyReportingEndpoint;
if (endpoint) {
const reportingEndpointPrefix = field.createChild('span', 'inline-name');
reportingEndpointPrefix.textContent = ls`reporting to`;
const reportingEndpointName = field.createChild('span');
reportingEndpointName.textContent = endpoint;
}
}
async _updateCoopCoepStatus() {
const model = this._frame.resourceTreeModel().target().model(SDK.NetworkManager.NetworkManager);
const info = model && await model.getSecurityIsolationStatus(this._frame.id);
if (!info) {
return;
}
/**
* @param {!Protocol.Network.CrossOriginEmbedderPolicyValue|!Protocol.Network.CrossOriginOpenerPolicyValue} value
*/
const coepIsEnabled = value => value !== Protocol.Network.CrossOriginEmbedderPolicyValue.None;
FrameDetailsView.fillCrossOriginPolicy(this._coepPolicy, coepIsEnabled, info.coep);
/**
* @param {!Protocol.Network.CrossOriginEmbedderPolicyValue|!Protocol.Network.CrossOriginOpenerPolicyValue} value
*/
const coopIsEnabled = value => value !== Protocol.Network.CrossOriginOpenerPolicyValue.UnsafeNone;
FrameDetailsView.fillCrossOriginPolicy(this._coopPolicy, coopIsEnabled, info.coop);
}
/**
* @param {?Protocol.Page.SecureContextType} type
* @returns {?string}
*/
_explanationFromSecureContextType(type) {
switch (type) {
case Protocol.Page.SecureContextType.Secure:
return null;
case Protocol.Page.SecureContextType.SecureLocalhost:
return ls`Localhost is always a secure context`;
case Protocol.Page.SecureContextType.InsecureAncestor:
return ls`A frame ancestor is an insecure context`;
case Protocol.Page.SecureContextType.InsecureScheme:
return ls`The frame's scheme is insecure`;
}
return null;
}
_updateContextStatus() {
if (this._frame.unreachableUrl()) {
this._isolationSection.setFieldVisible(ls`Secure Context`, false);
this._isolationSection.setFieldVisible(ls`Cross-Origin Isolated`, false);
return;
}
this._isolationSection.setFieldVisible(ls`Secure Context`, true);
this._isolationSection.setFieldVisible(ls`Cross-Origin Isolated`, true);
this._secureContext.textContent = booleanToYesNo(this._frame.isSecureContext());
const secureContextExplanation = this._explanationFromSecureContextType(this._frame.getSecureContextType());
if (secureContextExplanation) {
const secureContextType = this._secureContext.createChild('span', 'inline-comment');
secureContextType.textContent = secureContextExplanation;
}
this._crossOriginIsolatedContext.textContent = booleanToYesNo(this._frame.isCrossOriginIsolated());
}
/**
* @param {!Element} element
* @param {?SDK.Resource.Resource} resource
*/
static maybeAppendLinkToRequest(element, resource) {
if (resource && resource.request) {
const request = resource.request;
const revealRequest = linkifyIcon(
'mediumicon-network-panel', ls`Click to reveal in Network panel`,
() => Network.NetworkPanel.NetworkPanel.selectAndShowRequest(request, Network.NetworkItemView.Tabs.Headers));
element.appendChild(revealRequest);
}
}
_maybeAppendLinkForUnreachableUrl() {
if (!this._frame.unreachableUrl()) {
this._generalSection.setFieldVisible(ls`Unreachable URL`, false);
return;
}
this._generalSection.setFieldVisible(ls`Unreachable URL`, true);
this._unreachableURL.textContent = this._frame.unreachableUrl();
const unreachableUrl = Common.ParsedURL.ParsedURL.fromString(this._frame.unreachableUrl());
if (!unreachableUrl) {
return;
}
const revealRequest = linkifyIcon(
'mediumicon-network-panel', ls`Click to reveal in Network panel (might require page reload)`, () => {
Network.NetworkPanel.NetworkPanel.revealAndFilter([
{
filterType: 'domain',
filterValue: unreachableUrl.domain(),
},
{
filterType: null,
filterValue: unreachableUrl.path,
}
]);
});
this._unreachableURL.appendChild(revealRequest);
}
_updateAdStatus() {
switch (this._frame.adFrameType()) {
case Protocol.Page.AdFrameType.Root:
this._generalSection.setFieldVisible(ls`Ad Status`, true);
this._adStatus.textContent = ls`root`;
this._adStatus.title = ls`This frame has been identified as the root frame of an ad`;
break;
case Protocol.Page.AdFrameType.Child:
this._generalSection.setFieldVisible(ls`Ad Status`, true);
this._adStatus.textContent = ls`child`;
this._adStatus.title = ls`This frame has been identified as the a child frame of an ad`;
break;
default:
this._generalSection.setFieldVisible(ls`Ad Status`, false);
break;
}
}
}
/**
* @param {string} iconType
* @param {string} title
* @param {function():(void|!Promise<void>)} eventHandler
* @return {!Element}
*/
function linkifyIcon(iconType, title, eventHandler) {
const icon = UI.Icon.Icon.create(iconType, 'icon-link devtools-link');
const span = document.createElement('span');
span.title = title;
span.classList.add('devtools-link');
span.tabIndex = 0;
span.appendChild(icon);
span.addEventListener('click', () => eventHandler());
span.addEventListener('keydown', event => {
if (isEnterKey(event)) {
eventHandler();
}
});
return span;
}
/**
* @param {!SDK.ResourceTreeModel.ResourceTreeFrame|!Protocol.Page.FrameId|undefined} opener
* @return {!Promise<?Element>}
*/
async function maybeCreateLinkToElementsPanel(opener) {
/** @type {?SDK.ResourceTreeModel.ResourceTreeFrame} */
let openerFrame = null;
if (opener instanceof SDK.ResourceTreeModel.ResourceTreeFrame) {
openerFrame = opener;
} else if (opener) {
openerFrame = SDK.FrameManager.FrameManager.instance().getFrame(opener);
}
if (!openerFrame) {
return null;
}
const linkTargetDOMNode = await openerFrame.getOwnerDOMNodeOrDocument();
if (!linkTargetDOMNode) {
return null;
}
const linkElement = linkifyIcon(
'mediumicon-elements-panel', ls`Click to reveal in Elements panel`,
() => Common.Revealer.reveal(linkTargetDOMNode));
const label = document.createElement('span');
label.textContent = `<${linkTargetDOMNode.nodeName().toLocaleLowerCase()}>`;
linkElement.insertBefore(label, linkElement.firstChild);
linkElement.addEventListener('mouseenter', () => {
if (openerFrame) {
openerFrame.highlight();
}
});
linkElement.addEventListener('mouseleave', () => {
SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
});
return linkElement;
}
export class OpenedWindowDetailsView extends UI.ThrottledWidget.ThrottledWidget {
/**
* @param {!Protocol.Target.TargetInfo} targetInfo
* @param {boolean} isWindowClosed
*/
constructor(targetInfo, isWindowClosed) {
super();
this._targetInfo = targetInfo;
this._isWindowClosed = isWindowClosed;
this.registerRequiredCSS('resources/frameDetailsReportView.css');
this.contentElement.classList.add('frame-details-container');
this._reportView = new UI.ReportView.ReportView(this.buildTitle());
this._reportView.registerRequiredCSS('resources/frameDetailsReportView.css');
this._reportView.show(this.contentElement);
this._reportView.element.classList.add('frame-details-report-container');
this._documentSection = this._reportView.appendSection(ls`Document`);
this._URLFieldValue = this._documentSection.appendField(ls`URL`);
this._securitySection = this._reportView.appendSection(ls`Security`);
this._openerElementField = this._securitySection.appendField(ls`Opener Frame`);
this._securitySection.setFieldVisible(ls`Opener Frame`, false);
this._hasDOMAccessValue = this._securitySection.appendField(ls`Access to opener`);
this._hasDOMAccessValue.title = ls`Shows whether the opened window is able to access its opener and vice versa`;
this.update();
}
/**
* @override
* @return {!Promise<?>}
*/
async doUpdate() {
this._reportView.setTitle(this.buildTitle());
this._URLFieldValue.textContent = this._targetInfo.url;
this._hasDOMAccessValue.textContent = booleanToYesNo(this._targetInfo.canAccessOpener);
this.maybeDisplayOpenerFrame();
}
async maybeDisplayOpenerFrame() {
this._openerElementField.removeChildren();
const linkElement = await maybeCreateLinkToElementsPanel(this._targetInfo.openerFrameId);
if (linkElement) {
this._openerElementField.append(linkElement);
this._securitySection.setFieldVisible(ls`Opener Frame`, true);
return;
}
this._securitySection.setFieldVisible(ls`Opener Frame`, false);
}
/**
* @return {string}
*/
buildTitle() {
let title = this._targetInfo.title || ls`Window without title`;
if (this._isWindowClosed) {
title += ` (${ls`closed`})`;
}
return title;
}
/**
* @param {boolean} isWindowClosed
*/
setIsWindowClosed(isWindowClosed) {
this._isWindowClosed = isWindowClosed;
}
/**
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
setTargetInfo(targetInfo) {
this._targetInfo = targetInfo;
}
}
export class WorkerDetailsView extends UI.ThrottledWidget.ThrottledWidget {
/**
* @param {!Protocol.Target.TargetInfo} targetInfo
*/
constructor(targetInfo) {
super();
this._targetInfo = targetInfo;
this.registerRequiredCSS('resources/frameDetailsReportView.css');
this.contentElement.classList.add('frame-details-container');
this._reportView = new UI.ReportView.ReportView(this._targetInfo.title || this._targetInfo.url || ls`worker`);
this._reportView.registerRequiredCSS('resources/frameDetailsReportView.css');
this._reportView.show(this.contentElement);
this._reportView.element.classList.add('frame-details-report-container');
this._documentSection = this._reportView.appendSection(ls`Document`);
this._URLFieldValue = this._documentSection.appendField(ls`URL`);
this._URLFieldValue.textContent = this._targetInfo.url;
}
}