UNPKG

chrome-devtools-frontend

Version:
255 lines (236 loc) • 10.8 kB
// Copyright 2021 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. /* eslint-disable rulesdir/no-lit-render-outside-of-view */ import '../../../ui/components/icon_button/icon_button.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 SDK from '../../../core/sdk/sdk.js'; import * as Protocol from '../../../generated/protocol.js'; import * as NetworkForward from '../../../panels/network/forward/forward.js'; import * as Buttons from '../../../ui/components/buttons/buttons.js'; import * as RenderCoordinator from '../../../ui/components/render_coordinator/render_coordinator.js'; import * as Lit from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import permissionsPolicySectionStyles from './permissionsPolicySection.css.js'; const {html} = Lit; const UIStrings = { /** *@description Label for a button. When clicked more details (for the content this button refers to) will be shown. */ showDetails: 'Show details', /** *@description Label for a button. When clicked some details (for the content this button refers to) will be hidden. */ hideDetails: 'Hide details', /** *@description Label for a list of features which are allowed according to the current Permissions policy *(a mechanism that allows developers to enable/disable browser features and APIs (e.g. camera, geolocation, autoplay)) */ allowedFeatures: 'Allowed Features', /** *@description Label for a list of features which are disabled according to the current Permissions policy *(a mechanism that allows developers to enable/disable browser features and APIs (e.g. camera, geolocation, autoplay)) */ disabledFeatures: 'Disabled Features', /** *@description Tooltip text for a link to a specific request's headers in the Network panel. */ clickToShowHeader: 'Click to reveal the request whose "`Permissions-Policy`" HTTP header disables this feature.', /** *@description Tooltip text for a link to a specific iframe in the Elements panel (Iframes can be nested, the link goes * to the outer-most iframe which blocks a certain feature). */ clickToShowIframe: 'Click to reveal the top-most iframe which does not allow this feature in the elements panel.', /** *@description Text describing that a specific feature is blocked by not being included in the iframe's "allow" attribute. */ disabledByIframe: 'missing in iframe "`allow`" attribute', /** *@description Text describing that a specific feature is blocked by a Permissions Policy specified in a request header. */ disabledByHeader: 'disabled by "`Permissions-Policy`" header', /** *@description Text describing that a specific feature is blocked by virtue of being inside a fenced frame tree. */ disabledByFencedFrame: 'disabled inside a `fencedframe`', } as const; const str_ = i18n.i18n.registerUIStrings('panels/application/components/PermissionsPolicySection.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface PermissionsPolicySectionData { policies: Protocol.Page.PermissionsPolicyFeatureState[]; showDetails: boolean; } export function renderIconLink( iconName: string, title: Platform.UIString.LocalizedString, clickHandler: (() => void)|(() => Promise<void>), jsLogContext: string): Lit.TemplateResult { // Disabled until https://crbug.com/1079231 is fixed. // clang-format off return html` <devtools-button .iconName=${iconName} title=${title} .variant=${Buttons.Button.Variant.ICON} .size=${Buttons.Button.Size.SMALL} @click=${clickHandler} jslog=${VisualLogging.action().track({click: true}).context(jsLogContext)}></devtools-button> `; // clang-format on } export class PermissionsPolicySection extends HTMLElement { readonly #shadow = this.attachShadow({mode: 'open'}); #permissionsPolicySectionData: PermissionsPolicySectionData = {policies: [], showDetails: false}; set data(data: PermissionsPolicySectionData) { this.#permissionsPolicySectionData = data; void this.#render(); } #toggleShowPermissionsDisallowedDetails(): void { this.#permissionsPolicySectionData.showDetails = !this.#permissionsPolicySectionData.showDetails; void this.#render(); } #renderAllowed(): Lit.LitTemplate { const allowed = this.#permissionsPolicySectionData.policies.filter(p => p.allowed).map(p => p.feature).sort(); if (!allowed.length) { return Lit.nothing; } return html` <devtools-report-key>${i18nString(UIStrings.allowedFeatures)}</devtools-report-key> <devtools-report-value> ${allowed.join(', ')} </devtools-report-value> `; } async #renderDisallowed(): Promise<Lit.LitTemplate> { const disallowed = this.#permissionsPolicySectionData.policies.filter(p => !p.allowed) .sort((a, b) => a.feature.localeCompare(b.feature)); if (!disallowed.length) { return Lit.nothing; } if (!this.#permissionsPolicySectionData.showDetails) { return html` <devtools-report-key>${i18nString(UIStrings.disabledFeatures)}</devtools-report-key> <devtools-report-value> ${disallowed.map(p => p.feature).join(', ')} <devtools-button class="disabled-features-button" .variant=${Buttons.Button.Variant.OUTLINED} @click=${() => this.#toggleShowPermissionsDisallowedDetails()} jslog=${VisualLogging.action('show-disabled-features-details').track({ click: true, })}>${i18nString(UIStrings.showDetails)} </devtools-button> </devtools-report-value> `; } const frameManager = SDK.FrameManager.FrameManager.instance(); const featureRows = await Promise.all(disallowed.map(async policy => { const frame = policy.locator ? frameManager.getFrame(policy.locator.frameId) : null; const blockReason = policy.locator?.blockReason; const linkTargetDOMNode = await (blockReason === Protocol.Page.PermissionsPolicyBlockReason.IframeAttribute && frame?.getOwnerDOMNodeOrDocument()); const resource = frame?.resourceForURL(frame.url); const linkTargetRequest = blockReason === Protocol.Page.PermissionsPolicyBlockReason.Header && resource?.request; const blockReasonText = (() => { switch (blockReason) { case Protocol.Page.PermissionsPolicyBlockReason.IframeAttribute: return i18nString(UIStrings.disabledByIframe); case Protocol.Page.PermissionsPolicyBlockReason.Header: return i18nString(UIStrings.disabledByHeader); case Protocol.Page.PermissionsPolicyBlockReason.InFencedFrameTree: return i18nString(UIStrings.disabledByFencedFrame); default: return ''; } })(); const revealHeader = async(): Promise<void> => { if (!linkTargetRequest) { return; } const headerName = linkTargetRequest.responseHeaderValue('permissions-policy') ? 'permissions-policy' : 'feature-policy'; const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.responseHeaderMatch( linkTargetRequest, {name: headerName, value: ''}, ); await Common.Revealer.reveal(requestLocation); }; // Disabled until https://crbug.com/1079231 is fixed. // clang-format off return html` <div class="permissions-row"> <div> <devtools-icon class="allowed-icon" .data=${{ color: 'var(--icon-error)', iconName: 'cross-circle', width: '20px', height: '20px', }}> </devtools-icon> </div> <div class="feature-name text-ellipsis"> ${policy.feature} </div> <div class="block-reason">${blockReasonText}</div> <div> ${ linkTargetDOMNode ? renderIconLink( 'code-circle', i18nString(UIStrings.clickToShowIframe), () => Common.Revealer.reveal(linkTargetDOMNode), 'reveal-in-elements') : Lit.nothing} ${ linkTargetRequest ? renderIconLink( 'arrow-up-down-circle', i18nString(UIStrings.clickToShowHeader), revealHeader, 'reveal-in-network') : Lit.nothing} </div> </div> `; // clang-format on })); return html` <devtools-report-key>${i18nString(UIStrings.disabledFeatures)}</devtools-report-key> <devtools-report-value class="policies-list"> ${featureRows} <div class="permissions-row"> <devtools-button .variant=${Buttons.Button.Variant.OUTLINED} @click=${() => this.#toggleShowPermissionsDisallowedDetails()} jslog=${VisualLogging.action('hide-disabled-features-details').track({ click: true, })}>${i18nString(UIStrings.hideDetails)} </devtools-button> </div> </devtools-report-value> `; } async #render(): Promise<void> { await RenderCoordinator.write('PermissionsPolicySection render', () => { // Disabled until https://crbug.com/1079231 is fixed. // clang-format off Lit.render( html` <style>${permissionsPolicySectionStyles}</style> <devtools-report-section-header>${i18n.i18n.lockedString('Permissions Policy')}</devtools-report-section-header> ${this.#renderAllowed()} ${(this.#permissionsPolicySectionData.policies.findIndex(p => p.allowed) > 0 || this.#permissionsPolicySectionData.policies.findIndex(p => !p.allowed) > 0) ? html`<devtools-report-divider class="subsection-divider"></devtools-report-divider>` : Lit.nothing} ${Lit.Directives.until(this.#renderDisallowed(), Lit.nothing)} <devtools-report-divider></devtools-report-divider> `, this.#shadow, {host: this}, ); // clang-format on }); } } customElements.define('devtools-resources-permissions-policy-section', PermissionsPolicySection); declare global { interface HTMLElementTagNameMap { 'devtools-resources-permissions-policy-section': PermissionsPolicySection; } }