UNPKG

debug-server-next

Version:

Dev server for hippy-core.

1,108 lines (1,107 loc) 57.8 kB
// Copyright 2021 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /* * 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. */ /* eslint-disable rulesdir/no_underscored_properties */ import * as Common from '../../core/common/common.js'; import * as Host from '../../core/host/host.js'; import * as i18n from '../../core/i18n/i18n.js'; import * as Platform from '../../core/platform/platform.js'; import * as Root from '../../core/root/root.js'; import * as SDK from '../../core/sdk/sdk.js'; import * as Bindings from '../../models/bindings/bindings.js'; import * as Extensions from '../../models/extensions/extensions.js'; import * as TimelineModel from '../../models/timeline_model/timeline_model.js'; import * as PerfUI from '../../ui/legacy/components/perf_ui/perf_ui.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as MobileThrottling from '../mobile_throttling/mobile_throttling.js'; import { Events, PerformanceModel } from './PerformanceModel.js'; import { TimelineController } from './TimelineController.js'; import { TimelineEventOverviewCoverage, TimelineEventOverviewCPUActivity, TimelineEventOverviewFrames, TimelineEventOverviewInput, TimelineEventOverviewMemory, TimelineEventOverviewNetwork, TimelineEventOverviewResponsiveness, TimelineFilmStripOverview } from './TimelineEventOverview.js'; 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'; const UIStrings = { /** *@description Text that appears when user drag and drop something (for example, a file) in Timeline Panel of the Performance panel */ dropTimelineFileOrUrlHere: 'Drop timeline file or URL here', /** *@description Title of disable capture jsprofile setting in timeline panel of the performance panel */ disableJavascriptSamples: 'Disable JavaScript samples', /** *@description Title of capture layers and pictures setting in timeline panel of the performance panel */ enableAdvancedPaint: 'Enable advanced paint instrumentation (slow)', /** *@description Title of show screenshots setting in timeline panel of the performance panel */ screenshots: 'Screenshots', /** *@description Title of the 'Coverage' tool in the bottom drawer */ coverage: 'Coverage', /** *@description Text for the memory of the page */ memory: 'Memory', /** *@description Text in Timeline for the Web Vitals lane */ webVitals: 'Web Vitals', /** *@description Text to clear content */ clear: 'Clear', /** *@description Tooltip text that appears when hovering over the largeicon load button */ loadProfile: 'Load profile…', /** *@description Tooltip text that appears when hovering over the largeicon download button */ saveProfile: 'Save profile…', /** *@description Text to take screenshots */ captureScreenshots: 'Capture screenshots', /** *@description Text in Timeline Panel of the Performance panel */ showMemoryTimeline: 'Show memory timeline', /** *@description Text in Timeline for the Web Vitals lane checkbox */ showWebVitals: 'Show Web Vitals', /** *@description Text in Timeline Panel of the Performance panel */ recordCoverageWithPerformance: 'Record coverage with performance trace', /** *@description Tooltip text that appears when hovering over the largeicon settings gear in show settings pane setting in timeline panel of the performance panel */ captureSettings: 'Capture settings', /** *@description Text in Timeline Panel of the Performance panel */ disablesJavascriptSampling: 'Disables JavaScript sampling, reduces overhead when running against mobile devices', /** *@description Text in Timeline Panel of the Performance panel */ capturesAdvancedPaint: 'Captures advanced paint instrumentation, introduces significant performance overhead', /** *@description Text in Timeline Panel of the Performance panel */ network: 'Network:', /** *@description Text in Timeline Panel of the Performance panel */ cpu: 'CPU:', /** *@description Title of the 'Network conditions' tool in the bottom drawer */ networkConditions: 'Network conditions', /** *@description Text in Timeline Panel of the Performance panel *@example {wrong format} PH1 *@example {ERROR_FILE_NOT_FOUND} PH2 *@example {2} PH3 */ failedToSaveTimelineSSS: 'Failed to save timeline: {PH1} ({PH2}, {PH3})', /** *@description Text in Timeline Panel of the Performance panel */ CpuThrottlingIsEnabled: '- CPU throttling is enabled', /** *@description Text in Timeline Panel of the Performance panel */ NetworkThrottlingIsEnabled: '- Network throttling is enabled', /** *@description Text in Timeline Panel of the Performance panel */ SignificantOverheadDueToPaint: '- Significant overhead due to paint instrumentation', /** *@description Text in Timeline Panel of the Performance panel */ JavascriptSamplingIsDisabled: '- JavaScript sampling is disabled', /** *@description Text in Timeline Panel of the Performance panel */ stoppingTimeline: 'Stopping timeline…', /** *@description Text in Timeline Panel of the Performance panel */ received: 'Received', /** *@description Text to close something */ close: 'Close', /** *@description Status text to indicate the recording has failed in the Performance panel */ recordingFailed: 'Recording failed', /** * @description Text to indicate the progress of a profile. Informs the user that we are currently * creating a peformance profile. */ profiling: 'Profiling…', /** *@description Text in Timeline Panel of the Performance panel */ bufferUsage: 'Buffer usage', /** *@description Text for an option to learn more about something */ learnmore: 'Learn more', /** *@description Text in Timeline Panel of the Performance panel */ wasd: 'WASD', /** *@description Text in Timeline Panel of the Performance panel *@example {record} PH1 *@example {Ctrl + R} PH2 */ clickTheRecordButtonSOrHitSTo: 'Click the record button {PH1} or hit {PH2} to start a new recording.', /** * @description Text in Timeline Panel of the Performance panel * @example {reload button} PH1 * @example {Ctrl + R} PH2 */ clickTheReloadButtonSOrHitSTo: 'Click the reload button {PH1} or hit {PH2} to record the page load.', /** *@description Text in Timeline Panel of the Performance panel *@example {Ctrl + U} PH1 *@example {Learn more} PH2 */ afterRecordingSelectAnAreaOf: 'After recording, select an area of interest in the overview by dragging. Then, zoom and pan the timeline with the mousewheel or {PH1} keys. {PH2}', /** *@description Text in Timeline Panel of the Performance panel */ loadingProfile: 'Loading profile…', /** *@description Text in Timeline Panel of the Performance panel */ processingProfile: 'Processing profile…', /** *@description Text in Timeline Panel of the Performance panel */ initializingProfiler: 'Initializing profiler…', /** *@description Text for the status of something */ status: 'Status', /** *@description Text that refers to the time */ time: 'Time', /** *@description Text for the description of something */ description: 'Description', /** *@description Text of an item that stops the running task */ stop: 'Stop', /** *@description Time text content in Timeline Panel of the Performance panel *@example {2.12} PH1 */ ssec: '{PH1} sec', }; const str_ = i18n.i18n.registerUIStrings('panels/timeline/TimelinePanel.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); let timelinePanelInstance; export class TimelinePanel extends UI.Panel.Panel { _dropTarget; _recordingOptionUIControls; _state; _recordingPageReload; _millisecondsToRecordAfterLoadEvent; _toggleRecordAction; _recordReloadAction; _historyManager; _performanceModel; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _viewModeSetting; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _disableCaptureJSProfileSetting; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _captureLayersAndPicturesSetting; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _showScreenshotsSetting; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _startCoverage; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _showMemorySetting; // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _showWebVitalsSetting; _panelToolbar; _panelRightToolbar; _timelinePane; _overviewPane; _overviewControls; _statusPaneContainer; _flameChart; _searchableView; _showSettingsPaneButton; _showSettingsPaneSetting; _settingsPane; _controller; _clearButton; _loadButton; _saveButton; _statusPane; _landingPage; _loader; _showScreenshotsToolbarCheckbox; _showMemoryToolbarCheckbox; _showWebVitalsToolbarCheckbox; _startCoverageCheckbox; _networkThrottlingSelect; _cpuThrottlingSelect; _fileSelectorElement; _selection; constructor() { super('timeline'); this.registerRequiredCSS('panels/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], i18nString(UIStrings.dropTimelineFileOrUrlHere), this._handleDrop.bind(this)); this._recordingOptionUIControls = []; this._state = State.Idle; this._recordingPageReload = false; this._millisecondsToRecordAfterLoadEvent = 5000; this._toggleRecordAction = UI.ActionRegistry.ActionRegistry.instance().action('timeline.toggle-recording'); this._recordReloadAction = UI.ActionRegistry.ActionRegistry.instance().action('timeline.record-reload'); this._historyManager = new TimelineHistoryManager(); this._performanceModel = null; this._viewModeSetting = Common.Settings.Settings.instance().createSetting('timelineViewMode', ViewMode.FlameChart); this._disableCaptureJSProfileSetting = Common.Settings.Settings.instance().createSetting('timelineDisableJSSampling', false); this._disableCaptureJSProfileSetting.setTitle(i18nString(UIStrings.disableJavascriptSamples)); this._captureLayersAndPicturesSetting = Common.Settings.Settings.instance().createSetting('timelineCaptureLayersAndPictures', false); this._captureLayersAndPicturesSetting.setTitle(i18nString(UIStrings.enableAdvancedPaint)); this._showScreenshotsSetting = Common.Settings.Settings.instance().createSetting('timelineShowScreenshots', true); this._showScreenshotsSetting.setTitle(i18nString(UIStrings.screenshots)); this._showScreenshotsSetting.addChangeListener(this._updateOverviewControls, this); this._startCoverage = Common.Settings.Settings.instance().createSetting('timelineStartCoverage', false); this._startCoverage.setTitle(i18nString(UIStrings.coverage)); if (!Root.Runtime.experiments.isEnabled('recordCoverageWithPerformanceTracing')) { this._startCoverage.set(false); } this._showMemorySetting = Common.Settings.Settings.instance().createSetting('timelineShowMemory', false); this._showMemorySetting.setTitle(i18nString(UIStrings.memory)); this._showMemorySetting.addChangeListener(this._onModeChanged, this); this._showWebVitalsSetting = Common.Settings.Settings.instance().createSetting('timelineWebVitals', false); this._showWebVitalsSetting.setTitle(i18nString(UIStrings.webVitals)); this._showWebVitalsSetting.addChangeListener(this._onWebVitalsChanged, this); const timelineToolbarContainer = this.element.createChild('div', 'timeline-toolbar-container'); this._panelToolbar = new UI.Toolbar.Toolbar('timeline-main-toolbar', timelineToolbarContainer); this._panelToolbar.makeWrappable(true); 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); this._overviewControls = []; this._statusPaneContainer = this._timelinePane.element.createChild('div', 'status-pane-container fill'); this._createFileSelector(); SDK.TargetManager.TargetManager.instance().addModelListener(SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.Load, this._loadEventFired, this); this._flameChart = new TimelineFlameChartView(this); this._searchableView = new UI.SearchableView.SearchableView(this._flameChart, null); 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._onWebVitalsChanged(); this._populateToolbar(); this._showLandingPage(); this._updateTimelineControls(); Extensions.ExtensionServer.ExtensionServer.instance().addEventListener(Extensions.ExtensionServer.Events.TraceProviderAdded, this._appendExtensionsToToolbar, this); SDK.TargetManager.TargetManager.instance().addEventListener(SDK.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged, this); } static instance(opts = { forceNew: null }) { const { forceNew } = opts; if (!timelinePanelInstance || forceNew) { timelinePanelInstance = new TimelinePanel(); } return timelinePanelInstance; } searchableView() { return this._searchableView; } wasShown() { UI.Context.Context.instance().setFlavor(TimelinePanel, this); // Record the performance tool load time. Host.userMetrics.panelLoaded('timeline', 'DevTools.Launch.Timeline'); } willHide() { UI.Context.Context.instance().setFlavor(TimelinePanel, null); this._historyManager.cancelIfShowing(); } loadFromEvents(events) { if (this._state !== State.Idle) { return; } this._prepareToLoadTimeline(); this._loader = TimelineLoader.loadFromEvents(events, this); } _onOverviewWindowChanged(event) { if (!this._performanceModel) { return; } const left = event.data.startTime; const right = event.data.endTime; this._performanceModel.setWindow({ left, right }, /* animate */ true); } _onModelWindowChanged(event) { const window = event.data.window; this._overviewPane.setWindowTimes(window.left, window.right); } _setState(state) { this._state = state; this._updateTimelineControls(); } // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration // eslint-disable-next-line @typescript-eslint/no-explicit-any _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(i18nString(UIStrings.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(i18nString(UIStrings.loadProfile), 'largeicon-load'); this._loadButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, () => { Host.userMetrics.actionTaken(Host.UserMetrics.Action.PerfPanelTraceImported); this._selectFileToLoad(); }); this._saveButton = new UI.Toolbar.ToolbarButton(i18nString(UIStrings.saveProfile), 'largeicon-download'); this._saveButton.addEventListener(UI.Toolbar.ToolbarButton.Events.Click, _event => { Host.userMetrics.actionTaken(Host.UserMetrics.Action.PerfPanelTraceExported); 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, i18nString(UIStrings.captureScreenshots)); this._panelToolbar.appendToolbarItem(this._showScreenshotsToolbarCheckbox); this._showMemoryToolbarCheckbox = this._createSettingCheckbox(this._showMemorySetting, i18nString(UIStrings.showMemoryTimeline)); this._panelToolbar.appendToolbarItem(this._showMemoryToolbarCheckbox); this._showWebVitalsToolbarCheckbox = this._createSettingCheckbox(this._showWebVitalsSetting, i18nString(UIStrings.showWebVitals)); this._panelToolbar.appendToolbarItem(this._showWebVitalsToolbarCheckbox); if (Root.Runtime.experiments.isEnabled('recordCoverageWithPerformanceTracing')) { this._startCoverageCheckbox = this._createSettingCheckbox(this._startCoverage, i18nString(UIStrings.recordCoverageWithPerformance)); 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 = Common.Settings.Settings.instance().createSetting('timelineShowSettingsToolbar', false); this._showSettingsPaneButton = new UI.Toolbar.ToolbarSettingToggle(this._showSettingsPaneSetting, 'largeicon-settings-gear', i18nString(UIStrings.captureSettings)); SDK.NetworkManager.MultitargetNetworkManager.instance().addEventListener(SDK.NetworkManager.MultitargetNetworkManager.Events.ConditionsChanged, this._updateShowSettingsToolbarButton, this); SDK.CPUThrottlingManager.CPUThrottlingManager.instance().addEventListener(SDK.CPUThrottlingManager.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, i18nString(UIStrings.disablesJavascriptSampling))); captureToolbar.appendToolbarItem(this._createSettingCheckbox(this._captureLayersAndPicturesSetting, i18nString(UIStrings.capturesAdvancedPaint))); 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(i18nString(UIStrings.network)); this._networkThrottlingSelect = this._createNetworkConditionsSelect(); networkThrottlingToolbar.appendToolbarItem(this._networkThrottlingSelect); const cpuThrottlingToolbar = new UI.Toolbar.Toolbar('', throttlingPane.element); cpuThrottlingToolbar.appendText(i18nString(UIStrings.cpu)); this._cpuThrottlingSelect = MobileThrottling.ThrottlingManager.throttlingManager().createCPUThrottlingSelector(); cpuThrottlingToolbar.appendToolbarItem(this._cpuThrottlingSelect); this._showSettingsPaneSetting.addChangeListener(this._updateSettingsPaneVisibility.bind(this)); this._updateSettingsPaneVisibility(); } _appendExtensionsToToolbar(event) { const provider = event.data; const setting = TimelinePanel._settingForTraceProvider(provider); const checkbox = this._createSettingCheckbox(setting, provider.longDisplayName()); this._panelToolbar.appendToolbarItem(checkbox); } static _settingForTraceProvider(traceProvider) { let setting = traceProviderToSetting.get(traceProvider); if (!setting) { const providerId = traceProvider.persistentIdentifier(); setting = Common.Settings.Settings.instance().createSetting(providerId, false); setting.setTitle(traceProvider.shortDisplayName()); traceProviderToSetting.set(traceProvider, setting); } return setting; } _createNetworkConditionsSelect() { const toolbarItem = new UI.Toolbar.ToolbarComboBox(null, i18nString(UIStrings.networkConditions)); 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); } _contextMenu(event) { const contextMenu = new UI.ContextMenu.ContextMenu(event); contextMenu.appendItemsAtLocation('timelineMenu'); contextMenu.show(); } async _saveToFile() { if (this._state !== State.Idle) { return; } const performanceModel = this._performanceModel; if (!performanceModel) { return; } const now = new Date(); const fileName = 'Profile-' + Platform.DateUtilities.toISO8601Compact(now) + '.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; } Common.Console.Console.instance().error(i18nString(UIStrings.failedToSaveTimelineSSS, { PH1: error.message, PH2: error.name, PH3: error.code })); } async _showHistory() { const model = await this._historyManager.showHistoryDropDown(); if (model && model !== this._performanceModel) { this._setModel(model); } } _navigateHistory(direction) { const model = this._historyManager.navigate(direction); if (model && model !== this._performanceModel) { this._setModel(model); } return true; } _selectFileToLoad() { if (this._fileSelectorElement) { this._fileSelectorElement.click(); } } _loadFromFile(file) { if (this._state !== State.Idle) { return; } this._prepareToLoadTimeline(); this._loader = TimelineLoader.loadFromFile(file, this); this._createFileSelector(); } _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); } _onWebVitalsChanged() { this._flameChart.toggleWebVitalsLane(); } _updateSettingsPaneVisibility() { if (this._showSettingsPaneSetting.get()) { this._settingsPane.showWidget(); } else { this._settingsPane.hideWidget(); } } _updateShowSettingsToolbarButton() { const messages = []; if (SDK.CPUThrottlingManager.CPUThrottlingManager.instance().cpuThrottlingRate() !== 1) { messages.push(i18nString(UIStrings.CpuThrottlingIsEnabled)); } if (SDK.NetworkManager.MultitargetNetworkManager.instance().isThrottling()) { messages.push(i18nString(UIStrings.NetworkThrottlingIsEnabled)); } if (this._captureLayersAndPicturesSetting.get()) { messages.push(i18nString(UIStrings.SignificantOverheadDueToPaint)); } if (this._disableCaptureJSProfileSetting.get()) { messages.push(i18nString(UIStrings.JavascriptSamplingIsDisabled)); } this._showSettingsPaneButton.setDefaultWithRedColor(messages.length > 0); this._showSettingsPaneButton.setToggleWithRedColor(messages.length > 0); if (messages.length) { const tooltipElement = document.createElement('div'); messages.forEach(message => { tooltipElement.createChild('div').textContent = message; }); this._showSettingsPaneButton.setTitle(tooltipElement.textContent || ''); } else { this._showSettingsPaneButton.setTitle(i18nString(UIStrings.captureSettings)); } } _setUIControlsEnabled(enabled) { this._recordingOptionUIControls.forEach(control => control.setEnabled(enabled)); } async _getCoverageViewWidget() { const view = UI.ViewManager.ViewManager.instance().view('coverage'); return /** @type {!Coverage.CoverageView.CoverageView} */ await view.widget(); } 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 UI.ViewManager.ViewManager.instance() .showView('coverage') .then(() => this._getCoverageViewWidget()) .then(widget => widget.ensureRecordingStarted()); } this._showRecordingStarted(); const enabledTraceProviders = Extensions.ExtensionServer.ExtensionServer.instance().traceProviders().filter(provider => TimelinePanel._settingForTraceProvider(provider).get()); const mainTarget = SDK.TargetManager.TargetManager.instance().mainTarget(); if (UIDevtoolsUtils.isUiDevTools()) { this._controller = new UIDevtoolsController(mainTarget, this); } else { this._controller = new TimelineController(mainTarget, this); } this._setUIControlsEnabled(false); this._hideLandingPage(); try { const response = await this._controller.startRecording(recordingOptions, enabledTraceProviders); if (response.getError()) { throw new Error(response.getError()); } else { this._recordingStarted(); } } catch (e) { this._recordingFailed(e.message); } } async _stopRecording() { if (this._statusPane) { this._statusPane.finish(); this._statusPane.updateStatus(i18nString(UIStrings.stoppingTimeline)); this._statusPane.updateProgressBar(i18nString(UIStrings.received), 0); } this._setState(State.StopPending); if (this._startCoverage.get()) { await UI.ViewManager.ViewManager.instance() .showView('coverage') .then(() => this._getCoverageViewWidget()) .then(widget => widget.stopRecording()); } if (this._controller) { const model = await this._controller.stopRecording(); this._performanceModel = model; this._setUIControlsEnabled(true); this._controller.dispose(); this._controller = null; } } _recordingFailed(error) { if (this._statusPane) { this._statusPane.hide(); } this._statusPane = new StatusPane({ description: error, buttonText: i18nString(UIStrings.close), buttonDisabled: false, showProgress: undefined, showTimer: undefined, }, () => this.loadingComplete(null)); this._statusPane.showPane(this._statusPaneContainer); this._statusPane.updateStatus(i18nString(UIStrings.recordingFailed)); this._setState(State.RecordingFailed); this._performanceModel = null; this._setUIControlsEnabled(true); if (this._controller) { 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 && Boolean(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() { PerfUI.LineLevelProfile.Performance.instance().reset(); this._setModel(null); } _applyFilters(model) { if (model.timelineModel().isGenericTrace() || Root.Runtime.experiments.isEnabled('timelineShowAllEvents')) { return; } model.setFilters([TimelineUIUtils.visibleEventsFilter()]); } _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) { this._performanceModel.addEventListener(Events.WindowChanged, this._onModelWindowChanged, this); this._overviewPane.setNavStartTimes(model.timelineModel().navStartTimes()); this._overviewPane.setBounds(model.timelineModel().minimumRecordTime(), model.timelineModel().maximumRecordTime()); PerfUI.LineLevelProfile.Performance.instance().reset(); for (const profile of model.timelineModel().cpuProfiles()) { PerfUI.LineLevelProfile.Performance.instance().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 && this._controller) { const target = this._controller.mainTarget(); const resourceModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); if (resourceModel) { resourceModel.reloadPage(); } } this._reset(); this._setState(State.Recording); this._showRecordingStarted(); if (this._statusPane) { this._statusPane.enableAndFocusButton(); this._statusPane.updateStatus(i18nString(UIStrings.profiling)); this._statusPane.updateProgressBar(i18nString(UIStrings.bufferUsage), 0); this._statusPane.startTimer(); } this._hideLandingPage(); } recordingProgress(usage) { if (this._statusPane) { this._statusPane.updateProgressBar(i18nString(UIStrings.bufferUsage), usage * 100); } } _showLandingPage() { if (this._landingPage) { this._landingPage.show(this._statusPaneContainer); return; } function encloseWithTag(tagName, contents) { const e = document.createElement(tagName); e.textContent = contents; return e; } const learnMoreNode = UI.XLink.XLink.create('https://developer.chrome.com/docs/devtools/evaluate-performance/', i18nString(UIStrings.learnmore)); const recordKey = encloseWithTag('b', UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction('timeline.toggle-recording')[0].title()); const reloadKey = encloseWithTag('b', UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction('timeline.record-reload')[0].title()); const navigateNode = encloseWithTag('b', i18nString(UIStrings.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(i18n.i18n.getFormatLocalizedString(str_, UIStrings.clickTheRecordButtonSOrHitSTo, { PH1: recordButton, PH2: recordKey })); centered.createChild('p').appendChild(i18n.i18n.getFormatLocalizedString(str_, UIStrings.clickTheReloadButtonSOrHitSTo, { PH1: reloadButton, PH2: reloadKey })); centered.createChild('p').appendChild(i18n.i18n.getFormatLocalizedString(str_, UIStrings.afterRecordingSelectAnAreaOf, { PH1: navigateNode, PH2: learnMoreNode })); this._landingPage.show(this._statusPaneContainer); } _hideLandingPage() { this._landingPage.detach(); } loadingStarted() { this._hideLandingPage(); if (this._statusPane) { this._statusPane.hide(); } this._statusPane = new StatusPane({ showProgress: true, showTimer: undefined, buttonDisabled: undefined, buttonText: undefined, description: undefined, }, () => this._cancelLoading()); this._statusPane.showPane(this._statusPaneContainer); this._statusPane.updateStatus(i18nString(UIStrings.loadingProfile)); // FIXME: make loading from backend cancelable as well. if (!this._loader) { this._statusPane.finish(); } this.loadingProgress(0); } loadingProgress(progress) { if (typeof progress === 'number' && this._statusPane) { this._statusPane.updateProgressBar(i18nString(UIStrings.received), progress * 100); } } processingStarted() { if (this._statusPane) { this._statusPane.updateStatus(i18nString(UIStrings.processingProfile)); } } loadingComplete(tracingModel) { delete this._loader; this._setState(State.Idle); if (this._statusPane) { this._statusPane.hide(); } this._statusPane = null; 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()) { UI.ViewManager.ViewManager.instance() .showView('coverage') .then(() => this._getCoverageViewWidget()) .then(widget => widget.processBacklog()) .then(() => this._updateOverviewControls()); } } _showRecordingStarted() { if (this._statusPane) { return; } this._statusPane = new StatusPane({ showTimer: true, showProgress: true, buttonDisabled: true, description: undefined, buttonText: undefined, }, () => this._stopRecording()); this._statusPane.showPane(this._statusPaneContainer); this._statusPane.updateStatus(i18nString(UIStrings.initializingProfiler)); } _cancelLoading() { if (this._loader) { this._loader.cancel(); } } _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)); } // Add markers for navigation start times. for (const navStartTimeEvent of timelineModel.navStartTimes().values()) { markers.set(navStartTimeEvent.startTime, TimelineUIUtils.createEventDivider(navStartTimeEvent, zeroTime)); } this._overviewPane.setMarkers(markers); } async _loadEventFired(event) { if (this._state !== State.Recording || !this._recordingPageReload || !this._controller || 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(); } _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: if (!this._performanceModel) { return null; } return this._performanceModel.frameModel().getFramesWithinWindow(selection._endTime, selection._endTime)[0]; default: console.assert(false, 'Should never be reached'); return null; } } _jumpToFrame(offset) { const currentFrame = this._selection && this._frameForSelection(this._selection); if (!currentFrame || !this._performanceModel) { 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 = Platform.NumberUtilities.clamp(index + offset, 0, frames.length - 1); const frame = frames[index]; this._revealTimeRange(frame.startTime, frame.endTime); this.select(TimelineSelection.fromFrame(frame)); return true; } select(selection) { this._selection = selection; this._flameChart.setSelection(selection); } selectEntryAtTime(events, time) { if (!events) { return; } // Find best match, then backtrack to the first visible entry. for (let index = Platform.ArrayUtilities.upperBound(events, 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 && this._performanceModel.isVisible(event) && endTime >= time) { this.select(TimelineSelection.fromTraceEvent(event)); return; } } this.select(null); } highlightEvent(event) { this._flameChart.highlightEvent(event); } _revealTimeRange(startTime, endTime) { if (!this._performanceModel) { return; } 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); } _handleDrop(dataTransfer) { const items = dataTransfer.items; if (!items.length) { return; } const item = items[0]; Host.userMetrics.actionTaken(Host.UserMetrics.Action.PerfPanelTraceImported); 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)); } } } // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var State; (function (State) { State["Idle"] = "Idle"; State["StartPending"] = "StartPending"; State["Recording"] = "Recording"; State["StopPending"] = "StopPending"; State["Loading"] = "Loading"; State["RecordingFailed"] = "RecordingFailed"; })(State || (State = {})); // TODO(crbug.com/1167717): Make this a const enum again // eslint-disable-next-line rulesdir/const_enum export var ViewMode; (function (ViewMode) { ViewMode["FlameChart"] = "FlameChart"; ViewMode["BottomUp"] = "BottomUp"; ViewMode["CallTree"] = "CallTree"; ViewMode["EventLog"] = "EventLog"; })(ViewMode || (ViewMode = {})); // 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 {