UNPKG

chrome-devtools-frontend

Version:
725 lines (625 loc) • 29.6 kB
// 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. /* eslint-disable rulesdir/no-imperative-dom-api */ /* eslint-disable rulesdir/no-lit-render-outside-of-view */ import '../../../ui/legacy/legacy.js'; import * as Common from '../../../core/common/common.js'; import * as i18n from '../../../core/i18n/i18n.js'; import * 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 Buttons from '../../../ui/components/buttons/buttons.js'; // eslint-disable-next-line rulesdir/es-modules-import import emptyWidgetStyles from '../../../ui/legacy/emptyWidget.css.js'; import * as UI from '../../../ui/legacy/legacy.js'; import {html, render} from '../../../ui/lit/lit.js'; import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js'; import * as PreloadingComponents from './components/components.js'; import {ruleSetTagOrLocationShort} from './components/PreloadingString.js'; import type * as PreloadingHelper from './helper/helper.js'; import preloadingViewStyles from './preloadingView.css.js'; import preloadingViewDropDownStyles from './preloadingViewDropDown.css.js'; const UIStrings = { /** *@description DropDown title for filtering preloading attempts by rule set */ filterFilterByRuleSet: 'Filter by rule set', /** *@description DropDown text for filtering preloading attempts by rule set: No filter */ filterAllPreloads: 'All speculative loads', /** *@description Dropdown subtitle for filtering preloading attempts by rule set * when there are no rule sets in the page. */ noRuleSets: 'no rule sets', /** *@description Text in grid: Rule set is valid */ validityValid: 'Valid', /** *@description Text in grid: Rule set must be a valid JSON object */ validityInvalid: 'Invalid', /** *@description Text in grid: Rule set contains invalid rules and they are ignored */ validitySomeRulesInvalid: 'Some rules invalid', /** *@description Text in grid and details: Preloading attempt is not yet triggered. */ statusNotTriggered: 'Not triggered', /** *@description Text in grid and details: Preloading attempt is eligible but pending. */ statusPending: 'Pending', /** *@description Text in grid and details: Preloading is running. */ statusRunning: 'Running', /** *@description Text in grid and details: Preloading finished and the result is ready for the next navigation. */ statusReady: 'Ready', /** *@description Text in grid and details: Ready, then used. */ statusSuccess: 'Success', /** *@description Text in grid and details: Preloading failed. */ statusFailure: 'Failure', /** *@description Text to pretty print a file */ prettyPrint: 'Pretty print', /** *@description Placeholder text if there are no rules to show. https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules */ noRulesDetected: 'No rules detected', /** *@description Placeholder text if there are no rules to show. https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules */ rulesDescription: 'On this page you will see the speculation rules used to prefetch and prerender page navigations.', /** *@description Placeholder text if there are no speculation attempts for prefetching or prerendering urls. https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules */ noPrefetchAttempts: 'No speculation detected', /** *@description Placeholder text if there are no speculation attempts for prefetching or prerendering urls. https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules */ prefetchDescription: 'On this page you will see details on speculative loads.', /** *@description Text for a learn more link */ learnMore: 'Learn more', } as const; const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/PreloadingView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); const SPECULATION_EXPLANATION_URL = 'https://developer.chrome.com/docs/devtools/application/debugging-speculation-rules' as Platform.DevToolsPath.UrlString; // Used for selector, indicating no filter is specified. const AllRuleSetRootId = Symbol('AllRuleSetRootId'); class PreloadingUIUtils { static status(status: SDK.PreloadingModel.PreloadingStatus): string { // See content/public/browser/preloading.h PreloadingAttemptOutcome. switch (status) { case SDK.PreloadingModel.PreloadingStatus.NOT_TRIGGERED: return i18nString(UIStrings.statusNotTriggered); case SDK.PreloadingModel.PreloadingStatus.PENDING: return i18nString(UIStrings.statusPending); case SDK.PreloadingModel.PreloadingStatus.RUNNING: return i18nString(UIStrings.statusRunning); case SDK.PreloadingModel.PreloadingStatus.READY: return i18nString(UIStrings.statusReady); case SDK.PreloadingModel.PreloadingStatus.SUCCESS: return i18nString(UIStrings.statusSuccess); case SDK.PreloadingModel.PreloadingStatus.FAILURE: return i18nString(UIStrings.statusFailure); // NotSupported is used to handle unreachable case. For example, // there is no code path for // PreloadingTriggeringOutcome::kTriggeredButPending in prefetch, // which is mapped to NotSupported. So, we regard it as an // internal error. case SDK.PreloadingModel.PreloadingStatus.NOT_SUPPORTED: return i18n.i18n.lockedString('Internal error'); } } static preloadsStatusSummary(countsByStatus: Map<SDK.PreloadingModel.PreloadingStatus, number>): string { const LIST = [ SDK.PreloadingModel.PreloadingStatus.NOT_TRIGGERED, SDK.PreloadingModel.PreloadingStatus.PENDING, SDK.PreloadingModel.PreloadingStatus.RUNNING, SDK.PreloadingModel.PreloadingStatus.READY, SDK.PreloadingModel.PreloadingStatus.SUCCESS, SDK.PreloadingModel.PreloadingStatus.FAILURE, ]; return LIST.filter(status => (countsByStatus?.get(status) || 0) > 0) .map(status => (countsByStatus?.get(status) || 0) + ' ' + this.status(status)) .join(', ') .toLocaleLowerCase(); } // Summary of error of rule set shown in grid. static validity({errorType}: Protocol.Preload.RuleSet): string { switch (errorType) { case undefined: return i18nString(UIStrings.validityValid); case Protocol.Preload.RuleSetErrorType.SourceIsNotJsonObject: return i18nString(UIStrings.validityInvalid); case Protocol.Preload.RuleSetErrorType.InvalidRulesSkipped: return i18nString(UIStrings.validitySomeRulesInvalid); } } // Where a rule set came from, shown in grid. static location(ruleSet: Protocol.Preload.RuleSet): string { if (ruleSet.backendNodeId !== undefined) { return i18n.i18n.lockedString('<script>'); } if (ruleSet.url !== undefined) { return ruleSet.url; } throw new Error('unreachable'); } static processLocalId(id: Protocol.Preload.RuleSetId): string { // RuleSetId is form of '<processId>.<processLocalId>' const index = id.indexOf('.'); return index === -1 ? id : id.slice(index + 1); } } function pageURL(): Platform.DevToolsPath.UrlString { return SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() || ('' as Platform.DevToolsPath.UrlString); } export class PreloadingRuleSetView extends UI.Widget.VBox { private model: SDK.PreloadingModel.PreloadingModel; private focusedRuleSetId: Protocol.Preload.RuleSetId|null = null; private readonly warningsContainer: HTMLDivElement; private readonly warningsView = new PreloadingWarningsView(); private readonly hsplit: HTMLElement; private readonly ruleSetGrid = new PreloadingComponents.RuleSetGrid.RuleSetGrid(); private readonly ruleSetDetails = new PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView(); private shouldPrettyPrint = Common.Settings.Settings.instance().moduleSetting('auto-pretty-print-minified').get(); constructor(model: SDK.PreloadingModel.PreloadingModel) { super(/* isWebComponent */ true, /* delegatesFocus */ false); this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles); this.model = model; SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this)); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this, {scoped: true}); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, this.warningsView.onWarningsUpdated, this.warningsView, {scoped: true}); // this (VBox) // +- warningsContainer // +- PreloadingWarningsView // +- hsplit // +- leftContainer // +- RuleSetGrid // +- rightContainer // +- RuleSetDetailsView // // - If an row of RuleSetGrid selected, RuleSetDetailsView shows details of it. // - If not, RuleSetDetailsView hides. this.warningsContainer = document.createElement('div'); this.warningsContainer.classList.add('flex-none'); this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild); this.warningsView.show(this.warningsContainer); this.ruleSetGrid.addEventListener('select', this.onRuleSetsGridCellFocused.bind(this)); const onPrettyPrintToggle = (): void => { this.shouldPrettyPrint = !this.shouldPrettyPrint; this.updateRuleSetDetails(); }; // clang-format off render( html` <div class="empty-state"> <span class="empty-state-header">${i18nString(UIStrings.noRulesDetected)}</span> <div class="empty-state-description"> <span>${i18nString(UIStrings.rulesDescription)}</span> ${UI.XLink.XLink.create(SPECULATION_EXPLANATION_URL, i18nString(UIStrings.learnMore), 'x-link', undefined, 'learn-more')} </div> </div> <devtools-split-view sidebar-position="second"> <div slot="main"> ${this.ruleSetGrid} </div> <div slot="sidebar" jslog=${VisualLogging.section('rule-set-details')}> ${this.ruleSetDetails} </div> </devtools-split-view> <div class="pretty-print-button" style="border-top: 1px solid var(--sys-color-divider)"> <devtools-button .iconName=${'brackets'} .toggledIconName=${'brackets'} .toggled=${this.shouldPrettyPrint} .toggleType=${Buttons.Button.ToggleType.PRIMARY} .title=${i18nString(UIStrings.prettyPrint)} .variant=${Buttons.Button.Variant.ICON_TOGGLE} .size=${Buttons.Button.Size.REGULAR} @click=${onPrettyPrintToggle} jslog=${VisualLogging.action().track({click: true}).context('preloading-status-panel-pretty-print')}></devtools-button> </div>`, this.contentElement, {host: this}); // clang-format on this.hsplit = this.contentElement.querySelector('devtools-split-view') as HTMLElement; } override wasShown(): void { super.wasShown(); this.warningsView.wasShown(); this.render(); } onScopeChange(): void { const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel); assertNotNullOrUndefined(model); this.model = model; this.render(); } revealRuleSet(revealInfo: PreloadingHelper.PreloadingForward.RuleSetView): void { this.focusedRuleSetId = revealInfo.ruleSetId; this.render(); } private updateRuleSetDetails(): void { const id = this.focusedRuleSetId; const ruleSet = id === null ? null : this.model.getRuleSetById(id); this.ruleSetDetails.shouldPrettyPrint = this.shouldPrettyPrint; this.ruleSetDetails.data = ruleSet; if (ruleSet === null) { this.hsplit.setAttribute('sidebar-visibility', 'hidden'); } else { this.hsplit.removeAttribute('sidebar-visibility'); } } render(): void { // Update rule sets grid const countsByRuleSetId = this.model.getPreloadCountsByRuleSetId(); const ruleSetRows = this.model.getAllRuleSets().map(({id, value}) => { const countsByStatus = countsByRuleSetId.get(id) || new Map<SDK.PreloadingModel.PreloadingStatus, number>(); return { ruleSet: value, preloadsStatusSummary: PreloadingUIUtils.preloadsStatusSummary(countsByStatus), }; }); this.ruleSetGrid.update({rows: ruleSetRows, pageURL: pageURL()}); this.contentElement.classList.toggle('empty', ruleSetRows.length === 0); this.updateRuleSetDetails(); } private onRuleSetsGridCellFocused(event: Event): void { const focusedEvent = event as CustomEvent<Protocol.Preload.RuleSetId>; this.focusedRuleSetId = focusedEvent.detail; this.render(); } getInfobarContainerForTest(): HTMLElement { return this.warningsView.contentElement; } getRuleSetGridForTest(): PreloadingComponents.RuleSetGrid.RuleSetGrid { return this.ruleSetGrid; } getRuleSetDetailsForTest(): PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView { return this.ruleSetDetails; } } export class PreloadingAttemptView extends UI.Widget.VBox { private model: SDK.PreloadingModel.PreloadingModel; // Note that we use id of (representative) preloading attempt while we show pipelines in grid. // This is because `NOT_TRIGGERED` preloading attempts don't have pipeline id and we can use it. private focusedPreloadingAttemptId: SDK.PreloadingModel.PreloadingAttemptId|null = null; private readonly warningsContainer: HTMLDivElement; private readonly warningsView = new PreloadingWarningsView(); private readonly preloadingGrid = new PreloadingComponents.PreloadingGrid.PreloadingGrid(); private readonly preloadingDetails = new PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView(); private readonly ruleSetSelector: PreloadingRuleSetSelector; constructor(model: SDK.PreloadingModel.PreloadingModel) { super(/* isWebComponent */ true, /* delegatesFocus */ false); this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles); this.element.setAttribute('jslog', `${VisualLogging.pane('preloading-speculations')}`); this.model = model; SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this)); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this, {scoped: true}); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, this.warningsView.onWarningsUpdated, this.warningsView, {scoped: true}); // this (VBox) // +- warningsContainer // +- PreloadingWarningsView // +- VBox // +- toolbar (filtering) // +- hsplit // +- leftContainer // +- PreloadingGrid // +- rightContainer // +- PreloadingDetailsReportView // // - If an row of PreloadingGrid selected, PreloadingDetailsReportView shows details of it. // - If not, PreloadingDetailsReportView shows some messages. this.warningsContainer = document.createElement('div'); this.warningsContainer.classList.add('flex-none'); this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild); this.warningsView.show(this.warningsContainer); const vbox = new UI.Widget.VBox(); const toolbar = vbox.contentElement.createChild('devtools-toolbar', 'preloading-toolbar'); toolbar.setAttribute('jslog', `${VisualLogging.toolbar()}`); this.ruleSetSelector = new PreloadingRuleSetSelector(() => this.render()); toolbar.appendToolbarItem(this.ruleSetSelector.item()); this.preloadingGrid.addEventListener('select', this.onPreloadingGridCellFocused.bind(this)); render( html` <div class="empty-state"> <span class="empty-state-header">${i18nString(UIStrings.noPrefetchAttempts)}</span> <div class="empty-state-description"> <span>${i18nString(UIStrings.prefetchDescription)}</span> ${ UI.XLink.XLink.create( SPECULATION_EXPLANATION_URL, i18nString(UIStrings.learnMore), 'x-link', undefined, 'learn-more')} </div> </div> <devtools-split-view sidebar-position="second"> <div slot="main" class="overflow-auto" style="height: 100%"> ${this.preloadingGrid} </div> <div slot="sidebar" class="overflow-auto" style="height: 100%"> ${this.preloadingDetails} </div> </devtools-split-view>`, vbox.contentElement, {host: this}); vbox.show(this.contentElement); } override wasShown(): void { super.wasShown(); this.warningsView.wasShown(); this.render(); } onScopeChange(): void { const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel); assertNotNullOrUndefined(model); this.model = model; this.render(); } setFilter(filter: PreloadingHelper.PreloadingForward.AttemptViewWithFilter): void { let id: Protocol.Preload.RuleSetId|null = filter.ruleSetId; if (id !== null && this.model.getRuleSetById(id) === undefined) { id = null; } this.ruleSetSelector.select(id); } private updatePreloadingDetails(): void { const id = this.focusedPreloadingAttemptId; const preloadingAttempt = id === null ? null : this.model.getPreloadingAttemptById(id); if (preloadingAttempt === null) { this.preloadingDetails.data = null; } else { const pipeline = this.model.getPipeline(preloadingAttempt); const ruleSets = preloadingAttempt.ruleSetIds.map(id => this.model.getRuleSetById(id)).filter(x => x !== null); this.preloadingDetails.data = { pipeline, ruleSets, pageURL: pageURL(), }; } } render(): void { // Update preloading grid const filteringRuleSetId = this.ruleSetSelector.getSelected(); const rows = this.model.getRepresentativePreloadingAttempts(filteringRuleSetId).map(({id, value}) => { const attempt = value; const pipeline = this.model.getPipeline(attempt); const ruleSets = attempt.ruleSetIds.flatMap(id => { const ruleSet = this.model.getRuleSetById(id); return ruleSet === null ? [] : [ruleSet]; }); return { id, pipeline, ruleSets, }; }); this.preloadingGrid.update({rows, pageURL: pageURL()}); this.contentElement.classList.toggle('empty', rows.length === 0); this.updatePreloadingDetails(); } private onPreloadingGridCellFocused(event: Event): void { const focusedEvent = event as CustomEvent<SDK.PreloadingModel.PreloadingAttemptId>; this.focusedPreloadingAttemptId = focusedEvent.detail; this.render(); } getRuleSetSelectorToolbarItemForTest(): UI.Toolbar.ToolbarItem { return this.ruleSetSelector.item(); } getPreloadingGridForTest(): PreloadingComponents.PreloadingGrid.PreloadingGrid { return this.preloadingGrid; } getPreloadingDetailsForTest(): PreloadingComponents.PreloadingDetailsReportView.PreloadingDetailsReportView { return this.preloadingDetails; } selectRuleSetOnFilterForTest(id: Protocol.Preload.RuleSetId|null): void { this.ruleSetSelector.select(id); } } export class PreloadingSummaryView extends UI.Widget.VBox { private model: SDK.PreloadingModel.PreloadingModel; private readonly warningsContainer: HTMLDivElement; private readonly warningsView = new PreloadingWarningsView(); private readonly usedPreloading = new PreloadingComponents.UsedPreloadingView.UsedPreloadingView(); constructor(model: SDK.PreloadingModel.PreloadingModel) { super(/* isWebComponent */ true, /* delegatesFocus */ false); this.registerRequiredCSS(emptyWidgetStyles, preloadingViewStyles); this.element.setAttribute('jslog', `${VisualLogging.pane('speculative-loads')}`); this.model = model; SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this)); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.render, this, {scoped: true}); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.WARNINGS_UPDATED, this.warningsView.onWarningsUpdated, this.warningsView, {scoped: true}); this.warningsContainer = document.createElement('div'); this.warningsContainer.classList.add('flex-none'); this.contentElement.insertBefore(this.warningsContainer, this.contentElement.firstChild); this.warningsView.show(this.warningsContainer); const usedPreloadingContainer = new UI.Widget.VBox(); usedPreloadingContainer.contentElement.appendChild(this.usedPreloading); usedPreloadingContainer.show(this.contentElement); } override wasShown(): void { super.wasShown(); this.warningsView.wasShown(); this.render(); } onScopeChange(): void { const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel); assertNotNullOrUndefined(model); this.model = model; this.render(); } render(): void { this.usedPreloading.data = { pageURL: SDK.TargetManager.TargetManager.instance().scopeTarget()?.inspectedURL() || ('' as Platform.DevToolsPath.UrlString), previousAttempts: this.model.getRepresentativePreloadingAttemptsOfPreviousPage().map(({value}) => value), currentAttempts: this.model.getRepresentativePreloadingAttempts(null).map(({value}) => value), }; } getUsedPreloadingForTest(): PreloadingComponents.UsedPreloadingView.UsedPreloadingView { return this.usedPreloading; } } class PreloadingRuleSetSelector implements UI.Toolbar.Provider, UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> { private model: SDK.PreloadingModel.PreloadingModel; private readonly onSelectionChanged: () => void = () => {}; private readonly toolbarItem: UI.Toolbar.ToolbarItem; private readonly listModel: UI.ListModel.ListModel<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>; private readonly dropDown: UI.SoftDropDown.SoftDropDown<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>; constructor(onSelectionChanged: () => void) { const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel); assertNotNullOrUndefined(model); this.model = model; SDK.TargetManager.TargetManager.instance().addScopeChangeListener(this.onScopeChange.bind(this)); SDK.TargetManager.TargetManager.instance().addModelListener( SDK.PreloadingModel.PreloadingModel, SDK.PreloadingModel.Events.MODEL_UPDATED, this.onModelUpdated, this, {scoped: true}); this.listModel = new UI.ListModel.ListModel(); this.dropDown = new UI.SoftDropDown.SoftDropDown(this.listModel, this); this.dropDown.setRowHeight(36); this.dropDown.setPlaceholderText(i18nString(UIStrings.filterAllPreloads)); this.toolbarItem = new UI.Toolbar.ToolbarItem(this.dropDown.element); this.toolbarItem.setTitle(i18nString(UIStrings.filterFilterByRuleSet)); this.toolbarItem.element.classList.add('toolbar-has-dropdown'); this.toolbarItem.element.setAttribute( 'jslog', `${VisualLogging.action('filter-by-rule-set').track({click: true})}`); // Initializes `listModel` and `dropDown` using data of the model. this.onModelUpdated(); // Prevents emitting onSelectionChanged on the first call of `this.onModelUpdated()` for initialization. this.onSelectionChanged = onSelectionChanged; } private onScopeChange(): void { const model = SDK.TargetManager.TargetManager.instance().scopeTarget()?.model(SDK.PreloadingModel.PreloadingModel); assertNotNullOrUndefined(model); this.model = model; this.onModelUpdated(); } private onModelUpdated(): void { const ids = this.model.getAllRuleSets().map(({id}) => id); const items = [AllRuleSetRootId, ...ids] as [typeof AllRuleSetRootId, ...Protocol.Preload.RuleSetId[]]; const selected = this.dropDown.getSelectedItem(); // Use `AllRuleSetRootId` by default. For example, `selected` is null or has gone. const newSelected = (selected === null || !items.includes(selected)) ? AllRuleSetRootId : selected; this.listModel.replaceAll(items); this.dropDown.selectItem(newSelected); this.updateWidth(items); } // Updates the width for the DropDown element. private updateWidth(items: Array<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId>): void { // Width set by `UI.SoftDropDown`. const DEFAULT_WIDTH = 315; const urlLengths = items.map(x => this.titleFor(x).length); const maxLength = Math.max(...urlLengths); const width = Math.min(maxLength * 6 + 16, DEFAULT_WIDTH); this.dropDown.setWidth(width); } // AllRuleSetRootId is used within the selector to indicate the root item. When interacting with PreloadingModel, // it should be translated to null. private translateItemIdToRuleSetId(id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): Protocol.Preload.RuleSetId |null { if (id === AllRuleSetRootId) { return null; } return id as Protocol.Preload.RuleSetId; } getSelected(): Protocol.Preload.RuleSetId|null { const selectItem = this.dropDown.getSelectedItem(); if (selectItem === null) { return null; } return this.translateItemIdToRuleSetId(selectItem); } select(id: Protocol.Preload.RuleSetId|null): void { this.dropDown.selectItem(id); } // Method for UI.Toolbar.Provider item(): UI.Toolbar.ToolbarItem { return this.toolbarItem; } // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> titleFor(id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): string { const convertedId = this.translateItemIdToRuleSetId(id); if (convertedId === null) { return i18nString(UIStrings.filterAllPreloads); } const ruleSet = this.model.getRuleSetById(convertedId); if (ruleSet === null) { return i18n.i18n.lockedString('Internal error'); } return ruleSetTagOrLocationShort(ruleSet, pageURL()); } subtitleFor(id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): string { const convertedId = this.translateItemIdToRuleSetId(id); const countsByStatus = this.model.getPreloadCountsByRuleSetId().get(convertedId) || new Map<SDK.PreloadingModel.PreloadingStatus, number>(); return PreloadingUIUtils.preloadsStatusSummary(countsByStatus) || `(${i18nString(UIStrings.noRuleSets)})`; } // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> createElementForItem(id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): Element { const element = document.createElement('div'); const shadowRoot = UI.UIUtils.createShadowRootWithCoreStyles(element, {cssFile: preloadingViewDropDownStyles}); const title = shadowRoot.createChild('div', 'title'); UI.UIUtils.createTextChild(title, Platform.StringUtilities.trimEndWithMaxLength(this.titleFor(id), 100)); const subTitle = shadowRoot.createChild('div', 'subtitle'); UI.UIUtils.createTextChild(subTitle, this.subtitleFor(id)); return element; } // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> isItemSelectable(_id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): boolean { return true; } // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> itemSelected(_id: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId): void { this.onSelectionChanged(); } // Method for UI.SoftDropDown.Delegate<Protocol.Preload.RuleSetId|typeof AllRuleSetRootId> highlightedItemChanged( _from: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId, _to: Protocol.Preload.RuleSetId|typeof AllRuleSetRootId, _fromElement: Element|null, _toElement: Element|null): void { } } export class PreloadingWarningsView extends UI.Widget.VBox { private readonly infobar = new PreloadingComponents.PreloadingDisabledInfobar.PreloadingDisabledInfobar(); constructor() { super(/* isWebComponent */ false, /* delegatesFocus */ false); this.registerRequiredCSS(emptyWidgetStyles); } override wasShown(): void { super.wasShown(); this.contentElement.append(this.infobar); } onWarningsUpdated(args: Common.EventTarget.EventTargetEvent<Protocol.Preload.PreloadEnabledStateUpdatedEvent>): void { this.infobar.data = args.data; } }