tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6f8a6db621dd9496ef726b207141018c2e1f2520
parent 0b8b68e33fd82133ccc84174181a59330a6e2135
Author: Lorenz A <me@lorenzackermann.xyz>
Date:   Wed, 17 Dec 2025 14:43:23 +0000

Bug 2004236 - [devtools] Turn devtools/shared/heapsnapshot/census-tree-node.js into an ES class. r=devtools-reviewers,nchevobbe

Differential Revision: https://phabricator.services.mozilla.com/D276727

Diffstat:
Mdevtools/shared/heapsnapshot/census-tree-node.js | 535++++++++++++++++++++++++++++++++++++++++---------------------------------------
1 file changed, 268 insertions(+), 267 deletions(-)

diff --git a/devtools/shared/heapsnapshot/census-tree-node.js b/devtools/shared/heapsnapshot/census-tree-node.js @@ -38,113 +38,109 @@ function isSavedFrame(obj) { * this sharing when converting to CensusTreeNode, so before creating a new * CensusTreeNode, we look for an existing one in one of our CensusTreeNodeCaches. */ -function CensusTreeNodeCache() {} -CensusTreeNodeCache.prototype = null; - -/** - * The value of a single entry stored in a CensusTreeNodeCache. It is a pair of - * the CensusTreeNode for this cache value, and the subsequent - * CensusTreeNodeCache for this node's children. - * - * @param {SavedFrame} frame - * The frame being cached. - */ -function CensusTreeNodeCacheValue() { - // The CensusTreeNode for this cache value. - this.node = undefined; - // The CensusTreeNodeCache for this frame's children. - this.children = undefined; -} - -CensusTreeNodeCacheValue.prototype = null; +class CensusTreeNodeCache { + /** + * Create a unique string for the given SavedFrame (ignoring the frame's parent + * chain) that can be used as a hash to key this frame within a CensusTreeNodeCache. + * + * NB: We manually hash rather than using an ES6 Map because we are purposely + * ignoring the parent chain and wish to consider frames with everything the + * same except their parents as the same. + * + * @param {SavedFrame} frame + * The SavedFrame object we would like to lookup in or insert into a + * CensusTreeNodeCache. + * + * @returns {string} + * The unique string that can be used as a key in a CensusTreeNodeCache. + */ + static hashFrame(frame) { + // eslint-disable-next-line max-len + return `FRAME,${frame.functionDisplayName},${frame.source},${frame.line},${frame.column},${frame.asyncCause}`; + } -/** - * Create a unique string for the given SavedFrame (ignoring the frame's parent - * chain) that can be used as a hash to key this frame within a CensusTreeNodeCache. - * - * NB: We manually hash rather than using an ES6 Map because we are purposely - * ignoring the parent chain and wish to consider frames with everything the - * same except their parents as the same. - * - * @param {SavedFrame} frame - * The SavedFrame object we would like to lookup in or insert into a - * CensusTreeNodeCache. - * - * @returns {string} - * The unique string that can be used as a key in a CensusTreeNodeCache. - */ -CensusTreeNodeCache.hashFrame = function (frame) { - // eslint-disable-next-line max-len - return `FRAME,${frame.functionDisplayName},${frame.source},${frame.line},${frame.column},${frame.asyncCause}`; -}; + /** + * Create a unique string for the given CensusTreeNode **with regards to + * siblings at the current depth of the tree, not within the whole tree.** It + * can be used as a hash to key this node within a CensusTreeNodeCache. + * + * @param {CensusTreeNode} node + * The node we would like to lookup in or insert into a cache. + * + * @returns {string} + * The unique string that can be used as a key in a CensusTreeNodeCache. + */ + static hashNode(node) { + return isSavedFrame(node.name) + ? this.hashFrame(node.name) + : `NODE,${node.name}`; + } -/** - * Create a unique string for the given CensusTreeNode **with regards to - * siblings at the current depth of the tree, not within the whole tree.** It - * can be used as a hash to key this node within a CensusTreeNodeCache. - * - * @param {CensusTreeNode} node - * The node we would like to lookup in or insert into a cache. - * - * @returns {string} - * The unique string that can be used as a key in a CensusTreeNodeCache. - */ -CensusTreeNodeCache.hashNode = function (node) { - return isSavedFrame(node.name) - ? CensusTreeNodeCache.hashFrame(node.name) - : `NODE,${node.name}`; -}; + /** + * Insert the given CensusTreeNodeCacheValue whose node.name is a SavedFrame + * object in the given cache. + * + * @param {CensusTreeNodeCache} cache + * @param {CensusTreeNodeCacheValue} value + */ + static insertFrame(cache, value) { + cache[this.hashFrame(value.node.name)] = value; + } -/** - * Insert the given CensusTreeNodeCacheValue whose node.name is a SavedFrame - * object in the given cache. - * - * @param {CensusTreeNodeCache} cache - * @param {CensusTreeNodeCacheValue} value - */ -CensusTreeNodeCache.insertFrame = function (cache, value) { - cache[CensusTreeNodeCache.hashFrame(value.node.name)] = value; -}; + /** + * Insert the given value in the cache. + * + * @param {CensusTreeNodeCache} cache + * @param {CensusTreeNodeCacheValue} value + */ + static insertNode(cache, value) { + if (isSavedFrame(value.node.name)) { + this.insertFrame(cache, value); + } else { + cache[this.hashNode(value.node)] = value; + } + } -/** - * Insert the given value in the cache. - * - * @param {CensusTreeNodeCache} cache - * @param {CensusTreeNodeCacheValue} value - */ -CensusTreeNodeCache.insertNode = function (cache, value) { - if (isSavedFrame(value.node.name)) { - CensusTreeNodeCache.insertFrame(cache, value); - } else { - cache[CensusTreeNodeCache.hashNode(value.node)] = value; + /** + * Lookup `frame` in `cache` and return its value if it exists. + * + * @param {CensusTreeNodeCache} cache + * @param {SavedFrame} frame + * + * @returns {undefined|CensusTreeNodeCacheValue} + */ + static lookupFrame(cache, frame) { + return cache[this.hashFrame(frame)]; } -}; -/** - * Lookup `frame` in `cache` and return its value if it exists. - * - * @param {CensusTreeNodeCache} cache - * @param {SavedFrame} frame - * - * @returns {undefined|CensusTreeNodeCacheValue} - */ -CensusTreeNodeCache.lookupFrame = function (cache, frame) { - return cache[CensusTreeNodeCache.hashFrame(frame)]; -}; + /** + * Lookup `node` in `cache` and return its value if it exists. + * + * @param {CensusTreeNodeCache} cache + * @param {CensusTreeNode} node + * + * @returns {undefined|CensusTreeNodeCacheValue} + */ + static lookupNode(cache, node) { + return isSavedFrame(node.name) + ? this.lookupFrame(cache, node.name) + : cache[this.hashNode(node)]; + } +} /** - * Lookup `node` in `cache` and return its value if it exists. - * - * @param {CensusTreeNodeCache} cache - * @param {CensusTreeNode} node - * - * @returns {undefined|CensusTreeNodeCacheValue} + * The value of a single entry stored in a CensusTreeNodeCache. It is a pair of + * the CensusTreeNode for this cache value, and the subsequent + * CensusTreeNodeCache for this node's children. */ -CensusTreeNodeCache.lookupNode = function (cache, node) { - return isSavedFrame(node.name) - ? CensusTreeNodeCache.lookupFrame(cache, node.name) - : cache[CensusTreeNodeCache.hashNode(node)]; -}; +class CensusTreeNodeCacheValue { + constructor() { + // The CensusTreeNode for this cache value. + this.node = undefined; + // The CensusTreeNodeCache for this frame's children. + this.children = undefined; + } +} /** * Add `child` to `parent`'s set of children and store the parent ID @@ -265,202 +261,207 @@ function makeCensusTreeNodeSubTree(breakdown, report, edge, cache, outParams) { * A Visitor that walks a census report and creates the corresponding * CensusTreeNode tree. */ -function CensusTreeNodeVisitor() { - // The root of the resulting CensusTreeNode tree. - this._root = null; - - // The stack of CensusTreeNodes that we are in the process of building while - // walking the census report. - this._nodeStack = []; - - // To avoid unnecessary allocations, we reuse the same out parameter object - // passed to `makeCensusTreeNodeSubTree` every time we call it. - this._outParams = { - top: null, - bottom: null, - }; - - // The stack of `CensusTreeNodeCache`s that we use to aggregate many - // SavedFrame stacks into a single CensusTreeNode tree. - this._cacheStack = [new CensusTreeNodeCache()]; - - // The current index in the DFS of the census report tree. - this._index = -1; -} - -CensusTreeNodeVisitor.prototype = Object.create(Visitor); - -/** - * Create the CensusTreeNode subtree for this sub-report and link it to the - * parent CensusTreeNode. - * - * @overrides Visitor.prototype.enter - */ -CensusTreeNodeVisitor.prototype.enter = function (breakdown, report, edge) { - this._index++; - - const cache = this._cacheStack[this._cacheStack.length - 1]; - makeCensusTreeNodeSubTree(breakdown, report, edge, cache, this._outParams); - const { top, bottom } = this._outParams; - - if (!this._root) { - this._root = bottom; - } else if (bottom) { - addChild(this._nodeStack[this._nodeStack.length - 1], bottom); +class CensusTreeNodeVisitor extends Visitor { + constructor() { + super(); + // The root of the resulting CensusTreeNode tree. + this._root = null; + + // The stack of CensusTreeNodes that we are in the process of building while + // walking the census report. + this._nodeStack = []; + + // To avoid unnecessary allocations, we reuse the same out parameter object + // passed to `makeCensusTreeNodeSubTree` every time we call it. + this._outParams = { + top: null, + bottom: null, + }; + + // The stack of `CensusTreeNodeCache`s that we use to aggregate many + // SavedFrame stacks into a single CensusTreeNode tree. + this._cacheStack = [new CensusTreeNodeCache()]; + + // The current index in the DFS of the census report tree. + this._index = -1; } - this._cacheStack.push(new CensusTreeNodeCache()); - this._nodeStack.push(top); -}; - -function values(cache) { - return Object.keys(cache).map(k => cache[k]); -} + /** + * Create the CensusTreeNode subtree for this sub-report and link it to the + * parent CensusTreeNode. + * + * @override + */ + enter(breakdown, report, edge) { + this._index++; + + const cache = this._cacheStack[this._cacheStack.length - 1]; + makeCensusTreeNodeSubTree(breakdown, report, edge, cache, this._outParams); + const { top, bottom } = this._outParams; + + if (!this._root) { + this._root = bottom; + } else if (bottom) { + addChild(this._nodeStack[this._nodeStack.length - 1], bottom); + } -function isNonEmpty(node) { - return ( - (node.children !== undefined && node.children.length) || - node.bytes !== 0 || - node.count !== 0 - ); -} + this._cacheStack.push(new CensusTreeNodeCache()); + this._nodeStack.push(top); + } -/** - * We have finished adding children to the CensusTreeNode subtree for the - * current sub-report. Make sure that the children are sorted for every node in - * the subtree. - * - * @overrides Visitor.prototype.exit - */ -CensusTreeNodeVisitor.prototype.exit = function () { - // Ensure all children are sorted and have their counts/bytes aggregated. We - // only need to consider cache children here, because other children - // correspond to other sub-reports and we already fixed them up in an earlier - // invocation of `exit`. - - function dfs(node, childrenCache) { - if (childrenCache) { - const childValues = values(childrenCache); - for (let i = 0, length = childValues.length; i < length; i++) { - dfs(childValues[i].node, childValues[i].children); + /** + * We have finished adding children to the CensusTreeNode subtree for the + * current sub-report. Make sure that the children are sorted for every node in + * the subtree. + * + * @override + */ + exit() { + // Ensure all children are sorted and have their counts/bytes aggregated. We + // only need to consider cache children here, because other children + // correspond to other sub-reports and we already fixed them up in an earlier + // invocation of `exit`. + + function dfs(node, childrenCache) { + if (childrenCache) { + const childValues = values(childrenCache); + for (let i = 0, length = childValues.length; i < length; i++) { + dfs(childValues[i].node, childValues[i].children); + } } - } - node.totalCount = node.count; - node.totalBytes = node.bytes; + node.totalCount = node.count; + node.totalBytes = node.bytes; - if (node.children) { - // Prune empty leaves. - node.children = node.children.filter(isNonEmpty); + if (node.children) { + // Prune empty leaves. + node.children = node.children.filter(isNonEmpty); - node.children.sort(compareByTotal); + node.children.sort(compareByTotal); - for (let i = 0, length = node.children.length; i < length; i++) { - node.totalCount += node.children[i].totalCount; - node.totalBytes += node.children[i].totalBytes; + for (let i = 0, length = node.children.length; i < length; i++) { + node.totalCount += node.children[i].totalCount; + node.totalBytes += node.children[i].totalBytes; + } } } + + const top = this._nodeStack.pop(); + const cache = this._cacheStack.pop(); + dfs(top, cache); } - const top = this._nodeStack.pop(); - const cache = this._cacheStack.pop(); - dfs(top, cache); -}; + /** + * @override + */ + count(breakdown, report) { + const node = this._nodeStack[this._nodeStack.length - 1]; + node.reportLeafIndex = this._index; -/** - * @overrides Visitor.prototype.count - */ -CensusTreeNodeVisitor.prototype.count = function (breakdown, report) { - const node = this._nodeStack[this._nodeStack.length - 1]; - node.reportLeafIndex = this._index; + if (breakdown.count) { + node.count = report.count; + } - if (breakdown.count) { - node.count = report.count; + if (breakdown.bytes) { + node.bytes = report.bytes; + } } - if (breakdown.bytes) { - node.bytes = report.bytes; - } -}; + /** + * Get the root of the resulting CensusTreeNode tree. + * + * @returns {CensusTreeNode} + */ + root() { + if (!this._root) { + throw new Error( + "Attempt to get the root before walking the census report!" + ); + } -/** - * Get the root of the resulting CensusTreeNode tree. - * - * @returns {CensusTreeNode} - */ -CensusTreeNodeVisitor.prototype.root = function () { - if (!this._root) { - throw new Error( - "Attempt to get the root before walking the census report!" - ); - } + if (this._nodeStack.length) { + throw new Error( + "Attempt to get the root while walking the census report!" + ); + } - if (this._nodeStack.length) { - throw new Error("Attempt to get the root while walking the census report!"); + return this._root; } +} - return this._root; -}; +function values(cache) { + return Object.keys(cache).map(k => cache[k]); +} + +function isNonEmpty(node) { + return ( + (node.children !== undefined && node.children.length) || + node.bytes !== 0 || + node.count !== 0 + ); +} /** * Create a single, uninitialized CensusTreeNode. - * - * @param {null | string | SavedFrame} name */ -function CensusTreeNode(name) { - // Display name for this CensusTreeNode. Either null, a string, or a - // SavedFrame. - this.name = name; - - // The number of bytes occupied by matching things in the heap snapshot. - this.bytes = 0; - - // The sum of `this.bytes` and `child.totalBytes` for each child in - // `this.children`. - this.totalBytes = 0; - - // The number of things in the heap snapshot that match this node in the - // census tree. - this.count = 0; - - // The sum of `this.count` and `child.totalCount` for each child in - // `this.children`. - this.totalCount = 0; - - // An array of this node's children, or undefined if it has no children. - this.children = undefined; - - // The unique ID of this node. - this.id = ++censusTreeNodeIdCounter; - - // If present, the unique ID of this node's parent. If this node does not have - // a parent, then undefined. - this.parent = undefined; - - // The `reportLeafIndex` property allows mapping a CensusTreeNode node back to - // a leaf in the census report it was generated from. It is always one of the - // following variants: - // - // * A `Number` index pointing a leaf report in a pre-order DFS traversal of - // this CensusTreeNode's census report. - // - // * A `Set` object containing such indices, when this is part of an inverted - // CensusTreeNode tree and multiple leaves in the report map onto this node. - // - // * Finally, `undefined` when no leaves in the census report correspond with - // this node. - // - // The first and third cases are the common cases. The second case is rather - // uncommon, and to avoid doubling the number of allocations when creating - // CensusTreeNode trees, and objects that get structured cloned when sending - // such trees from the HeapAnalysesWorker to the main thread, we only allocate - // a Set object once a node actually does have multiple leaves it corresponds - // to. - this.reportLeafIndex = undefined; +class CensusTreeNode { + /** + * + * @param {null | string | SavedFrame} name + */ + constructor(name) { + // Display name for this CensusTreeNode. Either null, a string, or a + // SavedFrame. + this.name = name; + + // The number of bytes occupied by matching things in the heap snapshot. + this.bytes = 0; + + // The sum of `this.bytes` and `child.totalBytes` for each child in + // `this.children`. + this.totalBytes = 0; + + // The number of things in the heap snapshot that match this node in the + // census tree. + this.count = 0; + + // The sum of `this.count` and `child.totalCount` for each child in + // `this.children`. + this.totalCount = 0; + + // An array of this node's children, or undefined if it has no children. + this.children = undefined; + + // The unique ID of this node. + this.id = ++censusTreeNodeIdCounter; + + // If present, the unique ID of this node's parent. If this node does not have + // a parent, then undefined. + this.parent = undefined; + + // The `reportLeafIndex` property allows mapping a CensusTreeNode node back to + // a leaf in the census report it was generated from. It is always one of the + // following variants: + // + // * A `Number` index pointing a leaf report in a pre-order DFS traversal of + // this CensusTreeNode's census report. + // + // * A `Set` object containing such indices, when this is part of an inverted + // CensusTreeNode tree and multiple leaves in the report map onto this node. + // + // * Finally, `undefined` when no leaves in the census report correspond with + // this node. + // + // The first and third cases are the common cases. The second case is rather + // uncommon, and to avoid doubling the number of allocations when creating + // CensusTreeNode trees, and objects that get structured cloned when sending + // such trees from the HeapAnalysesWorker to the main thread, we only allocate + // a Set object once a node actually does have multiple leaves it corresponds + // to. + this.reportLeafIndex = undefined; + } } -CensusTreeNode.prototype = null; - /** * Compare the given nodes by their `totalBytes` properties, and breaking ties * with the `totalCount`, `bytes`, and `count` properties (in that order).