diff --git a/devtools/shared/heapsnapshot/DominatorTree.h b/devtools/shared/heapsnapshot/DominatorTree.h index dc7d9889979d..0a785dca531f 100644 --- a/devtools/shared/heapsnapshot/DominatorTree.h +++ b/devtools/shared/heapsnapshot/DominatorTree.h @@ -42,6 +42,7 @@ public: virtual JSObject* WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + uint64_t Root() const { return mDominatorTree.root().identifier(); } }; } // namespace devtools diff --git a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js index d8710d64c55a..cd980ed546a1 100644 --- a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js +++ b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js @@ -117,6 +117,13 @@ function saveNewHeapSnapshot(opts = { runtime: true }) { return filePath; } +function readHeapSnapshot(filePath) { + const snapshot = ChromeUtils.readHeapSnapshot(filePath); + ok(snapshot, "Should have read a heap snapshot back from " + filePath); + ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot"); + return snapshot; +} + /** * Save a heap snapshot to the file with the given name in the current * directory, read it back as a HeapSnapshot instance, and then take a census of @@ -138,18 +145,40 @@ function saveNewHeapSnapshot(opts = { runtime: true }) { */ function saveHeapSnapshotAndTakeCensus(dbg=null, censusOptions=undefined) { const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true }; - const filePath = ChromeUtils.saveHeapSnapshot(snapshotOptions); - ok(filePath, "Should get a file path to save the core dump to."); - ok(true, "Should have saved a heap snapshot to " + filePath); - - const snapshot = ChromeUtils.readHeapSnapshot(filePath); - ok(snapshot, "Should have read a heap snapshot back from " + filePath); - ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot"); + const filePath = saveNewHeapSnapshot(snapshotOptions); + const snapshot = readHeapSnapshot(filePath); equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method"); + return snapshot.takeCensus(censusOptions); } +/** + * Save a heap snapshot to disk, read it back as a HeapSnapshot instance, and + * then compute its dominator tree. + * + * @param {Debugger|null} dbg + * If a Debugger object is given, only serialize the subgraph covered by + * the Debugger's debuggees. If null, serialize the whole heap graph. + * + * @returns {DominatorTree} + */ +function saveHeapSnapshotAndComputeDominatorTree(dbg = null) { + const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true }; + const filePath = saveNewHeapSnapshot(snapshotOptions); + const snapshot = readHeapSnapshot(filePath); + + equal(typeof snapshot.computeDominatorTree, "function", + "snapshot should have a `computeDominatorTree` method"); + + const dominatorTree = snapshot.computeDominatorTree(); + + ok(dominatorTree, "Should be able to compute a dominator tree"); + ok(dominatorTree instanceof DominatorTree, "Should be an instance of DominatorTree"); + + return dominatorTree; +} + function isSavedFrame(obj) { return Object.prototype.toString.call(obj) === "[object SavedFrame]"; } diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js new file mode 100644 index 000000000000..0a14ce53dae0 --- /dev/null +++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js @@ -0,0 +1,13 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +// Test that we can get the root of dominator trees. + +function run_test() { + const dominatorTree = saveHeapSnapshotAndComputeDominatorTree(); + equal(typeof dominatorTree.root, "number", "root should be a number"); + equal(Math.floor(dominatorTree.root), dominatorTree.root, "root should be an integer"); + ok(dominatorTree.root >= 0, "root should be positive"); + ok(dominatorTree.root <= Math.pow(2, 48), "root should be less than or equal to 2^48"); + do_test_finished(); +} diff --git a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini index 8f63f0c61a71..53fbeaac99cf 100644 --- a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini +++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini @@ -30,6 +30,7 @@ support-files = [test_census-tree-node-08.js] [test_DominatorTree_01.js] [test_DominatorTree_02.js] +[test_DominatorTree_03.js] [test_HeapAnalyses_getCreationTime_01.js] [test_HeapAnalyses_readHeapSnapshot_01.js] [test_HeapAnalyses_takeCensusDiff_01.js] diff --git a/dom/webidl/DominatorTree.webidl b/dom/webidl/DominatorTree.webidl index b088e92205dc..98558f7c7649 100644 --- a/dom/webidl/DominatorTree.webidl +++ b/dom/webidl/DominatorTree.webidl @@ -4,6 +4,8 @@ * You can obtain one at http://mozilla.org/MPL/2.0/. */ +typedef unsigned long long NodeId; + /** * In a directed graph with a root node `R`, a node `A` is said to "dominate" a * node `B` iff every path from `R` to `B` contains `A`. A node `A` is said to @@ -37,5 +39,10 @@ */ [ChromeOnly, Exposed=(Window,System,Worker)] interface DominatorTree { - + /** + * The `NodeId` for the root of the dominator tree. This is a "meta-root" in + * that it has an edge to each GC root in the heap snapshot this dominator + * tree was created from. + */ + readonly attribute NodeId root; }; diff --git a/js/public/UbiNodeDominatorTree.h b/js/public/UbiNodeDominatorTree.h index 50829c18f330..9a2ec53c89c7 100644 --- a/js/public/UbiNodeDominatorTree.h +++ b/js/public/UbiNodeDominatorTree.h @@ -546,6 +546,13 @@ class JS_PUBLIC_API(DominatorTree) mozilla::Move(*maybeDominatedSets))); } + /** + * Get the root node for this dominator tree. + */ + const Node& root() const { + return postOrder[postOrder.length() - 1]; + } + /** * Return the immediate dominator of the given `node`. If `node` was not * reachable from the `root` that this dominator tree was constructed from,