| /* |
| * Copyright (C) 2012 Google Inc. All rights reserved. |
| * Copyright (C) 2012 Intel Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| import * as Bindings from '../bindings/bindings.js'; |
| import * as Common from '../common/common.js'; |
| import * as Extensions from '../extensions/extensions.js'; |
| import * as Host from '../host/host.js'; |
| import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js'; |
| import * as PerfUI from '../perf_ui/perf_ui.js'; |
| import * as ProtocolModule from '../protocol/protocol.js'; |
| import * as SDK from '../sdk/sdk.js'; |
| import * as TimelineModel from '../timeline_model/timeline_model.js'; |
| import * as UI from '../ui/ui.js'; |
| |
| import {Events, PerformanceModel, Window} from './PerformanceModel.js'; // eslint-disable-line no-unused-vars |
| import {Client, TimelineController} from './TimelineController.js'; // eslint-disable-line no-unused-vars |
| import {TimelineEventOverview, TimelineEventOverviewCoverage, TimelineEventOverviewCPUActivity, TimelineEventOverviewFrames, TimelineEventOverviewInput, TimelineEventOverviewMemory, TimelineEventOverviewNetwork, TimelineEventOverviewResponsiveness, TimelineFilmStripOverview,} from './TimelineEventOverview.js'; // eslint-disable-line no-unused-vars |
| import {TimelineFlameChartView} from './TimelineFlameChartView.js'; |
| import {TimelineHistoryManager} from './TimelineHistoryManager.js'; |
| import {TimelineLoader} from './TimelineLoader.js'; |
| import {TimelineUIUtils} from './TimelineUIUtils.js'; |
| import {UIDevtoolsController} from './UIDevtoolsController.js'; |
| import {UIDevtoolsUtils} from './UIDevtoolsUtils.js'; |
| |
| /** |
| * @implements {Client} |
| * @implements {TimelineModeViewDelegate} |
| * @unrestricted |
| */ |
| export class TimelinePanel extends UI.Panel.Panel { |
| constructor() { |
| super('timeline'); |
| this.registerRequiredCSS('timeline/timelinePanel.css'); |
| this.element.addEventListener('contextmenu', this._contextMenu.bind(this), false); |
| this._dropTarget = new UI.DropTarget.DropTarget( |
| this.element, [UI.DropTarget.Type.File, UI.DropTarget.Type.URI], |
| Common.UIString.UIString('Drop timeline file or URL here'), this._handleDrop.bind(this)); |
| |
| /** @type {!Array<!UI.Toolbar.ToolbarItem>} */ |
| this._recordingOptionUIControls = []; |
| this._state = State.Idle; |
| this._recordingPageReload = false; |
| this._millisecondsToRecordAfterLoadEvent = 3000; |
| this._toggleRecordAction = |
| /** @type {!UI.Action.Action }*/ (self.UI.actionRegistry.action('timeline.toggle-recording')); |
| this._recordReloadAction = |
| /** @type {!UI.Action.Action }*/ (self.UI.actionRegistry.action('timeline.record-reload')); |
| |
| this._historyManager = new TimelineHistoryManager(); |
| |
| /** @type {?PerformanceModel} */ |
| this._performanceModel = null; |
| |
| this._viewModeSetting = self.Common.settings.createSetting('timelineViewMode', ViewMode.FlameChart); |
| |
| this._disableCaptureJSProfileSetting = self.Common.settings.createSetting('timelineDisableJSSampling', false); |
| this._disableCaptureJSProfileSetting.setTitle(Common.UIString.UIString('Disable JavaScript samples')); |
| this._captureLayersAndPicturesSetting = |
| self.Common.settings.createSetting('timelineCaptureLayersAndPictures', false); |
| this._captureLayersAndPicturesSetting.setTitle( |
| Common.UIString.UIString('Enable advanced paint instrumentation (slow)')); |
| |
| this._showScreenshotsSetting = self.Common.settings.createSetting('timelineShowScreenshots', true); |
| this._showScreenshotsSetting.setTitle(Common.UIString.UIString('Screenshots')); |
| this._showScreenshotsSetting.addChangeListener(this._updateOverviewControls, this); |
| |
| this._startCoverage = self.Common.settings.createSetting('timelineStartCoverage', false); |
| this._startCoverage.setTitle(ls`Coverage`); |
| |
| if (!Root.Runtime.experiments.isEnabled('recordCoverageWithPerformanceTracing')) { |
| this._startCoverage.set(false); |
| } |
| |
| |
| this._showMemorySetting = self.Common.settings.createSetting('timelineShowMemory', false); |
| this._showMemorySetting.setTitle(Common.UIString.UIString('Memory')); |
| this._showMemorySetting.addChangeListener(this._onModeChanged, this); |
| |
| const timelineToolbarContainer = this.element.createChild('div', 'timeline-toolbar-container'); |
| this._panelToolbar = new UI.Toolbar.Toolbar('timeline-main-toolbar', timelineToolbarContainer); |
| this._panelRightToolbar = new UI.Toolbar.Toolbar('', timelineToolbarContainer); |
| this._createSettingsPane(); |
| this._updateShowSettingsToolbarButton(); |
| |
| this._timelinePane = new UI.Widget.VBox(); |
| this._timelinePane.show(this.element); |
| const topPaneElement = this._timelinePane.element.createChild('div', 'hbox'); |
| topPaneElement.id = 'timeline-overview-panel'; |
| |
| // Create top overview component. |
| this._overviewPane = new PerfUI.TimelineOverviewPane.TimelineOverviewPane('timeline'); |
| this._overviewPane.addEventListener( |
| PerfUI.TimelineOverviewPane.Events.WindowChanged, this._onOverviewWindowChanged.bind(this)); |
| this._overviewPane.show(topPaneElement); |
| /** @type {!Array<!TimelineEventOverview>} */ |
| this._overviewControls = []; |
| |
| this._statusPaneContainer = this._timelinePane.element.createChild('div', 'status-pane-container fill'); |
| |
| this._createFileSelector(); |
| |
| self.SDK.targetManager.addModelListener( |
| SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this); |
| |
| this._flameChart = new TimelineFlameChartView(this); |
| this._searchableView = new UI.SearchableView.SearchableView(this._flameChart); |
| this._searchableView.setMinimumSize(0, 100); |
| this._searchableView.element.classList.add('searchable-view'); |
| this._searchableView.show(this._timelinePane.element); |
| this._flameChart.show(this._searchableView.element); |
| this._flameChart.setSearchableView(this._searchableView); |
| this._searchableView.hideWidget(); |
| |
| this._onModeChanged(); |
| this._populateToolbar(); |
| this._showLandingPage(); |
| this._updateTimelineControls(); |
| |
| self.Extensions.extensionServer.addEventListener( |
| Extensions.ExtensionServer.Events.TraceProviderAdded, this._appendExtensionsToToolbar, this); |
| self.SDK.targetManager.addEventListener(SDK.SDKModel.Events.SuspendStateChanged, this._onSuspendStateChanged, this); |
| } |
| |
| /** |
| * @return {!TimelinePanel} |
| */ |
| static instance() { |
| return /** @type {!TimelinePanel} */ (self.runtime.sharedInstance(TimelinePanel)); |
| } |
| |
| /** |
| * @override |
| * @return {?UI.SearchableView.SearchableView} |
| */ |
| searchableView() { |
| return this._searchableView; |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| self.UI.context.setFlavor(TimelinePanel, this); |
| // Record the performance tool load time. |
| Host.userMetrics.panelLoaded('timeline', 'DevTools.Launch.Timeline'); |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| self.UI.context.setFlavor(TimelinePanel, null); |
| this._historyManager.cancelIfShowing(); |
| } |
| |
| /** |
| * @param {!Array.<!SDK.TracingManager.EventPayload>} events |
| */ |
| loadFromEvents(events) { |
| if (this._state !== State.Idle) { |
| return; |
| } |
| this._prepareToLoadTimeline(); |
| this._loader = TimelineLoader.loadFromEvents(events, this); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _onOverviewWindowChanged(event) { |
| const left = event.data.startTime; |
| const right = event.data.endTime; |
| this._performanceModel.setWindow({left, right}, /* animate */ true); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _onModelWindowChanged(event) { |
| const window = /** @type {!Window} */ (event.data.window); |
| this._overviewPane.setWindowTimes(window.left, window.right); |
| } |
| |
| /** |
| * @param {!State} state |
| */ |
| _setState(state) { |
| this._state = state; |
| this._updateTimelineControls(); |
| } |
| |
| /** |
| * @param {!Common.Settings.Setting} setting |
| * @param {string} tooltip |
| * @return {!UI.Toolbar.ToolbarItem} |
| */ |
| _createSettingCheckbox(setting, tooltip) { |
| const checkboxItem = new UI.Toolbar.ToolbarSettingCheckbox(setting, tooltip); |
| this._recordingOptionUIControls.push(checkboxItem); |
| return checkboxItem; |
| } |
| |
| _populateToolbar() { |
| // Record |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this._toggleRecordAction)); |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButton(this._recordReloadAction)); |
| this._clearButton = new UI.Toolbar.ToolbarButton(Common.UIString.UIString('Clear'), 'largeicon-clear'); |
| this._clearButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => this._onClearButton()); |
| this._panelToolbar.appendToolbarItem(this._clearButton); |
| |
| // Load / Save |
| this._loadButton = new UI.Toolbar.ToolbarButton(Common.UIString.UIString('Load profile…'), 'largeicon-load'); |
| this._loadButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => this._selectFileToLoad()); |
| this._saveButton = |
| new UI.Toolbar.ToolbarButton(Common.UIString.UIString('Save profile…'), 'largeicon-download'); |
| this._saveButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => this._saveToFile()); |
| this._panelToolbar.appendSeparator(); |
| this._panelToolbar.appendToolbarItem(this._loadButton); |
| this._panelToolbar.appendToolbarItem(this._saveButton); |
| |
| // History |
| this._panelToolbar.appendSeparator(); |
| this._panelToolbar.appendToolbarItem(this._historyManager.button()); |
| this._panelToolbar.appendSeparator(); |
| |
| // View |
| this._panelToolbar.appendSeparator(); |
| this._showScreenshotsToolbarCheckbox = |
| this._createSettingCheckbox(this._showScreenshotsSetting, Common.UIString.UIString('Capture screenshots')); |
| this._panelToolbar.appendToolbarItem(this._showScreenshotsToolbarCheckbox); |
| |
| this._showMemoryToolbarCheckbox = |
| this._createSettingCheckbox(this._showMemorySetting, Common.UIString.UIString('Show memory timeline')); |
| this._panelToolbar.appendToolbarItem(this._showMemoryToolbarCheckbox); |
| |
| if (Root.Runtime.experiments.isEnabled('recordCoverageWithPerformanceTracing')) { |
| this._startCoverageCheckbox = |
| this._createSettingCheckbox(this._startCoverage, ls`Record coverage with performance trace`); |
| this._panelToolbar.appendToolbarItem(this._startCoverageCheckbox); |
| } |
| |
| // GC |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.Toolbar.createActionButtonForId('components.collect-garbage')); |
| |
| // Settings |
| this._panelRightToolbar.appendSeparator(); |
| this._panelRightToolbar.appendToolbarItem(this._showSettingsPaneButton); |
| } |
| |
| _createSettingsPane() { |
| this._showSettingsPaneSetting = self.Common.settings.createSetting('timelineShowSettingsToolbar', false); |
| this._showSettingsPaneButton = new UI.Toolbar.ToolbarSettingToggle( |
| this._showSettingsPaneSetting, 'largeicon-settings-gear', Common.UIString.UIString('Capture settings')); |
| self.SDK.multitargetNetworkManager.addEventListener( |
| SDK.NetworkManager.MultitargetNetworkManager.Events.ConditionsChanged, this._updateShowSettingsToolbarButton, |
| this); |
| MobileThrottling.ThrottlingManager.throttlingManager().addEventListener( |
| MobileThrottling.ThrottlingManager.Events.RateChanged, this._updateShowSettingsToolbarButton, this); |
| this._disableCaptureJSProfileSetting.addChangeListener(this._updateShowSettingsToolbarButton, this); |
| this._captureLayersAndPicturesSetting.addChangeListener(this._updateShowSettingsToolbarButton, this); |
| |
| this._settingsPane = new UI.Widget.HBox(); |
| this._settingsPane.element.classList.add('timeline-settings-pane'); |
| this._settingsPane.show(this.element); |
| |
| const captureToolbar = new UI.Toolbar.Toolbar('', this._settingsPane.element); |
| captureToolbar.element.classList.add('flex-auto'); |
| captureToolbar.makeVertical(); |
| captureToolbar.appendToolbarItem(this._createSettingCheckbox( |
| this._disableCaptureJSProfileSetting, |
| Common.UIString.UIString( |
| 'Disables JavaScript sampling, reduces overhead when running against mobile devices'))); |
| captureToolbar.appendToolbarItem(this._createSettingCheckbox( |
| this._captureLayersAndPicturesSetting, |
| Common.UIString.UIString( |
| 'Captures advanced paint instrumentation, introduces significant performance overhead'))); |
| |
| const throttlingPane = new UI.Widget.VBox(); |
| throttlingPane.element.classList.add('flex-auto'); |
| throttlingPane.show(this._settingsPane.element); |
| |
| const networkThrottlingToolbar = new UI.Toolbar.Toolbar('', throttlingPane.element); |
| networkThrottlingToolbar.appendText(Common.UIString.UIString('Network:')); |
| this._networkThrottlingSelect = this._createNetworkConditionsSelect(); |
| networkThrottlingToolbar.appendToolbarItem(this._networkThrottlingSelect); |
| |
| const cpuThrottlingToolbar = new UI.Toolbar.Toolbar('', throttlingPane.element); |
| cpuThrottlingToolbar.appendText(Common.UIString.UIString('CPU:')); |
| this._cpuThrottlingSelect = MobileThrottling.ThrottlingManager.throttlingManager().createCPUThrottlingSelector(); |
| cpuThrottlingToolbar.appendToolbarItem(this._cpuThrottlingSelect); |
| |
| this._showSettingsPaneSetting.addChangeListener(this._updateSettingsPaneVisibility.bind(this)); |
| this._updateSettingsPaneVisibility(); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _appendExtensionsToToolbar(event) { |
| const provider = /** @type {!Extensions.ExtensionTraceProvider.ExtensionTraceProvider} */ (event.data); |
| const setting = TimelinePanel._settingForTraceProvider(provider); |
| const checkbox = this._createSettingCheckbox(setting, provider.longDisplayName()); |
| this._panelToolbar.appendToolbarItem(checkbox); |
| } |
| |
| /** |
| * @param {!Extensions.ExtensionTraceProvider.ExtensionTraceProvider} traceProvider |
| * @return {!Common.Settings.Setting<boolean>} |
| */ |
| static _settingForTraceProvider(traceProvider) { |
| let setting = traceProvider[traceProviderSettingSymbol]; |
| if (!setting) { |
| const providerId = traceProvider.persistentIdentifier(); |
| setting = self.Common.settings.createSetting(providerId, false); |
| setting.setTitle(traceProvider.shortDisplayName()); |
| traceProvider[traceProviderSettingSymbol] = setting; |
| } |
| return setting; |
| } |
| |
| /** |
| * @return {!UI.Toolbar.ToolbarComboBox} |
| */ |
| _createNetworkConditionsSelect() { |
| const toolbarItem = new UI.Toolbar.ToolbarComboBox(null, ls`Network conditions`); |
| toolbarItem.setMaxWidth(140); |
| MobileThrottling.ThrottlingManager.throttlingManager().decorateSelectWithNetworkThrottling( |
| toolbarItem.selectElement()); |
| return toolbarItem; |
| } |
| |
| _prepareToLoadTimeline() { |
| console.assert(this._state === State.Idle); |
| this._setState(State.Loading); |
| if (this._performanceModel) { |
| this._performanceModel.dispose(); |
| this._performanceModel = null; |
| } |
| } |
| |
| _createFileSelector() { |
| if (this._fileSelectorElement) { |
| this._fileSelectorElement.remove(); |
| } |
| this._fileSelectorElement = UI.UIUtils.createFileSelectorElement(this._loadFromFile.bind(this)); |
| this._timelinePane.element.appendChild(this._fileSelectorElement); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| _contextMenu(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.appendItemsAtLocation('timelineMenu'); |
| contextMenu.show(); |
| } |
| |
| /** |
| * @suppress {deprecated} |
| */ |
| async _saveToFile() { |
| if (this._state !== State.Idle) { |
| return; |
| } |
| const performanceModel = this._performanceModel; |
| if (!performanceModel) { |
| return; |
| } |
| |
| const now = new Date(); |
| const fileName = 'Profile-' + now.toISO8601Compact() + '.json'; |
| const stream = new Bindings.FileUtils.FileOutputStream(); |
| |
| const accepted = await stream.open(fileName); |
| if (!accepted) { |
| return; |
| } |
| |
| const error = await performanceModel.save(stream); |
| if (!error) { |
| return; |
| } |
| self.Common.console.error( |
| Common.UIString.UIString('Failed to save timeline: %s (%s, %s)', error.message, error.name, error.code)); |
| } |
| |
| async _showHistory() { |
| const model = await this._historyManager.showHistoryDropDown(); |
| if (model && model !== this._performanceModel) { |
| this._setModel(model); |
| } |
| } |
| |
| /** |
| * @param {number} direction |
| * @return {boolean} |
| */ |
| _navigateHistory(direction) { |
| const model = this._historyManager.navigate(direction); |
| if (model && model !== this._performanceModel) { |
| this._setModel(model); |
| } |
| return true; |
| } |
| |
| _selectFileToLoad() { |
| this._fileSelectorElement.click(); |
| } |
| |
| /** |
| * @param {!File} file |
| */ |
| _loadFromFile(file) { |
| if (this._state !== State.Idle) { |
| return; |
| } |
| this._prepareToLoadTimeline(); |
| this._loader = TimelineLoader.loadFromFile(file, this); |
| this._createFileSelector(); |
| } |
| |
| /** |
| * @param {string} url |
| */ |
| _loadFromURL(url) { |
| if (this._state !== State.Idle) { |
| return; |
| } |
| this._prepareToLoadTimeline(); |
| this._loader = TimelineLoader.loadFromURL(url, this); |
| } |
| |
| _updateOverviewControls() { |
| this._overviewControls = []; |
| this._overviewControls.push(new TimelineEventOverviewResponsiveness()); |
| if (Root.Runtime.experiments.isEnabled('inputEventsOnTimelineOverview')) { |
| this._overviewControls.push(new TimelineEventOverviewInput()); |
| } |
| this._overviewControls.push(new TimelineEventOverviewFrames()); |
| this._overviewControls.push(new TimelineEventOverviewCPUActivity()); |
| this._overviewControls.push(new TimelineEventOverviewNetwork()); |
| if (this._showScreenshotsSetting.get() && this._performanceModel && |
| this._performanceModel.filmStripModel().frames().length) { |
| this._overviewControls.push(new TimelineFilmStripOverview()); |
| } |
| if (this._showMemorySetting.get()) { |
| this._overviewControls.push(new TimelineEventOverviewMemory()); |
| } |
| if (this._startCoverage.get()) { |
| this._overviewControls.push(new TimelineEventOverviewCoverage()); |
| } |
| for (const control of this._overviewControls) { |
| control.setModel(this._performanceModel); |
| } |
| this._overviewPane.setOverviewControls(this._overviewControls); |
| } |
| |
| _onModeChanged() { |
| this._updateOverviewControls(); |
| this.doResize(); |
| this.select(null); |
| } |
| |
| _updateSettingsPaneVisibility() { |
| if (this._showSettingsPaneSetting.get()) { |
| this._settingsPane.showWidget(); |
| } else { |
| this._settingsPane.hideWidget(); |
| } |
| } |
| |
| _updateShowSettingsToolbarButton() { |
| const messages = []; |
| if (MobileThrottling.ThrottlingManager.throttlingManager().cpuThrottlingRate() !== 1) { |
| messages.push(Common.UIString.UIString('- CPU throttling is enabled')); |
| } |
| if (self.SDK.multitargetNetworkManager.isThrottling()) { |
| messages.push(Common.UIString.UIString('- Network throttling is enabled')); |
| } |
| if (this._captureLayersAndPicturesSetting.get()) { |
| messages.push(Common.UIString.UIString('- Significant overhead due to paint instrumentation')); |
| } |
| if (this._disableCaptureJSProfileSetting.get()) { |
| messages.push(Common.UIString.UIString('- JavaScript sampling is disabled')); |
| } |
| |
| this._showSettingsPaneButton.setDefaultWithRedColor(messages.length); |
| this._showSettingsPaneButton.setToggleWithRedColor(messages.length); |
| |
| if (messages.length) { |
| const tooltipElement = createElement('div'); |
| messages.forEach(message => { |
| tooltipElement.createChild('div').textContent = message; |
| }); |
| this._showSettingsPaneButton.setTitle(tooltipElement); |
| } else { |
| this._showSettingsPaneButton.setTitle(Common.UIString.UIString('Capture settings')); |
| } |
| } |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| _setUIControlsEnabled(enabled) { |
| this._recordingOptionUIControls.forEach(control => control.setEnabled(enabled)); |
| } |
| |
| async _startRecording() { |
| console.assert(!this._statusPane, 'Status pane is already opened.'); |
| this._setState(State.StartPending); |
| |
| const recordingOptions = { |
| enableJSSampling: !this._disableCaptureJSProfileSetting.get(), |
| capturePictures: this._captureLayersAndPicturesSetting.get(), |
| captureFilmStrip: this._showScreenshotsSetting.get(), |
| startCoverage: this._startCoverage.get() |
| }; |
| |
| if (recordingOptions.startCoverage) { |
| await self.UI.viewManager.showView('coverage') |
| .then(() => self.UI.viewManager.view('coverage').widget()) |
| .then(widget => widget.ensureRecordingStarted()); |
| } |
| |
| this._showRecordingStarted(); |
| |
| const enabledTraceProviders = self.Extensions.extensionServer.traceProviders().filter( |
| provider => TimelinePanel._settingForTraceProvider(provider).get()); |
| |
| const mainTarget = /** @type {!SDK.SDKModel.Target} */ (self.SDK.targetManager.mainTarget()); |
| if (UIDevtoolsUtils.isUiDevTools()) { |
| this._controller = new UIDevtoolsController(mainTarget, this); |
| } else { |
| this._controller = new TimelineController(mainTarget, this); |
| } |
| this._setUIControlsEnabled(false); |
| this._hideLandingPage(); |
| const response = await this._controller.startRecording(recordingOptions, enabledTraceProviders); |
| if (response[ProtocolModule.InspectorBackend.ProtocolError]) { |
| this._recordingFailed(response[ProtocolModule.InspectorBackend.ProtocolError]); |
| } else { |
| this._recordingStarted(); |
| } |
| } |
| |
| async _stopRecording() { |
| if (this._statusPane) { |
| this._statusPane.finish(); |
| this._statusPane.updateStatus(Common.UIString.UIString('Stopping timeline…')); |
| this._statusPane.updateProgressBar(Common.UIString.UIString('Received'), 0); |
| } |
| this._setState(State.StopPending); |
| if (this._startCoverage.get()) { |
| await self.UI.viewManager.showView('coverage') |
| .then(() => self.UI.viewManager.view('coverage').widget()) |
| .then(widget => widget.stopRecording()); |
| } |
| const model = await this._controller.stopRecording(); |
| this._performanceModel = model; |
| this._setUIControlsEnabled(true); |
| this._controller.dispose(); |
| this._controller = null; |
| } |
| |
| /** |
| * @param {string} error The error message to display |
| */ |
| _recordingFailed(error) { |
| if (this._statusPane) { |
| this._statusPane.hide(); |
| } |
| this._statusPane = new StatusPane({description: error}, () => this.loadingComplete(null)); |
| this._statusPane.showPane(this._statusPaneContainer); |
| this._statusPane.updateStatus(ls`Recording failed`); |
| this._statusPane.updateButton(ls`Close`); |
| |
| this._setState(State.RecordingFailed); |
| this._performanceModel = null; |
| this._setUIControlsEnabled(false); |
| this._controller.dispose(); |
| this._controller = null; |
| } |
| |
| _onSuspendStateChanged() { |
| this._updateTimelineControls(); |
| } |
| |
| _updateTimelineControls() { |
| const state = State; |
| this._toggleRecordAction.setToggled(this._state === state.Recording); |
| this._toggleRecordAction.setEnabled(this._state === state.Recording || this._state === state.Idle); |
| this._recordReloadAction.setEnabled(this._state === state.Idle); |
| this._historyManager.setEnabled(this._state === state.Idle); |
| this._clearButton.setEnabled(this._state === state.Idle); |
| this._panelToolbar.setEnabled(this._state !== state.Loading); |
| this._panelRightToolbar.setEnabled(this._state !== state.Loading); |
| this._dropTarget.setEnabled(this._state === state.Idle); |
| this._loadButton.setEnabled(this._state === state.Idle); |
| this._saveButton.setEnabled(this._state === state.Idle && !!this._performanceModel); |
| } |
| |
| _toggleRecording() { |
| if (this._state === State.Idle) { |
| this._recordingPageReload = false; |
| this._startRecording(); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.TimelineStarted); |
| } else if (this._state === State.Recording) { |
| this._stopRecording(); |
| } |
| } |
| |
| _recordReload() { |
| if (this._state !== State.Idle) { |
| return; |
| } |
| this._recordingPageReload = true; |
| this._startRecording(); |
| Host.userMetrics.actionTaken(Host.UserMetrics.Action.TimelinePageReloadStarted); |
| } |
| |
| _onClearButton() { |
| this._historyManager.clear(); |
| this._clear(); |
| } |
| |
| _clear() { |
| this._showLandingPage(); |
| this._reset(); |
| } |
| |
| _reset() { |
| self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance).reset(); |
| this._setModel(null); |
| } |
| |
| /** |
| * @param {!PerformanceModel} model |
| */ |
| _applyFilters(model) { |
| if (model.timelineModel().isGenericTrace() || Root.Runtime.experiments.isEnabled('timelineShowAllEvents')) { |
| return; |
| } |
| model.setFilters([TimelineUIUtils.visibleEventsFilter()]); |
| } |
| |
| /** |
| * @param {?PerformanceModel} model |
| */ |
| _setModel(model) { |
| if (this._performanceModel) { |
| this._performanceModel.removeEventListener(Events.WindowChanged, this._onModelWindowChanged, this); |
| } |
| this._performanceModel = model; |
| if (model) { |
| this._searchableView.showWidget(); |
| this._applyFilters(model); |
| } else { |
| this._searchableView.hideWidget(); |
| } |
| this._flameChart.setModel(model); |
| |
| this._updateOverviewControls(); |
| this._overviewPane.reset(); |
| if (model) { |
| this._performanceModel.addEventListener(Events.WindowChanged, this._onModelWindowChanged, this); |
| this._overviewPane.setBounds( |
| model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime()); |
| const lineLevelProfile = self.runtime.sharedInstance(PerfUI.LineLevelProfile.Performance); |
| lineLevelProfile.reset(); |
| for (const profile of model.timelineModel().cpuProfiles()) { |
| lineLevelProfile.appendCPUProfile(profile); |
| } |
| this._setMarkers(model.timelineModel()); |
| this._flameChart.setSelection(null); |
| this._overviewPane.setWindowTimes(model.window().left, model.window().right); |
| } |
| for (const control of this._overviewControls) { |
| control.setModel(model); |
| } |
| if (this._flameChart) { |
| this._flameChart.resizeToPreferredHeights(); |
| } |
| this._updateTimelineControls(); |
| } |
| |
| _recordingStarted() { |
| if (this._recordingPageReload) { |
| const target = this._controller.mainTarget(); |
| const resourceModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (resourceModel) { |
| resourceModel.reloadPage(); |
| } |
| } |
| this._reset(); |
| this._setState(State.Recording); |
| this._showRecordingStarted(); |
| this._statusPane.enableAndFocusStopButton(); |
| this._statusPane.updateStatus(Common.UIString.UIString('Profiling…')); |
| this._statusPane.updateProgressBar(Common.UIString.UIString('Buffer usage'), 0); |
| this._statusPane.startTimer(); |
| this._hideLandingPage(); |
| } |
| |
| /** |
| * @override |
| * @param {number} usage |
| */ |
| recordingProgress(usage) { |
| this._statusPane.updateProgressBar(Common.UIString.UIString('Buffer usage'), usage * 100); |
| } |
| |
| _showLandingPage() { |
| if (this._landingPage) { |
| this._landingPage.show(this._statusPaneContainer); |
| return; |
| } |
| |
| /** |
| * @param {string} tagName |
| * @param {string} contents |
| */ |
| function encloseWithTag(tagName, contents) { |
| const e = createElement(tagName); |
| e.textContent = contents; |
| return e; |
| } |
| |
| const learnMoreNode = UI.XLink.XLink.create( |
| 'https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/', |
| Common.UIString.UIString('Learn\xa0more')); |
| |
| const recordKey = |
| encloseWithTag('b', self.UI.shortcutRegistry.shortcutDescriptorsForAction('timeline.toggle-recording')[0].name); |
| const reloadKey = |
| encloseWithTag('b', self.UI.shortcutRegistry.shortcutDescriptorsForAction('timeline.record-reload')[0].name); |
| const navigateNode = encloseWithTag('b', Common.UIString.UIString('WASD')); |
| |
| this._landingPage = new UI.Widget.VBox(); |
| this._landingPage.contentElement.classList.add('timeline-landing-page', 'fill'); |
| const centered = this._landingPage.contentElement.createChild('div'); |
| |
| const recordButton = UI.UIUtils.createInlineButton(UI.Toolbar.Toolbar.createActionButton(this._toggleRecordAction)); |
| const reloadButton = |
| UI.UIUtils.createInlineButton(UI.Toolbar.Toolbar.createActionButtonForId('timeline.record-reload')); |
| |
| centered.createChild('p').appendChild(UI.UIUtils.formatLocalized( |
| 'Click the record button %s or hit %s to start a new recording.\nClick the reload button %s or hit %s to record the page load.', |
| [recordButton, recordKey, reloadButton, reloadKey])); |
| |
| centered.createChild('p').appendChild(UI.UIUtils.formatLocalized( |
| 'After recording, select an area of interest in the overview by dragging.\nThen, zoom and pan the timeline with the mousewheel or %s keys.\n%s', |
| [navigateNode, learnMoreNode])); |
| |
| this._landingPage.show(this._statusPaneContainer); |
| } |
| |
| _hideLandingPage() { |
| this._landingPage.detach(); |
| } |
| |
| /** |
| * @override |
| */ |
| loadingStarted() { |
| this._hideLandingPage(); |
| |
| if (this._statusPane) { |
| this._statusPane.hide(); |
| } |
| this._statusPane = new StatusPane({showProgress: true}, this._cancelLoading.bind(this)); |
| this._statusPane.showPane(this._statusPaneContainer); |
| this._statusPane.updateStatus(Common.UIString.UIString('Loading profile…')); |
| // FIXME: make loading from backend cancelable as well. |
| if (!this._loader) { |
| this._statusPane.finish(); |
| } |
| this.loadingProgress(0); |
| } |
| |
| /** |
| * @override |
| * @param {number=} progress |
| */ |
| loadingProgress(progress) { |
| if (typeof progress === 'number') { |
| this._statusPane.updateProgressBar(Common.UIString.UIString('Received'), progress * 100); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| processingStarted() { |
| this._statusPane.updateStatus(Common.UIString.UIString('Processing profile…')); |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.TracingModel.TracingModel} tracingModel |
| */ |
| loadingComplete(tracingModel) { |
| delete this._loader; |
| this._setState(State.Idle); |
| |
| if (this._statusPane) { |
| this._statusPane.hide(); |
| } |
| delete this._statusPane; |
| |
| if (!tracingModel) { |
| this._clear(); |
| return; |
| } |
| |
| if (!this._performanceModel) { |
| this._performanceModel = new PerformanceModel(); |
| } |
| this._performanceModel.setTracingModel(tracingModel); |
| this._setModel(this._performanceModel); |
| this._historyManager.addRecording(this._performanceModel); |
| |
| if (this._startCoverage.get()) { |
| self.UI.viewManager.showView('coverage') |
| .then(() => self.UI.viewManager.view('coverage').widget()) |
| .then(widget => widget.processBacklog()) |
| .then(() => this._updateOverviewControls()); |
| } |
| } |
| |
| _showRecordingStarted() { |
| if (this._statusPane) { |
| return; |
| } |
| this._statusPane = new StatusPane({showTimer: true, showProgress: true}, this._stopRecording.bind(this)); |
| this._statusPane.showPane(this._statusPaneContainer); |
| this._statusPane.updateStatus(Common.UIString.UIString('Initializing profiler…')); |
| } |
| |
| _cancelLoading() { |
| if (this._loader) { |
| this._loader.cancel(); |
| } |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.TimelineModelImpl} timelineModel |
| */ |
| _setMarkers(timelineModel) { |
| const markers = new Map(); |
| const recordTypes = TimelineModel.TimelineModel.RecordType; |
| const zeroTime = timelineModel.minimumRecordTime(); |
| for (const event of timelineModel.timeMarkerEvents()) { |
| if (event.name === recordTypes.TimeStamp || event.name === recordTypes.ConsoleTime) { |
| continue; |
| } |
| markers.set(event.startTime, TimelineUIUtils.createEventDivider(event, zeroTime)); |
| } |
| this._overviewPane.setMarkers(markers); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| async _loadEventFired(event) { |
| if (this._state !== State.Recording || !this._recordingPageReload || |
| this._controller.mainTarget() !== event.data.resourceTreeModel.target()) { |
| return; |
| } |
| const controller = this._controller; |
| await new Promise(r => setTimeout(r, this._millisecondsToRecordAfterLoadEvent)); |
| |
| // Check if we're still in the same recording session. |
| if (controller !== this._controller || this._state !== State.Recording) { |
| return; |
| } |
| this._stopRecording(); |
| } |
| |
| /** |
| * @param {!TimelineSelection} selection |
| * @return {?TimelineModel.TimelineFrameModel.TimelineFrame} |
| */ |
| _frameForSelection(selection) { |
| switch (selection.type()) { |
| case TimelineSelection.Type.Frame: |
| return /** @type {!TimelineModel.TimelineFrameModel.TimelineFrame} */ (selection.object()); |
| case TimelineSelection.Type.Range: |
| return null; |
| case TimelineSelection.Type.TraceEvent: |
| return this._performanceModel.frameModel().frames(selection._endTime, selection._endTime)[0]; |
| default: |
| console.assert(false, 'Should never be reached'); |
| return null; |
| } |
| } |
| |
| /** |
| * @param {number} offset |
| */ |
| _jumpToFrame(offset) { |
| const currentFrame = this._selection && this._frameForSelection(this._selection); |
| if (!currentFrame) { |
| return; |
| } |
| const frames = this._performanceModel.frames(); |
| let index = frames.indexOf(currentFrame); |
| console.assert(index >= 0, 'Can\'t find current frame in the frame list'); |
| index = Number.constrain(index + offset, 0, frames.length - 1); |
| const frame = frames[index]; |
| this._revealTimeRange(frame.startTime, frame.endTime); |
| this.select(TimelineSelection.fromFrame(frame)); |
| return true; |
| } |
| |
| /** |
| * @override |
| * @param {?TimelineSelection} selection |
| */ |
| select(selection) { |
| this._selection = selection; |
| this._flameChart.setSelection(selection); |
| } |
| |
| /** |
| * @override |
| * @param {?Array<!SDK.TracingModel.Event>} events |
| * @param {number} time |
| */ |
| selectEntryAtTime(events, time) { |
| if (!events) { |
| return; |
| } |
| // Find best match, then backtrack to the first visible entry. |
| for (let index = events.upperBound(time, (time, event) => time - event.startTime) - 1; index >= 0; --index) { |
| const event = events[index]; |
| const endTime = event.endTime || event.startTime; |
| if (SDK.TracingModel.TracingModel.isTopLevelEvent(event) && endTime < time) { |
| break; |
| } |
| if (this._performanceModel.isVisible(event) && endTime >= time) { |
| this.select(TimelineSelection.fromTraceEvent(event)); |
| return; |
| } |
| } |
| this.select(null); |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.TracingModel.Event} event |
| */ |
| highlightEvent(event) { |
| this._flameChart.highlightEvent(event); |
| } |
| |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| */ |
| _revealTimeRange(startTime, endTime) { |
| const window = this._performanceModel.window(); |
| let offset = 0; |
| if (window.right < endTime) { |
| offset = endTime - window.right; |
| } else if (window.left > startTime) { |
| offset = startTime - window.left; |
| } |
| this._performanceModel.setWindow({left: window.left + offset, right: window.right + offset}, /* animate */ true); |
| } |
| |
| /** |
| * @param {!DataTransfer} dataTransfer |
| */ |
| _handleDrop(dataTransfer) { |
| const items = dataTransfer.items; |
| if (!items.length) { |
| return; |
| } |
| const item = items[0]; |
| if (item.kind === 'string') { |
| const url = dataTransfer.getData('text/uri-list'); |
| if (new Common.ParsedURL.ParsedURL(url).isValid) { |
| this._loadFromURL(url); |
| } |
| } else if (item.kind === 'file') { |
| const entry = items[0].webkitGetAsEntry(); |
| if (!entry.isFile) { |
| return; |
| } |
| entry.file(this._loadFromFile.bind(this)); |
| } |
| } |
| } |
| |
| /** |
| * @enum {symbol} |
| */ |
| export const State = { |
| Idle: Symbol('Idle'), |
| StartPending: Symbol('StartPending'), |
| Recording: Symbol('Recording'), |
| StopPending: Symbol('StopPending'), |
| Loading: Symbol('Loading'), |
| RecordingFailed: Symbol('RecordingFailed') |
| }; |
| |
| /** |
| * @enum {string} |
| */ |
| export const ViewMode = { |
| FlameChart: 'FlameChart', |
| BottomUp: 'BottomUp', |
| CallTree: 'CallTree', |
| EventLog: 'EventLog' |
| }; |
| |
| // Define row and header height, should be in sync with styles for timeline graphs. |
| export const rowHeight = 18; |
| |
| export const headerHeight = 20; |
| |
| export class TimelineSelection { |
| /** |
| * @param {!TimelineSelection.Type} type |
| * @param {number} startTime |
| * @param {number} endTime |
| * @param {!Object=} object |
| */ |
| constructor(type, startTime, endTime, object) { |
| this._type = type; |
| this._startTime = startTime; |
| this._endTime = endTime; |
| this._object = object || null; |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineFrameModel.TimelineFrame} frame |
| * @return {!TimelineSelection} |
| */ |
| static fromFrame(frame) { |
| return new TimelineSelection(TimelineSelection.Type.Frame, frame.startTime, frame.endTime, frame); |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.NetworkRequest} request |
| * @return {!TimelineSelection} |
| */ |
| static fromNetworkRequest(request) { |
| return new TimelineSelection( |
| TimelineSelection.Type.NetworkRequest, request.startTime, request.endTime || request.startTime, request); |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @return {!TimelineSelection} |
| */ |
| static fromTraceEvent(event) { |
| return new TimelineSelection( |
| TimelineSelection.Type.TraceEvent, event.startTime, event.endTime || (event.startTime + 1), event); |
| } |
| |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| * @return {!TimelineSelection} |
| */ |
| static fromRange(startTime, endTime) { |
| return new TimelineSelection(TimelineSelection.Type.Range, startTime, endTime); |
| } |
| |
| /** |
| * @return {!TimelineSelection.Type} |
| */ |
| type() { |
| return this._type; |
| } |
| |
| /** |
| * @return {?Object} |
| */ |
| object() { |
| return this._object; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| startTime() { |
| return this._startTime; |
| } |
| |
| /** |
| * @return {number} |
| */ |
| endTime() { |
| return this._endTime; |
| } |
| } |
| |
| /** |
| * @enum {string} |
| */ |
| TimelineSelection.Type = { |
| Frame: 'Frame', |
| NetworkRequest: 'NetworkRequest', |
| TraceEvent: 'TraceEvent', |
| Range: 'Range' |
| }; |
| |
| /** |
| * @interface |
| */ |
| export class TimelineModeViewDelegate { |
| /** |
| * @param {?TimelineSelection} selection |
| */ |
| select(selection) { |
| } |
| |
| /** |
| * @param {?Array<!SDK.TracingModel.Event>} events |
| * @param {number} time |
| */ |
| selectEntryAtTime(events, time) { |
| } |
| |
| /** |
| * @param {?SDK.TracingModel.Event} event |
| */ |
| highlightEvent(event) { |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class StatusPane extends UI.Widget.VBox { |
| /** |
| * @param {!{showTimer: (boolean|undefined), showProgress: (boolean|undefined), description: (string|undefined)}} options - a collection of options controlling the appearance of the pane. |
| * The options object can have the following properties: |
| * - **showTimer** - `{boolean}` - Display seconds since dialog opened |
| * - **showProgress** - `{boolean}` - Display a progress bar |
| * - **description** - `{string}` - Display this string in a description line |
| * @param {function()} stopCallback |
| */ |
| constructor(options, stopCallback) { |
| super(true); |
| this.registerRequiredCSS('timeline/timelineStatusDialog.css'); |
| this.contentElement.classList.add('timeline-status-dialog'); |
| |
| const statusLine = this.contentElement.createChild('div', 'status-dialog-line status'); |
| statusLine.createChild('div', 'label').textContent = Common.UIString.UIString('Status'); |
| this._status = statusLine.createChild('div', 'content'); |
| UI.ARIAUtils.markAsStatus(this._status); |
| |
| if (options.showTimer) { |
| const timeLine = this.contentElement.createChild('div', 'status-dialog-line time'); |
| timeLine.createChild('div', 'label').textContent = Common.UIString.UIString('Time'); |
| this._time = timeLine.createChild('div', 'content'); |
| } |
| |
| if (options.showProgress) { |
| const progressLine = this.contentElement.createChild('div', 'status-dialog-line progress'); |
| this._progressLabel = progressLine.createChild('div', 'label'); |
| this._progressBar = progressLine.createChild('div', 'indicator-container').createChild('div', 'indicator'); |
| UI.ARIAUtils.markAsProgressBar(this._progressBar); |
| } |
| |
| if (typeof options.description === 'string') { |
| const descriptionLine = this.contentElement.createChild('div', 'status-dialog-line description'); |
| descriptionLine.createChild('div', 'label').textContent = ls`Description`; |
| this._description = descriptionLine.createChild('div', 'content'); |
| this._description.innerText = options.description; |
| } |
| |
| this._stopButton = UI.UIUtils.createTextButton(Common.UIString.UIString('Stop'), stopCallback, '', true); |
| // Profiling can't be stopped during initialization. |
| this._stopButton.disabled = true; |
| this.contentElement.createChild('div', 'stop-button').appendChild(this._stopButton); |
| } |
| |
| finish() { |
| this._stopTimer(); |
| this._stopButton.disabled = true; |
| } |
| |
| hide() { |
| this.element.parentNode.classList.remove('tinted'); |
| this.element.remove(); |
| } |
| |
| /** |
| * @param {!Element} parent |
| */ |
| showPane(parent) { |
| this.show(parent); |
| parent.classList.add('tinted'); |
| } |
| |
| enableAndFocusStopButton() { |
| this._stopButton.disabled = false; |
| this._stopButton.focus(); |
| } |
| |
| /** |
| * @param {string} text |
| */ |
| updateStatus(text) { |
| this._status.textContent = text; |
| } |
| |
| /** |
| * @param {string} activity |
| * @param {number} percent |
| */ |
| updateProgressBar(activity, percent) { |
| this._progressLabel.textContent = activity; |
| this._progressBar.style.width = percent.toFixed(1) + '%'; |
| UI.ARIAUtils.setValueNow(this._progressBar, percent); |
| this._updateTimer(); |
| } |
| |
| /** |
| * @param {string} caption |
| */ |
| updateButton(caption) { |
| this._stopButton.innerText = caption; |
| } |
| |
| startTimer() { |
| this._startTime = Date.now(); |
| this._timeUpdateTimer = setInterval(this._updateTimer.bind(this, false), 1000); |
| this._updateTimer(); |
| } |
| |
| _stopTimer() { |
| if (!this._timeUpdateTimer) { |
| return; |
| } |
| clearInterval(this._timeUpdateTimer); |
| this._updateTimer(true); |
| delete this._timeUpdateTimer; |
| } |
| |
| /** |
| * @param {boolean=} precise |
| */ |
| _updateTimer(precise) { |
| if (!this._timeUpdateTimer) { |
| return; |
| } |
| const elapsed = (Date.now() - this._startTime) / 1000; |
| this._time.textContent = Common.UIString.UIString('%s\xa0sec', elapsed.toFixed(precise ? 1 : 0)); |
| } |
| } |
| |
| |
| /** |
| * @implements {Common.QueryParamHandler.QueryParamHandler} |
| * @unrestricted |
| */ |
| export class LoadTimelineHandler { |
| /** |
| * @override |
| * @param {string} value |
| */ |
| handleQueryParam(value) { |
| self.UI.viewManager.showView('timeline').then(() => { |
| TimelinePanel.instance()._loadFromURL(window.decodeURIComponent(value)); |
| }); |
| } |
| } |
| |
| /** |
| * @implements {UI.ActionDelegate.ActionDelegate} |
| * @unrestricted |
| */ |
| export class ActionDelegate { |
| /** |
| * @override |
| * @param {!UI.Context.Context} context |
| * @param {string} actionId |
| * @return {boolean} |
| */ |
| handleAction(context, actionId) { |
| const panel = self.UI.context.flavor(TimelinePanel); |
| console.assert(panel && panel instanceof TimelinePanel); |
| switch (actionId) { |
| case 'timeline.toggle-recording': |
| panel._toggleRecording(); |
| return true; |
| case 'timeline.record-reload': |
| panel._recordReload(); |
| return true; |
| case 'timeline.save-to-file': |
| panel._saveToFile(); |
| return true; |
| case 'timeline.load-from-file': |
| panel._selectFileToLoad(); |
| return true; |
| case 'timeline.jump-to-previous-frame': |
| panel._jumpToFrame(-1); |
| return true; |
| case 'timeline.jump-to-next-frame': |
| panel._jumpToFrame(1); |
| return true; |
| case 'timeline.show-history': |
| panel._showHistory(); |
| return true; |
| case 'timeline.previous-recording': |
| panel._navigateHistory(1); |
| return true; |
| case 'timeline.next-recording': |
| panel._navigateHistory(-1); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| export const traceProviderSettingSymbol = Symbol('traceProviderSetting'); |