chrome-devtools-frontend
Version:
Chrome DevTools UI
160 lines (142 loc) • 5.74 kB
text/typescript
// Copyright 2024 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no-imperative-dom-api */
import * as i18n from '../../core/i18n/i18n.js';
import * as SDK from '../../core/sdk/sdk.js';
import type * as Protocol from '../../generated/protocol.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
import * as UI from '../../ui/legacy/legacy.js';
import * as Elements from '../elements/elements.js';
const UIStrings = {
/**
*@description Text in Heap Snapshot View of a profiler tool
*/
detachedNodes: 'Detached nodes',
/**
*@description Text in Heap Snapshot View of a profiler tool
*/
nodeSize: 'Node count',
/**
*@description Label for the detached elements table
*/
detachedElementsList: 'Detached elements list',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/profiler/HeapDetachedElementsDataGrid.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export class HeapDetachedElementsDataGrid extends DataGrid.DataGrid.DataGridImpl<unknown> {
constructor() {
const columns: DataGrid.DataGrid.ColumnDescriptor[] = [];
columns.push({
id: 'detached-node',
title: i18nString(UIStrings.detachedNodes),
sortable: false,
});
columns.push({
id: 'detached-node-count',
title: i18nString(UIStrings.nodeSize),
sortable: false,
disclosure: true,
});
super({
displayName: i18nString(UIStrings.detachedElementsList),
columns,
deleteCallback: undefined,
refreshCallback: undefined,
});
this.setStriped(true);
}
}
export class HeapDetachedElementsDataGridNode extends DataGrid.DataGrid.DataGridNode<unknown> {
private detachedElementInfo: Protocol.DOM.DetachedElementInfo;
domModel: SDK.DOMModel.DOMModel;
retainedNodeIds: Set<number> = new Set<number>();
constructor(detachedElementInfo: Protocol.DOM.DetachedElementInfo, domModel: SDK.DOMModel.DOMModel) {
super(null);
this.detachedElementInfo = detachedElementInfo;
this.domModel = domModel;
for (const retainedNodeId of detachedElementInfo.retainedNodeIds) {
this.retainedNodeIds.add(retainedNodeId as number);
}
}
override createCell(columnId: string): HTMLElement {
const cell = this.createTD(columnId);
switch (columnId) {
case 'detached-node': {
const DOMNode = SDK.DOMModel.DOMNode.create(this.domModel, null, false, this.detachedElementInfo.treeNode);
cell.appendChild(this.#nodeRenderer(DOMNode));
return cell;
}
case 'detached-node-count': {
const size = this.#getNodeSize(this.detachedElementInfo);
UI.UIUtils.createTextChild(cell, size.toString());
return cell;
}
}
return cell;
}
#getNodeSize(detachedElementInfo: Protocol.DOM.DetachedElementInfo): number {
let count = 1;
const queue: Protocol.DOM.Node[] = [];
let node: Protocol.DOM.Node|undefined;
queue.push(detachedElementInfo.treeNode);
while (queue.length > 0) {
node = queue.shift();
if (!node) {
break;
}
if (node.childNodeCount) {
count += node.childNodeCount;
}
if (node.children) {
for (const child of node.children) {
queue.push(child);
}
}
}
return count;
}
#nodeRenderer(node: SDK.DOMModel.DOMNode): HTMLElement {
const treeOutline = new Elements.ElementsTreeOutline.ElementsTreeOutline(
/* omitRootDOMNode: */ false, /* selectEnabled: */ false, /* hideGutter: */ true);
treeOutline.rootDOMNode = node;
const firstChild = treeOutline.firstChild();
if (!firstChild || (firstChild && !firstChild.isExpandable())) {
treeOutline.element.classList.add('single-node');
}
treeOutline.setVisible(true);
// @ts-expect-error used in console_test_runner
treeOutline.element.treeElementForTest = firstChild;
treeOutline.setShowSelectionOnKeyboardFocus(/* show: */ true, /* preventTabOrder: */ true);
const nodes: SDK.DOMModel.DOMNode[] = [node];
// Iterate through descendants to mark the nodes that were specifically retained in JS references.
while (nodes.length > 0) {
const descendantNode = nodes.shift() as SDK.DOMModel.DOMNode;
const descendantsChildren = descendantNode.children();
if (descendantsChildren) {
for (const child of descendantsChildren) {
nodes.push(child);
}
}
const treeElement = treeOutline.findTreeElement(descendantNode);
// If true, this node is retained in JS, and should be marked.
if (treeElement) {
if (this.retainedNodeIds.has(descendantNode.backendNodeId() as number)) {
const icon = new IconButton.Icon.Icon();
// this needs to be updated, data field is deprecated
icon.data = {iconName: 'small-status-dot', color: 'var(--icon-error)', width: '12px', height: '12px'};
icon.style.setProperty('vertical-align', 'middle');
treeElement.setLeadingIcons([icon]);
treeElement.listItemNode.classList.add('detached-elements-detached-node');
treeElement.listItemNode.style.setProperty('display', '-webkit-box');
treeElement.listItemNode.setAttribute('title', 'Retained Node');
} else {
treeElement.listItemNode.setAttribute('title', 'Node');
}
}
}
treeOutline.findTreeElement(node)?.listItemNode.setAttribute('title', 'Detached Tree Node');
return treeOutline.element;
}
}