UNPKG

chrome-devtools-frontend

Version:
148 lines (129 loc) 4.52 kB
// Copyright 2024 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import '../../ui/legacy/components/data_grid/data_grid.js'; 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 UI from '../../ui/legacy/legacy.js'; import * as Lit from '../../ui/lit/lit.js'; import * as Elements from '../elements/elements.js'; import heapDetachedElementsDataGridStyles from './heapDetachedElementsDataGrid.css.js'; const {html, render} = Lit; const {widget} = UI.Widget; const UIStrings = { /** * @description Column header in detached elements table displaying detached DOM nodes. */ detachedNodes: 'Detached nodes', /** * @description Column header in detached elements table displaying the number of detached DOM nodes. */ 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 interface HeapDetachedElements { detachedElements: Protocol.DOM.DetachedElementInfo[]; domModel: SDK.DOMModel.DOMModel; } export interface ParsedElement { elementInfo: Protocol.DOM.DetachedElementInfo; node: SDK.DOMModel.DOMNode; nodeCount: number; } export interface ViewInput { parsedElements: ParsedElement[]; } export type ViewOutput = undefined; const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLElement|ShadowRoot): void => { // clang-format off render(html` <devtools-data-grid striped name=${i18nString(UIStrings.detachedElementsList)}> <table> <tr> <th id="detached-node">${i18nString(UIStrings.detachedNodes)}</th> <th id="detached-node-count">${i18nString(UIStrings.nodeSize)}</th> </tr> ${input.parsedElements.map(parsed => html` <tr> <td> <devtools-widget ${widget(Elements.ElementsTreeOutline.DOMTreeWidget, { omitRootDOMNode: false, selectEnabled: true, hideGutter: true, rootDOMNode: parsed.node, showSelectionOnKeyboardFocus: true, preventTabOrder: true, deindentSingleNode: true, })} ></devtools-widget> </td> <td>${parsed.nodeCount}</td> </tr> `)} </table> </devtools-data-grid> `, target); // clang-format on }; function calculateDetachedNodeCount(treeNode?: Protocol.DOM.Node): number { if (!treeNode) { return 0; } let count = 1; const queue: Protocol.DOM.Node[] = []; let node: Protocol.DOM.Node|undefined; queue.push(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; } type View = typeof DEFAULT_VIEW; export class HeapDetachedElementsDataGrid extends UI.Widget.Widget { #view: View; #parsedElements: ParsedElement[] = []; constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) { super(element, {useShadowDom: true}); this.#view = view; this.registerRequiredCSS(heapDetachedElementsDataGridStyles); } override wasShown(): void { super.wasShown(); this.requestUpdate(); } override performUpdate(): void { this.#view({parsedElements: this.#parsedElements}, undefined, this.contentElement); } set data(data: HeapDetachedElements) { this.#parsedElements = []; if (!data || !data.detachedElements || !data.domModel) { this.requestUpdate(); return; } for (const elementInfo of data.detachedElements) { const retainedNodeIds = new Set((elementInfo.retainedNodeIds ?? []) as unknown as Protocol.DOM.BackendNodeId[]); const node = SDK.DOMModel.DOMNode.create(data.domModel, null, false, elementInfo.treeNode, retainedNodeIds); node.detached = true; this.#parsedElements.push({elementInfo, node, nodeCount: calculateDetachedNodeCount(elementInfo.treeNode)}); } this.requestUpdate(); } }