| // 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. |
| |
| 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 UI from '../../ui/legacy/legacy.js'; |
| |
| import {CoverageDecorationManager} from './CoverageDecorationManager.js'; |
| import {CoverageListView} from './CoverageListView.js'; |
| import coverageViewStyles from './coverageView.css.js'; |
| |
| import {CoverageModel, Events, CoverageType, type CoverageInfo, type URLCoverageInfo} from './CoverageModel.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 to clear everything |
| */ |
| clearAll: 'Clear all', |
| /** |
| *@description Tooltip text that appears when hovering over the largeicon download button in the Coverage View of the Coverage tab |
| */ |
| export: 'Export...', |
| /** |
| *@description Text in Coverage View of the Coverage tab |
| */ |
| urlFilter: 'URL filter', |
| /** |
| *@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 |
| *@example {record button icon} PH1 |
| */ |
| clickTheReloadButtonSToReloadAnd: 'Click the reload button {PH1} to reload and start capturing coverage.', |
| /** |
| *@description Message in Coverage View of the Coverage tab |
| *@example {record button icon} PH1 |
| */ |
| clickTheRecordButtonSToStart: 'Click the record button {PH1} 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.', |
| }; |
| 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 clearButton: UI.Toolbar.ToolbarButton; |
| private readonly saveButton: UI.Toolbar.ToolbarButton; |
| 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; |
| |
| private constructor() { |
| super(true); |
| |
| this.model = null; |
| this.decorationManager = null; |
| |
| const toolbarContainer = this.contentElement.createChild('div', 'coverage-toolbar-container'); |
| const toolbar = new UI.Toolbar.Toolbar('coverage-toolbar', toolbarContainer); |
| toolbar.makeWrappable(true); |
| |
| this.coverageTypeComboBox = new UI.Toolbar.ToolbarComboBox( |
| this.onCoverageTypeComboBoxSelectionChanged.bind(this), i18nString(UIStrings.chooseCoverageGranularityPer)); |
| const coverageTypes = [ |
| { |
| label: i18nString(UIStrings.perFunction), |
| value: CoverageType.JavaScript | CoverageType.JavaScriptPerFunction, |
| }, |
| { |
| label: i18nString(UIStrings.perBlock), |
| value: CoverageType.JavaScript, |
| }, |
| ]; |
| for (const type of coverageTypes) { |
| this.coverageTypeComboBox.addOption(this.coverageTypeComboBox.createOption(type.label, `${type.value}`)); |
| } |
| this.coverageTypeComboBoxSetting = Common.Settings.Settings.instance().createSetting('coverageViewCoverageType', 0); |
| this.coverageTypeComboBox.setSelectedIndex(this.coverageTypeComboBoxSetting.get()); |
| this.coverageTypeComboBox.setEnabled(true); |
| toolbar.appendToolbarItem(this.coverageTypeComboBox); |
| this.toggleRecordAction = |
| UI.ActionRegistry.ActionRegistry.instance().action('coverage.toggle-recording') as UI.ActionRegistration.Action; |
| this.toggleRecordButton = UI.Toolbar.Toolbar.createActionButton(this.toggleRecordAction); |
| toolbar.appendToolbarItem(this.toggleRecordButton); |
| |
| const mainTarget = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); |
| const mainTargetSupportsRecordOnReload = mainTarget && mainTarget.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| this.inlineReloadButton = null; |
| if (mainTargetSupportsRecordOnReload) { |
| const startWithReloadAction = UI.ActionRegistry.ActionRegistry.instance().action('coverage.start-with-reload') as |
| UI.ActionRegistration.Action; |
| this.startWithReloadButton = UI.Toolbar.Toolbar.createActionButton(startWithReloadAction); |
| toolbar.appendToolbarItem(this.startWithReloadButton); |
| this.toggleRecordButton.setEnabled(false); |
| this.toggleRecordButton.setVisible(false); |
| } |
| this.clearButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.clearAll), 'clear'); |
| this.clearButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, this.clear.bind(this)); |
| toolbar.appendToolbarItem(this.clearButton); |
| |
| toolbar.appendSeparator(); |
| this.saveButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.export), 'download'); |
| this.saveButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, _event => { |
| void this.exportReport(); |
| }); |
| toolbar.appendToolbarItem(this.saveButton); |
| this.saveButton.setEnabled(false); |
| |
| this.textFilterRegExp = null; |
| toolbar.appendSeparator(); |
| this.filterInput = new UI.Toolbar.ToolbarInput(i18nString(UIStrings.urlFilter), '', 0.4, 1); |
| this.filterInput.setEnabled(false); |
| this.filterInput.addEventListener(UI.Toolbar.ToolbarInput.Event.TextChanged, 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)); |
| const options = [ |
| { |
| label: i18nString(UIStrings.all), |
| value: '', |
| }, |
| { |
| label: i18nString(UIStrings.css), |
| value: CoverageType.CSS, |
| }, |
| { |
| label: i18nString(UIStrings.javascript), |
| value: CoverageType.JavaScript | CoverageType.JavaScriptPerFunction, |
| }, |
| ]; |
| 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('showContentScripts', 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.Widget.VBox(); |
| let message; |
| if (this.startWithReloadButton) { |
| this.inlineReloadButton = |
| UI.UIUtils.createInlineButton(UI.Toolbar.Toolbar.createActionButtonForId('coverage.start-with-reload')); |
| message = i18n.i18n.getFormatLocalizedString( |
| str_, UIStrings.clickTheReloadButtonSToReloadAnd, {PH1: this.inlineReloadButton}); |
| } else { |
| const recordButton = |
| UI.UIUtils.createInlineButton(UI.Toolbar.Toolbar.createActionButton(this.toggleRecordAction)); |
| message = i18n.i18n.getFormatLocalizedString(str_, UIStrings.clickTheRecordButtonSToStart, {PH1: recordButton}); |
| } |
| message.classList.add('message'); |
| widget.contentElement.appendChild(message); |
| widget.element.classList.add('landing-page'); |
| 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.createActionButtonForId('coverage.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; |
| } |
| |
| private 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.saveButton.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.JavaScript; |
| } |
| |
| private selectCoverageType(jsCoveragePerBlock: boolean): void { |
| const selectedIndex = jsCoveragePerBlock ? 1 : 0; |
| this.coverageTypeComboBox.setSelectedIndex(selectedIndex); |
| } |
| |
| private onCoverageTypeComboBoxSelectionChanged(): void { |
| this.coverageTypeComboBoxSetting.set(this.coverageTypeComboBox.selectedIndex()); |
| } |
| |
| async ensureRecordingStarted(): Promise<void> { |
| const enabled = this.toggleRecordAction.toggled(); |
| |
| if (enabled) { |
| await this.stopRecording(); |
| } |
| await this.startRecording({reload: false, jsCoveragePerBlock: false}); |
| } |
| |
| async startRecording(options: {reload: (boolean|undefined), jsCoveragePerBlock: (boolean|undefined)}| |
| null): Promise<void> { |
| let hadFocus, reloadButtonFocused; |
| if ((this.startWithReloadButton && this.startWithReloadButton.element.hasFocus()) || |
| (this.inlineReloadButton && 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); |
| const resourceTreeModel = |
| mainTarget.model(SDK.ResourceTreeModel.ResourceTreeModel) as SDK.ResourceTreeModel.ResourceTreeModel | null; |
| SDK.TargetManager.TargetManager.instance().addModelListener( |
| SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.PrimaryPageChanged, |
| this.onPrimaryPageChanged, this); |
| this.decorationManager = new CoverageDecorationManager(this.model as CoverageModel); |
| this.toggleRecordAction.setToggled(true); |
| this.clearButton.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); |
| } |
| |
| 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.clearButton.setEnabled(true); |
| } |
| |
| processBacklog(): void { |
| this.model && void this.model.processJSBacklog(); |
| } |
| |
| 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 as CoverageModel); |
| } |
| |
| 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 && this.model.entries() || []); |
| this.saveButton.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}; |
| let filterApplied = false; |
| if (this.model) { |
| for (const info of this.model.entries()) { |
| all.total += info.size(); |
| all.unused += info.unusedSize(); |
| if (this.isVisible(false, info)) { |
| filtered.total += info.size(); |
| filtered.unused += info.unusedSize(); |
| } else { |
| filterApplied = true; |
| } |
| } |
| } |
| 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: Platform.NumberUtilities.bytesToString(used), |
| PH2: Platform.NumberUtilities.bytesToString(total), |
| PH3: percentUsed, |
| PH4: Platform.NumberUtilities.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 && 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; |
| } |
| |
| return ignoreTextFilter || !this.textFilterRegExp || this.textFilterRegExp.test(url); |
| } |
| |
| private 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 { |
| super.wasShown(); |
| this.registerCSSFiles([coverageViewStyles]); |
| } |
| } |
| |
| let actionDelegateInstance: ActionDelegate; |
| |
| 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 && view.widget(); |
| }) |
| .then(widget => this.innerHandleAction(widget as CoverageView, actionId)); |
| |
| return true; |
| } |
| static instance(opts: {forceNew: boolean|null} = {forceNew: null}): ActionDelegate { |
| const {forceNew} = opts; |
| if (!actionDelegateInstance || forceNew) { |
| actionDelegateInstance = new ActionDelegate(); |
| } |
| return actionDelegateInstance; |
| } |
| |
| private innerHandleAction(coverageView: CoverageView, actionId: string): void { |
| let target = null; |
| let resourceTreeModel = null; |
| 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.reload': |
| target = SDK.TargetManager.TargetManager.instance().primaryPageTarget(); |
| if (target) { |
| resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (resourceTreeModel) { |
| resourceTreeModel.reloadPage(); |
| } |
| } |
| break; |
| default: |
| console.assert(false, `Unknown action: ${actionId}`); |
| } |
| } |
| } |