blob: b3588881076ca44fd4e7e4b1a53807563ae7b3cd [file] [log] [blame]
/*
* Copyright (C) 2012 Google 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.
*/
/**
* @unrestricted
*/
Profiler.HeapSnapshotSortableDataGrid = class extends DataGrid.DataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
* @param {!Array.<!DataGrid.DataGrid.ColumnDescriptor>} columns
*/
constructor(heapProfilerModel, dataDisplayDelegate, columns) {
// TODO(allada) This entire class needs to be converted to use the templates in DataGridNode.
super(columns);
this._heapProfilerModel = heapProfilerModel;
this._dataDisplayDelegate = dataDisplayDelegate;
const tooltips = [
['distance', ls`Distance from window object`], ['shallowSize', ls`Size of the object itself in bytes`],
['retainedSize', ls`Size of the object plus the graph it retains in bytes`]
];
for (const info of tooltips) {
const headerCell = this.headerTableHeader(info[0]);
if (headerCell) {
headerCell.setAttribute('title', info[1]);
}
}
/**
* @type {number}
*/
this._recursiveSortingDepth = 0;
/**
* @type {?Profiler.HeapSnapshotGridNode}
*/
this._highlightedNode = null;
/**
* @type {boolean}
*/
this._populatedAndSorted = false;
/**
* @type {?UI.ToolbarInput}
*/
this._nameFilter = null;
this._nodeFilter = new HeapSnapshotModel.NodeFilter();
this.addEventListener(Profiler.HeapSnapshotSortableDataGrid.Events.SortingComplete, this._sortingComplete, this);
this.addEventListener(DataGrid.DataGrid.Events.SortingChanged, this.sortingChanged, this);
this.setRowContextMenuCallback(this._populateContextMenu.bind(this));
}
/**
* @return {?SDK.HeapProfilerModel}
*/
heapProfilerModel() {
return this._heapProfilerModel;
}
/**
* @return {!Profiler.ProfileType.DataDisplayDelegate}
*/
dataDisplayDelegate() {
return this._dataDisplayDelegate;
}
/**
* @return {!HeapSnapshotModel.NodeFilter}
*/
nodeFilter() {
return this._nodeFilter;
}
/**
* @param {!UI.ToolbarInput} nameFilter
*/
setNameFilter(nameFilter) {
this._nameFilter = nameFilter;
}
/**
* @return {number}
*/
defaultPopulateCount() {
return 100;
}
_disposeAllNodes() {
const children = this.topLevelNodes();
for (let i = 0, l = children.length; i < l; ++i) {
children[i].dispose();
}
}
/**
* @override
*/
wasShown() {
if (this._nameFilter) {
this._nameFilter.addEventListener(UI.ToolbarInput.Event.TextChanged, this._onNameFilterChanged, this);
this.updateVisibleNodes(true);
}
if (this._populatedAndSorted) {
this.dispatchEventToListeners(Profiler.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
}
}
_sortingComplete() {
this.removeEventListener(Profiler.HeapSnapshotSortableDataGrid.Events.SortingComplete, this._sortingComplete, this);
this._populatedAndSorted = true;
this.dispatchEventToListeners(Profiler.HeapSnapshotSortableDataGrid.Events.ContentShown, this);
}
/**
* @override
*/
willHide() {
if (this._nameFilter) {
this._nameFilter.removeEventListener(UI.ToolbarInput.Event.TextChanged, this._onNameFilterChanged, this);
}
this._clearCurrentHighlight();
}
/**
* @param {!UI.ContextMenu} contextMenu
* @param {!DataGrid.DataGridNode} gridNode
*/
_populateContextMenu(contextMenu, gridNode) {
const node = /** @type {!Profiler.HeapSnapshotGridNode} */ (gridNode);
node.populateContextMenu(contextMenu, this._dataDisplayDelegate, this.heapProfilerModel());
if (gridNode.linkElement && !contextMenu.containsTarget(gridNode.linkElement)) {
contextMenu.appendApplicableItems(gridNode.linkElement);
}
}
resetSortingCache() {
delete this._lastSortColumnId;
delete this._lastSortAscending;
}
/**
* @return {!Array<!Profiler.HeapSnapshotGridNode>}
*/
topLevelNodes() {
return this.rootNode().children;
}
/**
* @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} heapSnapshotObjectId
* @return {!Promise<?Profiler.HeapSnapshotGridNode>}
*/
revealObjectByHeapSnapshotId(heapSnapshotObjectId) {
return Promise.resolve(/** @type {?Profiler.HeapSnapshotGridNode} */ (null));
}
/**
* @param {!Profiler.HeapSnapshotGridNode} node
*/
highlightNode(node) {
this._clearCurrentHighlight();
this._highlightedNode = node;
UI.runCSSAnimationOnce(this._highlightedNode.element(), 'highlighted-row');
}
_clearCurrentHighlight() {
if (!this._highlightedNode) {
return;
}
this._highlightedNode.element().classList.remove('highlighted-row');
this._highlightedNode = null;
}
resetNameFilter() {
this._nameFilter.setValue('');
}
_onNameFilterChanged() {
this.updateVisibleNodes(true);
this._deselectFilteredNodes();
}
_deselectFilteredNodes() {
let currentNode = this.selectedNode;
while (currentNode) {
if (this._isFilteredOut(currentNode)) {
this.selectedNode.deselect();
this.selectedNode = null;
return;
}
currentNode = currentNode.parent;
}
}
sortingChanged() {
const sortAscending = this.isSortOrderAscending();
const sortColumnId = this.sortColumnId();
if (this._lastSortColumnId === sortColumnId && this._lastSortAscending === sortAscending) {
return;
}
this._lastSortColumnId = sortColumnId;
this._lastSortAscending = sortAscending;
const sortFields = this._sortFields(sortColumnId, sortAscending);
function SortByTwoFields(nodeA, nodeB) {
let field1 = nodeA[sortFields[0]];
let field2 = nodeB[sortFields[0]];
let result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
if (!sortFields[1]) {
result = -result;
}
if (result !== 0) {
return result;
}
field1 = nodeA[sortFields[2]];
field2 = nodeB[sortFields[2]];
result = field1 < field2 ? -1 : (field1 > field2 ? 1 : 0);
if (!sortFields[3]) {
result = -result;
}
return result;
}
this._performSorting(SortByTwoFields);
}
_performSorting(sortFunction) {
this.recursiveSortingEnter();
const children = this.allChildren(this.rootNode());
this.rootNode().removeChildren();
children.sort(sortFunction);
for (let i = 0, l = children.length; i < l; ++i) {
const child = children[i];
this.appendChildAfterSorting(child);
if (child.expanded) {
child.sort();
}
}
this.recursiveSortingLeave();
}
appendChildAfterSorting(child) {
const revealed = child.revealed;
this.rootNode().appendChild(child);
child.revealed = revealed;
}
recursiveSortingEnter() {
++this._recursiveSortingDepth;
}
recursiveSortingLeave() {
if (!this._recursiveSortingDepth) {
return;
}
if (--this._recursiveSortingDepth) {
return;
}
this.updateVisibleNodes(true);
this.dispatchEventToListeners(Profiler.HeapSnapshotSortableDataGrid.Events.SortingComplete);
}
/**
* @param {boolean} force
*/
updateVisibleNodes(force) {
}
/**
* @param {!DataGrid.DataGridNode} parent
* @return {!Array.<!Profiler.HeapSnapshotGridNode>}
*/
allChildren(parent) {
return parent.children;
}
/**
* @param {!DataGrid.DataGridNode} parent
* @param {!DataGrid.DataGridNode} node
* @param {number} index
*/
insertChild(parent, node, index) {
parent.insertChild(node, index);
}
/**
* @param {!Profiler.HeapSnapshotGridNode} parent
* @param {number} index
*/
removeChildByIndex(parent, index) {
parent.removeChild(parent.children[index]);
}
/**
* @param {!Profiler.HeapSnapshotGridNode} parent
*/
removeAllChildren(parent) {
parent.removeChildren();
}
};
/** @override @suppress {checkPrototypalTypes} @enum {symbol} */
Profiler.HeapSnapshotSortableDataGrid.Events = {
ContentShown: Symbol('ContentShown'),
SortingComplete: Symbol('SortingComplete')
};
/**
* @unrestricted
*/
Profiler.HeapSnapshotViewportDataGrid = class extends Profiler.HeapSnapshotSortableDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
* @param {!Array.<!DataGrid.DataGrid.ColumnDescriptor>} columns
*/
constructor(heapProfilerModel, dataDisplayDelegate, columns) {
super(heapProfilerModel, dataDisplayDelegate, columns);
this.scrollContainer.addEventListener('scroll', this._onScroll.bind(this), true);
this._topPaddingHeight = 0;
this._bottomPaddingHeight = 0;
}
/**
* @override
* @return {!Array.<!Profiler.HeapSnapshotGridNode>}
*/
topLevelNodes() {
return this.allChildren(this.rootNode());
}
/**
* @override
*/
appendChildAfterSorting(child) {
// Do nothing here, it will be added in updateVisibleNodes.
}
/**
* @override
* @param {boolean} force
*/
updateVisibleNodes(force) {
// Guard zone is used to ensure there are always some extra items
// above and below the viewport to support keyboard navigation.
const guardZoneHeight = 40;
const scrollHeight = this.scrollContainer.scrollHeight;
let scrollTop = this.scrollContainer.scrollTop;
let scrollBottom = scrollHeight - scrollTop - this.scrollContainer.offsetHeight;
scrollTop = Math.max(0, scrollTop - guardZoneHeight);
scrollBottom = Math.max(0, scrollBottom - guardZoneHeight);
let viewPortHeight = scrollHeight - scrollTop - scrollBottom;
// Do nothing if populated nodes still fit the viewport.
if (!force && scrollTop >= this._topPaddingHeight && scrollBottom >= this._bottomPaddingHeight) {
return;
}
const hysteresisHeight = 500;
scrollTop -= hysteresisHeight;
viewPortHeight += 2 * hysteresisHeight;
const selectedNode = this.selectedNode;
this.rootNode().removeChildren();
this._topPaddingHeight = 0;
this._bottomPaddingHeight = 0;
this._addVisibleNodes(this.rootNode(), scrollTop, scrollTop + viewPortHeight);
this.setVerticalPadding(this._topPaddingHeight, this._bottomPaddingHeight);
if (selectedNode) {
// Keep selection even if the node is not in the current viewport.
if (selectedNode.parent) {
selectedNode.select(true);
} else {
this.selectedNode = selectedNode;
}
}
}
/**
* @param {!DataGrid.DataGridNode} parentNode
* @param {number} topBound
* @param {number} bottomBound
* @return {number}
*/
_addVisibleNodes(parentNode, topBound, bottomBound) {
if (!parentNode.expanded) {
return 0;
}
const children = this.allChildren(parentNode);
let topPadding = 0;
// Iterate over invisible nodes beyond the upper bound of viewport.
// Do not insert them into the grid, but count their total height.
let i = 0;
for (; i < children.length; ++i) {
const child = children[i];
if (this._isFilteredOut(child)) {
continue;
}
const newTop = topPadding + this._nodeHeight(child);
if (newTop > topBound) {
break;
}
topPadding = newTop;
}
// Put visible nodes into the data grid.
let position = topPadding;
for (; i < children.length && position < bottomBound; ++i) {
const child = children[i];
if (this._isFilteredOut(child)) {
continue;
}
const hasChildren = child.hasChildren();
child.removeChildren();
child.setHasChildren(hasChildren);
parentNode.appendChild(child);
position += child.nodeSelfHeight();
position += this._addVisibleNodes(child, topBound - position, bottomBound - position);
}
// Count the invisible nodes beyond the bottom bound of the viewport.
let bottomPadding = 0;
for (; i < children.length; ++i) {
const child = children[i];
if (this._isFilteredOut(child)) {
continue;
}
bottomPadding += this._nodeHeight(child);
}
this._topPaddingHeight += topPadding;
this._bottomPaddingHeight += bottomPadding;
return position + bottomPadding;
}
/**
* @param {!Profiler.HeapSnapshotGridNode} node
* @return {boolean}
*/
_isFilteredOut(node) {
const nameFilterValue = this._nameFilter ? this._nameFilter.value().toLowerCase() : '';
if (nameFilterValue && node.filteredOut && node.filteredOut(nameFilterValue)) {
return true;
} else {
return false;
}
}
/**
* @param {!Profiler.HeapSnapshotGridNode} node
* @return {number}
*/
_nodeHeight(node) {
let result = node.nodeSelfHeight();
if (!node.expanded) {
return result;
}
const children = this.allChildren(node);
for (let i = 0; i < children.length; i++) {
result += this._nodeHeight(children[i]);
}
return result;
}
/**
* @param {!Array<!Profiler.HeapSnapshotGridNode>} pathToReveal
* @return {!Promise<!Profiler.HeapSnapshotGridNode>}
*/
revealTreeNode(pathToReveal) {
const height = this._calculateOffset(pathToReveal);
const node = /** @type {!Profiler.HeapSnapshotGridNode} */ (pathToReveal.peekLast());
const scrollTop = this.scrollContainer.scrollTop;
const scrollBottom = scrollTop + this.scrollContainer.offsetHeight;
if (height >= scrollTop && height < scrollBottom) {
return Promise.resolve(node);
}
const scrollGap = 40;
this.scrollContainer.scrollTop = Math.max(0, height - scrollGap);
return new Promise(resolve => {
console.assert(!this._scrollToResolveCallback);
this._scrollToResolveCallback = resolve.bind(null, node);
// Still resolve the promise if it does not scroll for some reason.
this.scrollContainer.window().requestAnimationFrame(() => {
if (!this._scrollToResolveCallback) {
return;
}
this._scrollToResolveCallback();
this._scrollToResolveCallback = null;
});
});
}
/**
* @param {!Array.<!Profiler.HeapSnapshotGridNode>} pathToReveal
* @return {number}
*/
_calculateOffset(pathToReveal) {
let parentNode = this.rootNode();
let height = 0;
for (let i = 0; i < pathToReveal.length; ++i) {
const node = pathToReveal[i];
const children = this.allChildren(parentNode);
for (let j = 0; j < children.length; ++j) {
const child = children[j];
if (node === child) {
height += node.nodeSelfHeight();
break;
}
height += this._nodeHeight(child);
}
parentNode = node;
}
return height - pathToReveal.peekLast().nodeSelfHeight();
}
/**
* @override
* @param {!DataGrid.DataGridNode} parent
* @return {!Array.<!Profiler.HeapSnapshotGridNode>}
*/
allChildren(parent) {
return parent._allChildren || (parent._allChildren = []);
}
/**
* @param {!DataGrid.DataGridNode} parent
* @param {!Profiler.HeapSnapshotGridNode} node
*/
appendNode(parent, node) {
this.allChildren(parent).push(node);
}
/**
* @override
* @param {!DataGrid.DataGridNode} parent
* @param {!DataGrid.DataGridNode} node
* @param {number} index
*/
insertChild(parent, node, index) {
this.allChildren(parent).splice(index, 0, /** @type {!Profiler.HeapSnapshotGridNode} */ (node));
}
/**
* @override
*/
removeChildByIndex(parent, index) {
this.allChildren(parent).splice(index, 1);
}
/**
* @override
*/
removeAllChildren(parent) {
parent._allChildren = [];
}
removeTopLevelNodes() {
this._disposeAllNodes();
this.rootNode().removeChildren();
this.rootNode()._allChildren = [];
}
/**
* @param {!Element} element
* @return {boolean}
*/
_isScrolledIntoView(element) {
const viewportTop = this.scrollContainer.scrollTop;
const viewportBottom = viewportTop + this.scrollContainer.clientHeight;
const elemTop = element.offsetTop;
const elemBottom = elemTop + element.offsetHeight;
return elemBottom <= viewportBottom && elemTop >= viewportTop;
}
/**
* @override
*/
onResize() {
super.onResize();
this.updateVisibleNodes(false);
}
/**
* @param {!Event} event
*/
_onScroll(event) {
this.updateVisibleNodes(false);
if (this._scrollToResolveCallback) {
this._scrollToResolveCallback();
this._scrollToResolveCallback = null;
}
}
};
/**
* @unrestricted
*/
Profiler.HeapSnapshotContainmentDataGrid = class extends Profiler.HeapSnapshotSortableDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
* @param {!Array.<!DataGrid.DataGrid.ColumnDescriptor>=} columns
*/
constructor(heapProfilerModel, dataDisplayDelegate, columns) {
columns = columns || (/** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'object', title: ls`Object`, disclosure: true, sortable: true},
{id: 'distance', title: ls`Distance`, width: '70px', sortable: true, fixedWidth: true},
{id: 'shallowSize', title: ls`Shallow Size`, width: '110px', sortable: true, fixedWidth: true}, {
id: 'retainedSize',
title: ls`Retained Size`,
width: '110px',
sortable: true,
fixedWidth: true,
sort: DataGrid.DataGrid.Order.Descending
}
]));
super(heapProfilerModel, dataDisplayDelegate, columns);
}
/**
* @param {!Profiler.HeapSnapshotProxy} snapshot
* @param {number} nodeIndex
*/
setDataSource(snapshot, nodeIndex) {
this.snapshot = snapshot;
const node = {nodeIndex: nodeIndex || snapshot.rootNodeIndex};
const fakeEdge = {node: node};
this.setRootNode(this._createRootNode(snapshot, fakeEdge));
this.rootNode().sort();
}
_createRootNode(snapshot, fakeEdge) {
return new Profiler.HeapSnapshotObjectNode(this, snapshot, fakeEdge, null);
}
/**
* @override
*/
sortingChanged() {
const rootNode = this.rootNode();
if (rootNode.hasChildren()) {
rootNode.sort();
}
}
};
/**
* @unrestricted
*/
Profiler.HeapSnapshotRetainmentDataGrid = class extends Profiler.HeapSnapshotContainmentDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
*/
constructor(heapProfilerModel, dataDisplayDelegate) {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'object', title: ls`Object`, disclosure: true, sortable: true}, {
id: 'distance',
title: ls`Distance`,
width: '70px',
sortable: true,
fixedWidth: true,
sort: DataGrid.DataGrid.Order.Ascending
},
{id: 'shallowSize', title: ls`Shallow Size`, width: '110px', sortable: true, fixedWidth: true},
{id: 'retainedSize', title: ls`Retained Size`, width: '110px', sortable: true, fixedWidth: true}
]);
super(heapProfilerModel, dataDisplayDelegate, columns);
}
/**
* @override
*/
_createRootNode(snapshot, fakeEdge) {
return new Profiler.HeapSnapshotRetainingObjectNode(this, snapshot, fakeEdge, null);
}
_sortFields(sortColumn, sortAscending) {
return {
object: ['_name', sortAscending, '_count', false],
count: ['_count', sortAscending, '_name', true],
shallowSize: ['_shallowSize', sortAscending, '_name', true],
retainedSize: ['_retainedSize', sortAscending, '_name', true],
distance: ['_distance', sortAscending, '_name', true]
}[sortColumn];
}
reset() {
this.rootNode().removeChildren();
this.resetSortingCache();
}
/**
* @override
* @param {!Profiler.HeapSnapshotProxy} snapshot
* @param {number} nodeIndex
*/
setDataSource(snapshot, nodeIndex) {
super.setDataSource(snapshot, nodeIndex);
this.rootNode().expand();
}
};
/** @override @suppress {checkPrototypalTypes} @enum {symbol} */
Profiler.HeapSnapshotRetainmentDataGrid.Events = {
ExpandRetainersComplete: Symbol('ExpandRetainersComplete')
};
/**
* @unrestricted
*/
Profiler.HeapSnapshotConstructorsDataGrid = class extends Profiler.HeapSnapshotViewportDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
*/
constructor(heapProfilerModel, dataDisplayDelegate) {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'object', title: ls`Constructor`, disclosure: true, sortable: true},
{id: 'distance', title: ls`Distance`, width: '70px', sortable: true, fixedWidth: true},
{id: 'shallowSize', title: ls`Shallow Size`, width: '110px', sortable: true, fixedWidth: true}, {
id: 'retainedSize',
title: ls`Retained Size`,
width: '110px',
sort: DataGrid.DataGrid.Order.Descending,
sortable: true,
fixedWidth: true
}
]);
super(heapProfilerModel, dataDisplayDelegate, columns);
this._profileIndex = -1;
this._objectIdToSelect = null;
}
/**
* @param {string} sortColumn
* @param {boolean} sortAscending
* @return {!Array}
*/
_sortFields(sortColumn, sortAscending) {
return {
object: ['_name', sortAscending, '_retainedSize', false],
distance: ['_distance', sortAscending, '_retainedSize', false],
shallowSize: ['_shallowSize', sortAscending, '_name', true],
retainedSize: ['_retainedSize', sortAscending, '_name', true]
}[sortColumn];
}
/**
* @override
* @param {!Protocol.HeapProfiler.HeapSnapshotObjectId} id
* @return {!Promise<?Profiler.HeapSnapshotGridNode>}
*/
async revealObjectByHeapSnapshotId(id) {
if (!this.snapshot) {
this._objectIdToSelect = id;
return null;
}
const className = await this.snapshot.nodeClassName(parseInt(id, 10));
if (!className) {
return null;
}
const parent = this.topLevelNodes().find(classNode => classNode._name === className);
if (!parent) {
return null;
}
const nodes = await parent.populateNodeBySnapshotObjectId(parseInt(id, 10));
return nodes.length ? this.revealTreeNode(nodes) : null;
}
clear() {
this._nextRequestedFilter = null;
this._lastFilter = null;
this.removeTopLevelNodes();
}
/**
* @param {!Profiler.HeapSnapshotProxy} snapshot
*/
setDataSource(snapshot) {
this.snapshot = snapshot;
if (this._profileIndex === -1) {
this._populateChildren();
}
if (this._objectIdToSelect) {
this.revealObjectByHeapSnapshotId(this._objectIdToSelect);
this._objectIdToSelect = null;
}
}
/**
* @param {number} minNodeId
* @param {number} maxNodeId
*/
setSelectionRange(minNodeId, maxNodeId) {
this._nodeFilter = new HeapSnapshotModel.NodeFilter(minNodeId, maxNodeId);
this._populateChildren(this._nodeFilter);
}
/**
* @param {number} allocationNodeId
*/
setAllocationNodeId(allocationNodeId) {
this._nodeFilter = new HeapSnapshotModel.NodeFilter();
this._nodeFilter.allocationNodeId = allocationNodeId;
this._populateChildren(this._nodeFilter);
}
/**
* @param {!HeapSnapshotModel.NodeFilter} nodeFilter
* @param {!Object<string, !HeapSnapshotModel.Aggregate>} aggregates
*/
_aggregatesReceived(nodeFilter, aggregates) {
this._filterInProgress = null;
if (this._nextRequestedFilter) {
this.snapshot.aggregatesWithFilter(this._nextRequestedFilter)
.then(this._aggregatesReceived.bind(this, this._nextRequestedFilter));
this._filterInProgress = this._nextRequestedFilter;
this._nextRequestedFilter = null;
}
this.removeTopLevelNodes();
this.resetSortingCache();
for (const constructor in aggregates) {
this.appendNode(
this.rootNode(),
new Profiler.HeapSnapshotConstructorNode(this, constructor, aggregates[constructor], nodeFilter));
}
this.sortingChanged();
this._lastFilter = nodeFilter;
}
/**
* @param {!HeapSnapshotModel.NodeFilter=} maybeNodeFilter
*/
async _populateChildren(maybeNodeFilter) {
const nodeFilter = maybeNodeFilter || new HeapSnapshotModel.NodeFilter();
if (this._filterInProgress) {
this._nextRequestedFilter = this._filterInProgress.equals(nodeFilter) ? null : nodeFilter;
return;
}
if (this._lastFilter && this._lastFilter.equals(nodeFilter)) {
return;
}
this._filterInProgress = nodeFilter;
const aggregates = await this.snapshot.aggregatesWithFilter(nodeFilter);
this._aggregatesReceived(nodeFilter, aggregates);
}
filterSelectIndexChanged(profiles, profileIndex) {
this._profileIndex = profileIndex;
this._nodeFilter = undefined;
if (profileIndex !== -1) {
const minNodeId = profileIndex > 0 ? profiles[profileIndex - 1].maxJSObjectId : 0;
const maxNodeId = profiles[profileIndex].maxJSObjectId;
this._nodeFilter = new HeapSnapshotModel.NodeFilter(minNodeId, maxNodeId);
}
this._populateChildren(this._nodeFilter);
}
};
/**
* @unrestricted
*/
Profiler.HeapSnapshotDiffDataGrid = class extends Profiler.HeapSnapshotViewportDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
*/
constructor(heapProfilerModel, dataDisplayDelegate) {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'object', title: ls`Constructor`, disclosure: true, sortable: true},
{id: 'addedCount', title: ls`# New`, width: '75px', sortable: true, fixedWidth: true},
{id: 'removedCount', title: ls`# Deleted`, width: '75px', sortable: true, fixedWidth: true},
{id: 'countDelta', title: ls`# Delta`, width: '65px', sortable: true, fixedWidth: true}, {
id: 'addedSize',
title: ls`Alloc. Size`,
width: '75px',
sortable: true,
fixedWidth: true,
sort: DataGrid.DataGrid.Order.Descending
},
{id: 'removedSize', title: ls`Freed Size`, width: '75px', sortable: true, fixedWidth: true},
{id: 'sizeDelta', title: ls`Size Delta`, width: '75px', sortable: true, fixedWidth: true}
]);
super(heapProfilerModel, dataDisplayDelegate, columns);
}
/**
* @override
* @return {number}
*/
defaultPopulateCount() {
return 50;
}
_sortFields(sortColumn, sortAscending) {
return {
object: ['_name', sortAscending, '_count', false],
addedCount: ['_addedCount', sortAscending, '_name', true],
removedCount: ['_removedCount', sortAscending, '_name', true],
countDelta: ['_countDelta', sortAscending, '_name', true],
addedSize: ['_addedSize', sortAscending, '_name', true],
removedSize: ['_removedSize', sortAscending, '_name', true],
sizeDelta: ['_sizeDelta', sortAscending, '_name', true]
}[sortColumn];
}
setDataSource(snapshot) {
this.snapshot = snapshot;
}
/**
* @param {!Profiler.HeapSnapshotProxy} baseSnapshot
*/
setBaseDataSource(baseSnapshot) {
this.baseSnapshot = baseSnapshot;
this.removeTopLevelNodes();
this.resetSortingCache();
if (this.baseSnapshot === this.snapshot) {
this.dispatchEventToListeners(Profiler.HeapSnapshotSortableDataGrid.Events.SortingComplete);
return;
}
this._populateChildren();
}
async _populateChildren() {
// Two snapshots live in different workers isolated from each other. That is why
// we first need to collect information about the nodes in the first snapshot and
// then pass it to the second snapshot to calclulate the diff.
const aggregatesForDiff = await this.baseSnapshot.aggregatesForDiff();
const diffByClassName = await this.snapshot.calculateSnapshotDiff(this.baseSnapshot.uid, aggregatesForDiff);
for (const className in diffByClassName) {
const diff = diffByClassName[className];
this.appendNode(this.rootNode(), new Profiler.HeapSnapshotDiffNode(this, className, diff));
}
this.sortingChanged();
}
};
/**
* @unrestricted
*/
Profiler.AllocationDataGrid = class extends Profiler.HeapSnapshotViewportDataGrid {
/**
* @param {?SDK.HeapProfilerModel} heapProfilerModel
* @param {!Profiler.ProfileType.DataDisplayDelegate} dataDisplayDelegate
*/
constructor(heapProfilerModel, dataDisplayDelegate) {
const columns = /** @type {!Array<!DataGrid.DataGrid.ColumnDescriptor>} */ ([
{id: 'liveCount', title: ls`Live Count`, width: '75px', sortable: true, fixedWidth: true},
{id: 'count', title: ls`Count`, width: '65px', sortable: true, fixedWidth: true},
{id: 'liveSize', title: ls`Live Size`, width: '75px', sortable: true, fixedWidth: true},
{
id: 'size',
title: ls`Size`,
width: '75px',
sortable: true,
fixedWidth: true,
sort: DataGrid.DataGrid.Order.Descending
},
{id: 'name', title: ls`Function`, disclosure: true, sortable: true},
]);
super(heapProfilerModel, dataDisplayDelegate, columns);
this._linkifier = new Components.Linkifier();
}
dispose() {
this._linkifier.reset();
}
/**
* @param {!Profiler.HeapSnapshotProxy} snapshot
*/
async setDataSource(snapshot) {
this.snapshot = snapshot;
this._topNodes = await this.snapshot.allocationTracesTops();
this._populateChildren();
}
_populateChildren() {
this.removeTopLevelNodes();
const root = this.rootNode();
const tops = this._topNodes;
for (const top of tops) {
this.appendNode(root, new Profiler.AllocationGridNode(this, top));
}
this.updateVisibleNodes(true);
}
/**
* @override
*/
sortingChanged() {
this._topNodes.sort(this._createComparator());
this.rootNode().removeChildren();
this._populateChildren();
}
/**
* @return {function(!Object, !Object):number}
*/
_createComparator() {
const fieldName = this.sortColumnId();
const compareResult = (this.sortOrder() === DataGrid.DataGrid.Order.Ascending) ? +1 : -1;
/**
* @param {!Object} a
* @param {!Object} b
* @return {number}
*/
function compare(a, b) {
if (a[fieldName] > b[fieldName]) {
return compareResult;
}
if (a[fieldName] < b[fieldName]) {
return -compareResult;
}
return 0;
}
return compare;
}
};