| /* |
| * 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; |
| } |
| }; |