| /* |
| * 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. |
| */ |
| |
| /** |
| * @implements {Timeline.TimelineController.Client} |
| * @implements {Timeline.TimelineModeViewDelegate} |
| * @unrestricted |
| */ |
| Timeline.TimelinePanel = class extends UI.Panel { |
| constructor() { |
| super('timeline'); |
| this.registerRequiredCSS('timeline/timelinePanel.css'); |
| this.element.addEventListener('contextmenu', this._contextMenu.bind(this), false); |
| this._dropTarget = new UI.DropTarget( |
| this.element, [UI.DropTarget.Types.Files, UI.DropTarget.Types.URIList], |
| Common.UIString('Drop timeline file or URL here'), this._handleDrop.bind(this)); |
| |
| /** @type {!Array<!UI.ToolbarItem>} */ |
| this._recordingOptionUIControls = []; |
| this._state = Timeline.TimelinePanel.State.Idle; |
| this._windowStartTime = 0; |
| this._windowEndTime = Infinity; |
| this._millisecondsToRecordAfterLoadEvent = 3000; |
| this._toggleRecordAction = |
| /** @type {!UI.Action }*/ (UI.actionRegistry.action('timeline.toggle-recording')); |
| |
| /** @type {!Array<!TimelineModel.TimelineModelFilter>} */ |
| this._filters = []; |
| if (!Runtime.experiments.isEnabled('timelineShowAllEvents')) { |
| this._filters.push(Timeline.TimelineUIUtils.visibleEventsFilter()); |
| this._filters.push(new TimelineModel.ExcludeTopLevelFilter()); |
| } |
| |
| /** @type {?Timeline.PerformanceModel} */ |
| this._performanceModel = null; |
| /** @type {?Timeline.PerformanceModel} */ |
| this._pendingPerformanceModel = null; |
| |
| this._cpuThrottlingManager = new Components.CPUThrottlingManager(); |
| |
| /** @type {!Array<!Timeline.TimelineModeView>} */ |
| this._currentViews = []; |
| |
| this._viewModeSetting = |
| Common.settings.createSetting('timelineViewMode', Timeline.TimelinePanel.ViewMode.FlameChart); |
| |
| this._disableCaptureJSProfileSetting = Common.settings.createSetting('timelineDisableJSSampling', false); |
| this._disableCaptureJSProfileSetting.setTitle(Common.UIString('Disable JavaScript Samples')); |
| this._captureLayersAndPicturesSetting = Common.settings.createSetting('timelineCaptureLayersAndPictures', false); |
| this._captureLayersAndPicturesSetting.setTitle(Common.UIString('Enable advanced paint instrumentation (slow)')); |
| |
| this._showScreenshotsSetting = Common.settings.createSetting('timelineShowScreenshots', true); |
| this._showScreenshotsSetting.setTitle(Common.UIString('Screenshots')); |
| this._showScreenshotsSetting.addChangeListener(this._onModeChanged, this); |
| this._showMemorySetting = Common.settings.createSetting('timelineShowMemory', false); |
| this._showMemorySetting.setTitle(Common.UIString('Memory')); |
| this._showMemorySetting.addChangeListener(this._onModeChanged, this); |
| |
| this._panelToolbar = new UI.Toolbar('', this.element); |
| this._createSettingsPane(); |
| this._updateShowSettingsToolbarButton(); |
| |
| this._timelinePane = new UI.VBox(); |
| this._timelinePane.show(this.element); |
| var topPaneElement = this._timelinePane.element.createChild('div', 'hbox'); |
| topPaneElement.id = 'timeline-overview-panel'; |
| |
| // Create top overview component. |
| this._overviewPane = new PerfUI.TimelineOverviewPane('timeline'); |
| this._overviewPane.addEventListener( |
| PerfUI.TimelineOverviewPane.Events.WindowChanged, this._onWindowChanged.bind(this)); |
| this._overviewPane.show(topPaneElement); |
| /** @type {!Array<!Timeline.TimelineEventOverview>} */ |
| this._overviewControls = []; |
| |
| this._statusPaneContainer = this._timelinePane.element.createChild('div', 'status-pane-container fill'); |
| |
| this._createFileSelector(); |
| |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.PageReloadRequested, this._pageReloadRequested, this); |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.Load, this._loadEventFired, this); |
| |
| this._stackView = new UI.StackView(false); |
| this._stackView.element.classList.add('timeline-view-stack'); |
| |
| if (Runtime.experiments.isEnabled('timelineMultipleMainViews')) { |
| const viewMode = Timeline.TimelinePanel.ViewMode; |
| this._tabbedPane = new UI.TabbedPane(); |
| this._tabbedPane.appendTab(viewMode.FlameChart, Common.UIString('Flame Chart'), new UI.VBox()); |
| this._tabbedPane.appendTab(viewMode.BottomUp, Common.UIString('Bottom-Up'), new UI.VBox()); |
| this._tabbedPane.appendTab(viewMode.CallTree, Common.UIString('Call Tree'), new UI.VBox()); |
| this._tabbedPane.appendTab(viewMode.EventLog, Common.UIString('Event Log'), new UI.VBox()); |
| this._tabbedPane.setTabIcon(viewMode.FlameChart, UI.Icon.create('largeicon-perf-flamechart')); |
| this._tabbedPane.setTabIcon(viewMode.BottomUp, UI.Icon.create('largeicon-perf-bottom-up-tree')); |
| this._tabbedPane.setTabIcon(viewMode.CallTree, UI.Icon.create('largeicon-perf-call-tree')); |
| this._tabbedPane.setTabIcon(viewMode.EventLog, UI.Icon.create('largeicon-perf-event-list')); |
| this._tabbedPane.addEventListener(UI.TabbedPane.Events.TabSelected, this._onMainViewChanged.bind(this)); |
| this._tabbedPane.selectTab(this._viewModeSetting.get()); |
| } else { |
| // Create top level properties splitter. |
| this._detailsSplitWidget = new UI.SplitWidget(false, true, 'timelinePanelDetailsSplitViewState', 400); |
| this._detailsSplitWidget.element.classList.add('timeline-details-split'); |
| this._detailsView = new Timeline.TimelineDetailsView(this._filters, this); |
| this._detailsSplitWidget.installResizer(this._detailsView.headerElement()); |
| this._detailsSplitWidget.setSidebarWidget(this._detailsView); |
| this._detailsSplitWidget.setMainWidget(this._stackView); |
| this._detailsSplitWidget.hideSidebar(); |
| this._detailsSplitWidget.show(this._timelinePane.element); |
| } |
| |
| this._onModeChanged(); |
| this._populateToolbar(); |
| this._showLandingPage(); |
| |
| Extensions.extensionServer.addEventListener( |
| Extensions.ExtensionServer.Events.TraceProviderAdded, this._appendExtensionsToToolbar, this); |
| SDK.targetManager.addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged, this); |
| } |
| |
| /** |
| * @return {!Timeline.TimelinePanel} |
| */ |
| static instance() { |
| return /** @type {!Timeline.TimelinePanel} */ (self.runtime.sharedInstance(Timeline.TimelinePanel)); |
| } |
| |
| /** |
| * @override |
| * @return {?UI.SearchableView} |
| */ |
| searchableView() { |
| return this._searchableView; |
| } |
| |
| /** |
| * @override |
| */ |
| wasShown() { |
| UI.context.setFlavor(Timeline.TimelinePanel, this); |
| } |
| |
| /** |
| * @override |
| */ |
| willHide() { |
| UI.context.setFlavor(Timeline.TimelinePanel, null); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onWindowChanged(event) { |
| this._windowStartTime = event.data.startTime; |
| this._windowEndTime = event.data.endTime; |
| |
| for (var i = 0; i < this._currentViews.length; ++i) |
| this._currentViews[i].setWindowTimes(this._windowStartTime, this._windowEndTime); |
| |
| if (!this._selection || this._selection.type() === Timeline.TimelineSelection.Type.Range) |
| this.select(null); |
| } |
| |
| _onMainViewChanged() { |
| this._viewModeSetting.set(this._tabbedPane.selectedTabId); |
| this._onModeChanged(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _onOverviewSelectionChanged(event) { |
| var selection = /** @type {!Timeline.TimelineSelection} */ (event.data); |
| this.select(selection); |
| } |
| |
| /** |
| * @override |
| * @param {number} windowStartTime |
| * @param {number} windowEndTime |
| */ |
| requestWindowTimes(windowStartTime, windowEndTime) { |
| this._overviewPane.requestWindowTimes(windowStartTime, windowEndTime); |
| } |
| |
| /** |
| * @param {!Timeline.TimelineModeView} modeView |
| */ |
| _addModeView(modeView) { |
| modeView.setModel(this._performanceModel); |
| modeView.setWindowTimes(this._windowStartTime, this._windowEndTime); |
| var splitWidget = |
| this._stackView.appendView(modeView.view(), 'timelinePanelTimelineStackSplitViewState', undefined, 112); |
| var resizer = modeView.resizerElement(); |
| if (splitWidget && resizer) { |
| splitWidget.hideDefaultResizer(); |
| splitWidget.installResizer(resizer); |
| } |
| this._currentViews.push(modeView); |
| } |
| |
| _removeAllModeViews() { |
| this._currentViews = []; |
| this._stackView.detachChildWidgets(); |
| } |
| |
| /** |
| * @param {!Timeline.TimelinePanel.State} state |
| */ |
| _setState(state) { |
| this._state = state; |
| this._updateTimelineControls(); |
| } |
| |
| /** |
| * @param {!Common.Setting} setting |
| * @param {string} tooltip |
| * @return {!UI.ToolbarItem} |
| */ |
| _createSettingCheckbox(setting, tooltip) { |
| const checkboxItem = new UI.ToolbarSettingCheckbox(setting, tooltip); |
| this._recordingOptionUIControls.push(checkboxItem); |
| return checkboxItem; |
| } |
| |
| _populateToolbar() { |
| // Record |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButton(this._toggleRecordAction)); |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('main.reload')); |
| var clearButton = new UI.ToolbarButton(Common.UIString('Clear'), 'largeicon-clear'); |
| clearButton.addEventListener(UI.ToolbarButton.Events.Click, () => this._clear()); |
| this._panelToolbar.appendToolbarItem(clearButton); |
| this._panelToolbar.appendSeparator(); |
| |
| // View |
| this._panelToolbar.appendSeparator(); |
| this._showScreenshotsToolbarCheckbox = |
| this._createSettingCheckbox(this._showScreenshotsSetting, Common.UIString('Capture screenshots')); |
| this._panelToolbar.appendToolbarItem(this._showScreenshotsToolbarCheckbox); |
| |
| this._showMemoryToolbarCheckbox = |
| this._createSettingCheckbox(this._showMemorySetting, Common.UIString('Show memory timeline')); |
| this._panelToolbar.appendToolbarItem(this._showMemoryToolbarCheckbox); |
| |
| // GC |
| this._panelToolbar.appendToolbarItem(UI.Toolbar.createActionButtonForId('components.collect-garbage')); |
| |
| // Settings |
| this._panelToolbar.appendSpacer(); |
| this._panelToolbar.appendText(''); |
| this._panelToolbar.appendSeparator(); |
| this._panelToolbar.appendToolbarItem(this._showSettingsPaneButton); |
| } |
| |
| _createSettingsPane() { |
| this._showSettingsPaneSetting = Common.settings.createSetting('timelineShowSettingsToolbar', false); |
| this._showSettingsPaneButton = new UI.ToolbarSettingToggle( |
| this._showSettingsPaneSetting, 'largeicon-settings-gear', Common.UIString('Capture settings')); |
| SDK.multitargetNetworkManager.addEventListener( |
| SDK.MultitargetNetworkManager.Events.ConditionsChanged, this._updateShowSettingsToolbarButton, this); |
| this._cpuThrottlingManager.addEventListener( |
| Components.CPUThrottlingManager.Events.RateChanged, this._updateShowSettingsToolbarButton, this); |
| this._disableCaptureJSProfileSetting.addChangeListener(this._updateShowSettingsToolbarButton, this); |
| this._captureLayersAndPicturesSetting.addChangeListener(this._updateShowSettingsToolbarButton, this); |
| |
| this._settingsPane = new UI.HBox(); |
| this._settingsPane.element.classList.add('timeline-settings-pane'); |
| this._settingsPane.show(this.element); |
| |
| var captureToolbar = new UI.Toolbar('', this._settingsPane.element); |
| captureToolbar.element.classList.add('flex-auto'); |
| captureToolbar.makeVertical(); |
| captureToolbar.appendToolbarItem(this._createSettingCheckbox( |
| this._disableCaptureJSProfileSetting, |
| Common.UIString('Disables JavaScript sampling, reduces overhead when running against mobile devices'))); |
| captureToolbar.appendToolbarItem(this._createSettingCheckbox( |
| this._captureLayersAndPicturesSetting, |
| Common.UIString('Captures advanced paint instrumentation, introduces significant performance overhead'))); |
| |
| var throttlingPane = new UI.VBox(); |
| throttlingPane.element.classList.add('flex-auto'); |
| throttlingPane.show(this._settingsPane.element); |
| |
| var throttlingToolbar1 = new UI.Toolbar('', throttlingPane.element); |
| throttlingToolbar1.appendText(Common.UIString('Network:')); |
| throttlingToolbar1.appendToolbarItem(this._createNetworkConditionsSelect()); |
| var throttlingToolbar2 = new UI.Toolbar('', throttlingPane.element); |
| throttlingToolbar2.appendText(Common.UIString('CPU:')); |
| throttlingToolbar2.appendToolbarItem(this._cpuThrottlingManager.createControl()); |
| |
| this._showSettingsPaneSetting.addChangeListener(this._updateSettingsPaneVisibility.bind(this)); |
| this._updateSettingsPaneVisibility(); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _appendExtensionsToToolbar(event) { |
| var provider = /** @type {!Extensions.ExtensionTraceProvider} */ (event.data); |
| const setting = Timeline.TimelinePanel._settingForTraceProvider(provider); |
| const checkbox = this._createSettingCheckbox(setting, provider.longDisplayName()); |
| this._panelToolbar.appendToolbarItem(checkbox); |
| } |
| |
| /** |
| * @param {!Extensions.ExtensionTraceProvider} traceProvider |
| * @return {!Common.Setting<boolean>} |
| */ |
| static _settingForTraceProvider(traceProvider) { |
| var setting = traceProvider[Timeline.TimelinePanel._traceProviderSettingSymbol]; |
| if (!setting) { |
| var providerId = traceProvider.persistentIdentifier(); |
| setting = Common.settings.createSetting(providerId, false); |
| setting.setTitle(traceProvider.shortDisplayName()); |
| traceProvider[Timeline.TimelinePanel._traceProviderSettingSymbol] = setting; |
| } |
| return setting; |
| } |
| |
| /** |
| * @return {!UI.ToolbarComboBox} |
| */ |
| _createNetworkConditionsSelect() { |
| var toolbarItem = new UI.ToolbarComboBox(null); |
| toolbarItem.setMaxWidth(140); |
| NetworkConditions.NetworkConditionsSelector.decorateSelect(toolbarItem.selectElement()); |
| return toolbarItem; |
| } |
| |
| _prepareToLoadTimeline() { |
| console.assert(this._state === Timeline.TimelinePanel.State.Idle); |
| this._setState(Timeline.TimelinePanel.State.Loading); |
| this._pendingPerformanceModel = new Timeline.PerformanceModel(); |
| } |
| |
| _createFileSelector() { |
| if (this._fileSelectorElement) |
| this._fileSelectorElement.remove(); |
| this._fileSelectorElement = UI.createFileSelectorElement(this._loadFromFile.bind(this)); |
| this._timelinePane.element.appendChild(this._fileSelectorElement); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| _contextMenu(event) { |
| var contextMenu = new UI.ContextMenu(event); |
| contextMenu.appendItemsAtLocation('timelineMenu'); |
| contextMenu.show(); |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| _saveToFile() { |
| if (this._state !== Timeline.TimelinePanel.State.Idle) |
| return true; |
| if (!this._performanceModel) |
| return true; |
| |
| var now = new Date(); |
| var fileName = 'Profile-' + now.toISO8601Compact() + '.json'; |
| var stream = new Bindings.FileOutputStream(); |
| |
| /** |
| * @param {boolean} accepted |
| * @this {Timeline.TimelinePanel} |
| */ |
| function callback(accepted) { |
| if (!accepted) |
| return; |
| var saver = new Timeline.TracingTimelineSaver(); |
| this._backingStorage.writeToStream(stream, saver); |
| } |
| stream.open(fileName, callback.bind(this)); |
| return true; |
| } |
| |
| /** |
| * @return {boolean} |
| */ |
| _selectFileToLoad() { |
| this._fileSelectorElement.click(); |
| return true; |
| } |
| |
| /** |
| * @param {!File} file |
| */ |
| _loadFromFile(file) { |
| if (this._state !== Timeline.TimelinePanel.State.Idle) |
| return; |
| this._prepareToLoadTimeline(); |
| this._loader = Timeline.TimelineLoader.loadFromFile(file, this); |
| this._createFileSelector(); |
| } |
| |
| /** |
| * @param {string} url |
| */ |
| _loadFromURL(url) { |
| if (this._state !== Timeline.TimelinePanel.State.Idle) |
| return; |
| this._prepareToLoadTimeline(); |
| this._loader = Timeline.TimelineLoader.loadFromURL(url, this); |
| } |
| |
| _onModeChanged() { |
| const showMemory = this._showMemorySetting.get(); |
| const showScreenshots = this._showScreenshotsSetting.get(); |
| // Set up overview controls. |
| this._overviewControls = []; |
| this._overviewControls.push(new Timeline.TimelineEventOverviewResponsiveness()); |
| if (Runtime.experiments.isEnabled('inputEventsOnTimelineOverview')) |
| this._overviewControls.push(new Timeline.TimelineEventOverviewInput()); |
| this._overviewControls.push(new Timeline.TimelineEventOverviewFrames()); |
| this._overviewControls.push(new Timeline.TimelineEventOverviewCPUActivity()); |
| this._overviewControls.push(new Timeline.TimelineEventOverviewNetwork()); |
| if (showScreenshots) |
| this._overviewControls.push(new Timeline.TimelineFilmStripOverview()); |
| if (showMemory) |
| this._overviewControls.push(new Timeline.TimelineEventOverviewMemory()); |
| for (var control of this._overviewControls) |
| control.setModel(this._performanceModel); |
| this._overviewPane.setOverviewControls(this._overviewControls); |
| |
| // Set up the main view. |
| this._removeAllModeViews(); |
| |
| var viewMode = Timeline.TimelinePanel.ViewMode.FlameChart; |
| this._flameChart = null; |
| if (Runtime.experiments.isEnabled('timelineMultipleMainViews')) { |
| viewMode = this._tabbedPane.selectedTabId; |
| this._stackView.detach(); |
| this._stackView.show(this._tabbedPane.visibleView.element); |
| } |
| var mainView; |
| if (viewMode === Timeline.TimelinePanel.ViewMode.FlameChart) { |
| this._flameChart = new Timeline.TimelineFlameChartView(this, this._filters); |
| this._addModeView(this._flameChart); |
| if (showMemory) |
| this._addModeView(new Timeline.CountersGraph(this)); |
| mainView = this._flameChart; |
| } else { |
| switch (viewMode) { |
| case Timeline.TimelinePanel.ViewMode.CallTree: |
| mainView = new Timeline.CallTreeTimelineTreeView(this._filters); |
| break; |
| case Timeline.TimelinePanel.ViewMode.EventLog: |
| mainView = new Timeline.EventsTimelineTreeView(this._filters, this); |
| break; |
| default: |
| mainView = new Timeline.BottomUpTimelineTreeView(this._filters); |
| break; |
| } |
| var treeView = new Timeline.TimelineTreeModeView(this, mainView); |
| this._addModeView(treeView); |
| } |
| if (this._searchableView) |
| this._searchableView.detach(); |
| this._searchableView = new UI.SearchableView(mainView); |
| this._searchableView.setMinimumSize(0, 100); |
| this._searchableView.element.classList.add('searchable-view'); |
| this._searchableView.show(this._timelinePane.element); |
| this._tabbedPane.show(this._searchableView.element); |
| mainView.setSearchableView(this._searchableView); |
| if (this._lastViewMode !== viewMode) { |
| this._lastViewMode = viewMode; |
| this._searchableView.cancelSearch(); |
| } |
| |
| this.doResize(); |
| this.select(null); |
| } |
| |
| _updateSettingsPaneVisibility() { |
| if (this._showSettingsPaneSetting.get()) |
| this._settingsPane.showWidget(); |
| else |
| this._settingsPane.hideWidget(); |
| } |
| |
| _updateShowSettingsToolbarButton() { |
| var messages = []; |
| if (this._cpuThrottlingManager.rate() !== 1) |
| messages.push(Common.UIString('- CPU throttling is enabled')); |
| if (SDK.multitargetNetworkManager.isThrottling()) |
| messages.push(Common.UIString('- Network throttling is enabled')); |
| if (this._captureLayersAndPicturesSetting.get()) |
| messages.push(Common.UIString('- Significant overhead due to paint instrumentation')); |
| if (this._disableCaptureJSProfileSetting.get()) |
| messages.push(Common.UIString('- JavaScript sampling is disabled')); |
| |
| this._showSettingsPaneButton.setDefaultWithRedColor(messages.length); |
| this._showSettingsPaneButton.setToggleWithRedColor(messages.length); |
| |
| if (messages.length) { |
| var tooltipElement = createElement('div'); |
| messages.forEach(message => { |
| tooltipElement.createChild('div').textContent = message; |
| }); |
| this._showSettingsPaneButton.setTitle(tooltipElement); |
| } else { |
| this._showSettingsPaneButton.setTitle(Common.UIString('Capture settings')); |
| } |
| } |
| |
| /** |
| * @param {boolean} enabled |
| */ |
| _setUIControlsEnabled(enabled) { |
| this._recordingOptionUIControls.forEach(control => control.setEnabled(enabled)); |
| } |
| |
| /** |
| * @param {boolean} userInitiated |
| */ |
| _startRecording(userInitiated) { |
| console.assert(!this._statusPane, 'Status pane is already opened.'); |
| var mainTarget = SDK.targetManager.mainTarget(); |
| if (!mainTarget) |
| return; |
| this._setState(Timeline.TimelinePanel.State.StartPending); |
| this._showRecordingStarted(); |
| |
| this._autoRecordGeneration = userInitiated ? null : Symbol('Generation'); |
| var enabledTraceProviders = Extensions.extensionServer.traceProviders().filter( |
| provider => Timeline.TimelinePanel._settingForTraceProvider(provider).get()); |
| |
| const recordingOptions = { |
| enableJSSampling: !this._disableCaptureJSProfileSetting.get(), |
| capturePictures: this._captureLayersAndPicturesSetting.get(), |
| captureFilmStrip: this._showScreenshotsSetting.get() |
| }; |
| |
| this._pendingPerformanceModel = new Timeline.PerformanceModel(); |
| this._controller = new Timeline.TimelineController(mainTarget, this._pendingPerformanceModel, this); |
| this._controller.startRecording(recordingOptions, enabledTraceProviders); |
| |
| Host.userMetrics.actionTaken( |
| userInitiated ? Host.UserMetrics.Action.TimelineStarted : Host.UserMetrics.Action.TimelinePageReloadStarted); |
| this._setUIControlsEnabled(false); |
| this._hideLandingPage(); |
| } |
| |
| _stopRecording() { |
| if (this._statusPane) { |
| this._statusPane.finish(); |
| this._statusPane.updateStatus(Common.UIString('Stopping timeline\u2026')); |
| this._statusPane.updateProgressBar(Common.UIString('Received'), 0); |
| } |
| this._setState(Timeline.TimelinePanel.State.StopPending); |
| this._autoRecordGeneration = null; |
| this._controller.stopRecording(); |
| this._controller = null; |
| this._setUIControlsEnabled(true); |
| } |
| |
| _onSuspendStateChanged() { |
| this._updateTimelineControls(); |
| } |
| |
| _updateTimelineControls() { |
| var state = Timeline.TimelinePanel.State; |
| this._toggleRecordAction.setToggled(this._state === state.Recording); |
| this._toggleRecordAction.setEnabled(this._state === state.Recording || this._state === state.Idle); |
| this._panelToolbar.setEnabled(this._state !== state.Loading); |
| this._dropTarget.setEnabled(this._state === state.Idle); |
| } |
| |
| _toggleRecording() { |
| if (this._state === Timeline.TimelinePanel.State.Idle) |
| this._startRecording(true); |
| else if (this._state === Timeline.TimelinePanel.State.Recording) |
| this._stopRecording(); |
| } |
| |
| _clear() { |
| this._showLandingPage(); |
| if (this._detailsSplitWidget) |
| this._detailsSplitWidget.hideSidebar(); |
| this._reset(); |
| } |
| |
| _reset() { |
| PerfUI.LineLevelProfile.instance().reset(); |
| this._setModel(null); |
| delete this._selection; |
| } |
| |
| /** |
| * @param {?Timeline.PerformanceModel} model |
| */ |
| _setModel(model) { |
| if (this._performanceModel) |
| this._performanceModel.dispose(); |
| this._performanceModel = model; |
| this._currentViews.forEach(view => view.setModel(this._performanceModel)); |
| |
| this._overviewPane.reset(); |
| if (model) { |
| this._overviewPane.setBounds( |
| model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime()); |
| } |
| for (var control of this._overviewControls) |
| control.setModel(model); |
| |
| if (model) { |
| var cpuProfiles = model.timelineModel().cpuProfiles(); |
| cpuProfiles.forEach(profile => PerfUI.LineLevelProfile.instance().appendCPUProfile(profile)); |
| |
| this._setAutoWindowTimes(model.timelineModel()); |
| this._setMarkers(model.timelineModel()); |
| } else { |
| this.requestWindowTimes(0, Infinity); |
| } |
| this._overviewPane.scheduleUpdate(); |
| if (this._detailsView) |
| this._detailsView.setModel(model); |
| |
| this.select(null); |
| if (this._flameChart) |
| this._flameChart.resizeToPreferredHeights(); |
| } |
| |
| /** |
| * @override |
| */ |
| recordingStarted() { |
| this._reset(); |
| this._setState(Timeline.TimelinePanel.State.Recording); |
| this._showRecordingStarted(); |
| this._statusPane.updateStatus(Common.UIString('Profiling\u2026')); |
| this._statusPane.updateProgressBar(Common.UIString('Buffer usage'), 0); |
| this._statusPane.startTimer(); |
| this._hideLandingPage(); |
| } |
| |
| /** |
| * @override |
| * @param {number} usage |
| */ |
| recordingProgress(usage) { |
| this._statusPane.updateProgressBar(Common.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) { |
| var e = createElement(tagName); |
| e.textContent = contents; |
| return e; |
| } |
| |
| var learnMoreNode = UI.createExternalLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/', Common.UIString('Learn\xa0more')); |
| var recordKey = |
| encloseWithTag('b', UI.shortcutRegistry.shortcutDescriptorsForAction('timeline.toggle-recording')[0].name); |
| var reloadKey = encloseWithTag('b', UI.shortcutRegistry.shortcutDescriptorsForAction('main.reload')[0].name); |
| var navigateNode = encloseWithTag('b', Common.UIString('WASD')); |
| |
| this._landingPage = new UI.VBox(); |
| this._landingPage.contentElement.classList.add('timeline-landing-page', 'fill'); |
| var centered = this._landingPage.contentElement.createChild('div'); |
| |
| var recordButton = UI.Toolbar.createActionButton(this._toggleRecordAction).element; |
| var reloadButton = UI.Toolbar.createActionButtonForId('main.reload').element; |
| |
| centered.createChild('p').appendChild(UI.formatLocalized( |
| 'Click the record button %s or hit %s to capture a new recording.\n' + |
| 'Click the reload button %s or hit %s to record and evaluate the page load.', |
| [recordButton, recordKey, reloadButton, reloadKey])); |
| |
| centered.createChild('p').appendChild(UI.formatLocalized( |
| 'After recording, select an area of interest in the overview by dragging.\n' + |
| 'Then, zoom and pan the timeline with the mousewheel or %s keys.\n%s', |
| [navigateNode, learnMoreNode])); |
| |
| var cpuProfilerHintSetting = Common.settings.createSetting('timelineShowProfilerHint', true); |
| if (cpuProfilerHintSetting.get()) { |
| var warning = centered.createChild('p', 'timeline-landing-warning'); |
| var closeButton = warning.createChild('div', 'timeline-landing-warning-close', 'dt-close-button'); |
| closeButton.addEventListener('click', () => { |
| warning.style.visibility = 'hidden'; |
| cpuProfilerHintSetting.set(false); |
| }, false); |
| var performanceSpan = encloseWithTag('b', Common.UIString('Performance')); |
| warning.createChild('div').appendChild(UI.formatLocalized( |
| 'The %s panel provides the combined functionality of Timeline and CPU profiler.%s' + |
| 'The JavaScript CPU profiler will be removed shortly. Meanwhile, it\'s available under ' + |
| '%s \u2192 More Tools \u2192 JavaScript Profiler.', |
| [performanceSpan, createElement('p'), UI.Icon.create('largeicon-menu')])); |
| } |
| |
| this._landingPage.show(this._statusPaneContainer); |
| } |
| |
| _hideLandingPage() { |
| this._landingPage.detach(); |
| } |
| |
| /** |
| * @override |
| */ |
| loadingStarted() { |
| this._hideLandingPage(); |
| |
| if (this._statusPane) |
| this._statusPane.hide(); |
| this._statusPane = new Timeline.TimelinePanel.StatusPane(false, this._cancelLoading.bind(this)); |
| this._statusPane.showPane(this._statusPaneContainer); |
| this._statusPane.updateStatus(Common.UIString('Loading profile\u2026')); |
| // 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('Received'), progress * 100); |
| } |
| |
| /** |
| * @override |
| */ |
| processingStarted() { |
| this._statusPane.updateStatus(Common.UIString('Processing profile\u2026')); |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.TracingModel} tracingModel |
| * @param {?Bindings.TempFileBackingStorage} backingStorage |
| */ |
| loadingComplete(tracingModel, backingStorage) { |
| delete this._loader; |
| this._setState(Timeline.TimelinePanel.State.Idle); |
| var performanceModel = this._pendingPerformanceModel; |
| this._pendingPerformanceModel = null; |
| this._backingStorage = backingStorage; |
| |
| if (this._statusPane) |
| this._statusPane.hide(); |
| delete this._statusPane; |
| |
| if (!tracingModel) { |
| performanceModel.dispose(); |
| this._clear(); |
| return; |
| } |
| |
| performanceModel.setTracingModel(tracingModel); |
| this._backingStorage = backingStorage; |
| this._setModel(performanceModel); |
| |
| if (this._detailsSplitWidget) |
| this._detailsSplitWidget.showBoth(); |
| } |
| |
| _showRecordingStarted() { |
| if (this._statusPane) |
| return; |
| this._statusPane = new Timeline.TimelinePanel.StatusPane(true, this._stopRecording.bind(this)); |
| this._statusPane.showPane(this._statusPaneContainer); |
| this._statusPane.updateStatus(Common.UIString('Initializing profiler\u2026')); |
| } |
| |
| _cancelLoading() { |
| if (this._loader) |
| this._loader.cancel(); |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel} timelineModel |
| */ |
| _setMarkers(timelineModel) { |
| var markers = new Map(); |
| var recordTypes = TimelineModel.TimelineModel.RecordType; |
| var zeroTime = timelineModel.minimumRecordTime(); |
| for (var event of timelineModel.eventDividers()) { |
| if (event.name === recordTypes.TimeStamp || event.name === recordTypes.ConsoleTime) |
| continue; |
| markers.set(event.startTime, Timeline.TimelineUIUtils.createEventDivider(event, zeroTime)); |
| } |
| this._overviewPane.setMarkers(markers); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _pageReloadRequested(event) { |
| if (this._state !== Timeline.TimelinePanel.State.Idle || !this.isShowing()) |
| return; |
| this._startRecording(false); |
| } |
| |
| /** |
| * @param {!Common.Event} event |
| */ |
| _loadEventFired(event) { |
| if (this._state !== Timeline.TimelinePanel.State.Recording || !this._autoRecordGeneration) |
| return; |
| setTimeout(stopRecordingOnReload.bind(this, this._autoRecordGeneration), this._millisecondsToRecordAfterLoadEvent); |
| |
| /** |
| * @this {Timeline.TimelinePanel} |
| * @param {!Object} recordGeneration |
| */ |
| function stopRecordingOnReload(recordGeneration) { |
| // Check if we're still in the same recording session. |
| if (this._state !== Timeline.TimelinePanel.State.Recording || this._autoRecordGeneration !== recordGeneration) |
| return; |
| this._stopRecording(); |
| } |
| } |
| |
| /** |
| * @param {!Timeline.TimelineSelection} selection |
| * @return {?TimelineModel.TimelineFrame} |
| */ |
| _frameForSelection(selection) { |
| switch (selection.type()) { |
| case Timeline.TimelineSelection.Type.Frame: |
| return /** @type {!TimelineModel.TimelineFrame} */ (selection.object()); |
| case Timeline.TimelineSelection.Type.Range: |
| return null; |
| case Timeline.TimelineSelection.Type.TraceEvent: |
| return this._performanceModel.frameModel().filteredFrames(selection._endTime, selection._endTime)[0]; |
| default: |
| console.assert(false, 'Should never be reached'); |
| return null; |
| } |
| } |
| |
| /** |
| * @param {number} offset |
| */ |
| _jumpToFrame(offset) { |
| var currentFrame = this._frameForSelection(this._selection); |
| if (!currentFrame) |
| return; |
| var frames = this._performanceModel.frames(); |
| var 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); |
| var frame = frames[index]; |
| this._revealTimeRange(frame.startTime, frame.endTime); |
| this.select(Timeline.TimelineSelection.fromFrame(frame)); |
| return true; |
| } |
| |
| /** |
| * @override |
| * @param {?Timeline.TimelineSelection} selection |
| * @param {!Timeline.TimelineDetailsView.Tab=} preferredTab |
| */ |
| select(selection, preferredTab) { |
| if (!selection) |
| selection = Timeline.TimelineSelection.fromRange(this._windowStartTime, this._windowEndTime); |
| this._selection = selection; |
| if (preferredTab && this._detailsView) |
| this._detailsView.setPreferredTab(preferredTab); |
| for (var view of this._currentViews) |
| view.setSelection(selection); |
| if (this._detailsView) |
| this._detailsView.setSelection(selection); |
| } |
| |
| /** |
| * @override |
| * @param {number} time |
| */ |
| selectEntryAtTime(time) { |
| var events = this._performanceModel ? this._performanceModel.timelineModel().mainThreadEvents() : []; |
| // Find best match, then backtrack to the first visible entry. |
| for (var index = events.upperBound(time, (time, event) => time - event.startTime) - 1; index >= 0; --index) { |
| var event = events[index]; |
| var endTime = event.endTime || event.startTime; |
| if (SDK.TracingModel.isTopLevelEvent(event) && endTime < time) |
| break; |
| if (TimelineModel.TimelineModel.isVisible(this._filters, event) && endTime >= time) { |
| this.select(Timeline.TimelineSelection.fromTraceEvent(event)); |
| return; |
| } |
| } |
| this.select(null); |
| } |
| |
| /** |
| * @override |
| * @param {?SDK.TracingModel.Event} event |
| */ |
| highlightEvent(event) { |
| for (var view of this._currentViews) |
| view.highlightEvent(event); |
| } |
| |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| */ |
| _revealTimeRange(startTime, endTime) { |
| var timeShift = 0; |
| if (this._windowEndTime < endTime) |
| timeShift = endTime - this._windowEndTime; |
| else if (this._windowStartTime > startTime) |
| timeShift = startTime - this._windowStartTime; |
| if (timeShift) |
| this.requestWindowTimes(this._windowStartTime + timeShift, this._windowEndTime + timeShift); |
| } |
| |
| /** |
| * @param {!DataTransfer} dataTransfer |
| */ |
| _handleDrop(dataTransfer) { |
| var items = dataTransfer.items; |
| if (!items.length) |
| return; |
| var item = items[0]; |
| if (item.kind === 'string') { |
| var url = dataTransfer.getData('text/uri-list'); |
| if (new Common.ParsedURL(url).isValid) |
| this._loadFromURL(url); |
| } else if (item.kind === 'file') { |
| var entry = items[0].webkitGetAsEntry(); |
| if (!entry.isFile) |
| return; |
| entry.file(this._loadFromFile.bind(this)); |
| } |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel} timelineModel |
| */ |
| _setAutoWindowTimes(timelineModel) { |
| var tasks = timelineModel.mainThreadTasks(); |
| if (!tasks.length) { |
| this.requestWindowTimes(timelineModel.minimumRecordTime(), timelineModel.maximumRecordTime()); |
| return; |
| } |
| /** |
| * @param {number} startIndex |
| * @param {number} stopIndex |
| * @return {number} |
| */ |
| function findLowUtilizationRegion(startIndex, stopIndex) { |
| var /** @const */ threshold = 0.1; |
| var cutIndex = startIndex; |
| var cutTime = (tasks[cutIndex].startTime + tasks[cutIndex].endTime) / 2; |
| var usedTime = 0; |
| var step = Math.sign(stopIndex - startIndex); |
| for (var i = startIndex; i !== stopIndex; i += step) { |
| var task = tasks[i]; |
| var taskTime = (task.startTime + task.endTime) / 2; |
| var interval = Math.abs(cutTime - taskTime); |
| if (usedTime < threshold * interval) { |
| cutIndex = i; |
| cutTime = taskTime; |
| usedTime = 0; |
| } |
| usedTime += task.duration; |
| } |
| return cutIndex; |
| } |
| var rightIndex = findLowUtilizationRegion(tasks.length - 1, 0); |
| var leftIndex = findLowUtilizationRegion(0, rightIndex); |
| var leftTime = tasks[leftIndex].startTime; |
| var rightTime = tasks[rightIndex].endTime; |
| var span = rightTime - leftTime; |
| var totalSpan = timelineModel.maximumRecordTime() - timelineModel.minimumRecordTime(); |
| if (span < totalSpan * 0.1) { |
| leftTime = timelineModel.minimumRecordTime(); |
| rightTime = timelineModel.maximumRecordTime(); |
| } else { |
| leftTime = Math.max(leftTime - 0.05 * span, timelineModel.minimumRecordTime()); |
| rightTime = Math.min(rightTime + 0.05 * span, timelineModel.maximumRecordTime()); |
| } |
| this.requestWindowTimes(leftTime, rightTime); |
| } |
| }; |
| |
| /** |
| * @enum {symbol} |
| */ |
| Timeline.TimelinePanel.State = { |
| Idle: Symbol('Idle'), |
| StartPending: Symbol('StartPending'), |
| Recording: Symbol('Recording'), |
| StopPending: Symbol('StopPending'), |
| Loading: Symbol('Loading') |
| }; |
| |
| /** |
| * @enum {string} |
| */ |
| Timeline.TimelinePanel.ViewMode = { |
| FlameChart: 'FlameChart', |
| BottomUp: 'BottomUp', |
| CallTree: 'CallTree', |
| EventLog: 'EventLog' |
| }; |
| |
| // Define row and header height, should be in sync with styles for timeline graphs. |
| Timeline.TimelinePanel.rowHeight = 18; |
| Timeline.TimelinePanel.headerHeight = 20; |
| |
| |
| Timeline.TimelineSelection = class { |
| /** |
| * @param {!Timeline.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.TimelineFrame} frame |
| * @return {!Timeline.TimelineSelection} |
| */ |
| static fromFrame(frame) { |
| return new Timeline.TimelineSelection(Timeline.TimelineSelection.Type.Frame, frame.startTime, frame.endTime, frame); |
| } |
| |
| /** |
| * @param {!TimelineModel.TimelineModel.NetworkRequest} request |
| * @return {!Timeline.TimelineSelection} |
| */ |
| static fromNetworkRequest(request) { |
| return new Timeline.TimelineSelection( |
| Timeline.TimelineSelection.Type.NetworkRequest, request.startTime, request.endTime || request.startTime, |
| request); |
| } |
| |
| /** |
| * @param {!SDK.TracingModel.Event} event |
| * @return {!Timeline.TimelineSelection} |
| */ |
| static fromTraceEvent(event) { |
| return new Timeline.TimelineSelection( |
| Timeline.TimelineSelection.Type.TraceEvent, event.startTime, event.endTime || (event.startTime + 1), event); |
| } |
| |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| * @return {!Timeline.TimelineSelection} |
| */ |
| static fromRange(startTime, endTime) { |
| return new Timeline.TimelineSelection(Timeline.TimelineSelection.Type.Range, startTime, endTime); |
| } |
| |
| /** |
| * @return {!Timeline.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} |
| */ |
| Timeline.TimelineSelection.Type = { |
| Frame: 'Frame', |
| NetworkRequest: 'NetworkRequest', |
| TraceEvent: 'TraceEvent', |
| Range: 'Range' |
| }; |
| |
| |
| /** |
| * @interface |
| * @extends {Common.EventTarget} |
| */ |
| Timeline.TimelineModeView = function() {}; |
| |
| Timeline.TimelineModeView.prototype = { |
| /** |
| * @return {!UI.Widget} |
| */ |
| view() {}, |
| |
| /** |
| * @return {?Element} |
| */ |
| resizerElement() {}, |
| |
| /** |
| * @param {?Timeline.PerformanceModel} model |
| */ |
| setModel(model) {}, |
| |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| */ |
| setWindowTimes(startTime, endTime) {}, |
| |
| /** |
| * @param {?Timeline.TimelineSelection} selection |
| */ |
| setSelection(selection) {}, |
| |
| /** |
| * @param {?SDK.TracingModel.Event} event |
| */ |
| highlightEvent(event) {} |
| }; |
| |
| /** |
| * @interface |
| */ |
| Timeline.TimelineModeViewDelegate = function() {}; |
| |
| Timeline.TimelineModeViewDelegate.prototype = { |
| /** |
| * @param {number} startTime |
| * @param {number} endTime |
| */ |
| requestWindowTimes(startTime, endTime) {}, |
| |
| /** |
| * @param {?Timeline.TimelineSelection} selection |
| * @param {!Timeline.TimelineDetailsView.Tab=} preferredTab |
| */ |
| select(selection, preferredTab) {}, |
| |
| /** |
| * @param {number} time |
| */ |
| selectEntryAtTime(time) {}, |
| |
| /** |
| * @param {?SDK.TracingModel.Event} event |
| */ |
| highlightEvent(event) {} |
| }; |
| |
| /** |
| * @unrestricted |
| */ |
| Timeline.TimelinePanel.StatusPane = class extends UI.VBox { |
| /** |
| * @param {boolean} showTimer |
| * @param {function()} stopCallback |
| */ |
| constructor(showTimer, stopCallback) { |
| super(true); |
| this.registerRequiredCSS('timeline/timelineStatusDialog.css'); |
| this.contentElement.classList.add('timeline-status-dialog'); |
| |
| var statusLine = this.contentElement.createChild('div', 'status-dialog-line status'); |
| statusLine.createChild('div', 'label').textContent = Common.UIString('Status'); |
| this._status = statusLine.createChild('div', 'content'); |
| |
| if (showTimer) { |
| var timeLine = this.contentElement.createChild('div', 'status-dialog-line time'); |
| timeLine.createChild('div', 'label').textContent = Common.UIString('Time'); |
| this._time = timeLine.createChild('div', 'content'); |
| } |
| var 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'); |
| |
| this._stopButton = UI.createTextButton(Common.UIString('Stop'), stopCallback); |
| 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'); |
| } |
| |
| /** |
| * @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) + '%'; |
| this._updateTimer(); |
| } |
| |
| 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; |
| var elapsed = (Date.now() - this._startTime) / 1000; |
| this._time.textContent = Common.UIString('%s\u2009sec', elapsed.toFixed(precise ? 1 : 0)); |
| } |
| }; |
| |
| |
| /** |
| * @implements {Common.QueryParamHandler} |
| * @unrestricted |
| */ |
| Timeline.LoadTimelineHandler = class { |
| /** |
| * @override |
| * @param {string} value |
| */ |
| handleQueryParam(value) { |
| UI.viewManager.showView('timeline').then(() => { |
| Timeline.TimelinePanel.instance()._loadFromURL(window.decodeURIComponent(value)); |
| }); |
| } |
| }; |
| |
| /** |
| * @implements {UI.ActionDelegate} |
| * @unrestricted |
| */ |
| Timeline.TimelinePanel.ActionDelegate = class { |
| /** |
| * @override |
| * @param {!UI.Context} context |
| * @param {string} actionId |
| * @return {boolean} |
| */ |
| handleAction(context, actionId) { |
| var panel = UI.context.flavor(Timeline.TimelinePanel); |
| console.assert(panel && panel instanceof Timeline.TimelinePanel); |
| switch (actionId) { |
| case 'timeline.toggle-recording': |
| panel._toggleRecording(); |
| 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; |
| } |
| return false; |
| } |
| }; |
| |
| Timeline.TimelinePanel._traceProviderSettingSymbol = Symbol('traceProviderSetting'); |