blob: 9cbaeac3ec876d5968ebb37b89bf98fc488f63f4 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @implements {PerfUI.FlameChartDelegate}
* @implements {UI.Searchable}
* @unrestricted
*/
Timeline.TimelineFlameChartView = class extends UI.VBox {
/**
* @param {!Timeline.TimelineModeViewDelegate} delegate
*/
constructor(delegate) {
super();
this.element.classList.add('timeline-flamechart');
this._delegate = delegate;
/** @type {?Timeline.PerformanceModel} */
this._model = null;
/** @type {!Array<number>|undefined} */
this._searchResults;
/** @type {!Array<!Common.EventTarget.EventDescriptor>} */
this._eventListeners = [];
this._showMemoryGraphSetting = Common.settings.createSetting('timelineShowMemory', false);
// Create main and network flamecharts.
this._networkSplitWidget = new UI.SplitWidget(false, false, 'timelineFlamechartMainView', 150);
const mainViewGroupExpansionSetting = Common.settings.createSetting('timelineFlamechartMainViewGroupExpansion', {});
this._mainDataProvider = new Timeline.TimelineFlameChartDataProvider();
this._mainDataProvider.addEventListener(
Timeline.TimelineFlameChartDataProvider.Events.DataChanged, () => this._mainFlameChart.scheduleUpdate());
this._mainFlameChart = new PerfUI.FlameChart(this._mainDataProvider, this, mainViewGroupExpansionSetting);
this._mainFlameChart.alwaysShowVerticalScroll();
this._mainFlameChart.enableRuler(false);
this._networkFlameChartGroupExpansionSetting =
Common.settings.createSetting('timelineFlamechartNetworkViewGroupExpansion', {});
this._networkDataProvider = new Timeline.TimelineFlameChartNetworkDataProvider();
this._networkFlameChart = new PerfUI.FlameChart(
this._networkDataProvider, this, this._networkFlameChartGroupExpansionSetting);
this._networkFlameChart.alwaysShowVerticalScroll();
this._networkFlameChart.disableRangeSelection();
this._networkPane = new UI.VBox();
this._networkPane.setMinimumSize(23, 23);
this._networkFlameChart.show(this._networkPane.element);
this._splitResizer = this._networkPane.element.createChild('div', 'timeline-flamechart-resizer');
this._networkSplitWidget.hideDefaultResizer(true);
this._networkSplitWidget.installResizer(this._splitResizer);
this._networkSplitWidget.setMainWidget(this._mainFlameChart);
this._networkSplitWidget.setSidebarWidget(this._networkPane);
// Create counters chart splitter.
this._chartSplitWidget = new UI.SplitWidget(false, true, 'timelineCountersSplitViewState');
this._countersView = new Timeline.CountersGraph(this._delegate);
this._chartSplitWidget.setMainWidget(this._networkSplitWidget);
this._chartSplitWidget.setSidebarWidget(this._countersView);
this._chartSplitWidget.hideDefaultResizer();
this._chartSplitWidget.installResizer(/** @type {!Element} */ (this._countersView.resizerElement()));
this._updateCountersGraphToggle();
// Create top level properties splitter.
this._detailsSplitWidget = new UI.SplitWidget(false, true, 'timelinePanelDetailsSplitViewState');
this._detailsSplitWidget.element.classList.add('timeline-details-split');
this._detailsView = new Timeline.TimelineDetailsView(delegate);
this._detailsSplitWidget.installResizer(this._detailsView.headerElement());
this._detailsSplitWidget.setMainWidget(this._chartSplitWidget);
this._detailsSplitWidget.setSidebarWidget(this._detailsView);
this._detailsSplitWidget.show(this.element);
this._onMainEntrySelected = this._onEntrySelected.bind(this, this._mainDataProvider);
this._onNetworkEntrySelected = this._onEntrySelected.bind(this, this._networkDataProvider);
this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, this._onMainEntrySelected, this);
this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryInvoked, this._onMainEntrySelected, this);
this._networkFlameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, this._onNetworkEntrySelected, this);
this._networkFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryInvoked, this._onNetworkEntrySelected, this);
this._mainFlameChart.addEventListener(PerfUI.FlameChart.Events.EntryHighlighted, this._onEntryHighlighted, this);
this._nextExtensionIndex = 0;
this._boundRefresh = this._refresh.bind(this);
this._selectedTrack = null;
this._mainDataProvider.setEventColorMapping(Timeline.TimelineUIUtils.eventColor);
this._groupBySetting =
Common.settings.createSetting('timelineTreeGroupBy', Timeline.AggregatedTimelineTreeView.GroupBy.None);
this._groupBySetting.addChangeListener(this._updateColorMapper, this);
this._updateColorMapper();
}
_updateColorMapper() {
/** @type {!Map<string, string>} */
this._urlToColorCache = new Map();
if (!this._model) {
return;
}
this._mainDataProvider.setEventColorMapping(Timeline.TimelineUIUtils.eventColor);
this._mainFlameChart.update();
}
/**
* @param {!Common.Event} event
*/
_onWindowChanged(event) {
const window = /** @type {!Timeline.PerformanceModel.Window} */ (event.data.window);
const animate = !!event.data.animate;
this._mainFlameChart.setWindowTimes(window.left, window.right, animate);
this._networkFlameChart.setWindowTimes(window.left, window.right, animate);
this._networkDataProvider.setWindowTimes(window.left, window.right);
this._updateSearchResults(false, false);
}
/**
* @override
* @param {number} windowStartTime
* @param {number} windowEndTime
* @param {boolean} animate
*/
windowChanged(windowStartTime, windowEndTime, animate) {
this._model.setWindow({left: windowStartTime, right: windowEndTime}, animate);
}
/**
* @override
* @param {number} startTime
* @param {number} endTime
*/
updateRangeSelection(startTime, endTime) {
this._delegate.select(Timeline.TimelineSelection.fromRange(startTime, endTime));
}
/**
* @override
* @param {!PerfUI.FlameChart} flameChart
* @param {?PerfUI.FlameChart.Group} group
*/
updateSelectedGroup(flameChart, group) {
if (flameChart !== this._mainFlameChart) {
return;
}
const track = group ? this._mainDataProvider.groupTrack(group) : null;
this._selectedTrack = track;
this._updateTrack();
}
/**
* @param {?Timeline.PerformanceModel} model
*/
setModel(model) {
if (model === this._model) {
return;
}
Common.EventTarget.removeEventListeners(this._eventListeners);
this._model = model;
this._selectedTrack = null;
this._mainDataProvider.setModel(this._model);
this._networkDataProvider.setModel(this._model);
if (this._model) {
this._eventListeners = [
this._model.addEventListener(Timeline.PerformanceModel.Events.WindowChanged, this._onWindowChanged, this),
this._model.addEventListener(
Timeline.PerformanceModel.Events.ExtensionDataAdded, this._appendExtensionData, this)
];
const window = model.window();
this._mainFlameChart.setWindowTimes(window.left, window.right);
this._networkFlameChart.setWindowTimes(window.left, window.right);
this._networkDataProvider.setWindowTimes(window.left, window.right);
this._updateSearchResults(false, false);
}
this._updateColorMapper();
this._updateTrack();
this._nextExtensionIndex = 0;
this._appendExtensionData();
this._refresh();
}
_updateTrack() {
this._countersView.setModel(this._model, this._selectedTrack);
this._detailsView.setModel(this._model, this._selectedTrack);
}
_refresh() {
if (this._networkDataProvider.isEmpty()) {
this._mainFlameChart.enableRuler(true);
this._networkSplitWidget.hideSidebar();
} else {
this._mainFlameChart.enableRuler(false);
this._networkSplitWidget.showBoth();
this.resizeToPreferredHeights();
}
this._mainFlameChart.reset();
this._networkFlameChart.reset();
this._updateSearchResults(false, false);
}
_appendExtensionData() {
if (!this._model) {
return;
}
const extensions = this._model.extensionInfo();
while (this._nextExtensionIndex < extensions.length) {
this._mainDataProvider.appendExtensionEvents(extensions[this._nextExtensionIndex++]);
}
this._mainFlameChart.scheduleUpdate();
}
/**
* @param {!Common.Event} commonEvent
*/
_onEntryHighlighted(commonEvent) {
SDK.OverlayModel.hideDOMNodeHighlight();
const entryIndex = /** @type {number} */ (commonEvent.data);
const event = this._mainDataProvider.eventByIndex(entryIndex);
if (!event) {
return;
}
const target = this._model && this._model.timelineModel().targetByEvent(event);
if (!target) {
return;
}
const timelineData = TimelineModel.TimelineData.forEvent(event);
const backendNodeId = timelineData.backendNodeId;
if (!backendNodeId) {
return;
}
new SDK.DeferredDOMNode(target, backendNodeId).highlight();
}
/**
* @param {?SDK.TracingModel.Event} event
*/
highlightEvent(event) {
const entryIndex =
event ? this._mainDataProvider.entryIndexForSelection(Timeline.TimelineSelection.fromTraceEvent(event)) : -1;
if (entryIndex >= 0) {
this._mainFlameChart.highlightEntry(entryIndex);
} else {
this._mainFlameChart.hideHighlight();
}
}
/**
* @override
*/
willHide() {
this._networkFlameChartGroupExpansionSetting.removeChangeListener(this.resizeToPreferredHeights, this);
this._showMemoryGraphSetting.removeChangeListener(this._updateCountersGraphToggle, this);
Bindings.blackboxManager.removeChangeListener(this._boundRefresh);
}
/**
* @override
*/
wasShown() {
this._networkFlameChartGroupExpansionSetting.addChangeListener(this.resizeToPreferredHeights, this);
this._showMemoryGraphSetting.addChangeListener(this._updateCountersGraphToggle, this);
Bindings.blackboxManager.addChangeListener(this._boundRefresh);
if (this._needsResizeToPreferredHeights) {
this.resizeToPreferredHeights();
}
this._mainFlameChart.scheduleUpdate();
this._networkFlameChart.scheduleUpdate();
}
_updateCountersGraphToggle() {
if (this._showMemoryGraphSetting.get()) {
this._chartSplitWidget.showBoth();
} else {
this._chartSplitWidget.hideSidebar();
}
}
/**
* @param {?Timeline.TimelineSelection} selection
*/
setSelection(selection) {
let index = this._mainDataProvider.entryIndexForSelection(selection);
this._mainFlameChart.setSelectedEntry(index);
index = this._networkDataProvider.entryIndexForSelection(selection);
this._networkFlameChart.setSelectedEntry(index);
if (this._detailsView) {
this._detailsView.setSelection(selection);
}
}
/**
* @param {!PerfUI.FlameChartDataProvider} dataProvider
* @param {!Common.Event} event
*/
_onEntrySelected(dataProvider, event) {
const entryIndex = /** @type{number} */ (event.data);
if (Root.Runtime.experiments.isEnabled('timelineEventInitiators') && dataProvider === this._mainDataProvider) {
if (this._mainDataProvider.buildFlowForInitiator(entryIndex)) {
this._mainFlameChart.scheduleUpdate();
}
}
this._delegate.select(dataProvider.createSelection(entryIndex));
}
resizeToPreferredHeights() {
if (!this.isShowing()) {
this._needsResizeToPreferredHeights = true;
return;
}
this._needsResizeToPreferredHeights = false;
this._networkPane.element.classList.toggle(
'timeline-network-resizer-disabled', !this._networkDataProvider.isExpanded());
this._networkSplitWidget.setSidebarSize(
this._networkDataProvider.preferredHeight() + this._splitResizer.clientHeight + PerfUI.FlameChart.HeaderHeight +
2);
}
/**
* @param {!UI.SearchableView} searchableView
*/
setSearchableView(searchableView) {
this._searchableView = searchableView;
}
// UI.Searchable implementation
/**
* @override
*/
jumpToNextSearchResult() {
if (!this._searchResults || !this._searchResults.length) {
return;
}
const index = typeof this._selectedSearchResult !== 'undefined' ?
this._searchResults.indexOf(this._selectedSearchResult) :
-1;
this._selectSearchResult(mod(index + 1, this._searchResults.length));
}
/**
* @override
*/
jumpToPreviousSearchResult() {
if (!this._searchResults || !this._searchResults.length) {
return;
}
const index =
typeof this._selectedSearchResult !== 'undefined' ? this._searchResults.indexOf(this._selectedSearchResult) : 0;
this._selectSearchResult(mod(index - 1, this._searchResults.length));
}
/**
* @override
* @return {boolean}
*/
supportsCaseSensitiveSearch() {
return true;
}
/**
* @override
* @return {boolean}
*/
supportsRegexSearch() {
return true;
}
/**
* @param {number} index
*/
_selectSearchResult(index) {
this._searchableView.updateCurrentMatchIndex(index);
this._selectedSearchResult = this._searchResults[index];
this._delegate.select(this._mainDataProvider.createSelection(this._selectedSearchResult));
}
/**
* @param {boolean} shouldJump
* @param {boolean=} jumpBackwards
*/
_updateSearchResults(shouldJump, jumpBackwards) {
const oldSelectedSearchResult = this._selectedSearchResult;
delete this._selectedSearchResult;
this._searchResults = [];
if (!this._searchRegex || !this._model) {
return;
}
const regExpFilter = new Timeline.TimelineFilters.RegExp(this._searchRegex);
const window = this._model.window();
this._searchResults = this._mainDataProvider.search(window.left, window.right, regExpFilter);
this._searchableView.updateSearchMatchesCount(this._searchResults.length);
if (!shouldJump || !this._searchResults.length) {
return;
}
let selectedIndex = this._searchResults.indexOf(oldSelectedSearchResult);
if (selectedIndex === -1) {
selectedIndex = jumpBackwards ? this._searchResults.length - 1 : 0;
}
this._selectSearchResult(selectedIndex);
}
/**
* @override
*/
searchCanceled() {
if (typeof this._selectedSearchResult !== 'undefined') {
this._delegate.select(null);
}
delete this._searchResults;
delete this._selectedSearchResult;
delete this._searchRegex;
}
/**
* @override
* @param {!UI.SearchableView.SearchConfig} searchConfig
* @param {boolean} shouldJump
* @param {boolean=} jumpBackwards
*/
performSearch(searchConfig, shouldJump, jumpBackwards) {
this._searchRegex = searchConfig.toSearchRegex();
this._updateSearchResults(shouldJump, jumpBackwards);
}
};
/**
* @unrestricted
*/
Timeline.TimelineFlameChartView.Selection = class {
/**
* @param {!Timeline.TimelineSelection} selection
* @param {number} entryIndex
*/
constructor(selection, entryIndex) {
this.timelineSelection = selection;
this.entryIndex = entryIndex;
}
};
Timeline.FlameChartStyle = {
textColor: '#333'
};
/**
* @implements {PerfUI.FlameChartMarker}
* @unrestricted
*/
Timeline.TimelineFlameChartMarker = class {
/**
* @param {number} startTime
* @param {number} startOffset
* @param {!Timeline.TimelineMarkerStyle} style
*/
constructor(startTime, startOffset, style) {
this._startTime = startTime;
this._startOffset = startOffset;
this._style = style;
}
/**
* @override
* @return {number}
*/
startTime() {
return this._startTime;
}
/**
* @override
* @return {string}
*/
color() {
return this._style.color;
}
/**
* @override
* @return {?string}
*/
title() {
if (this._style.lowPriority) {
return null;
}
const startTime = Number.millisToString(this._startOffset);
return ls`${this._style.title} at ${startTime}`;
}
/**
* @override
* @param {!CanvasRenderingContext2D} context
* @param {number} x
* @param {number} height
* @param {number} pixelsPerMillisecond
*/
draw(context, x, height, pixelsPerMillisecond) {
const lowPriorityVisibilityThresholdInPixelsPerMs = 4;
if (this._style.lowPriority && pixelsPerMillisecond < lowPriorityVisibilityThresholdInPixelsPerMs) {
return;
}
context.save();
if (this._style.tall) {
context.strokeStyle = this._style.color;
context.lineWidth = this._style.lineWidth;
context.translate(this._style.lineWidth < 1 || (this._style.lineWidth & 1) ? 0.5 : 0, 0.5);
context.beginPath();
context.moveTo(x, 0);
context.setLineDash(this._style.dashStyle);
context.lineTo(x, context.canvas.height);
context.stroke();
}
context.restore();
}
};
/** @enum {string} */
Timeline.TimelineFlameChartView._ColorBy = {
URL: 'URL',
};