mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 21:54:47 +08:00
BUG=836030 Change-Id: I0a11b2dd04b21b06a33e0fd9e20c9cc299901c6f Reviewed-on: https://chromium-review.googlesource.com/1032232 Commit-Queue: Alexei Filippov <alph@chromium.org> Reviewed-by: Dmitry Gozman <dgozman@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#554592} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: b79085234f1897b39abeb8632008f5dd91c3f0fe
515 lines
17 KiB
JavaScript
515 lines
17 KiB
JavaScript
// 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._networkFlameChart.addEventListener(PerfUI.FlameChart.Events.EntrySelected, 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();
|
|
ProductRegistry.instance().then(registry => this._productRegistry = registry);
|
|
}
|
|
|
|
_updateColorMapper() {
|
|
/** @type {!Map<string, string>} */
|
|
this._urlToColorCache = new Map();
|
|
if (!this._model)
|
|
return;
|
|
const colorByProduct = this._groupBySetting.get() === Timeline.AggregatedTimelineTreeView.GroupBy.Product;
|
|
this._mainDataProvider.setEventColorMapping(
|
|
colorByProduct ? this._colorByProductForEvent.bind(this) : Timeline.TimelineUIUtils.eventColor);
|
|
this._mainFlameChart.update();
|
|
}
|
|
|
|
/**
|
|
* @param {!SDK.TracingModel.Event} event
|
|
* @return {string}
|
|
*/
|
|
_colorByProductForEvent(event) {
|
|
return Timeline.TimelineUIUtils.eventColorByProduct(
|
|
this._productRegistry, this._model.timelineModel(), this._urlToColorCache, event);
|
|
}
|
|
|
|
/**
|
|
* @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 (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() {
|
|
const startTime = Number.millisToString(this._startOffset);
|
|
return Common.UIString('%s at %s', this._style.title, 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.lowPriority) {
|
|
context.strokeStyle = this._style.color;
|
|
context.lineWidth = 2;
|
|
context.beginPath();
|
|
context.moveTo(x, 0);
|
|
context.lineTo(x, height);
|
|
context.stroke();
|
|
}
|
|
|
|
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, height);
|
|
context.setLineDash(this._style.dashStyle);
|
|
context.lineTo(x, context.canvas.height);
|
|
context.stroke();
|
|
}
|
|
context.restore();
|
|
}
|
|
};
|
|
|
|
/** @enum {string} */
|
|
Timeline.TimelineFlameChartView._ColorBy = {
|
|
URL: 'URL',
|
|
Product: 'Product'
|
|
};
|