blob: 2a7714843e005ccb4e70d75a0dd18cb1c42a7594 [file] [log] [blame]
// Copyright 2015 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.
/**
* @unrestricted
*/
Timeline.TimelineTreeView = class extends UI.VBox {
constructor() {
super();
this.element.classList.add('timeline-tree-view');
}
/**
* @param {!SDK.TracingModel.Event} event
* @return {string}
*/
static eventNameForSorting(event) {
if (event.name === TimelineModel.TimelineModel.RecordType.JSFrame) {
var data = event.args['data'];
return data['functionName'] + '@' + (data['scriptId'] || data['url'] || '');
}
return event.name + ':@' + TimelineModel.TimelineProfileTree.eventURL(event);
}
/**
* @param {!TimelineModel.TimelineModel} model
* @param {!Array<!TimelineModel.TimelineModel.Filter>} filters
*/
_init(model, filters) {
this._model = model;
this._linkifier = new Components.Linkifier();
this._filters = filters.slice();
var columns = /** @type {!Array<!UI.DataGrid.ColumnDescriptor>} */ ([]);
this._populateColumns(columns);
var mainView = new UI.VBox();
this._populateToolbar(mainView.element);
this._dataGrid = new UI.SortableDataGrid(columns);
this._dataGrid.addEventListener(UI.DataGrid.Events.SortingChanged, this._sortingChanged, this);
this._dataGrid.element.addEventListener('mousemove', this._onMouseMove.bind(this), true);
this._dataGrid.setResizeMethod(UI.DataGrid.ResizeMethod.Last);
this._dataGrid.asWidget().show(mainView.element);
this._splitWidget = new UI.SplitWidget(true, true, 'timelineTreeViewDetailsSplitWidget');
this._splitWidget.show(this.element);
this._splitWidget.setMainWidget(mainView);
this._detailsView = new UI.VBox();
this._detailsView.element.classList.add('timeline-details-view', 'timeline-details-view-body');
this._splitWidget.setSidebarWidget(this._detailsView);
this._dataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._updateDetailsForSelection, this);
/** @type {?TimelineModel.TimelineProfileTree.Node|undefined} */
this._lastSelectedNode;
}
/**
* @param {!Timeline.TimelineSelection} selection
*/
updateContents(selection) {
this.setRange(selection.startTime(), selection.endTime());
}
/**
* @param {number} startTime
* @param {number} endTime
*/
setRange(startTime, endTime) {
this._startTime = startTime;
this._endTime = endTime;
this._refreshTree();
}
/**
* @return {boolean}
*/
_exposePercentages() {
return false;
}
/**
* @param {!Element} parent
*/
_populateToolbar(parent) {
}
/**
* @param {?TimelineModel.TimelineProfileTree.Node} node
*/
_onHover(node) {
}
/**
* @param {!SDK.TracingModel.Event} event
* @return {?Element}
*/
_linkifyLocation(event) {
var target = this._model.targetByEvent(event);
if (!target)
return null;
var frame = TimelineModel.TimelineProfileTree.eventStackFrame(event);
if (!frame)
return null;
return this._linkifier.maybeLinkifyConsoleCallFrame(target, frame);
}
/**
* @param {!TimelineModel.TimelineProfileTree.Node} treeNode
* @param {boolean} suppressSelectedEvent
*/
selectProfileNode(treeNode, suppressSelectedEvent) {
var pathToRoot = [];
for (var node = treeNode; node; node = node.parent)
pathToRoot.push(node);
for (var i = pathToRoot.length - 1; i > 0; --i) {
var gridNode = this._dataGridNodeForTreeNode(pathToRoot[i]);
if (gridNode && gridNode.dataGrid)
gridNode.expand();
}
var gridNode = this._dataGridNodeForTreeNode(treeNode);
if (gridNode.dataGrid) {
gridNode.reveal();
gridNode.select(suppressSelectedEvent);
}
}
_refreshTree() {
this._linkifier.reset();
this._dataGrid.rootNode().removeChildren();
var tree = this._buildTree();
if (!tree.children)
return;
var maxSelfTime = 0;
var maxTotalTime = 0;
for (var child of tree.children.values()) {
maxSelfTime = Math.max(maxSelfTime, child.selfTime);
maxTotalTime = Math.max(maxTotalTime, child.totalTime);
}
for (var child of tree.children.values()) {
// Exclude the idle time off the total calculation.
var gridNode =
new Timeline.TimelineTreeView.TreeGridNode(child, tree.totalTime, maxSelfTime, maxTotalTime, this);
this._dataGrid.insertChild(gridNode);
}
this._sortingChanged();
this._updateDetailsForSelection();
}
/**
* @return {!TimelineModel.TimelineProfileTree.Node}
*/
_buildTree() {
throw new Error('Not Implemented');
}
/**
* @param {function(!SDK.TracingModel.Event):(string|symbol)=} eventIdCallback
* @return {!TimelineModel.TimelineProfileTree.Node}
*/
_buildTopDownTree(eventIdCallback) {
return TimelineModel.TimelineProfileTree.buildTopDown(
this._model.mainThreadEvents(), this._filters, this._startTime, this._endTime, eventIdCallback);
}
/**
* @param {!Array<!UI.DataGrid.ColumnDescriptor>} columns
*/
_populateColumns(columns) {
columns.push(
{id: 'self', title: Common.UIString('Self Time'), width: '110px', fixedWidth: true, sortable: true});
columns.push(
{id: 'total', title: Common.UIString('Total Time'), width: '110px', fixedWidth: true, sortable: true});
columns.push({id: 'activity', title: Common.UIString('Activity'), disclosure: true, sortable: true});
}
_sortingChanged() {
var columnId = this._dataGrid.sortColumnId();
if (!columnId)
return;
var sortFunction;
switch (columnId) {
case 'startTime':
sortFunction = compareStartTime;
break;
case 'self':
sortFunction = compareNumericField.bind(null, 'selfTime');
break;
case 'total':
sortFunction = compareNumericField.bind(null, 'totalTime');
break;
case 'activity':
sortFunction = compareName;
break;
default:
console.assert(false, 'Unknown sort field: ' + columnId);
return;
}
this._dataGrid.sortNodes(sortFunction, !this._dataGrid.isSortOrderAscending());
/**
* @param {string} field
* @param {!UI.DataGridNode} a
* @param {!UI.DataGridNode} b
* @return {number}
*/
function compareNumericField(field, a, b) {
var nodeA = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (a);
var nodeB = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (b);
return nodeA._profileNode[field] - nodeB._profileNode[field];
}
/**
* @param {!UI.DataGridNode} a
* @param {!UI.DataGridNode} b
* @return {number}
*/
function compareStartTime(a, b) {
var nodeA = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (a);
var nodeB = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (b);
return nodeA._profileNode.event.startTime - nodeB._profileNode.event.startTime;
}
/**
* @param {!UI.DataGridNode} a
* @param {!UI.DataGridNode} b
* @return {number}
*/
function compareName(a, b) {
var nodeA = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (a);
var nodeB = /** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (b);
var nameA = Timeline.TimelineTreeView.eventNameForSorting(nodeA._profileNode.event);
var nameB = Timeline.TimelineTreeView.eventNameForSorting(nodeB._profileNode.event);
return nameA.localeCompare(nameB);
}
}
_updateDetailsForSelection() {
var selectedNode = this._dataGrid.selectedNode ?
/** @type {!Timeline.TimelineTreeView.TreeGridNode} */ (this._dataGrid.selectedNode)._profileNode :
null;
if (selectedNode === this._lastSelectedNode)
return;
this._lastSelectedNode = selectedNode;
this._detailsView.detachChildWidgets();
this._detailsView.element.removeChildren();
if (!selectedNode || !this._showDetailsForNode(selectedNode)) {
var banner = this._detailsView.element.createChild('div', 'full-widget-dimmed-banner');
banner.createTextChild(Common.UIString('Select item for details.'));
}
}
/**
* @param {!TimelineModel.TimelineProfileTree.Node} node
* @return {boolean}
*/
_showDetailsForNode(node) {
return false;
}
/**
* @param {!Event} event
*/
_onMouseMove(event) {
var gridNode = event.target && (event.target instanceof Node) ?
/** @type {?Timeline.TimelineTreeView.TreeGridNode} */ (
this._dataGrid.dataGridNodeFromNode(/** @type {!Node} */ (event.target))) :
null;
var profileNode = gridNode && gridNode._profileNode;
if (profileNode === this._lastHoveredProfileNode)
return;
this._lastHoveredProfileNode = profileNode;
this._onHover(profileNode);
}
/**
* @param {!TimelineModel.TimelineProfileTree.Node} treeNode
* @return {?Timeline.TimelineTreeView.GridNode}
*/
_dataGridNodeForTreeNode(treeNode) {
return treeNode[Timeline.TimelineTreeView.TreeGridNode._gridNodeSymbol] || null;
}
};
/**
* @unrestricted
*/
Timeline.TimelineTreeView.GridNode = class extends UI.SortableDataGridNode {
/**
* @param {!TimelineModel.TimelineProfileTree.Node} profileNode
* @param {number} grandTotalTime
* @param {number} maxSelfTime
* @param {number} maxTotalTime
* @param {!Timeline.TimelineTreeView} treeView
*/
constructor(profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView) {
super(null, false);
this._populated = false;
this._profileNode = profileNode;
this._treeView = treeView;
this._grandTotalTime = grandTotalTime;
this._maxSelfTime = maxSelfTime;
this._maxTotalTime = maxTotalTime;
}
/**
* @override
* @param {string} columnId
* @return {!Element}
*/
createCell(columnId) {
if (columnId === 'activity')
return this._createNameCell(columnId);
return this._createValueCell(columnId) || super.createCell(columnId);
}
/**
* @param {string} columnId
* @return {!Element}
*/
_createNameCell(columnId) {
const cell = this.createTD(columnId);
const container = cell.createChild('div', 'name-container');
const icon = container.createChild('div', 'activity-icon');
const name = container.createChild('div', 'activity-name');
const event = this._profileNode.event;
if (this._profileNode.isGroupNode()) {
const treeView = /** @type {!Timeline.AggregatedTimelineTreeView} */ (this._treeView);
const info = treeView._displayInfoForGroupNode(this._profileNode);
name.textContent = info.name;
icon.style.backgroundColor = info.color;
} else if (event) {
const data = event.args['data'];
const deoptReason = data && data['deoptReason'];
if (deoptReason) {
container.createChild('div', 'activity-warning').title =
Common.UIString('Not optimized: %s', deoptReason);
}
name.textContent = Timeline.TimelineUIUtils.eventTitle(event);
const link = this._treeView._linkifyLocation(event);
if (link)
container.createChild('div', 'activity-link').appendChild(link);
icon.style.backgroundColor = Timeline.TimelineUIUtils.eventColor(event);
}
return cell;
}
/**
* @param {string} columnId
* @return {?Element}
*/
_createValueCell(columnId) {
if (columnId !== 'self' && columnId !== 'total' && columnId !== 'startTime')
return null;
var showPercents = false;
var value;
var maxTime;
switch (columnId) {
case 'startTime':
value = this._profileNode.event.startTime - this._treeView._model.minimumRecordTime();
break;
case 'self':
value = this._profileNode.selfTime;
maxTime = this._maxSelfTime;
showPercents = true;
break;
case 'total':
value = this._profileNode.totalTime;
maxTime = this._maxTotalTime;
showPercents = true;
break;
default:
return null;
}
var cell = this.createTD(columnId);
cell.className = 'numeric-column';
var textDiv = cell.createChild('div');
textDiv.createChild('span').textContent = Common.UIString('%.1f\u2009ms', value);
if (showPercents && this._treeView._exposePercentages())
textDiv.createChild('span', 'percent-column').textContent =
Common.UIString('%.1f\u2009%%', value / this._grandTotalTime * 100);
if (maxTime) {
textDiv.classList.add('background-percent-bar');
cell.createChild('div', 'background-bar-container').createChild('div', 'background-bar').style.width =
(value * 100 / maxTime).toFixed(1) + '%';
}
return cell;
}
};
/**
* @unrestricted
*/
Timeline.TimelineTreeView.TreeGridNode = class extends Timeline.TimelineTreeView.GridNode {
/**
* @param {!TimelineModel.TimelineProfileTree.Node} profileNode
* @param {number} grandTotalTime
* @param {number} maxSelfTime
* @param {number} maxTotalTime
* @param {!Timeline.TimelineTreeView} treeView
*/
constructor(profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView) {
super(profileNode, grandTotalTime, maxSelfTime, maxTotalTime, treeView);
this.hasChildren = this._profileNode.children ? this._profileNode.children.size > 0 : false;
profileNode[Timeline.TimelineTreeView.TreeGridNode._gridNodeSymbol] = this;
}
/**
* @override
*/
populate() {
if (this._populated)
return;
this._populated = true;
if (!this._profileNode.children)
return;
for (var node of this._profileNode.children.values()) {
var gridNode = new Timeline.TimelineTreeView.TreeGridNode(
node, this._grandTotalTime, this._maxSelfTime, this._maxTotalTime, this._treeView);
this.insertChildOrdered(gridNode);
}
}
};
Timeline.TimelineTreeView.TreeGridNode._gridNodeSymbol = Symbol('treeGridNode');
/**
* @unrestricted
*/
Timeline.AggregatedTimelineTreeView = class extends Timeline.TimelineTreeView {
/**
* @param {!TimelineModel.TimelineModel} model
* @param {!Array<!TimelineModel.TimelineModel.Filter>} filters
*/
constructor(model, filters) {
super();
this._groupBySetting =
Common.settings.createSetting('timelineTreeGroupBy', Timeline.AggregatedTimelineTreeView.GroupBy.Category);
this._init(model, filters);
var nonessentialEvents = [
TimelineModel.TimelineModel.RecordType.EventDispatch, TimelineModel.TimelineModel.RecordType.FunctionCall,
TimelineModel.TimelineModel.RecordType.TimerFire
];
this._filters.push(new TimelineModel.ExclusiveNameFilter(nonessentialEvents));
this._stackView = new Timeline.TimelineStackView(this);
this._stackView.addEventListener(
Timeline.TimelineStackView.Events.SelectionChanged, this._onStackViewSelectionChanged, this);
}
/**
* @override
* @param {!Timeline.TimelineSelection} selection
*/
updateContents(selection) {
this._updateExtensionResolver();
super.updateContents(selection);
var rootNode = this._dataGrid.rootNode();
if (rootNode.children.length)
rootNode.children[0].revealAndSelect();
}
_updateExtensionResolver() {
this._executionContextNamesByOrigin = new Map();
for (var target of SDK.targetManager.targets()) {
for (var context of target.runtimeModel.executionContexts())
this._executionContextNamesByOrigin.set(context.origin, context.name);
}
}
/**
* @param {!TimelineModel.TimelineProfileTree.Node} node
* @return {!{name: string, color: string}}
*/
_displayInfoForGroupNode(node) {
var categories = Timeline.TimelineUIUtils.categories();
var color = node.id ? Timeline.TimelineUIUtils.eventColor(node.event) : categories['other'].color;
switch (this._groupBySetting.get()) {
case Timeline.AggregatedTimelineTreeView.GroupBy.Category:
var category = categories[node.id] || categories['other'];
return {name: category.title, color: category.color};
case Timeline.AggregatedTimelineTreeView.GroupBy.Domain:
case Timeline.AggregatedTimelineTreeView.GroupBy.Subdomain:
var name = node.id;
if (Timeline.AggregatedTimelineTreeView._isExtensionInternalURL(name))
name = Common.UIString('[Chrome extensions overhead]');
else if (name.startsWith('chrome-extension'))
name = this._executionContextNamesByOrigin.get(name) || name;
return {name: name || Common.UIString('unattributed'), color: color};
case Timeline.AggregatedTimelineTreeView.GroupBy.EventName:
var name = node.event.name === TimelineModel.TimelineModel.RecordType.JSFrame ?
Common.UIString('JavaScript') :
Timeline.TimelineUIUtils.eventTitle(node.event);
return {
name: name,
color: node.event.name === TimelineModel.TimelineModel.RecordType.JSFrame ?
Timeline.TimelineUIUtils.eventStyle(node.event).category.color :
color
};
case Timeline.AggregatedTimelineTreeView.GroupBy.URL:
break;
case Timeline.AggregatedTimelineTreeView.GroupBy.Frame:
var frame = this._model.pageFrameById(node.id);
var frameName = frame ? Timeline.TimelineUIUtils.displayNameForFrame(frame, 80) : Common.UIString('Page');
return {
name: frameName,
color: color
};
break;
default:
console.assert(false, 'Unexpected aggregation type');
}
return {name: node.id || Common.UIString('unattributed'), color: color};
}
/**
* @override
* @param {!Element} parent
*/
_populateToolbar(parent) {
var panelToolbar = new UI.Toolbar('', parent);
this._groupByCombobox = new UI.ToolbarComboBox(this._onGroupByChanged.bind(this));
/**
* @param {string} name
* @param {string} id
* @this {Timeline.TimelineTreeView}
*/
function addGroupingOption(name, id) {
var option = this._groupByCombobox.createOption(name, '', id);
this._groupByCombobox.addOption(option);
if (id === this._groupBySetting.get())
this._groupByCombobox.select(option);
}
const groupBy = Timeline.AggregatedTimelineTreeView.GroupBy;
addGroupingOption.call(this, Common.UIString('No Grouping'), groupBy.None);
addGroupingOption.call(this, Common.UIString('Group by Activity'), groupBy.EventName);
addGroupingOption.call(this, Common.UIString('Group by Category'), groupBy.Category);
addGroupingOption.call(this, Common.UIString('Group by Domain'), groupBy.Domain);
addGroupingOption.call(this, Common.UIString('Group by Subdomain'), groupBy.Subdomain);
addGroupingOption.call(this, Common.UIString('Group by URL'), groupBy.URL);
addGroupingOption.call(this, Common.UIString('Group by Frame'), groupBy.Frame);
panelToolbar.appendToolbarItem(this._groupByCombobox);
}
/**
* @param {!TimelineModel.TimelineProfileTree.Node} treeNode
* @return {!Array<!TimelineModel.TimelineProfileTree.Node>}
*/
_buildHeaviestStack(treeNode) {
console.assert(!!treeNode.parent, 'Attempt to build stack for tree root');
var result = [];
// Do not add root to the stack, as it's the tree itself.
for (var node = treeNode; node && node.parent; node = node.parent)
result.push(node);
result = result.reverse();
for (node = treeNode; node && node.children && node.children.size;) {
var children = Array.from(node.children.values());
node = children.reduce((a, b) => a.totalTime > b.totalTime ? a : b);
result.push(node);
}
return result;
}
/**
* @override
* @return {boolean}
*/
_exposePercentages() {
return true;
}
_onGroupByChanged() {
this._groupBySetting.set(this._groupByCombobox.selectedOption().value);
this._refreshTree();
}
_onStackViewSelectionChanged() {
var treeNode = this._stackView.selectedTreeNode();
if (treeNode)
this.selectProfileNode(treeNode, true);
}
/**
* @override
* @param {!TimelineModel.TimelineProfileTree.Node} node
* @return {boolean}
*/
_showDetailsForNode(node) {
var stack = this._buildHeaviestStack(node);
this._stackView.setStack(stack, node);
this._stackView.show(this._detailsView.element);
return true;
}
/**
* @param {!Timeline.AggregatedTimelineTreeView.GroupBy} groupBy
* @return {function(!SDK.TracingModel.Event):string}
*/
_groupingFunction(groupBy) {
/**
* @param {!SDK.TracingModel.Event} event
* @return {string}
*/
function groupByURL(event) {
return TimelineModel.TimelineProfileTree.eventURL(event) || '';
}
/**
* @param {boolean} groupSubdomains
* @param {!SDK.TracingModel.Event} event
* @return {string}
*/
function groupByDomain(groupSubdomains, event) {
var url = TimelineModel.TimelineProfileTree.eventURL(event) || '';
if (Timeline.AggregatedTimelineTreeView._isExtensionInternalURL(url))
return Timeline.AggregatedTimelineTreeView._extensionInternalPrefix;
var parsedURL = url.asParsedURL();
if (!parsedURL)
return '';
if (parsedURL.scheme === 'chrome-extension')
return parsedURL.scheme + '://' + parsedURL.host;
if (!groupSubdomains)
return parsedURL.host;
if (/^[.0-9]+$/.test(parsedURL.host))
return parsedURL.host;
var domainMatch = /([^.]*\.)?[^.]*$/.exec(parsedURL.host);
return domainMatch && domainMatch[0] || '';
}
switch (groupBy) {
case Timeline.AggregatedTimelineTreeView.GroupBy.None:
return () => Symbol('uniqueGroupId');
case Timeline.AggregatedTimelineTreeView.GroupBy.EventName:
return event => Timeline.TimelineUIUtils.eventStyle(event).title;
case Timeline.AggregatedTimelineTreeView.GroupBy.Category:
return event => Timeline.TimelineUIUtils.eventStyle(event).category.name;
case Timeline.AggregatedTimelineTreeView.GroupBy.Subdomain:
return groupByDomain.bind(null, false);
case Timeline.AggregatedTimelineTreeView.GroupBy.Domain:
return groupByDomain.bind(null, true);
case Timeline.AggregatedTimelineTreeView.GroupBy.URL:
return groupByURL;
case Timeline.AggregatedTimelineTreeView.GroupBy.Frame:
return event => TimelineModel.TimelineData.forEvent(event).frameId;
default:
console.assert(false, `Unexpected aggregation setting: ${groupBy}`);
return () => Symbol('uniqueGroupId');
}
}
/**
* @param {string} url
* @return {boolean}
*/
static _isExtensionInternalURL(url) {
return url.startsWith(Timeline.AggregatedTimelineTreeView._extensionInternalPrefix);
}
};
Timeline.AggregatedTimelineTreeView._extensionInternalPrefix = 'extensions::';
/**
* @enum {string}
*/
Timeline.AggregatedTimelineTreeView.GroupBy = {
None: 'None',
EventName: 'EventName',
Category: 'Category',
Domain: 'Domain',
Subdomain: 'Subdomain',
URL: 'URL',
Frame: 'Frame'
};
/**
* @unrestricted
*/
Timeline.CallTreeTimelineTreeView = class extends Timeline.AggregatedTimelineTreeView {
/**
* @param {!TimelineModel.TimelineModel} model
* @param {!Array<!TimelineModel.TimelineModel.Filter>} filters
*/
constructor(model, filters) {
super(model, filters);
this._dataGrid.markColumnAsSortedBy('total', UI.DataGrid.Order.Descending);
}
/**
* @override
* @return {!TimelineModel.TimelineProfileTree.Node}
*/
_buildTree() {
var grouping = this._groupBySetting.get();
var topDown = this._buildTopDownTree(this._groupingFunction(grouping));
if (grouping === Timeline.AggregatedTimelineTreeView.GroupBy.None)
return topDown;
return new TimelineModel.TimelineAggregator().performGrouping(topDown);
}
};
/**
* @unrestricted
*/
Timeline.BottomUpTimelineTreeView = class extends Timeline.AggregatedTimelineTreeView {
/**
* @param {!TimelineModel.TimelineModel} model
* @param {!Array<!TimelineModel.TimelineModel.Filter>} filters
*/
constructor(model, filters) {
super(model, filters);
this._dataGrid.markColumnAsSortedBy('self', UI.DataGrid.Order.Descending);
}
/**
* @override
* @return {!TimelineModel.TimelineProfileTree.Node}
*/
_buildTree() {
var topDown = this._buildTopDownTree(this._groupingFunction(this._groupBySetting.get()));
return TimelineModel.TimelineProfileTree.buildBottomUp(topDown);
}
};
/**
* @unrestricted
*/
Timeline.EventsTimelineTreeView = class extends Timeline.TimelineTreeView {
/**
* @param {!TimelineModel.TimelineModel} model
* @param {!Array<!TimelineModel.TimelineModel.Filter>} filters
* @param {!Timeline.TimelineModeViewDelegate} delegate
*/
constructor(model, filters, delegate) {
super();
this._filtersControl = new Timeline.TimelineFilters();
this._filtersControl.addEventListener(
Timeline.TimelineFilters.Events.FilterChanged, this._onFilterChanged, this);
this._init(model, filters);
this._delegate = delegate;
this._filters.push.apply(this._filters, this._filtersControl.filters());
this._dataGrid.markColumnAsSortedBy('startTime', UI.DataGrid.Order.Ascending);
}
/**
* @override
* @param {!Timeline.TimelineSelection} selection
*/
updateContents(selection) {
super.updateContents(selection);
if (selection.type() === Timeline.TimelineSelection.Type.TraceEvent) {
var event = /** @type {!SDK.TracingModel.Event} */ (selection.object());
this._selectEvent(event, true);
}
}
/**
* @override
* @return {!TimelineModel.TimelineProfileTree.Node}
*/
_buildTree() {
this._currentTree = this._buildTopDownTree();
return this._currentTree;
}
_onFilterChanged() {
var selectedEvent = this._lastSelectedNode && this._lastSelectedNode.event;
this._refreshTree();
if (selectedEvent)
this._selectEvent(selectedEvent, false);
}
/**
* @param {!SDK.TracingModel.Event} event
* @return {?TimelineModel.TimelineProfileTree.Node}
*/
_findNodeWithEvent(event) {
var iterators = [this._currentTree.children.values()];
while (iterators.length) {
var iterator = iterators.peekLast().next();
if (iterator.done) {
iterators.pop();
continue;
}
var child = /** @type {!TimelineModel.TimelineProfileTree.Node} */ (iterator.value);
if (child.event === event)
return child;
if (child.children)
iterators.push(child.children.values());
}
return null;
}
/**
* @param {!SDK.TracingModel.Event} event
* @param {boolean=} expand
*/
_selectEvent(event, expand) {
var node = this._findNodeWithEvent(event);
if (!node)
return;
this.selectProfileNode(node, false);
if (expand)
this._dataGridNodeForTreeNode(node).expand();
}
/**
* @override
* @param {!Array<!UI.DataGrid.ColumnDescriptor>} columns
*/
_populateColumns(columns) {
columns.push({
id: 'startTime',
title: Common.UIString('Start Time'),
width: '110px',
fixedWidth: true,
sortable: true
});
super._populateColumns(columns);
}
/**
* @override
* @param {!Element} parent
*/
_populateToolbar(parent) {
var filtersWidget = this._filtersControl.filtersWidget();
filtersWidget.forceShowFilterBar();
filtersWidget.show(parent);
}
/**
* @override
* @param {!TimelineModel.TimelineProfileTree.Node} node
* @return {boolean}
*/
_showDetailsForNode(node) {
var traceEvent = node.event;
if (!traceEvent)
return false;
Timeline.TimelineUIUtils.buildTraceEventDetails(
traceEvent, this._model, this._linkifier, false, showDetails.bind(this));
return true;
/**
* @param {!DocumentFragment} fragment
* @this {Timeline.EventsTimelineTreeView}
*/
function showDetails(fragment) {
this._detailsView.element.appendChild(fragment);
}
}
/**
* @override
* @param {?TimelineModel.TimelineProfileTree.Node} node
*/
_onHover(node) {
this._delegate.highlightEvent(node && node.event);
}
};
/**
* @unrestricted
*/
Timeline.TimelineStackView = class extends UI.VBox {
constructor(treeView) {
super();
var header = this.element.createChild('div', 'timeline-stack-view-header');
header.textContent = Common.UIString('Heaviest stack');
this._treeView = treeView;
var columns = /** @type {!Array<!UI.DataGrid.ColumnDescriptor>} */ ([
{id: 'total', title: Common.UIString('Total Time'), fixedWidth: true, width: '110px'},
{id: 'activity', title: Common.UIString('Activity')}
]);
this._dataGrid = new UI.ViewportDataGrid(columns);
this._dataGrid.setResizeMethod(UI.DataGrid.ResizeMethod.Last);
this._dataGrid.addEventListener(UI.DataGrid.Events.SelectedNode, this._onSelectionChanged, this);
this._dataGrid.asWidget().show(this.element);
}
/**
* @param {!Array<!TimelineModel.TimelineProfileTree.Node>} stack
* @param {!TimelineModel.TimelineProfileTree.Node} selectedNode
*/
setStack(stack, selectedNode) {
var rootNode = this._dataGrid.rootNode();
rootNode.removeChildren();
var nodeToReveal = null;
var totalTime = Math.max.apply(Math, stack.map(node => node.totalTime));
for (var node of stack) {
var gridNode = new Timeline.TimelineTreeView.GridNode(node, totalTime, totalTime, totalTime, this._treeView);
rootNode.appendChild(gridNode);
if (node === selectedNode)
nodeToReveal = gridNode;
}
nodeToReveal.revealAndSelect();
}
/**
* @return {?TimelineModel.TimelineProfileTree.Node}
*/
selectedTreeNode() {
var selectedNode = this._dataGrid.selectedNode;
return selectedNode && /** @type {!Timeline.TimelineTreeView.GridNode} */ (selectedNode)._profileNode;
}
_onSelectionChanged() {
this.dispatchEventToListeners(Timeline.TimelineStackView.Events.SelectionChanged);
}
};
/** @enum {symbol} */
Timeline.TimelineStackView.Events = {
SelectionChanged: Symbol('SelectionChanged')
};