| // 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. |
| |
| Coverage.CoverageView = class extends UI.VBox { |
| constructor() { |
| super(true); |
| |
| /** @type {?Coverage.CoverageModel} */ |
| this._model = null; |
| /** @type {number|undefined} */ |
| this._pollTimer; |
| /** @type {?Coverage.CoverageDecorationManager} */ |
| this._decorationManager = null; |
| /** @type {?SDK.ResourceTreeModel} */ |
| this._resourceTreeModel = null; |
| |
| this.registerRequiredCSS('coverage/coverageView.css'); |
| |
| const toolbarContainer = this.contentElement.createChild('div', 'coverage-toolbar-container'); |
| const toolbar = new UI.Toolbar('coverage-toolbar', toolbarContainer); |
| |
| this._toggleRecordAction = |
| /** @type {!UI.Action }*/ (UI.actionRegistry.action('coverage.toggle-recording')); |
| this._toggleRecordButton = UI.Toolbar.createActionButton(this._toggleRecordAction); |
| toolbar.appendToolbarItem(this._toggleRecordButton); |
| |
| const mainTarget = SDK.targetManager.mainTarget(); |
| if (mainTarget && mainTarget.model(SDK.ResourceTreeModel)) { |
| const startWithReloadAction = |
| /** @type {!UI.Action }*/ (UI.actionRegistry.action('coverage.start-with-reload')); |
| this._startWithReloadButton = UI.Toolbar.createActionButton(startWithReloadAction); |
| toolbar.appendToolbarItem(this._startWithReloadButton); |
| } |
| this._clearButton = new UI.ToolbarButton(Common.UIString('Clear all'), 'largeicon-clear'); |
| this._clearButton.addEventListener(UI.ToolbarButton.Events.Click, this._clear.bind(this)); |
| toolbar.appendToolbarItem(this._clearButton); |
| |
| /** @type {?RegExp} */ |
| this._textFilterRegExp = null; |
| |
| toolbar.appendSeparator(); |
| this._filterInput = new UI.ToolbarInput(Common.UIString('URL filter'), 0.4, 1); |
| this._filterInput.setEnabled(false); |
| this._filterInput.addEventListener(UI.ToolbarInput.Event.TextChanged, this._onFilterChanged, this); |
| toolbar.appendToolbarItem(this._filterInput); |
| |
| toolbar.appendSeparator(); |
| this._showContentScriptsSetting = Common.settings.createSetting('showContentScripts', false); |
| this._showContentScriptsSetting.addChangeListener(this._onFilterChanged, this); |
| const contentScriptsCheckbox = new UI.ToolbarSettingCheckbox( |
| this._showContentScriptsSetting, Common.UIString('Include extension content scripts'), |
| Common.UIString('Content scripts')); |
| toolbar.appendToolbarItem(contentScriptsCheckbox); |
| |
| this._coverageResultsElement = this.contentElement.createChild('div', 'coverage-results'); |
| this._landingPage = this._buildLandingPage(); |
| this._listView = new Coverage.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); |
| } |
| |
| /** |
| * @return {!UI.VBox} |
| */ |
| _buildLandingPage() { |
| const recordButton = UI.createInlineButton(UI.Toolbar.createActionButton(this._toggleRecordAction)); |
| const widget = new UI.VBox(); |
| let message; |
| if (this._startWithReloadButton) { |
| const reloadButton = UI.createInlineButton(UI.Toolbar.createActionButtonForId('coverage.start-with-reload')); |
| message = UI.formatLocalized( |
| 'Click the record button %s to start capturing coverage.\n' + |
| 'Click the reload button %s to reload and start capturing coverage.', |
| [recordButton, reloadButton]); |
| } else { |
| message = UI.formatLocalized('Click the record button %s to start capturing coverage.', [recordButton]); |
| } |
| message.classList.add('message'); |
| widget.contentElement.appendChild(message); |
| widget.element.classList.add('landing-page'); |
| return widget; |
| } |
| |
| _clear() { |
| this._model = null; |
| this._reset(); |
| } |
| |
| _reset() { |
| 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); |
| } |
| |
| _toggleRecording() { |
| const enable = !this._toggleRecordAction.toggled(); |
| |
| if (enable) |
| this._startRecording(false); |
| else |
| this._stopRecording(); |
| } |
| |
| /** |
| * @param {boolean} reload |
| */ |
| _startRecording(reload) { |
| this._reset(); |
| const mainTarget = SDK.targetManager.mainTarget(); |
| if (!mainTarget) |
| return; |
| if (!this._model || reload) |
| this._model = new Coverage.CoverageModel(mainTarget); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.CoverageStarted); |
| if (!this._model.start()) |
| return; |
| this._resourceTreeModel = /** @type {?SDK.ResourceTreeModel} */ (mainTarget.model(SDK.ResourceTreeModel)); |
| if (this._resourceTreeModel) { |
| this._resourceTreeModel.addEventListener( |
| SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this); |
| } |
| this._decorationManager = new Coverage.CoverageDecorationManager(this._model); |
| this._toggleRecordAction.setToggled(true); |
| this._clearButton.setEnabled(false); |
| if (this._startWithReloadButton) |
| this._startWithReloadButton.setEnabled(false); |
| this._filterInput.setEnabled(true); |
| if (this._landingPage.isShowing()) |
| this._landingPage.detach(); |
| this._listView.show(this._coverageResultsElement); |
| if (reload && this._resourceTreeModel) |
| this._resourceTreeModel.reloadPage(); |
| else |
| this._poll(); |
| } |
| |
| async _poll() { |
| delete this._pollTimer; |
| const updates = await this._model.poll(); |
| this._updateViews(updates); |
| this._pollTimer = setTimeout(() => this._poll(), 700); |
| } |
| |
| async _stopRecording() { |
| if (this._pollTimer) { |
| clearTimeout(this._pollTimer); |
| delete this._pollTimer; |
| } |
| if (this._resourceTreeModel) { |
| this._resourceTreeModel.removeEventListener( |
| SDK.ResourceTreeModel.Events.MainFrameNavigated, this._onMainFrameNavigated, this); |
| this._resourceTreeModel = null; |
| } |
| const updatedEntries = await this._model.stop(); |
| this._updateViews(updatedEntries); |
| this._toggleRecordAction.setToggled(false); |
| if (this._startWithReloadButton) |
| this._startWithReloadButton.setEnabled(true); |
| this._clearButton.setEnabled(true); |
| } |
| |
| _onMainFrameNavigated() { |
| this._model.reset(); |
| this._decorationManager.reset(); |
| this._listView.reset(); |
| this._poll(); |
| } |
| |
| /** |
| * @param {!Array<!Coverage.CoverageInfo>} updatedEntries |
| */ |
| async _updateViews(updatedEntries) { |
| this._updateStats(); |
| this._listView.update(this._model.entries()); |
| this._decorationManager.update(updatedEntries); |
| } |
| |
| _updateStats() { |
| let total = 0; |
| let unused = 0; |
| for (const info of this._model.entries()) { |
| if (!this._isVisible(true, info)) |
| continue; |
| total += info.size(); |
| unused += info.unusedSize(); |
| } |
| |
| const percentUnused = total ? Math.round(100 * unused / total) : 0; |
| this._statusMessageElement.textContent = Common.UIString( |
| '%s of %s bytes are not used. (%d%%)', Number.bytesToString(unused), Number.bytesToString(total), |
| percentUnused); |
| } |
| |
| _onFilterChanged() { |
| if (!this._listView) |
| return; |
| const text = this._filterInput.value(); |
| this._textFilterRegExp = text ? createPlainTextSearchRegex(text, 'i') : null; |
| this._listView.updateFilterAndHighlight(this._textFilterRegExp); |
| this._updateStats(); |
| } |
| |
| /** |
| * @param {boolean} ignoreTextFilter |
| * @param {!Coverage.URLCoverageInfo} coverageInfo |
| * @return {boolean} |
| */ |
| _isVisible(ignoreTextFilter, coverageInfo) { |
| const url = coverageInfo.url(); |
| if (url.startsWith(Coverage.CoverageView._extensionBindingsURLPrefix)) |
| return false; |
| if (coverageInfo.isContentScript() && !this._showContentScriptsSetting.get()) |
| return false; |
| return ignoreTextFilter || !this._textFilterRegExp || this._textFilterRegExp.test(url); |
| } |
| }; |
| |
| Coverage.CoverageView._extensionBindingsURLPrefix = 'extensions::'; |
| |
| /** |
| * @implements {UI.ActionDelegate} |
| */ |
| Coverage.CoverageView.ActionDelegate = class { |
| /** |
| * @override |
| * @param {!UI.Context} context |
| * @param {string} actionId |
| * @return {boolean} |
| */ |
| handleAction(context, actionId) { |
| const coverageViewId = 'coverage'; |
| UI.viewManager.showView(coverageViewId) |
| .then(() => UI.viewManager.view(coverageViewId).widget()) |
| .then(widget => this._innerHandleAction(/** @type !Coverage.CoverageView} */ (widget), actionId)); |
| |
| return true; |
| } |
| |
| /** |
| * @param {!Coverage.CoverageView} coverageView |
| * @param {string} actionId |
| */ |
| _innerHandleAction(coverageView, actionId) { |
| switch (actionId) { |
| case 'coverage.toggle-recording': |
| coverageView._toggleRecording(); |
| break; |
| case 'coverage.start-with-reload': |
| coverageView._startRecording(true); |
| break; |
| default: |
| console.assert(false, `Unknown action: ${actionId}`); |
| } |
| } |
| }; |