UNPKG

chrome-devtools-frontend

Version:
1,508 lines (1,385 loc) • 52.1 kB
/* * Copyright (C) 2011 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. */ import * as Common from '../common/common.js'; import * as DataGrid from '../data_grid/data_grid.js'; import * as HeapSnapshotModel from '../heap_snapshot_model/heap_snapshot_model.js'; import * as i18n from '../i18n/i18n.js'; import * as Platform from '../platform/platform.js'; import * as SDK from '../sdk/sdk.js'; import * as UI from '../ui/ui.js'; import {ChildrenProvider} from './ChildrenProvider.js'; // eslint-disable-line no-unused-vars import {AllocationDataGrid, HeapSnapshotConstructorsDataGrid, HeapSnapshotDiffDataGrid, HeapSnapshotRetainmentDataGrid, HeapSnapshotRetainmentDataGridEvents, HeapSnapshotSortableDataGrid,} from './HeapSnapshotDataGrids.js'; // eslint-disable-line no-unused-vars import {HeapSnapshotProviderProxy, HeapSnapshotProxy} from './HeapSnapshotProxy.js'; // eslint-disable-line no-unused-vars import {DataDisplayDelegate} from './ProfileHeader.js'; // eslint-disable-line no-unused-vars export const UIStrings = { /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool *@example {2} PH1 */ emptyPlaceholder: '{PH1}', /** *@description Generic text with two placeholders separated by a comma *@example {1 613 680} PH1 *@example {44 %} PH2 */ genericStringsTwoPlaceholders: '{PH1}, {PH2}', /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool */ internalArray: '(internal array)[]', /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool */ userObjectReachableFromWindow: 'User object reachable from window', /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool */ detachedFromDomTree: 'Detached from DOM tree', /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool */ previewIsNotAvailable: 'Preview is not available', /** *@description A context menu item in the Heap Profiler Panel of a profiler tool */ revealInSummaryView: 'Reveal in Summary view', /** *@description Text for the summary view */ summary: 'Summary', /** *@description A context menu item in the Heap Profiler Panel of a profiler tool *@example {SomeClassConstructor} PH1 *@example {12345} PH2 */ revealObjectSWithIdSInSummary: 'Reveal object \'{PH1}\' with id @{PH2} in Summary view', /** *@description Text to store an HTML element or JavaScript variable or expression result as a global variable */ storeAsGlobalVariable: 'Store as global variable', /** *@description Text in Heap Snapshot Grid Nodes of a profiler tool that indicates an element contained in another * element. */ inElement: 'in', }; const str_ = i18n.i18n.registerUIStrings('profiler/HeapSnapshotGridNodes.js', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); /** * @abstract * @extends {DataGrid.DataGrid.DataGridNode<!HeapSnapshotGridNode>} */ export class HeapSnapshotGridNode extends DataGrid.DataGrid.DataGridNode { /** * @param {!HeapSnapshotSortableDataGrid} tree * @param {boolean} hasChildren */ constructor(tree, hasChildren) { super(null, hasChildren); this._dataGrid = tree; this._instanceCount = 0; /** @type {!Map<number, !HeapSnapshotGridNode>} */ this._savedChildren = new Map(); /** * List of position ranges for all visible nodes: [startPos1, endPos1),...,[startPosN, endPosN) * Position is an item position in the provider. * @type {!Array<!{from: number, to: number}>} */ this._retrievedChildrenRanges = []; /** * @type {?ChildrenProvider} */ this._providerObject = null; this._reachableFromWindow = false; } /** * @return {string|undefined} */ get name() { return undefined; } /** * @return {!HeapSnapshotSortableDataGrid} */ heapSnapshotDataGrid() { return this._dataGrid; } /** * @return {!ChildrenProvider} */ createProvider() { throw new Error('Not implemented.'); } /** * @return {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} */ comparator() { throw new Error('Not implemented.'); } /** * @return {number} */ _getHash() { throw new Error('Not implemented.'); } /** * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotGridNode} */ _createChildNode(item) { throw new Error('Not implemented.'); } /** * @return {?{snapshot:!HeapSnapshotProxy, snapshotNodeIndex:number}} */ retainersDataSource() { return null; } /** * @return {!ChildrenProvider} */ _provider() { if (!this._providerObject) { this._providerObject = this.createProvider(); } return this._providerObject; } /** * @override * @param {string} columnId * @return {!HTMLElement} */ createCell(columnId) { return super.createCell(columnId); } /** * @override */ collapse() { super.collapse(); this._dataGrid.updateVisibleNodes(true); } /** * @override */ expand() { super.expand(); this._dataGrid.updateVisibleNodes(true); } dispose() { if (this._providerObject) { this._providerObject.dispose(); } for (let node = /** @type {?HeapSnapshotGridNode} */ (this.children[0]); node; node = /** @type {?HeapSnapshotGridNode} */ (node.traverseNextNode(true, this, true))) { node.dispose(); } } /** * @param {!SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel * @param {string} objectGroupName * @return {!Promise<!SDK.RemoteObject.RemoteObject>} */ queryObjectContent(heapProfilerModel, objectGroupName) { throw new Error('Not implemented'); } /** * @param {!SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel * @param {string} objectGroupName * @return {!Promise<?SDK.RemoteObject.RemoteObject>} */ tryQueryObjectContent(heapProfilerModel, objectGroupName) { throw new Error('Not implemented'); } /** * @param {!UI.ContextMenu.ContextMenu} contextMenu * @param {!DataDisplayDelegate} dataDisplayDelegate * @param {?SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel */ populateContextMenu(contextMenu, dataDisplayDelegate, heapProfilerModel) { } /** * @param {number} num * @return {string} */ _toPercentString(num) { return num.toFixed(0) + '\xa0%'; // \xa0 is a non-breaking space. } /** * @param {number} distance * @return {string} */ _toUIDistance(distance) { const baseSystemDistance = HeapSnapshotModel.HeapSnapshotModel.baseSystemDistance; return distance >= 0 && distance < baseSystemDistance ? i18nString(UIStrings.emptyPlaceholder, {PH1: distance}) : '\u2212'; } /** * @return {!Array.<!HeapSnapshotGridNode>} */ allChildren() { return /** @type {!Array.<!HeapSnapshotGridNode>} */ (this._dataGrid.allChildren(this)); } /** * @param {number} index */ removeChildByIndex(index) { this._dataGrid.removeChildByIndex(this, index); } /** * @param {number} nodePosition * @return {?HeapSnapshotGridNode} */ childForPosition(nodePosition) { let indexOfFirstChildInRange = 0; for (let i = 0; i < this._retrievedChildrenRanges.length; i++) { const range = this._retrievedChildrenRanges[i]; if (range.from <= nodePosition && nodePosition < range.to) { const childIndex = indexOfFirstChildInRange + nodePosition - range.from; return this.allChildren()[childIndex]; } indexOfFirstChildInRange += range.to - range.from + 1; } return null; } /** * @param {string} columnId * @return {!HTMLElement} */ _createValueCell(columnId) { const cell = /** @type {!HTMLElement} */ (UI.Fragment.html`<td class="numeric-column" />`); const dataGrid = /** @type {!HeapSnapshotSortableDataGrid} */ (this.dataGrid); if (dataGrid.snapshot && dataGrid.snapshot.totalSize !== 0) { const div = document.createElement('div'); const valueSpan = UI.Fragment.html`<span>${this.data[columnId]}</span>`; div.appendChild(valueSpan); const percentColumn = columnId + '-percent'; if (percentColumn in this.data) { const percentSpan = UI.Fragment.html`<span class="percent-column">${this.data[percentColumn]}</span>`; div.appendChild(percentSpan); div.classList.add('profile-multiple-values'); UI.ARIAUtils.markAsHidden(valueSpan); UI.ARIAUtils.markAsHidden(percentSpan); this.setCellAccessibleName( i18nString( UIStrings.genericStringsTwoPlaceholders, {PH1: this.data[columnId], PH2: this.data[percentColumn]}), cell, columnId); } cell.appendChild(div); } return cell; } /** * @override */ populate() { if (this._populated) { return; } this._populated = true; this._provider().sortAndRewind(this.comparator()).then(() => this._populateChildren()); } /** * @return {!Promise<?>} */ expandWithoutPopulate() { // Make sure default populate won't take action. this._populated = true; this.expand(); return this._provider().sortAndRewind(this.comparator()); } /** * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} entity * @return {number} */ _childHashForEntity(entity) { if ('edgeIndex' in entity) { return entity.edgeIndex; } return entity.id; } /** * @param {?number=} fromPosition * @param {?number=} toPosition * @return {!Promise<void>} */ _populateChildren(fromPosition, toPosition) { return new Promise(resolve => { fromPosition = fromPosition || 0; toPosition = toPosition || fromPosition + this._dataGrid.defaultPopulateCount(); let firstNotSerializedPosition = fromPosition; serializeNextChunk.call(this, toPosition); /** * @this {HeapSnapshotGridNode} * @param {number} toPosition */ function serializeNextChunk(toPosition) { if (firstNotSerializedPosition >= toPosition) { return; } const end = Math.min(firstNotSerializedPosition + this._dataGrid.defaultPopulateCount(), toPosition); this._provider() .serializeItemsRange(firstNotSerializedPosition, end) .then(itemsRange => childrenRetrieved.call(this, itemsRange, toPosition)); firstNotSerializedPosition = end; } /** * @this {HeapSnapshotGridNode} * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @param {number} insertionIndex */ function insertRetrievedChild(item, insertionIndex) { if (this._savedChildren) { const hash = this._childHashForEntity(item); const child = this._savedChildren.get(hash); if (child) { this._dataGrid.insertChild(this, child, insertionIndex); return; } } this._dataGrid.insertChild(this, this._createChildNode(item), insertionIndex); } /** * @this {HeapSnapshotGridNode} * @param {number} from * @param {number} to * @param {number} insertionIndex */ function insertShowMoreButton(from, to, insertionIndex) { const button = /** @type {*} */ (new DataGrid.ShowMoreDataGridNode.ShowMoreDataGridNode( this._populateChildren.bind(this), from, to, this._dataGrid.defaultPopulateCount())); this._dataGrid.insertChild(this, /** @type {!HeapSnapshotGridNode} */ (button), insertionIndex); } /** * @param {!HeapSnapshotModel.HeapSnapshotModel.ItemsRange} itemsRange * @param {number} toPosition * @this {HeapSnapshotGridNode} */ function childrenRetrieved(itemsRange, toPosition) { let itemIndex = 0; let itemPosition = itemsRange.startPosition; const items = itemsRange.items; let insertionIndex = 0; if (!this._retrievedChildrenRanges.length) { if (itemsRange.startPosition > 0) { this._retrievedChildrenRanges.push({from: 0, to: 0}); insertShowMoreButton.call(this, 0, itemsRange.startPosition, insertionIndex++); } this._retrievedChildrenRanges.push({from: itemsRange.startPosition, to: itemsRange.endPosition}); for (let i = 0, l = items.length; i < l; ++i) { insertRetrievedChild.call(this, items[i], insertionIndex++); } if (itemsRange.endPosition < itemsRange.totalLength) { insertShowMoreButton.call(this, itemsRange.endPosition, itemsRange.totalLength, insertionIndex++); } } else { let rangeIndex = 0; let found = false; let range = {from: 0, to: 0}; while (rangeIndex < this._retrievedChildrenRanges.length) { range = this._retrievedChildrenRanges[rangeIndex]; if (range.to >= itemPosition) { found = true; break; } insertionIndex += range.to - range.from; // Skip the button if there is one. if (range.to < itemsRange.totalLength) { insertionIndex += 1; } ++rangeIndex; } if (!found || itemsRange.startPosition < range.from) { // Update previous button. const button = /** @type {*} */ (this.allChildren()[insertionIndex - 1]); button.setEndPosition(itemsRange.startPosition); insertShowMoreButton.call( this, itemsRange.startPosition, found ? range.from : itemsRange.totalLength, insertionIndex); range = {from: itemsRange.startPosition, to: itemsRange.startPosition}; if (!found) { rangeIndex = this._retrievedChildrenRanges.length; } this._retrievedChildrenRanges.splice(rangeIndex, 0, range); } else { insertionIndex += itemPosition - range.from; } // At this point insertionIndex is always an index before button or between nodes. // Also it is always true here that range.from <= itemPosition <= range.to // Stretch the range right bound to include all new items. while (range.to < itemsRange.endPosition) { // Skip already added nodes. const skipCount = range.to - itemPosition; insertionIndex += skipCount; itemIndex += skipCount; itemPosition = range.to; // We're at the position before button: ...<?node>x<button> const nextRange = this._retrievedChildrenRanges[rangeIndex + 1]; let newEndOfRange = nextRange ? nextRange.from : itemsRange.totalLength; if (newEndOfRange > itemsRange.endPosition) { newEndOfRange = itemsRange.endPosition; } while (itemPosition < newEndOfRange) { insertRetrievedChild.call(this, items[itemIndex++], insertionIndex++); ++itemPosition; } // Merge with the next range. if (nextRange && newEndOfRange === nextRange.from) { range.to = nextRange.to; // Remove "show next" button if there is one. this.removeChildByIndex(insertionIndex); this._retrievedChildrenRanges.splice(rangeIndex + 1, 1); } else { range.to = newEndOfRange; // Remove or update next button. if (newEndOfRange === itemsRange.totalLength) { this.removeChildByIndex(insertionIndex); } else { /** @type {*} */ (this.allChildren()[insertionIndex]).setStartPosition(itemsRange.endPosition); } } } } // TODO: fix this. this._instanceCount += items.length; if (firstNotSerializedPosition < toPosition) { serializeNextChunk.call(this, toPosition); return; } if (this.expanded) { this._dataGrid.updateVisibleNodes(true); } resolve(); this.dispatchEventToListeners(HeapSnapshotGridNode.Events.PopulateComplete); } }); } _saveChildren() { this._savedChildren.clear(); const children = this.allChildren(); for (let i = 0, l = children.length; i < l; ++i) { const child = children[i]; if (!child.expanded) { continue; } this._savedChildren.set(child._getHash(), child); } } async sort() { this._dataGrid.recursiveSortingEnter(); await this._provider().sortAndRewind(this.comparator()); this._saveChildren(); this._dataGrid.removeAllChildren(this); this._retrievedChildrenRanges = []; const instanceCount = this._instanceCount; this._instanceCount = 0; await this._populateChildren(0, instanceCount); for (const child of this.allChildren()) { if (child.expanded) { child.sort(); } } this._dataGrid.recursiveSortingLeave(); } } /** @enum {symbol} */ HeapSnapshotGridNode.Events = { PopulateComplete: Symbol('PopulateComplete') }; export class HeapSnapshotGenericObjectNode extends HeapSnapshotGridNode { /** * @param {!HeapSnapshotSortableDataGrid} dataGrid * @param {!HeapSnapshotModel.HeapSnapshotModel.Node} node */ constructor(dataGrid, node) { super(dataGrid, false); // node is null for DataGrid root nodes. if (!node) { return; } /** @type {string|null} */ this._referenceName = null; this._name = node.name; this._type = node.type; this._distance = node.distance; this._shallowSize = node.selfSize; this._retainedSize = node.retainedSize; this.snapshotNodeId = node.id; this.snapshotNodeIndex = node.nodeIndex; if (this._type === 'string') { this._reachableFromWindow = true; } else if (this._type === 'object' && this._name.startsWith('Window')) { this._name = this.shortenWindowURL(this._name, false); this._reachableFromWindow = true; } else if (node.canBeQueried) { this._reachableFromWindow = true; } if (node.detachedDOMTreeNode) { this.detachedDOMTreeNode = true; } const snapshot = /** @type {!HeapSnapshotProxy} */ (dataGrid.snapshot); const shallowSizePercent = this._shallowSize / snapshot.totalSize * 100.0; const retainedSizePercent = this._retainedSize / snapshot.totalSize * 100.0; this.data = { 'distance': this._toUIDistance(this._distance), 'shallowSize': Number.withThousandsSeparator(this._shallowSize), 'retainedSize': Number.withThousandsSeparator(this._retainedSize), 'shallowSize-percent': this._toPercentString(shallowSizePercent), 'retainedSize-percent': this._toPercentString(retainedSizePercent) }; } /** * @override */ get name() { return this._name; } /** * @override * @return {?{snapshot:!HeapSnapshotProxy, snapshotNodeIndex:number}} */ retainersDataSource() { return this.snapshotNodeIndex === undefined ? null : { snapshot: /** @type {!HeapSnapshotProxy} */ (this._dataGrid.snapshot), snapshotNodeIndex: this.snapshotNodeIndex }; } /** * @override * @param {string} columnId * @return {!HTMLElement} */ createCell(columnId) { const cell = columnId !== 'object' ? this._createValueCell(columnId) : this._createObjectCell(); return cell; } /** * @return {!HTMLElement} */ _createObjectCell() { let value = this._name; let valueStyle = 'object'; switch (this._type) { case 'concatenated string': case 'string': value = `"${value}"`; valueStyle = 'string'; break; case 'regexp': value = `/${value}/`; valueStyle = 'string'; break; case 'closure': value = `${value}()`; valueStyle = 'function'; break; case 'bigint': valueStyle = 'bigint'; break; case 'number': valueStyle = 'number'; break; case 'hidden': valueStyle = 'null'; break; case 'array': value = value ? `${value}[]` : i18nString(UIStrings.internalArray); break; } return this._createObjectCellWithValue(valueStyle, value || ''); } /** * @param {string} valueStyle * @param {string} value * @return {!HTMLElement} */ _createObjectCellWithValue(valueStyle, value) { const fragment = UI.Fragment.Fragment.build` <td class="object-column disclosure"> <div class="source-code event-properties" style="overflow: visible" $="container"> <span class="value object-value-${valueStyle}">${value}</span> <span class="object-value-id">@${this.snapshotNodeId}</span> </div> </td>`; const div = fragment.$('container'); this._prefixObjectCell(div); if (this._reachableFromWindow) { div.appendChild(UI.Fragment.html `<span class="heap-object-tag" title="${i18nString(UIStrings.userObjectReachableFromWindow)}">🗖</span>`); } if (this.detachedDOMTreeNode) { div.appendChild(UI.Fragment.html`<span class="heap-object-tag" title="${ i18nString(UIStrings.detachedFromDomTree)}">✀</span>`); } this._appendSourceLocation(div); const cell = /** @type {!HTMLElement} */ (fragment.element()); if (this.depth) { cell.style.setProperty( 'padding-left', (this.depth * /** @type {!HeapSnapshotSortableDataGrid} */ (this.dataGrid).indentWidth) + 'px'); } return cell; } /** * @param {!Element} div */ _prefixObjectCell(div) { } /** * @param {!Element} div */ async _appendSourceLocation(div) { const linkContainer = UI.Fragment.html`<span class="heap-object-source-link" />`; div.appendChild(linkContainer); const link = await this._dataGrid.dataDisplayDelegate().linkifyObject(/** @type {number} */ (this.snapshotNodeIndex)); if (link) { linkContainer.appendChild(link); this.linkElement = link; } else { linkContainer.remove(); } } /** * @override * @param {!SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel * @param {string} objectGroupName * @return {!Promise<!SDK.RemoteObject.RemoteObject>} */ async queryObjectContent(heapProfilerModel, objectGroupName) { const remoteObject = await this.tryQueryObjectContent(heapProfilerModel, objectGroupName); return remoteObject || heapProfilerModel.runtimeModel().createRemoteObjectFromPrimitiveValue( i18nString(UIStrings.previewIsNotAvailable)); } /** * @override * @param {!SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel * @param {string} objectGroupName * @return {!Promise<?SDK.RemoteObject.RemoteObject>} */ async tryQueryObjectContent(heapProfilerModel, objectGroupName) { if (this._type === 'string') { return heapProfilerModel.runtimeModel().createRemoteObjectFromPrimitiveValue(this._name); } return await heapProfilerModel.objectForSnapshotObjectId(String(this.snapshotNodeId), objectGroupName); } async updateHasChildren() { const isEmpty = await this._provider().isEmpty(); this.setHasChildren(!isEmpty); } /** * @param {string} fullName * @param {boolean} hasObjectId * @return {string} */ shortenWindowURL(fullName, hasObjectId) { const startPos = fullName.indexOf('/'); const endPos = hasObjectId ? fullName.indexOf('@') : fullName.length; if (startPos === -1 || endPos === -1) { return fullName; } const fullURL = fullName.substring(startPos + 1, endPos).trimLeft(); let url = Platform.StringUtilities.trimURL(fullURL); if (url.length > 40) { url = Platform.StringUtilities.trimMiddle(url, 40); } return fullName.substr(0, startPos + 2) + url + fullName.substr(endPos); } /** * @override * @param {!UI.ContextMenu.ContextMenu} contextMenu * @param {!DataDisplayDelegate} dataDisplayDelegate * @param {?SDK.HeapProfilerModel.HeapProfilerModel} heapProfilerModel */ populateContextMenu(contextMenu, dataDisplayDelegate, heapProfilerModel) { contextMenu.revealSection().appendItem(i18nString(UIStrings.revealInSummaryView), () => { dataDisplayDelegate.showObject(String(this.snapshotNodeId), i18nString(UIStrings.summary)); }); if (this._referenceName) { for (const match of this._referenceName.matchAll(/\((?<objectName>[^@)]*) @(?<snapshotNodeId>\d+)\)/g)) { const {objectName, snapshotNodeId} = /** @type {!{objectName:string, snapshotNodeId:string}} */ (match.groups); contextMenu.revealSection().appendItem( i18nString(UIStrings.revealObjectSWithIdSInSummary, {PH1: objectName, PH2: snapshotNodeId}), () => { dataDisplayDelegate.showObject(snapshotNodeId, i18nString(UIStrings.summary)); }); } } if (heapProfilerModel) { contextMenu.revealSection().appendItem(i18nString(UIStrings.storeAsGlobalVariable), async () => { const remoteObject = await this.tryQueryObjectContent( /** @type {!SDK.HeapProfilerModel.HeapProfilerModel} */ (heapProfilerModel), ''); if (!remoteObject) { Common.Console.Console.instance().error(i18nString(UIStrings.previewIsNotAvailable)); } else { await SDK.ConsoleModel.ConsoleModel.instance().saveToTempVariable( UI.Context.Context.instance().flavor(SDK.RuntimeModel.ExecutionContext), remoteObject); } }); } } } export class HeapSnapshotObjectNode extends HeapSnapshotGenericObjectNode { /** * @param {!HeapSnapshotSortableDataGrid} dataGrid * @param {!HeapSnapshotProxy} snapshot * @param {!HeapSnapshotModel.HeapSnapshotModel.Edge} edge * @param {?HeapSnapshotObjectNode} parentObjectNode */ constructor(dataGrid, snapshot, edge, parentObjectNode) { super(dataGrid, edge.node); this._referenceName = edge.name; this._referenceType = edge.type; this._edgeIndex = edge.edgeIndex; this._snapshot = snapshot; this._parentObjectNode = parentObjectNode; this._cycledWithAncestorGridNode = this._findAncestorWithSameSnapshotNodeId(); if (!this._cycledWithAncestorGridNode) { this.updateHasChildren(); } const data = this.data; data['count'] = ''; data['addedCount'] = ''; data['removedCount'] = ''; data['countDelta'] = ''; data['addedSize'] = ''; data['removedSize'] = ''; data['sizeDelta'] = ''; } /** * @override * @return {?{snapshot:!HeapSnapshotProxy, snapshotNodeIndex:number}} */ retainersDataSource() { return this.snapshotNodeIndex === undefined ? null : {snapshot: this._snapshot, snapshotNodeIndex: this.snapshotNodeIndex}; } /** * @override * @return {!HeapSnapshotProviderProxy} */ createProvider() { if (this.snapshotNodeIndex === undefined) { throw new Error('Cannot create a provider on a root node'); } return this._snapshot.createEdgesProvider(this.snapshotNodeIndex); } _findAncestorWithSameSnapshotNodeId() { let ancestor = this._parentObjectNode; while (ancestor) { if (ancestor.snapshotNodeId === this.snapshotNodeId) { return ancestor; } ancestor = ancestor._parentObjectNode; } return null; } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotObjectNode} */ _createChildNode(item) { return new HeapSnapshotObjectNode( this._dataGrid, this._snapshot, /** @type {!HeapSnapshotModel.HeapSnapshotModel.Edge} */ (item), this); } /** * @override * @return {number} */ _getHash() { return this._edgeIndex; } /** * @override * @return {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} */ comparator() { const sortAscending = this._dataGrid.isSortOrderAscending(); const sortColumnId = this._dataGrid.sortColumnId(); switch (sortColumnId) { case 'object': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( '!edgeName', sortAscending, 'retainedSize', false); case 'count': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('!edgeName', true, 'retainedSize', false); case 'shallowSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, '!edgeName', true); case 'retainedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( 'retainedSize', sortAscending, '!edgeName', true); case 'distance': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('distance', sortAscending, '_name', true); default: return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('!edgeName', true, 'retainedSize', false); } } /** * @override * @param {!Element} div */ _prefixObjectCell(div) { let name = this._referenceName || '(empty)'; let nameClass = 'name'; switch (this._referenceType) { case 'context': nameClass = 'object-value-number'; break; case 'internal': case 'hidden': case 'weak': nameClass = 'object-value-null'; break; case 'element': name = `[${name}]`; break; } if (this._cycledWithAncestorGridNode) { div.classList.add('cycled-ancessor-node'); } div.prepend(UI.Fragment.html`<span class="property-name ${nameClass}">${name}</span> <span class="grayed">${this._edgeNodeSeparator()}</span>`); } /** * @return {string} */ _edgeNodeSeparator() { return '::'; } } export class HeapSnapshotRetainingObjectNode extends HeapSnapshotObjectNode { /** * @param {!HeapSnapshotSortableDataGrid} dataGrid * @param {!HeapSnapshotProxy} snapshot * @param {!HeapSnapshotModel.HeapSnapshotModel.Edge} edge * @param {?HeapSnapshotRetainingObjectNode} parentRetainingObjectNode */ constructor(dataGrid, snapshot, edge, parentRetainingObjectNode) { super(dataGrid, snapshot, edge, parentRetainingObjectNode); } /** * @override * @return {!HeapSnapshotProviderProxy} */ createProvider() { if (this.snapshotNodeIndex === undefined) { throw new Error('Cannot create providers on root nodes'); } return this._snapshot.createRetainingEdgesProvider(this.snapshotNodeIndex); } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotRetainingObjectNode} */ _createChildNode(item) { return new HeapSnapshotRetainingObjectNode( this._dataGrid, this._snapshot, /** @type {!HeapSnapshotModel.HeapSnapshotModel.Edge}*/ (item), this); } /** * @override * @return {string} */ _edgeNodeSeparator() { // TODO(l10n): improve description or clarify intention. return i18nString(UIStrings.inElement); } /** * @override */ expand() { this._expandRetainersChain(20); } /** * @param {number} maxExpandLevels */ _expandRetainersChain(maxExpandLevels) { if (!this._populated) { this.once(HeapSnapshotGridNode.Events.PopulateComplete).then(() => this._expandRetainersChain(maxExpandLevels)); this.populate(); return; } super.expand(); if (--maxExpandLevels > 0 && this.children.length > 0) { const retainer = /** @type {!HeapSnapshotRetainingObjectNode} */ (this.children[0]); if ((retainer._distance || 0) > 1) { retainer._expandRetainersChain(maxExpandLevels); return; } } this._dataGrid.dispatchEventToListeners(HeapSnapshotRetainmentDataGridEvents.ExpandRetainersComplete); } } export class HeapSnapshotInstanceNode extends HeapSnapshotGenericObjectNode { /** * @param {!HeapSnapshotSortableDataGrid} dataGrid * @param {!HeapSnapshotProxy} snapshot * @param {!HeapSnapshotModel.HeapSnapshotModel.Node} node * @param {boolean} isDeletedNode */ constructor(dataGrid, snapshot, node, isDeletedNode) { super(dataGrid, node); this._baseSnapshotOrSnapshot = snapshot; this._isDeletedNode = isDeletedNode; this.updateHasChildren(); const data = this.data; data['count'] = ''; data['countDelta'] = ''; data['sizeDelta'] = ''; if (this._isDeletedNode) { data['addedCount'] = ''; data['addedSize'] = ''; data['removedCount'] = '\u2022'; data['removedSize'] = Number.withThousandsSeparator(this._shallowSize || 0); } else { data['addedCount'] = '\u2022'; data['addedSize'] = Number.withThousandsSeparator(this._shallowSize || 0); data['removedCount'] = ''; data['removedSize'] = ''; } } /** * @override * @return {?{snapshot:!HeapSnapshotProxy, snapshotNodeIndex:number}} */ retainersDataSource() { return this.snapshotNodeIndex === undefined ? null : {snapshot: this._baseSnapshotOrSnapshot, snapshotNodeIndex: this.snapshotNodeIndex}; } /** * @override * @return {!HeapSnapshotProviderProxy} */ createProvider() { if (this.snapshotNodeIndex === undefined) { throw new Error('Cannot create providers on root nodes'); } return this._baseSnapshotOrSnapshot.createEdgesProvider(this.snapshotNodeIndex); } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotObjectNode} */ _createChildNode(item) { return new HeapSnapshotObjectNode( this._dataGrid, this._baseSnapshotOrSnapshot, /** @type {!HeapSnapshotModel.HeapSnapshotModel.Edge} */ (item), null); } /** * @override * @return {number} */ _getHash() { if (this.snapshotNodeId === undefined) { throw new Error('Cannot hash root nodes'); } return this.snapshotNodeId; } /** * @override * @return {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} */ comparator() { const sortAscending = this._dataGrid.isSortOrderAscending(); const sortColumnId = this._dataGrid.sortColumnId(); switch (sortColumnId) { case 'object': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( '!edgeName', sortAscending, 'retainedSize', false); case 'distance': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( 'distance', sortAscending, 'retainedSize', false); case 'count': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('!edgeName', true, 'retainedSize', false); case 'addedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, '!edgeName', true); case 'removedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, '!edgeName', true); case 'shallowSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, '!edgeName', true); case 'retainedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( 'retainedSize', sortAscending, '!edgeName', true); default: return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('!edgeName', true, 'retainedSize', false); } } } export class HeapSnapshotConstructorNode extends HeapSnapshotGridNode { /** * @param {!HeapSnapshotConstructorsDataGrid} dataGrid * @param {string} className * @param {!HeapSnapshotModel.HeapSnapshotModel.Aggregate} aggregate * @param {!HeapSnapshotModel.HeapSnapshotModel.NodeFilter} nodeFilter */ constructor(dataGrid, className, aggregate, nodeFilter) { super(dataGrid, aggregate.count > 0); this._name = className; this._nodeFilter = nodeFilter; this._distance = aggregate.distance; this._count = aggregate.count; this._shallowSize = aggregate.self; this._retainedSize = aggregate.maxRet; const snapshot = /** @type {!HeapSnapshotProxy} */ (dataGrid.snapshot); const retainedSizePercent = this._retainedSize / snapshot.totalSize * 100.0; const shallowSizePercent = this._shallowSize / snapshot.totalSize * 100.0; this.data = { 'object': className, 'count': Number.withThousandsSeparator(this._count), 'distance': this._toUIDistance(this._distance), 'shallowSize': Number.withThousandsSeparator(this._shallowSize), 'retainedSize': Number.withThousandsSeparator(this._retainedSize), 'shallowSize-percent': this._toPercentString(shallowSizePercent), 'retainedSize-percent': this._toPercentString(retainedSizePercent) }; } /** * @override */ get name() { return this._name; } /** * @override * @return {!HeapSnapshotProviderProxy} */ createProvider() { return /** @type {!HeapSnapshotProviderProxy} */ ( /** @type {!HeapSnapshotProxy} */ (this._dataGrid.snapshot).createNodesProviderForClass(this._name, this._nodeFilter)); } /** * @param {number} snapshotObjectId * @return {!Promise<!Array<!HeapSnapshotGridNode>>} */ async populateNodeBySnapshotObjectId(snapshotObjectId) { this._dataGrid.resetNameFilter(); await this.expandWithoutPopulate(); const nodePosition = await this._provider().nodePosition(snapshotObjectId); if (nodePosition === -1) { this.collapse(); return []; } await this._populateChildren(nodePosition, null); const node = /** @type {?HeapSnapshotGridNode} */ (this.childForPosition(nodePosition)); return node ? [this, node] : []; } /** * @param {string} filterValue * @return {boolean} */ filteredOut(filterValue) { return this._name.toLowerCase().indexOf(filterValue) === -1; } /** * @override * @param {string} columnId * @return {!HTMLElement} */ createCell(columnId) { const cell = columnId === 'object' ? super.createCell(columnId) : this._createValueCell(columnId); if (columnId === 'object' && this._count > 1) { cell.appendChild(UI.Fragment.html`<span class="objects-count">×${this._count}</span>`); } return cell; } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotInstanceNode} */ _createChildNode(item) { return new HeapSnapshotInstanceNode( this._dataGrid, /** @type {!HeapSnapshotProxy} */ (this._dataGrid.snapshot), /** @type {!HeapSnapshotModel.HeapSnapshotModel.Node} */ (item), false); } /** * @override * @return {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} */ comparator() { const sortAscending = this._dataGrid.isSortOrderAscending(); const sortColumnId = this._dataGrid.sortColumnId(); switch (sortColumnId) { case 'object': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('name', sortAscending, 'id', true); case 'distance': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig( 'distance', sortAscending, 'retainedSize', false); case 'shallowSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, 'id', true); case 'retainedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('retainedSize', sortAscending, 'id', true); default: throw new Error(`Invalid sort column id ${sortColumnId}`); } } } /** * @implements {ChildrenProvider} */ export class HeapSnapshotDiffNodesProvider { /** * @param {!HeapSnapshotProviderProxy} addedNodesProvider * @param {!HeapSnapshotProviderProxy} deletedNodesProvider * @param {number} addedCount * @param {number} removedCount */ constructor(addedNodesProvider, deletedNodesProvider, addedCount, removedCount) { this._addedNodesProvider = addedNodesProvider; this._deletedNodesProvider = deletedNodesProvider; this._addedCount = addedCount; this._removedCount = removedCount; } /** * @override */ dispose() { this._addedNodesProvider.dispose(); this._deletedNodesProvider.dispose(); } /** * @override * @param {number} snapshotObjectId * @return {!Promise<number>} */ nodePosition(snapshotObjectId) { throw new Error('Unreachable'); } /** * @override * @return {!Promise<boolean>} */ isEmpty() { return Promise.resolve(false); } /** * @override * @param {number} beginPosition * @param {number} endPosition * @return {!Promise<!HeapSnapshotModel.HeapSnapshotModel.ItemsRange>} */ async serializeItemsRange(beginPosition, endPosition) { let itemsRange; let addedItems; if (beginPosition < this._addedCount) { itemsRange = await this._addedNodesProvider.serializeItemsRange(beginPosition, endPosition); for (const item of itemsRange.items) { item.isAddedNotRemoved = true; } if (itemsRange.endPosition >= endPosition) { itemsRange.totalLength = this._addedCount + this._removedCount; return itemsRange; } addedItems = itemsRange; itemsRange = await this._deletedNodesProvider.serializeItemsRange(0, endPosition - itemsRange.endPosition); } else { addedItems = new HeapSnapshotModel.HeapSnapshotModel.ItemsRange(0, 0, 0, []); itemsRange = await this._deletedNodesProvider.serializeItemsRange( beginPosition - this._addedCount, endPosition - this._addedCount); } if (!addedItems.items.length) { addedItems.startPosition = this._addedCount + itemsRange.startPosition; } for (const item of itemsRange.items) { item.isAddedNotRemoved = false; } addedItems.items.push(...itemsRange.items); addedItems.endPosition = this._addedCount + itemsRange.endPosition; addedItems.totalLength = this._addedCount + this._removedCount; return addedItems; } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} comparator * @return {!Promise<void>} */ async sortAndRewind(comparator) { await this._addedNodesProvider.sortAndRewind(comparator); await this._deletedNodesProvider.sortAndRewind(comparator); } } export class HeapSnapshotDiffNode extends HeapSnapshotGridNode { /** * @param {!HeapSnapshotDiffDataGrid} dataGrid * @param {string} className * @param {!HeapSnapshotModel.HeapSnapshotModel.DiffForClass} diffForClass */ constructor(dataGrid, className, diffForClass) { super(dataGrid, true); this._name = className; this._addedCount = diffForClass.addedCount; this._removedCount = diffForClass.removedCount; this._countDelta = diffForClass.countDelta; this._addedSize = diffForClass.addedSize; this._removedSize = diffForClass.removedSize; this._sizeDelta = diffForClass.sizeDelta; this._deletedIndexes = diffForClass.deletedIndexes; this.data = { 'object': className, 'addedCount': Number.withThousandsSeparator(this._addedCount), 'removedCount': Number.withThousandsSeparator(this._removedCount), 'countDelta': this._signForDelta(this._countDelta) + Number.withThousandsSeparator(Math.abs(this._countDelta)), 'addedSize': Number.withThousandsSeparator(this._addedSize), 'removedSize': Number.withThousandsSeparator(this._removedSize), 'sizeDelta': this._signForDelta(this._sizeDelta) + Number.withThousandsSeparator(Math.abs(this._sizeDelta)) }; } /** * @override */ get name() { return this._name; } /** * @override * @return {!HeapSnapshotDiffNodesProvider} */ createProvider() { const tree = /** @type {!HeapSnapshotDiffDataGrid} */ (this._dataGrid); if (tree.snapshot === null || tree.baseSnapshot === undefined || tree.baseSnapshot.uid === undefined) { throw new Error('Data sources have not been set correctly'); } const addedNodesProvider = tree.snapshot.createAddedNodesProvider(tree.baseSnapshot.uid, this._name); const deletedNodesProvider = tree.baseSnapshot.createDeletedNodesProvider(this._deletedIndexes); if (!addedNodesProvider || !deletedNodesProvider) { throw new Error('Failed to create node providers'); } return new HeapSnapshotDiffNodesProvider( addedNodesProvider, deletedNodesProvider, this._addedCount, this._removedCount); } /** * @override * @param {string} columnId * @return {!HTMLElement} */ createCell(columnId) { const cell = super.createCell(columnId); if (columnId !== 'object') { cell.classList.add('numeric-column'); } return cell; } /** * @override * @param {!HeapSnapshotModel.HeapSnapshotModel.Node|!HeapSnapshotModel.HeapSnapshotModel.Edge} item * @return {!HeapSnapshotInstanceNode} */ _createChildNode(item) { const dataGrid = /** @type {!HeapSnapshotDiffDataGrid} */ (this._dataGrid); if (item.isAddedNotRemoved) { if (dataGrid.snapshot === null) { throw new Error('Data sources have not been set correctly'); } return new HeapSnapshotInstanceNode( this._dataGrid, dataGrid.snapshot, /** @type {!HeapSnapshotModel.HeapSnapshotModel.Node} */ (item), false); } if (dataGrid.baseSnapshot === undefined) { throw new Error('Data sources have not been set correctly'); } return new HeapSnapshotInstanceNode( this._dataGrid, dataGrid.baseSnapshot, /** @type {!HeapSnapshotModel.HeapSnapshotModel.Node} */ (item), true); } /** * @override * @return {!HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig} */ comparator() { const sortAscending = this._dataGrid.isSortOrderAscending(); const sortColumnId = this._dataGrid.sortColumnId(); switch (sortColumnId) { case 'object': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('name', sortAscending, 'id', true); case 'addedCount': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('name', true, 'id', true); case 'removedCount': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('name', true, 'id', true); case 'countDelta': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('name', true, 'id', true); case 'addedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, 'id', true); case 'removedSize': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, 'id', true); case 'sizeDelta': return new HeapSnapshotModel.HeapSnapshotModel.ComparatorConfig('selfSize', sortAscending, 'id', true); default: throw new Error(`Invalid sort column ${sortColumnId}`); } } /** * @param {string} filterValue * @return {boolean} */ filteredOut(filterValue) { return this._name.toLowerCase().indexOf(filterValue) === -1; } /** * @param {number} delta */ _signForDelta(delta) { if (delta === 0) { return ''; } if (delta > 0) { return '+'; } return '\u2212'; // Math minus sign, same width as plus. } } export class AllocationGridNode extends HeapSnapshotGridNode { /** * @param {!AllocationDataGrid} dataGrid * @param {!HeapSnapshotModel.HeapSnapshotModel.SerializedAllocationNode} data */ constructor(dataGrid, data) { super(dataGrid, data.hasChildren); this._populated = false; this._allocationNode = data; this.data = { 'liveCount': Number.withThousandsSeparator(data.liveCount), 'count': Number.withThousandsSeparator(data.count), 'liveSize': Number.withThousandsSeparator(data.liveSize), 'size': Number.withThousandsSeparator(data.size), 'name': data.name }; } /** * @override */ populate() { if (this._populated) { return; } this._doPopulate(); } async _doPopulate() { this._populated = true; const callers = awa