chrome-devtools-frontend
Version:
Chrome DevTools UI
669 lines (607 loc) • 26.7 kB
text/typescript
// Copyright (c) 2016 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 */
import '../../ui/legacy/legacy.js';
import * as Common from '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Bindings from '../../models/bindings/bindings.js';
import * as Workspace from '../../models/workspace/workspace.js';
import * as Buttons from '../../ui/components/buttons/buttons.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
import {CoverageDecorationManager} from './CoverageDecorationManager.js';
import {CoverageListView} from './CoverageListView.js';
import {type CoverageInfo, CoverageModel, CoverageType, Events, type URLCoverageInfo} from './CoverageModel.js';
import coverageViewStyles from './coverageView.css.js';
const UIStrings = {
/**
*@description Tooltip in Coverage List View of the Coverage tab for selecting JavaScript coverage mode
*/
chooseCoverageGranularityPer:
'Choose coverage granularity: Per function has low overhead, per block has significant overhead.',
/**
*@description Text in Coverage List View of the Coverage tab
*/
perFunction: 'Per function',
/**
*@description Text in Coverage List View of the Coverage tab
*/
perBlock: 'Per block',
/**
*@description Text in Coverage View of the Coverage tab
*/
filterByUrl: 'Filter by URL',
/**
*@description Label for the type filter in the Converage Panel
*/
filterCoverageByType: 'Filter coverage by type',
/**
*@description Text for everything
*/
all: 'All',
/**
*@description Text that appears on a button for the css resource type filter.
*/
css: 'CSS',
/**
*@description Text in Timeline Tree View of the Performance panel
*/
javascript: 'JavaScript',
/**
*@description Tooltip text that appears on the setting when hovering over it in Coverage View of the Coverage tab
*/
includeExtensionContentScripts: 'Include extension content scripts',
/**
*@description Title for a type of source files
*/
contentScripts: 'Content scripts',
/**
*@description Message in Coverage View of the Coverage tab
*/
noCoverageData: 'No coverage data',
/**
*@description Message in Coverage View of the Coverage tab
*/
reloadPage: 'Reload page',
/**
*@description Message in Coverage View of the Coverage tab
*/
startRecording: 'Start recording',
/**
*@description Message in Coverage View of the Coverage tab
*@example {Reload page} PH1
*/
clickTheReloadButtonSToReloadAnd: 'Click the "{PH1}" button to reload and start capturing coverage.',
/**
*@description Message in Coverage View of the Coverage tab
*@example {Start recording} PH1
*/
clickTheRecordButtonSToStart: 'Click the "{PH1}" button to start capturing coverage.',
/**
*@description Message in the Coverage View explaining that DevTools could not capture coverage.
*/
bfcacheNoCapture: 'Could not capture coverage info because the page was served from the back/forward cache.',
/**
*@description Message in the Coverage View explaining that DevTools could not capture coverage.
*/
activationNoCapture: 'Could not capture coverage info because the page was prerendered in the background.',
/**
*@description Message in the Coverage View prompting the user to reload the page.
*@example {reload button icon} PH1
*/
reloadPrompt: 'Click the reload button {PH1} to reload and get coverage.',
/**
*@description Footer message in Coverage View of the Coverage tab
*@example {300k used, 600k unused} PH1
*@example {500k used, 800k unused} PH2
*/
filteredSTotalS: 'Filtered: {PH1} Total: {PH2}',
/**
*@description Footer message in Coverage View of the Coverage tab
*@example {1.5 MB} PH1
*@example {2.1 MB} PH2
*@example {71%} PH3
*@example {29%} PH4
*/
sOfSSUsedSoFarSUnused: '{PH1} of {PH2} ({PH3}%) used so far, {PH4} unused.',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/coverage/CoverageView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
let coverageViewInstance: CoverageView|undefined;
export class CoverageView extends UI.Widget.VBox {
private model: CoverageModel|null;
private decorationManager: CoverageDecorationManager|null;
private readonly coverageTypeComboBox: UI.Toolbar.ToolbarComboBox;
private readonly coverageTypeComboBoxSetting: Common.Settings.Setting<number>;
private toggleRecordAction: UI.ActionRegistration.Action;
private readonly toggleRecordButton: UI.Toolbar.ToolbarButton;
private inlineReloadButton: Element|null;
private readonly startWithReloadButton: UI.Toolbar.ToolbarButton|undefined;
private readonly clearAction: UI.ActionRegistration.Action;
private readonly exportAction: UI.ActionRegistration.Action;
private textFilterRegExp: RegExp|null;
private readonly filterInput: UI.Toolbar.ToolbarInput;
private typeFilterValue: number|null;
private readonly filterByTypeComboBox: UI.Toolbar.ToolbarComboBox;
private showContentScriptsSetting: Common.Settings.Setting<boolean>;
private readonly contentScriptsCheckbox: UI.Toolbar.ToolbarSettingCheckbox;
private readonly coverageResultsElement: HTMLElement;
private readonly landingPage: UI.Widget.VBox;
private readonly bfcacheReloadPromptPage: UI.Widget.VBox;
private readonly activationReloadPromptPage: UI.Widget.VBox;
private listView: CoverageListView;
private readonly statusToolbarElement: HTMLElement;
private statusMessageElement: HTMLElement;
constructor() {
super(true);
this.registerRequiredCSS(coverageViewStyles);
this.element.setAttribute('jslog', `${VisualLogging.panel('coverage').track({resize: true})}`);
this.model = null;
this.decorationManager = null;
const toolbarContainer = this.contentElement.createChild('div', 'coverage-toolbar-container');
toolbarContainer.setAttribute('jslog', `${VisualLogging.toolbar()}`);
toolbarContainer.role = 'toolbar';
const toolbar = toolbarContainer.createChild('devtools-toolbar', 'coverage-toolbar');
toolbar.role = 'presentation';
toolbar.wrappable = true;
this.coverageTypeComboBox = new UI.Toolbar.ToolbarComboBox(
this.onCoverageTypeComboBoxSelectionChanged.bind(this), i18nString(UIStrings.chooseCoverageGranularityPer),
undefined, 'coverage-type');
const coverageTypes = [
{
label: i18nString(UIStrings.perFunction),
value: CoverageType.JAVA_SCRIPT | CoverageType.JAVA_SCRIPT_PER_FUNCTION,
},
{
label: i18nString(UIStrings.perBlock),
value: CoverageType.JAVA_SCRIPT,
},
];
for (const type of coverageTypes) {
this.coverageTypeComboBox.addOption(this.coverageTypeComboBox.createOption(type.label, `${type.value}`));
}
this.coverageTypeComboBoxSetting =
Common.Settings.Settings.instance().createSetting('coverage-view-coverage-type', 0);
this.coverageTypeComboBox.setSelectedIndex(this.coverageTypeComboBoxSetting.get());
this.coverageTypeComboBox.setEnabled(true);
toolbar.appendToolbarItem(this.coverageTypeComboBox);
this.toggleRecordAction = UI.ActionRegistry.ActionRegistry.instance().getAction('coverage.toggle-recording');
this.toggleRecordButton = UI.Toolbar.Toolbar.createActionButton(this.toggleRecordAction);
toolbar.appendToolbarItem(this.toggleRecordButton);
const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
const mainTargetSupportsRecordOnReload = mainTarget?.model(SDK.ResourceTreeModel.ResourceTreeModel);
this.inlineReloadButton = null;
if (mainTargetSupportsRecordOnReload) {
this.startWithReloadButton = UI.Toolbar.Toolbar.createActionButton('coverage.start-with-reload');
toolbar.appendToolbarItem(this.startWithReloadButton);
this.toggleRecordButton.setEnabled(false);
this.toggleRecordButton.setVisible(false);
}
this.clearAction = UI.ActionRegistry.ActionRegistry.instance().getAction('coverage.clear');
this.clearAction.setEnabled(false);
toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this.clearAction));
toolbar.appendSeparator();
this.exportAction = UI.ActionRegistry.ActionRegistry.instance().getAction('coverage.export');
this.exportAction.setEnabled(false);
toolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this.exportAction));
this.textFilterRegExp = null;
toolbar.appendSeparator();
this.filterInput = new UI.Toolbar.ToolbarFilter(i18nString(UIStrings.filterByUrl), 1, 1);
this.filterInput.setEnabled(false);
this.filterInput.addEventListener(UI.Toolbar.ToolbarInput.Event.TEXT_CHANGED, this.onFilterChanged, this);
toolbar.appendToolbarItem(this.filterInput);
toolbar.appendSeparator();
this.typeFilterValue = null;
this.filterByTypeComboBox = new UI.Toolbar.ToolbarComboBox(
this.onFilterByTypeChanged.bind(this), i18nString(UIStrings.filterCoverageByType), undefined,
'coverage-by-type');
const options = [
{
label: i18nString(UIStrings.all),
value: '',
},
{
label: i18nString(UIStrings.css),
value: CoverageType.CSS,
},
{
label: i18nString(UIStrings.javascript),
value: CoverageType.JAVA_SCRIPT | CoverageType.JAVA_SCRIPT_PER_FUNCTION,
},
];
for (const option of options) {
this.filterByTypeComboBox.addOption(this.filterByTypeComboBox.createOption(option.label, `${option.value}`));
}
this.filterByTypeComboBox.setSelectedIndex(0);
this.filterByTypeComboBox.setEnabled(false);
toolbar.appendToolbarItem(this.filterByTypeComboBox);
toolbar.appendSeparator();
this.showContentScriptsSetting = Common.Settings.Settings.instance().createSetting('show-content-scripts', false);
this.showContentScriptsSetting.addChangeListener(this.onFilterChanged, this);
this.contentScriptsCheckbox = new UI.Toolbar.ToolbarSettingCheckbox(
this.showContentScriptsSetting, i18nString(UIStrings.includeExtensionContentScripts),
i18nString(UIStrings.contentScripts));
this.contentScriptsCheckbox.setEnabled(false);
toolbar.appendToolbarItem(this.contentScriptsCheckbox);
this.coverageResultsElement = this.contentElement.createChild('div', 'coverage-results');
this.landingPage = this.buildLandingPage();
this.bfcacheReloadPromptPage = this.buildReloadPromptPage(i18nString(UIStrings.bfcacheNoCapture), 'bfcache-page');
this.activationReloadPromptPage =
this.buildReloadPromptPage(i18nString(UIStrings.activationNoCapture), 'prerender-page');
this.listView = new CoverageListView(this.isVisible.bind(this, false));
this.statusToolbarElement = this.contentElement.createChild('div', 'coverage-toolbar-summary');
this.statusMessageElement = this.statusToolbarElement.createChild('div', 'coverage-message');
this.landingPage.show(this.coverageResultsElement);
}
static instance(): CoverageView {
if (!coverageViewInstance) {
coverageViewInstance = new CoverageView();
}
return coverageViewInstance;
}
static removeInstance(): void {
coverageViewInstance = undefined;
}
private buildLandingPage(): UI.Widget.VBox {
const widget = new UI.EmptyWidget.EmptyWidget(i18nString(UIStrings.noCoverageData), '');
widget.link = 'https://developer.chrome.com/docs/devtools/coverage' as Platform.DevToolsPath.UrlString;
if (this.startWithReloadButton) {
const action = UI.ActionRegistry.ActionRegistry.instance().getAction('coverage.start-with-reload');
if (action) {
widget.text = i18nString(UIStrings.clickTheReloadButtonSToReloadAnd, {PH1: i18nString(UIStrings.reloadPage)});
const button = UI.UIUtils.createTextButton(
i18nString(UIStrings.reloadPage), () => action.execute(),
{jslogContext: action.id(), variant: Buttons.Button.Variant.TONAL});
widget.contentElement.append(button);
}
} else {
widget.text = i18nString(UIStrings.clickTheRecordButtonSToStart, {PH1: i18nString(UIStrings.startRecording)});
const button = UI.UIUtils.createTextButton(
i18nString(UIStrings.startRecording), () => this.toggleRecordAction.execute(),
{jslogContext: this.toggleRecordAction.id(), variant: Buttons.Button.Variant.TONAL});
widget.contentElement.append(button);
}
return widget;
}
private buildReloadPromptPage(message: Common.UIString.LocalizedString, className: string): UI.Widget.VBox {
const widget = new UI.Widget.VBox();
const reasonDiv = document.createElement('div');
reasonDiv.classList.add('message');
reasonDiv.textContent = message;
widget.contentElement.appendChild(reasonDiv);
this.inlineReloadButton =
UI.UIUtils.createInlineButton(UI.Toolbar.Toolbar.createActionButton('inspector-main.reload'));
const messageElement =
i18n.i18n.getFormatLocalizedString(str_, UIStrings.reloadPrompt, {PH1: this.inlineReloadButton});
messageElement.classList.add('message');
widget.contentElement.appendChild(messageElement);
widget.element.classList.add(className);
return widget;
}
clear(): void {
if (this.model) {
this.model.reset();
}
this.reset();
}
private reset(): void {
if (this.decorationManager) {
this.decorationManager.dispose();
this.decorationManager = null;
}
this.listView.reset();
this.listView.detach();
this.landingPage.show(this.coverageResultsElement);
this.statusMessageElement.textContent = '';
this.filterInput.setEnabled(false);
this.filterByTypeComboBox.setEnabled(false);
this.contentScriptsCheckbox.setEnabled(false);
this.exportAction.setEnabled(false);
}
toggleRecording(): void {
const enable = !this.toggleRecordAction.toggled();
if (enable) {
void this.startRecording({reload: false, jsCoveragePerBlock: this.isBlockCoverageSelected()});
} else {
void this.stopRecording();
}
}
isBlockCoverageSelected(): boolean {
const option = this.coverageTypeComboBox.selectedOption();
const coverageType = Number(option ? option.value : Number.NaN);
// Check that Coverage.CoverageType.JavaScriptPerFunction is not present.
return coverageType === CoverageType.JAVA_SCRIPT;
}
private selectCoverageType(jsCoveragePerBlock: boolean): void {
const selectedIndex = jsCoveragePerBlock ? 1 : 0;
this.coverageTypeComboBox.setSelectedIndex(selectedIndex);
}
private onCoverageTypeComboBoxSelectionChanged(): void {
this.coverageTypeComboBoxSetting.set(this.coverageTypeComboBox.selectedIndex());
}
async startRecording(options: {reload: (boolean|undefined), jsCoveragePerBlock: (boolean|undefined)}|null):
Promise<void> {
let hadFocus, reloadButtonFocused;
if ((this.startWithReloadButton?.element.hasFocus()) || (this.inlineReloadButton?.hasFocus())) {
reloadButtonFocused = true;
} else if (this.hasFocus()) {
hadFocus = true;
}
this.reset();
const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget();
if (!mainTarget) {
return;
}
const {reload, jsCoveragePerBlock} = {reload: false, jsCoveragePerBlock: false, ...options};
if (!this.model || reload) {
this.model = mainTarget.model(CoverageModel);
}
if (!this.model) {
return;
}
Host.userMetrics.actionTaken(Host.UserMetrics.Action.CoverageStarted);
if (jsCoveragePerBlock) {
Host.userMetrics.actionTaken(Host.UserMetrics.Action.CoverageStartedPerBlock);
}
const success = await this.model.start(Boolean(jsCoveragePerBlock));
if (!success) {
return;
}
this.selectCoverageType(Boolean(jsCoveragePerBlock));
this.model.addEventListener(Events.CoverageUpdated, this.onCoverageDataReceived, this);
this.model.addEventListener(Events.SourceMapResolved, this.updateListView, this);
const resourceTreeModel = mainTarget.model(SDK.ResourceTreeModel.ResourceTreeModel);
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged,
this.onPrimaryPageChanged, this);
this.decorationManager = new CoverageDecorationManager(
this.model, Workspace.Workspace.WorkspaceImpl.instance(),
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(),
Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance());
this.toggleRecordAction.setToggled(true);
this.clearAction.setEnabled(false);
if (this.startWithReloadButton) {
this.startWithReloadButton.setEnabled(false);
this.startWithReloadButton.setVisible(false);
this.toggleRecordButton.setEnabled(true);
this.toggleRecordButton.setVisible(true);
if (reloadButtonFocused) {
this.toggleRecordButton.focus();
}
}
this.coverageTypeComboBox.setEnabled(false);
this.filterInput.setEnabled(true);
this.filterByTypeComboBox.setEnabled(true);
this.contentScriptsCheckbox.setEnabled(true);
if (this.landingPage.isShowing()) {
this.landingPage.detach();
}
this.listView.show(this.coverageResultsElement);
if (hadFocus && !reloadButtonFocused) {
this.listView.focus();
}
if (reload && resourceTreeModel) {
resourceTreeModel.reloadPage();
} else {
void this.model.startPolling();
}
}
private onCoverageDataReceived(event: Common.EventTarget.EventTargetEvent<CoverageInfo[]>): void {
const data = event.data;
this.updateViews(data);
}
private updateListView(): void {
this.listView.update(this.model?.entries() || []);
}
async stopRecording(): Promise<void> {
SDK.TargetManager.TargetManager.instance().removeModelListener(
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged,
this.onPrimaryPageChanged, this);
if (this.hasFocus()) {
this.listView.focus();
}
// Stopping the model triggers one last poll to get the final data.
if (this.model) {
await this.model.stop();
this.model.removeEventListener(Events.CoverageUpdated, this.onCoverageDataReceived, this);
}
this.toggleRecordAction.setToggled(false);
this.coverageTypeComboBox.setEnabled(true);
if (this.startWithReloadButton) {
this.startWithReloadButton.setEnabled(true);
this.startWithReloadButton.setVisible(true);
this.toggleRecordButton.setEnabled(false);
this.toggleRecordButton.setVisible(false);
}
this.clearAction.setEnabled(true);
}
private async onPrimaryPageChanged(
event: Common.EventTarget.EventTargetEvent<
{frame: SDK.ResourceTreeModel.ResourceTreeFrame, type: SDK.ResourceTreeModel.PrimaryPageChangeType}>):
Promise<void> {
const frame = event.data.frame;
const coverageModel = frame.resourceTreeModel().target().model(CoverageModel);
if (!coverageModel) {
return;
}
// If the primary page target has changed (due to MPArch activation), switch to new CoverageModel.
if (this.model !== coverageModel) {
if (this.model) {
await this.model.stop();
this.model.removeEventListener(Events.CoverageUpdated, this.onCoverageDataReceived, this);
}
this.model = coverageModel;
const success = await this.model.start(this.isBlockCoverageSelected());
if (!success) {
return;
}
this.model.addEventListener(Events.CoverageUpdated, this.onCoverageDataReceived, this);
this.decorationManager = new CoverageDecorationManager(
this.model, Workspace.Workspace.WorkspaceImpl.instance(),
Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(),
Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance());
}
if (this.bfcacheReloadPromptPage.isShowing()) {
this.bfcacheReloadPromptPage.detach();
this.listView.show(this.coverageResultsElement);
}
if (this.activationReloadPromptPage.isShowing()) {
this.activationReloadPromptPage.detach();
this.listView.show(this.coverageResultsElement);
}
if (frame.backForwardCacheDetails.restoredFromCache) {
this.listView.detach();
this.bfcacheReloadPromptPage.show(this.coverageResultsElement);
}
if (event.data.type === SDK.ResourceTreeModel.PrimaryPageChangeType.ACTIVATION) {
this.listView.detach();
this.activationReloadPromptPage.show(this.coverageResultsElement);
}
this.model.reset();
this.decorationManager && this.decorationManager.reset();
this.listView.reset();
void this.model.startPolling();
}
private updateViews(updatedEntries: CoverageInfo[]): void {
this.updateStats();
this.listView.update(this.model?.entries() || []);
this.exportAction.setEnabled(this.model !== null && this.model.entries().length > 0);
this.decorationManager && this.decorationManager.update(updatedEntries);
}
private updateStats(): void {
const all = {total: 0, unused: 0};
const filtered = {total: 0, unused: 0};
const filterApplied = this.textFilterRegExp !== null;
if (this.model) {
for (const info of this.model.entries()) {
all.total += info.size();
all.unused += info.unusedSize();
if (this.isVisible(false, info)) {
if (this.textFilterRegExp?.test(info.url())) {
filtered.total += info.size();
filtered.unused += info.unusedSize();
} else {
// If it doesn't match the filter, calculate the stats from visible children if there are any
for (const childInfo of info.sourcesURLCoverageInfo.values()) {
if (this.isVisible(false, childInfo)) {
filtered.total += childInfo.size();
filtered.unused += childInfo.unusedSize();
}
}
}
}
}
}
this.statusMessageElement.textContent = filterApplied ?
i18nString(UIStrings.filteredSTotalS, {PH1: formatStat(filtered), PH2: formatStat(all)}) :
formatStat(all);
function formatStat({total, unused}: {total: number, unused: number}): string {
const used = total - unused;
const percentUsed = total ? Math.round(100 * used / total) : 0;
return i18nString(UIStrings.sOfSSUsedSoFarSUnused, {
PH1: i18n.ByteUtilities.bytesToString(used),
PH2: i18n.ByteUtilities.bytesToString(total),
PH3: percentUsed,
PH4: i18n.ByteUtilities.bytesToString(unused),
});
}
}
private onFilterChanged(): void {
if (!this.listView) {
return;
}
const text = this.filterInput.value();
this.textFilterRegExp = text ? Platform.StringUtilities.createPlainTextSearchRegex(text, 'i') : null;
this.listView.updateFilterAndHighlight(this.textFilterRegExp);
this.updateStats();
}
private onFilterByTypeChanged(): void {
if (!this.listView) {
return;
}
Host.userMetrics.actionTaken(Host.UserMetrics.Action.CoverageReportFiltered);
const option = this.filterByTypeComboBox.selectedOption();
const type = option?.value;
this.typeFilterValue = parseInt(type || '', 10) || null;
this.listView.updateFilterAndHighlight(this.textFilterRegExp);
this.updateStats();
}
private isVisible(ignoreTextFilter: boolean, coverageInfo: URLCoverageInfo): boolean {
const url = coverageInfo.url();
if (url.startsWith(CoverageView.EXTENSION_BINDINGS_URL_PREFIX)) {
return false;
}
if (coverageInfo.isContentScript() && !this.showContentScriptsSetting.get()) {
return false;
}
if (this.typeFilterValue && !(coverageInfo.type() & this.typeFilterValue)) {
return false;
}
// If it's a parent, check if any children are visible
if (coverageInfo.sourcesURLCoverageInfo.size > 0) {
for (const sourceURLCoverageInfo of coverageInfo.sourcesURLCoverageInfo.values()) {
if (this.isVisible(ignoreTextFilter, sourceURLCoverageInfo)) {
return true;
}
}
}
return ignoreTextFilter || !this.textFilterRegExp || this.textFilterRegExp.test(url);
}
async exportReport(): Promise<void> {
const fos = new Bindings.FileUtils.FileOutputStream();
const fileName =
`Coverage-${Platform.DateUtilities.toISO8601Compact(new Date())}.json` as Platform.DevToolsPath.RawPathString;
const accepted = await fos.open(fileName);
if (!accepted) {
return;
}
this.model && await this.model.exportReport(fos);
}
selectCoverageItemByUrl(url: string): void {
this.listView.selectByUrl(url);
}
static readonly EXTENSION_BINDINGS_URL_PREFIX = 'extensions::';
override wasShown(): void {
UI.Context.Context.instance().setFlavor(CoverageView, this);
super.wasShown();
}
override willHide(): void {
super.willHide();
UI.Context.Context.instance().setFlavor(CoverageView, null);
}
}
export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
handleAction(_context: UI.Context.Context, actionId: string): boolean {
const coverageViewId = 'coverage';
void UI.ViewManager.ViewManager.instance()
.showView(coverageViewId, /** userGesture= */ false, /** omitFocus= */ true)
.then(() => {
const view = UI.ViewManager.ViewManager.instance().view(coverageViewId);
return view?.widget();
})
.then(widget => this.innerHandleAction(widget as CoverageView, actionId));
return true;
}
private innerHandleAction(coverageView: CoverageView, actionId: string): void {
switch (actionId) {
case 'coverage.toggle-recording':
coverageView.toggleRecording();
break;
case 'coverage.start-with-reload':
void coverageView.startRecording({reload: true, jsCoveragePerBlock: coverageView.isBlockCoverageSelected()});
break;
case 'coverage.clear':
coverageView.clear();
break;
case 'coverage.export':
void coverageView.exportReport();
break;
default:
console.assert(false, `Unknown action: ${actionId}`);
}
}
}