// Copyright 2023 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/legacy/components/data_grid/data_grid.js'; import '../../../../ui/kit/kit.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 {assertNotNullOrUndefined} from '../../../../core/platform/platform.js'; import * as SDK from '../../../../core/sdk/sdk.js'; import * as Protocol from '../../../../generated/protocol.js'; import * as UI from '../../../../ui/legacy/legacy.js'; import {Directives, html, type LitTemplate, nothing, render} from '../../../../ui/lit/lit.js'; import * as VisualLogging from '../../../../ui/visual_logging/visual_logging.js'; import * as NetworkForward from '../../../network/forward/forward.js'; import * as PreloadingHelper from '../helper/helper.js'; import * as PreloadingString from './PreloadingString.js'; import ruleSetGridStyles from './ruleSetGrid.css.js'; const {styleMap} = Directives; const UIStrings = { /** * @description Column header: Short URL of rule set. */ ruleSet: 'Rule set', /** * @description Column header: Show how many preloads are associated if valid, error counts if invalid. */ status: 'Status', /** * @description button: Title of button to reveal the corresponding request of rule set in Elements panel */ clickToOpenInElementsPanel: 'Click to open in Elements panel', /** * @description button: Title of button to reveal the corresponding request of rule set in Network panel */ clickToOpenInNetworkPanel: 'Click to open in Network panel', /** * @description Value of status, specifying rule set contains how many errors. */ errors: '{errorCount, plural, =1 {# error} other {# errors}}', /** * @description button: Title of button to reveal preloading attempts with filter by selected rule set */ buttonRevealPreloadsAssociatedWithRuleSet: 'Reveal speculative loads associated with this rule set', } as const; const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/components/RuleSetGrid.ts', UIStrings); export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); export interface RuleSetGridData { rows: RuleSetGridRow[]; pageURL: Platform.DevToolsPath.UrlString; } export interface RuleSetGridRow { ruleSet: Protocol.Preload.RuleSet; preloadsStatusSummary: string; } export type View = (input: ViewInput, output: ViewOutput, target: HTMLElement) => void; export interface ViewInput { data: RuleSetGridData|null; onSelect: (ruleSetId: Protocol.Preload.RuleSetId) => void; onRevealInElements: (ruleSet: Protocol.Preload.RuleSet) => void; onRevealInNetwork: (ruleSet: Protocol.Preload.RuleSet) => void; onRevealPreloadsAssociatedWithRuleSet: (ruleSet: Protocol.Preload.RuleSet) => void; } export type ViewOutput = unknown; export const DEFAULT_VIEW: View = (input, _output, target) => { let template: LitTemplate = nothing; if (input.data !== null) { const {rows, pageURL} = input.data; // Disabled until https://crbug.com/1079231 is fixed. // clang-format off template = html`
${rows.map(({ruleSet, preloadsStatusSummary}) => { const location = PreloadingString.ruleSetTagOrLocationShort(ruleSet, pageURL); const revealInElements = ruleSet.backendNodeId !== undefined; const revealInNetwork = ruleSet.url !== undefined && ruleSet.requestId; return html` input.onSelect(ruleSet.id)}> `;})}
${i18nString(UIStrings.ruleSet)} ${i18nString(UIStrings.status)}
${revealInElements || revealInNetwork ? html` ` : location} ${ruleSet.errorType !== undefined ? html` ${i18nString(UIStrings.errors, {errorCount: 1})} ` : ''} ${ruleSet.errorType !== Protocol.Preload.RuleSetErrorType.SourceIsNotJsonObject && ruleSet.errorType !== Protocol.Preload.RuleSetErrorType.InvalidRulesetLevelTag ? html` ` : ''}
`; // clang-format on } render(template, target); }; /** Grid component to show SpeculationRules rule sets. **/ export class RuleSetGrid extends Common.ObjectWrapper.eventMixin(UI.Widget.VBox) { readonly #view: View; #data: RuleSetGridData|null = null; constructor(view: View = DEFAULT_VIEW) { super({useShadowDom: true}); this.#view = view; } get data(): RuleSetGridData|null { return this.#data; } set data(data: RuleSetGridData|null) { this.#data = data; this.requestUpdate(); } override performUpdate(): void { const input: ViewInput = { data: this.#data, onSelect: this.dispatchEventToListeners.bind(this, Events.SELECT), onRevealInElements: this.#revealSpeculationRulesInElements.bind(this), onRevealInNetwork: this.#revealSpeculationRulesInNetwork.bind(this), onRevealPreloadsAssociatedWithRuleSet: this.#revealAttemptViewWithFilter.bind(this), }; const output = undefined; this.#view(input, output, this.contentElement); } #revealSpeculationRulesInElements(ruleSet: Protocol.Preload.RuleSet): void { assertNotNullOrUndefined(ruleSet.backendNodeId); const target = SDK.TargetManager.TargetManager.instance().scopeTarget(); if (target === null) { return; } void Common.Revealer.reveal(new SDK.DOMModel.DeferredDOMNode(target, ruleSet.backendNodeId)); } #revealSpeculationRulesInNetwork(ruleSet: Protocol.Preload.RuleSet): void { assertNotNullOrUndefined(ruleSet.requestId); const request = SDK.TargetManager.TargetManager.instance() .scopeTarget() ?.model(SDK.NetworkManager.NetworkManager) ?.requestForId(ruleSet.requestId) || null; if (request === null) { return; } const requestLocation = NetworkForward.UIRequestLocation.UIRequestLocation.tab( request, NetworkForward.UIRequestLocation.UIRequestTabs.PREVIEW, {clearFilter: false}); void Common.Revealer.reveal(requestLocation); } #revealAttemptViewWithFilter(ruleSet: Protocol.Preload.RuleSet): void { void Common.Revealer.reveal(new PreloadingHelper.PreloadingForward.AttemptViewWithFilter(ruleSet.id)); } } export const enum Events { SELECT = 'select', } export interface EventTypes { [Events.SELECT]: Protocol.Preload.RuleSetId; }