From 2e9e4df8a2ada61c24c647c0590ae65e2f2857f0 Mon Sep 17 00:00:00 2001 From: Antonin Hildebrand Date: Sat, 4 Mar 2017 20:59:29 +0100 Subject: [PATCH] squash 'resources/unpacked/devtools' changes from e6e277b39..eae34e4ba eae34e4ba Revert of [DevTools] Move 'this' evaluation from ExecutionContext to autocomplete. (patchset #1 id:1 of https://codereview.chromium.org/2729743002/ ) 398859ce3 [DevTools] Move 'this' evaluation from ExecutionContext to autocomplete. 11b4bbe4e DevTools: add metrics for command menu/quick open 658254c59 DevTools: do not update timeline tree view details if not visible. 9897b993f DevTools: Remove monospace from FilteredListWidget f055b38cf DevTools Timeline: fix NPE when collapsing the last group 10af4da47 Timeline: fix NPE in heavy stack view when clearing Timeline 64854c093 DevTools: keep count instead of boolean wasUsed internally b70b50528 Timeline: fix NPE when hovering over Interactions strip d440cf6bf DevTools: move the filtered items caption into the toolbar. 23ca7f746 DevTools: sort sourceMap mappings since they might not be ordered. e4336f60e DevTools: finalize audits2 worker as remote module 0198df1c0 [DevTools] 'Continue to location markers' should be always correctly shown 587f32a45 DevTools: Support reading CPU profile format on Performance panel 50a62de18 DevTools: Collect UMA on CPU profile "focus" & "exclude" 214d1b67f DevTools: debug remote modules loaded by ServiceManager 7ac4fb360 DevTools: assorted polish for coverage pane b5cfdfb31 [Devtools] Fixed flicker in network for large stream of requests git-subtree-dir: resources/unpacked/devtools git-subtree-split: eae34e4ba51c0c8b0286dea59e938881b8365b9b --- BUILD.gn | 2 +- front_end/console/ConsoleView.js | 19 ++--- front_end/coverage/CoverageListView.js | 21 +++-- front_end/coverage/CoverageView.js | 22 ++--- front_end/coverage/coverageListView.css | 33 +++++++- front_end/data_grid/ViewportDataGrid.js | 8 +- front_end/host/UserMetrics.js | 4 + front_end/network/NetworkLogView.js | 2 + front_end/perf_ui/FlameChart.js | 2 +- front_end/profiler/ProfileView.js | 2 + front_end/quick_open/CommandMenu.js | 9 +-- front_end/quick_open/FilteredListWidget.js | 12 --- front_end/sdk/SourceMap.js | 14 ++++ front_end/services/ServiceManager.js | 5 +- front_end/sources/JavaScriptSourceFrame.js | 6 +- front_end/sources/OpenResourceDialog.js | 2 + front_end/timeline/TimelineFlameChart.js | 4 +- front_end/timeline/TimelineFlameChartView.js | 13 ++- front_end/timeline/TimelineLoader.js | 42 ++++++++-- front_end/timeline/TimelineTreeView.js | 7 +- front_end/timeline_model/TimelineJSProfile.js | 80 +++++++++++++++++++ 21 files changed, 234 insertions(+), 75 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index 36f41cff83..26edbcdecd 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -824,7 +824,6 @@ generated_resources = [ # this contains non-autostart non-remote modules only. "$resources_out_dir/animation/animation_module.js", "$resources_out_dir/audits/audits_module.js", - "$resources_out_dir/audits2_worker/audits2_worker_module.js", "$resources_out_dir/audits2/audits2_module.js", "$resources_out_dir/cm/cm_module.js", "$resources_out_dir/color_picker/color_picker_module.js", @@ -859,6 +858,7 @@ generated_resources = [ generated_remote_modules = [ "$resources_out_dir/accessibility/accessibility_module.js", + "$resources_out_dir/audits2_worker/audits2_worker_module.js", "$resources_out_dir/cm_modes/cm_modes_module.js", "$resources_out_dir/emulated_devices/emulated_devices_module.js", "$resources_out_dir/gonzales/gonzales_module.js", diff --git a/front_end/console/ConsoleView.js b/front_end/console/ConsoleView.js index 084a5c4f22..6c7014df12 100644 --- a/front_end/console/ConsoleView.js +++ b/front_end/console/ConsoleView.js @@ -60,10 +60,11 @@ Console.ConsoleView = class extends UI.VBox { this._executionContextComboBox.setMaxWidth(80); this._consoleContextSelector = new Console.ConsoleContextSelector(this._executionContextComboBox.selectElement()); + this._filterStatusText = new UI.ToolbarText(); + this._filterStatusText.element.classList.add('dimmed'); this._showSettingsPaneSetting = Common.settings.createSetting('consoleShowSettingsToolbar', false); this._showSettingsPaneButton = new UI.ToolbarSettingToggle( this._showSettingsPaneSetting, 'largeicon-settings-gear', Common.UIString('Console settings')); - this._progressToolbarItem = new UI.ToolbarItem(createElement('div')); var toolbar = new UI.Toolbar('', this._contentsElement); @@ -76,7 +77,7 @@ Console.ConsoleView = class extends UI.VBox { toolbar.appendToolbarItem(this._filter._levelComboBox); toolbar.appendToolbarItem(this._progressToolbarItem); toolbar.appendSpacer(); - toolbar.appendText(''); + toolbar.appendToolbarItem(this._filterStatusText); toolbar.appendSeparator(); toolbar.appendToolbarItem(this._showSettingsPaneButton); @@ -121,11 +122,6 @@ Console.ConsoleView = class extends UI.VBox { this._viewportThrottler = new Common.Throttler(50); - this._filterStatusMessageElement = createElementWithClass('div', 'console-message'); - this._messagesElement.insertBefore(this._filterStatusMessageElement, this._messagesElement.firstChild); - this._filterStatusTextElement = this._filterStatusMessageElement.createChild('span', 'console-info'); - this._filterStatusMessageElement.createTextChild(' '); - this._topGroup = Console.ConsoleGroup.createTopGroup(); this._currentGroup = this._topGroup; @@ -435,11 +431,10 @@ Console.ConsoleView = class extends UI.VBox { } _updateFilterStatus() { - this._filterStatusTextElement.removeChildren(); - this._filterStatusTextElement.createTextChild(Common.UIString( - this._hiddenByFilterCount === 1 ? '1 message is hidden by filters.' : - this._hiddenByFilterCount + ' messages are hidden by filters.')); - this._filterStatusMessageElement.style.display = this._hiddenByFilterCount ? '' : 'none'; + this._filterStatusText.setText(Common.UIString( + this._hiddenByFilterCount === 1 ? '1 item hidden by filters' : + this._hiddenByFilterCount + ' items hidden by filters')); + this._filterStatusText.setVisible(!!this._hiddenByFilterCount); } /** diff --git a/front_end/coverage/CoverageListView.js b/front_end/coverage/CoverageListView.js index abc86f261e..1f277738c9 100644 --- a/front_end/coverage/CoverageListView.js +++ b/front_end/coverage/CoverageListView.js @@ -7,7 +7,8 @@ Coverage.CoverageListView = class extends UI.VBox { super(true); this.registerRequiredCSS('coverage/coverageListView.css'); var columns = [ - {id: 'url', title: Common.UIString('URL'), width: '300px', fixedWidth: false, sortable: true}, { + {id: 'url', title: Common.UIString('URL'), width: '300px', fixedWidth: false, sortable: true}, + {id: 'type', title: Common.UIString('Type'), width: '45px', fixedWidth: true, sortable: true}, { id: 'size', title: Common.UIString('Total Bytes'), width: '60px', @@ -15,7 +16,7 @@ Coverage.CoverageListView = class extends UI.VBox { sortable: true, align: DataGrid.DataGrid.Align.Right }, - {id: 'type', title: Common.UIString('Type'), width: '30px', fixedWidth: true, sortable: true}, { + { id: 'unusedSize', title: Common.UIString('Unused Bytes'), width: '60px', @@ -116,7 +117,7 @@ Coverage.CoverageListView = class extends UI.VBox { var nodeA = /** @type {!Coverage.CoverageListView.GridNode} */ (a); var nodeB = /** @type {!Coverage.CoverageListView.GridNode} */ (b); - return nodeA._coverageInfo.url.localeCompare(nodeB._coverageInfo.url); + return nodeA._displayURL.localeCompare(nodeB._displayURL); } /** @@ -154,6 +155,7 @@ Coverage.CoverageListView.GridNode = class extends DataGrid.SortableDataGridNode constructor(coverageInfo, maxSize) { super(); this._coverageInfo = coverageInfo; + this._displayURL = new Common.ParsedURL(coverageInfo.url).displayName; this._maxSize = maxSize; } @@ -166,19 +168,26 @@ Coverage.CoverageListView.GridNode = class extends DataGrid.SortableDataGridNode var cell = this.createTD(columnId); switch (columnId) { case 'url': - cell.textContent = this._coverageInfo.url; cell.title = this._coverageInfo.url; + var outer = cell.createChild('div', 'url-outer'); + var prefix = outer.createChild('div', 'url-prefix'); + var suffix = outer.createChild('div', 'url-suffix'); + var splitURL = /^(.*)(\/[^/]*)$/.exec(this._coverageInfo.url); + prefix.textContent = splitURL ? splitURL[1] : this._coverageInfo.url; + suffix.textContent = splitURL ? splitURL[2] : ''; break; case 'type': cell.textContent = Coverage.CoverageListView._typeToString(this._coverageInfo.type); break; case 'size': cell.classList.add('numeric-column'); - cell.textContent = this._coverageInfo.size; + cell.textContent = Number.withThousandsSeparator(this._coverageInfo.size || 0); break; case 'unusedSize': cell.classList.add('numeric-column'); - cell.textContent = this._coverageInfo.unusedSize; + cell.textContent = Number.withThousandsSeparator(this._coverageInfo.unusedSize || 0); + if (this._coverageInfo.size) + cell.title = Math.round(100 * this._coverageInfo.unusedSize / this._coverageInfo.size) + '%'; break; case 'bars': var barContainer = cell.createChild('div', 'bar-container'); diff --git a/front_end/coverage/CoverageView.js b/front_end/coverage/CoverageView.js index fe27fdd867..2ab6a0f7e1 100644 --- a/front_end/coverage/CoverageView.js +++ b/front_end/coverage/CoverageView.js @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -/** @typedef {{range: !Common.TextRange, wasUsed: boolean}} */ -Coverage.RangeUsage; +/** @typedef {{range: !Common.TextRange, count: number}} */ +Coverage.RangeUseCount; -/** @typedef {{styleSheetHeader: !SDK.CSSStyleSheetHeader, ranges: !Array}} */ +/** @typedef {{styleSheetHeader: !SDK.CSSStyleSheetHeader, ranges: !Array}} */ Coverage.StyleSheetUsage; /** @typedef {{ @@ -14,7 +14,7 @@ Coverage.StyleSheetUsage; * unusedSize: (number|undefined), * usedSize: (number|undefined), * type: !Coverage.CoverageType, - * ranges: !Array + * ranges: !Array * }} */ Coverage.CoverageInfo; @@ -169,7 +169,7 @@ Coverage.CoverageView = class extends UI.VBox { for (var range of func.ranges) { var textRange = new Common.TextRange( range.startLineNumber, range.startColumnNumber, range.endLineNumber, range.endColumnNumber); - ranges.push({range: textRange, wasUsed: !!range.count}); + ranges.push({range: textRange, count: range.count}); } } promises.push(Coverage.CoverageView._coverageInfoForText(script, script.lineOffset, script.columnOffset, ranges)); @@ -197,7 +197,7 @@ Coverage.CoverageView = class extends UI.VBox { * @return {!Promise>} */ static async _processCSSCoverage(cssModel, ruleUsageList) { - /** @type {!Map>} */ + /** @type {!Map>} */ var rulesByStyleSheet = new Map(); for (var rule of ruleUsageList) { var styleSheetHeader = cssModel.styleSheetHeaderForId(rule.styleSheetId); @@ -211,7 +211,7 @@ Coverage.CoverageView = class extends UI.VBox { rule.range.startColumn + (rule.range.startLine ? 0 : styleSheetHeader.startColumn), rule.range.endLine + styleSheetHeader.startLine, rule.range.endColumn + (rule.range.endLine ? 0 : styleSheetHeader.startColumn)); - ranges.push({range: textRange, wasUsed: rule.wasUsed}); + ranges.push({range: textRange, count: Number(rule.wasUsed)}); } return Promise.all(Array.from( rulesByStyleSheet.entries(), entry => Coverage.CoverageView._coverageInfoForText( @@ -222,7 +222,7 @@ Coverage.CoverageView = class extends UI.VBox { * @param {!Common.ContentProvider} contentProvider * @param {number} startLine * @param {number} startColumn - * @param {!Array} ranges + * @param {!Array} ranges * @return {!Promise} */ static async _coverageInfoForText(contentProvider, startLine, startColumn, ranges) { @@ -246,7 +246,7 @@ Coverage.CoverageView = class extends UI.VBox { return { start: text.offsetFromPosition(range.startLine, range.startColumn), end: text.offsetFromPosition(range.endLine, range.endColumn), - wasUsed: r.wasUsed + count: r.count }; }); @@ -271,7 +271,7 @@ Coverage.CoverageView = class extends UI.VBox { var usedSize = 0; var unusedSize = 0; for (var entry of offsetRanges) { - if (entry.wasUsed) + if (entry.count) usedSize += entry.ownSize; else unusedSize += entry.ownSize; @@ -309,7 +309,7 @@ Coverage.CoverageView = class extends UI.VBox { if (!uiSourceCode) continue; for (var range of info.ranges) - uiSourceCode.addDecoration(range.range, Coverage.CoverageView.LineDecorator.type, range.wasUsed); + uiSourceCode.addDecoration(range.range, Coverage.CoverageView.LineDecorator.type, range.count); } } }; diff --git a/front_end/coverage/coverageListView.css b/front_end/coverage/coverageListView.css index 50e33bb13a..adfa5caa8e 100644 --- a/front_end/coverage/coverageListView.css +++ b/front_end/coverage/coverageListView.css @@ -1,10 +1,41 @@ +.data-grid { + border: none; +} + +.data-grid td .url-outer { + width: 100%; + display: inline-flex; + justify-content: flex-start; +} + +.data-grid td .url-prefix { + overflow-x: hidden; + text-overflow: ellipsis; +} + +.data-grid td .url-suffix { + flex: none; +} + .data-grid td .bar { display: inline-block; height: 8px; } .data-grid .selected td .bar { - border: 0.5px solid white; + border-top: 1px white solid; + border-bottom: 1px white solid; +} + +.data-grid .selected td .bar:last-child { + border-right: 1px white solid; +} + +.data-grid .selected td .bar:first-child { + border-left: 1px white solid; +} + +.data-grid td .bar-container { } .data-grid td .bar-slack-size { diff --git a/front_end/data_grid/ViewportDataGrid.js b/front_end/data_grid/ViewportDataGrid.js index 5b75ec34be..94904508a8 100644 --- a/front_end/data_grid/ViewportDataGrid.js +++ b/front_end/data_grid/ViewportDataGrid.js @@ -101,10 +101,10 @@ DataGrid.ViewportDataGrid = class extends DataGrid.DataGrid { this._updateAnimationFrameId = this.element.window().requestAnimationFrame(this._update.bind(this)); } - updateInstantlyForTests() { - if (!this._updateAnimationFrameId) - return; - this.element.window().cancelAnimationFrame(this._updateAnimationFrameId); + // TODO(allada) This should be fixed to never be needed. It is needed right now for network because removing + // elements happens followed by a scheduleRefresh() which causes white space to be visible, but the waterfall + // updates instantly. + updateInstantly() { this._update(); } diff --git a/front_end/host/UserMetrics.js b/front_end/host/UserMetrics.js index 1e3d888c94..f670292a6f 100644 --- a/front_end/host/UserMetrics.js +++ b/front_end/host/UserMetrics.js @@ -84,6 +84,10 @@ Host.UserMetrics.Action = { ConnectToNodeJSFromFrontend: 19, ConnectToNodeJSDirectly: 20, CpuThrottlingEnabled: 21, + CpuProfileNodeFocused: 22, + CpuProfileNodeExcluded: 23, + SelectFileFromFilePicker: 24, + SelectCommandFromCommandMenu: 25, }; Host.UserMetrics._PanelCodes = { diff --git a/front_end/network/NetworkLogView.js b/front_end/network/NetworkLogView.js index e83ecc0225..8b1134aa30 100644 --- a/front_end/network/NetworkLogView.js +++ b/front_end/network/NetworkLogView.js @@ -853,6 +853,8 @@ Network.NetworkLogView = class extends UI.VBox { if (nodesToInsert.size) this._columns.sortByCurrentColumn(); + + this._dataGrid.updateInstantly(); } /** diff --git a/front_end/perf_ui/FlameChart.js b/front_end/perf_ui/FlameChart.js index 57ddf918da..49249c4e7a 100644 --- a/front_end/perf_ui/FlameChart.js +++ b/front_end/perf_ui/FlameChart.js @@ -353,7 +353,7 @@ PerfUI.FlameChart = class extends PerfUI.ChartViewport { var timelineData = this._timelineData(); var level = timelineData.entryLevels[this._selectedEntryIndex]; if (this._selectedEntryIndex >= 0 && level >= group.startLevel && - (groupIndex === groups.length || groups[groupIndex + 1].startLevel > level)) + (groupIndex >= groups.length - 1 || groups[groupIndex + 1].startLevel > level)) this._selectedEntryIndex = -1; } diff --git a/front_end/profiler/ProfileView.js b/front_end/profiler/ProfileView.js index 6f340d53cc..cad9b64187 100644 --- a/front_end/profiler/ProfileView.js +++ b/front_end/profiler/ProfileView.js @@ -336,6 +336,7 @@ Profiler.ProfileView = class extends UI.SimpleView { this.profileDataGridTree.focus(this.dataGrid.selectedNode); this.refresh(); this.refreshVisibleData(); + Host.userMetrics.actionTaken(Host.UserMetrics.Action.CpuProfileNodeFocused); } /** @@ -353,6 +354,7 @@ Profiler.ProfileView = class extends UI.SimpleView { this.profileDataGridTree.exclude(selectedNode); this.refresh(); this.refreshVisibleData(); + Host.userMetrics.actionTaken(Host.UserMetrics.Action.CpuProfileNodeExcluded); } /** diff --git a/front_end/quick_open/CommandMenu.js b/front_end/quick_open/CommandMenu.js index 9f11fdd529..42625907ff 100644 --- a/front_end/quick_open/CommandMenu.js +++ b/front_end/quick_open/CommandMenu.js @@ -217,14 +217,7 @@ QuickOpen.CommandMenuDelegate = class extends QuickOpen.FilteredListWidget.Deleg if (itemIndex === null) return; this._commands[itemIndex].execute(); - } - - /** - * @override - * @return {boolean} - */ - renderMonospace() { - return false; + Host.userMetrics.actionTaken(Host.UserMetrics.Action.SelectCommandFromCommandMenu); } /** diff --git a/front_end/quick_open/FilteredListWidget.js b/front_end/quick_open/FilteredListWidget.js index be222d573b..0a2eb14068 100644 --- a/front_end/quick_open/FilteredListWidget.js +++ b/front_end/quick_open/FilteredListWidget.js @@ -41,11 +41,6 @@ QuickOpen.FilteredListWidget = class extends UI.VBox { this._itemElementsContainer.classList.add('container'); this._bottomElementsContainer.appendChild(this._itemElementsContainer); - if (delegate.renderMonospace()) { - this._list.element.classList.add('monospace'); - this._promptElement.classList.add('monospace'); - } - this._notFoundElement = this._bottomElementsContainer.createChild('div', 'not-found-text'); this._notFoundElement.classList.add('hidden'); @@ -500,13 +495,6 @@ QuickOpen.FilteredListWidget.Delegate = class { renderItem(itemIndex, query, titleElement, subtitleElement) { } - /** - * @return {boolean} - */ - renderMonospace() { - return true; - } - /** * @return {boolean} */ diff --git a/front_end/sdk/SourceMap.js b/front_end/sdk/SourceMap.js index 8101ab0fef..07c42d6371 100644 --- a/front_end/sdk/SourceMap.js +++ b/front_end/sdk/SourceMap.js @@ -83,6 +83,17 @@ SDK.SourceMapEntry = class { this.sourceColumnNumber = sourceColumnNumber; this.name = name; } + + /** + * @param {!SDK.SourceMapEntry} entry1 + * @param {!SDK.SourceMapEntry} entry2 + * @return {number} + */ + static compare(entry1, entry2) { + if (entry1.lineNumber !== entry2.lineNumber) + return entry1.lineNumber - entry2.lineNumber; + return entry1.columnNumber - entry2.columnNumber; + } }; /** @@ -481,6 +492,9 @@ SDK.TextSourceMap = class { this._mappings.push(new SDK.SourceMapEntry( lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber, names[nameIndex])); } + + // As per spec, mappings are not necessarily sorted. + this._mappings.stableSort(SDK.SourceMapEntry.compare); } /** diff --git a/front_end/services/ServiceManager.js b/front_end/services/ServiceManager.js index 0d08aeda73..786dac282b 100644 --- a/front_end/services/ServiceManager.js +++ b/front_end/services/ServiceManager.js @@ -31,10 +31,13 @@ Services.ServiceManager = class { createAppService(appName, serviceName, isSharedWorker) { var url = appName + '.js'; var remoteBase = Runtime.queryParam('remoteBase'); + var debugFrontend = Runtime.queryParam('debugFrontend'); // Do not pass additional query parameters to shared worker to avoid URLMismatchError - // in case another instance of DevTools with different remoteBase creates same shared worker. + // in case another instance of DevTools with different query parameters creates same shared worker. if (remoteBase && !isSharedWorker) url += '?remoteBase=' + remoteBase; + if (debugFrontend && !isSharedWorker) + url += '?debugFrontend=' + debugFrontend; var worker = isSharedWorker ? new SharedWorker(url, appName) : new Worker(url); var connection = new Services.ServiceManager.Connection(new Services.ServiceManager.WorkerServicePort(worker)); diff --git a/front_end/sources/JavaScriptSourceFrame.js b/front_end/sources/JavaScriptSourceFrame.js index 2dfa997d88..626fbdbcfd 100644 --- a/front_end/sources/JavaScriptSourceFrame.js +++ b/front_end/sources/JavaScriptSourceFrame.js @@ -161,7 +161,11 @@ Sources.JavaScriptSourceFrame = class extends SourceFrame.UISourceCodeFrame { super.wasShown(); if (this._executionLocation && this.loaded) { // We need SourcesTextEditor to be initialized prior to this call. @see crbug.com/499889 - setImmediate(this._generateValuesInSource.bind(this)); + setImmediate(() => { + this._generateValuesInSource(); + if (Runtime.experiments.isEnabled('continueToLocationMarkers')) + this._showContinueToLocations(); + }); } } diff --git a/front_end/sources/OpenResourceDialog.js b/front_end/sources/OpenResourceDialog.js index 72be08cda8..e6bb0f3eb8 100644 --- a/front_end/sources/OpenResourceDialog.js +++ b/front_end/sources/OpenResourceDialog.js @@ -40,6 +40,8 @@ Sources.OpenResourceDialog = class extends Sources.FilteredUISourceCodeListDeleg * @param {number=} columnNumber */ uiSourceCodeSelected(uiSourceCode, lineNumber, columnNumber) { + Host.userMetrics.actionTaken(Host.UserMetrics.Action.SelectFileFromFilePicker); + if (!uiSourceCode) uiSourceCode = this._sourcesView.currentUISourceCode(); if (!uiSourceCode) diff --git a/front_end/timeline/TimelineFlameChart.js b/front_end/timeline/TimelineFlameChart.js index 42aad62509..356dd8ecb3 100644 --- a/front_end/timeline/TimelineFlameChart.js +++ b/front_end/timeline/TimelineFlameChart.js @@ -497,9 +497,9 @@ Timeline.TimelineFlameChartDataProvider = class { */ highlightEntry(entryIndex) { SDK.DOMModel.hideDOMNodeHighlight(); - var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryIndex]); - if (!event) + if (this._entryType(entryIndex) !== Timeline.TimelineFlameChartEntryType.Event) return; + var event = /** @type {!SDK.TracingModel.Event} */ (this._entryData[entryIndex]); var target = this._model.targetByEvent(event); if (!target) return; diff --git a/front_end/timeline/TimelineFlameChartView.js b/front_end/timeline/TimelineFlameChartView.js index 01a9b47a7a..d0f1305467 100644 --- a/front_end/timeline/TimelineFlameChartView.js +++ b/front_end/timeline/TimelineFlameChartView.js @@ -115,20 +115,19 @@ Timeline.TimelineFlameChartView = class extends UI.VBox { this._model = model; if (this._model) this._model.addEventListener(extensionDataAdded, this._appendExtensionData, this); - this._updateSearchHighlight(false, true); - this._refresh(); - } - - _refresh() { this._mainDataProvider.setModel(this._model); this._networkDataProvider.setModel(this._model); this._countersView.setModel(this._model); - if (this._detailsView) - this._detailsView.setModel(this._model); + this._detailsView.setModel(this._model); this._nextExtensionIndex = 0; this._appendExtensionData(); + this._updateSearchHighlight(false, true); + this._refresh(); + } + + _refresh() { if (this._networkDataProvider.isEmpty()) { this._mainFlameChart.enableRuler(true); this._networkSplitWidget.hideSidebar(); diff --git a/front_end/timeline/TimelineLoader.js b/front_end/timeline/TimelineLoader.js index 395654428c..f3c056af42 100644 --- a/front_end/timeline/TimelineLoader.js +++ b/front_end/timeline/TimelineLoader.js @@ -20,6 +20,7 @@ Timeline.TimelineLoader = class { this._canceledCallback = null; this._state = Timeline.TimelineLoader.State.Initial; this._buffer = ''; + this._firstRawChunk = true; this._firstChunk = true; this._loadedBytes = 0; @@ -79,11 +80,16 @@ Timeline.TimelineLoader = class { if (!this._client) return; this._loadedBytes += chunk.length; - if (!this._firstChunk) + if (this._firstRawChunk) + this._client.loadingStarted(); + else this._client.loadingProgress(this._totalSize ? this._loadedBytes / this._totalSize : undefined); + this._firstRawChunk = false; if (this._state === Timeline.TimelineLoader.State.Initial) { - if (chunk[0] === '{') { + if (chunk.startsWith('{"nodes":[')) { + this._state = Timeline.TimelineLoader.State.LoadingCPUProfileFormat; + } else if (chunk[0] === '{') { this._state = Timeline.TimelineLoader.State.LookingForEvents; } else if (chunk[0] === '[') { this._state = Timeline.TimelineLoader.State.ReadingEvents; @@ -93,6 +99,11 @@ Timeline.TimelineLoader = class { } } + if (this._state === Timeline.TimelineLoader.State.LoadingCPUProfileFormat) { + this._buffer += chunk; + return; + } + if (this._state === Timeline.TimelineLoader.State.LookingForEvents) { var objectName = '"traceEvents":'; var startPos = this._buffer.length - objectName.length; @@ -121,9 +132,7 @@ Timeline.TimelineLoader = class { _writeBalancedJSON(data) { var json = data + ']'; - if (this._firstChunk) { - this._client.loadingStarted(); - } else { + if (!this._firstChunk) { var commaIndex = json.indexOf(','); if (commaIndex !== -1) json = json.slice(commaIndex + 1); @@ -150,7 +159,6 @@ Timeline.TimelineLoader = class { this._tracingModel.addEvents(items); } catch (e) { this._reportErrorAndCancelLoading(Common.UIString('Malformed timeline data: %s', e.toString())); - return; } } @@ -182,6 +190,10 @@ Timeline.TimelineLoader = class { } _finalizeTrace() { + if (this._state === Timeline.TimelineLoader.State.LoadingCPUProfileFormat) { + this._parseCPUProfileFormat(this._buffer); + this._buffer = ''; + } this._tracingModel.tracingComplete(); this._client.loadingComplete(this._tracingModel, this._backingStorage); } @@ -225,6 +237,21 @@ Timeline.TimelineLoader = class { Common.UIString('An error occurred while reading the file "%s"', reader.fileName())); } } + + /** + * @param {string} text + */ + _parseCPUProfileFormat(text) { + var traceEvents; + try { + var profile = JSON.parse(text); + traceEvents = TimelineModel.TimelineJSProfileProcessor.buildTraceProfileFromCpuProfile(profile); + } catch (e) { + this._reportErrorAndCancelLoading(Common.UIString('Malformed CPU profile format')); + return; + } + this._tracingModel.addEvents(traceEvents); + } }; @@ -259,7 +286,8 @@ Timeline.TimelineLoader.State = { Initial: Symbol('Initial'), LookingForEvents: Symbol('LookingForEvents'), ReadingEvents: Symbol('ReadingEvents'), - SkippingTail: Symbol('SkippingTail') + SkippingTail: Symbol('SkippingTail'), + LoadingCPUProfileFormat: Symbol('LoadingCPUProfileFormat') }; /** diff --git a/front_end/timeline/TimelineTreeView.js b/front_end/timeline/TimelineTreeView.js index abe08608bb..3ad13c7103 100644 --- a/front_end/timeline/TimelineTreeView.js +++ b/front_end/timeline/TimelineTreeView.js @@ -84,6 +84,7 @@ Timeline.TimelineTreeView = class extends UI.VBox { this._splitWidget.setSidebarWidget(this._detailsView); this._splitWidget.hideSidebar(); this._splitWidget.show(this.element); + this._splitWidget.addEventListener(UI.SplitWidget.Events.ShowModeChanged, this._updateDetailsForSelection, this); /** @type {?TimelineModel.TimelineProfileTree.Node|undefined} */ this._lastSelectedNode; @@ -240,8 +241,10 @@ Timeline.TimelineTreeView = class extends UI.VBox { if (this._searchableView) this._searchableView.cancelSearch(); this._dataGrid.rootNode().removeChildren(); - if (!this._model) + if (!this._model) { + this._updateDetailsForSelection(); return; + } this._root = this._buildTree(); var children = this._root.children(); var maxSelfTime = 0; @@ -356,6 +359,8 @@ Timeline.TimelineTreeView = class extends UI.VBox { if (selectedNode === this._lastSelectedNode) return; this._lastSelectedNode = selectedNode; + if (this._splitWidget.showMode() === UI.SplitWidget.ShowMode.OnlyMain) + return; this._detailsView.detachChildWidgets(); this._detailsView.element.removeChildren(); if (!selectedNode || !this._showDetailsForNode(selectedNode)) { diff --git a/front_end/timeline_model/TimelineJSProfile.js b/front_end/timeline_model/TimelineJSProfile.js index 3769b45820..0d15c5816f 100644 --- a/front_end/timeline_model/TimelineJSProfile.js +++ b/front_end/timeline_model/TimelineJSProfile.js @@ -231,6 +231,86 @@ TimelineModel.TimelineJSProfileProcessor = class { } return map.get(nativeName) || null; } + + /** + * @param {*} profile + * @return {!Array} + */ + static buildTraceProfileFromCpuProfile(profile) { + if (!profile) + return []; + var events = []; + appendEvent('TracingStartedInPage', {'sessionId': '1'}, 0, 0, 'M'); + var idToNode = new Map(); + var nodes = profile['nodes']; + for (var i = 0; i < nodes.length; ++i) + idToNode.set(nodes[i].id, nodes[i]); + var programEvent = null; + var functionEvent = null; + var nextTime = profile.startTime; + var currentTime; + var samples = profile['samples']; + var timeDeltas = profile['timeDeltas']; + for (var i = 0; i < samples.length; ++i) { + currentTime = nextTime; + nextTime += timeDeltas[i]; + var node = idToNode.get(samples[i]); + var name = node.callFrame.functionName; + if (name === '(idle)') { + closeEvents(); + continue; + } + if (!programEvent) + programEvent = appendEvent('MessageLoop::RunTask', {}, currentTime, 0, 'X', 'toplevel'); + if (name === '(program)') { + if (functionEvent) { + functionEvent.dur = currentTime - functionEvent.ts; + functionEvent = null; + } + } else { + // A JS function. + if (!functionEvent) + functionEvent = appendEvent('FunctionCall', {'sessionId': '1'}, currentTime); + } + } + closeEvents(); + appendEvent('CpuProfile', {'cpuProfile': profile}, profile.endTime, 0, 'I'); + return events; + + function closeEvents() { + if (programEvent) + programEvent.dur = currentTime - programEvent.ts; + if (functionEvent) + functionEvent.dur = currentTime - functionEvent.ts; + programEvent = null; + functionEvent = null; + } + + /** + * @param {string} name + * @param {*} data + * @param {number} ts + * @param {number=} dur + * @param {string=} ph + * @param {string=} cat + * @return {!SDK.TracingManager.EventPayload} + */ + function appendEvent(name, data, ts, dur, ph, cat) { + var event = /** @type {!SDK.TracingManager.EventPayload} */ ({ + cat: cat || 'disabled-by-default-devtools.timeline', + name: name, + ph: ph || 'X', + pid: 1, + tid: 1, + ts: ts, + args: {data: data} + }); + if (dur) + event.dur = dur; + events.push(event); + return event; + } + } }; /** @enum {string} */