summaryrefslogtreecommitdiffstats
path: root/devtools/shared/heapsnapshot
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/heapsnapshot')
-rw-r--r--devtools/shared/heapsnapshot/AutoMemMap.cpp67
-rw-r--r--devtools/shared/heapsnapshot/AutoMemMap.h77
-rw-r--r--devtools/shared/heapsnapshot/CensusUtils.js502
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.pb.cc2242
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.pb.h2883
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.proto152
-rw-r--r--devtools/shared/heapsnapshot/DeserializedNode.cpp124
-rw-r--r--devtools/shared/heapsnapshot/DeserializedNode.h308
-rw-r--r--devtools/shared/heapsnapshot/DominatorTree.cpp133
-rw-r--r--devtools/shared/heapsnapshot/DominatorTree.h66
-rw-r--r--devtools/shared/heapsnapshot/DominatorTreeNode.js378
-rw-r--r--devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp81
-rw-r--r--devtools/shared/heapsnapshot/FileDescriptorOutputStream.h38
-rw-r--r--devtools/shared/heapsnapshot/HeapAnalyses.worker.js336
-rw-r--r--devtools/shared/heapsnapshot/HeapAnalysesClient.js285
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshot.cpp1581
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshot.h216
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js85
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h31
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp56
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h36
-rw-r--r--devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl37
-rw-r--r--devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp79
-rw-r--r--devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h69
-rw-r--r--devtools/shared/heapsnapshot/census-tree-node.js764
-rwxr-xr-xdevtools/shared/heapsnapshot/generate-core-dump-sources.sh26
-rw-r--r--devtools/shared/heapsnapshot/moz.build59
-rw-r--r--devtools/shared/heapsnapshot/shortest-paths.js93
-rw-r--r--devtools/shared/heapsnapshot/tests/browser/browser.toml9
-rw-r--r--devtools/shared/heapsnapshot/tests/browser/browser_saveHeapSnapshot_e10s_01.js30
-rw-r--r--devtools/shared/heapsnapshot/tests/chrome/chrome.toml7
-rw-r--r--devtools/shared/heapsnapshot/tests/chrome/test_DominatorTree_01.html40
-rw-r--r--devtools/shared/heapsnapshot/tests/chrome/test_SaveHeapSnapshot.html27
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp95
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp98
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp7
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DevTools.h217
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp70
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp61
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp49
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp34
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp27
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/moz.build32
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/.eslintrc.js6
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/Census.sys.mjs176
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/Match.sys.mjs218
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/dominator-tree-worker.js54
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/head_heapsnapshot.js554
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/heap-snapshot-worker.js52
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_01.js49
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_02.js48
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_03.js50
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_04.js55
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_attachShortestPaths_01.js141
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_getNodeByIdAlongPath_01.js49
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_01.js119
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_02.js37
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_03.js124
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_partialTraversal_01.js150
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_01.js24
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_02.js41
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_03.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_04.js30
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_05.js97
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_06.js62
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_01.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_02.js20
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_01.js56
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_02.js19
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_03.js60
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCensusIndividuals_01.js111
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCreationTime_01.js57
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_01.js87
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_02.js28
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getImmediatelyDominated_01.js91
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_readHeapSnapshot_01.js15
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_01.js62
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_02.js64
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_01.js24
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_02.js26
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_03.js53
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_04.js133
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_05.js50
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_06.js114
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_07.js57
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_01.js93
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_02.js50
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_creationTime_01.js33
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_deepStack_01.js88
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_describeNode_01.js43
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_getObjectNodeId_01.js61
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_01.js33
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_02.js61
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_03.js35
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_04.js43
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_05.js31
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_06.js127
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_07.js124
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_08.js85
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_09.js111
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_10.js74
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_11.js131
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_12.js60
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_allocations.js51
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_utf8_paths.js27
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_worker.js41
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_SaveHeapSnapshot.js121
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-01.js77
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-02.js139
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-03.js97
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-04.js160
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-05.js150
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-06.js199
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-07.js206
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-08.js143
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-09.js44
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-10.js51
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_01.js75
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_02.js26
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_03.js87
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_04.js64
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_05.js35
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_06.js139
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_01.js111
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_02.js125
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_03.js67
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_04.js105
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_05.js81
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_countToBucketBreakdown_01.js37
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_deduplicatePaths_01.js87
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_getCensusIndividuals_01.js60
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_getReportLeaves_01.js135
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/test_saveHeapSnapshot_e10s_01.js9
-rw-r--r--devtools/shared/heapsnapshot/tests/xpcshell/xpcshell.toml184
135 files changed, 19152 insertions, 0 deletions
diff --git a/devtools/shared/heapsnapshot/AutoMemMap.cpp b/devtools/shared/heapsnapshot/AutoMemMap.cpp
new file mode 100644
index 0000000000..8a7c89b4bf
--- /dev/null
+++ b/devtools/shared/heapsnapshot/AutoMemMap.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/AutoMemMap.h"
+
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+namespace devtools {
+
+AutoMemMap::~AutoMemMap() {
+ if (addr) {
+ Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
+ addr = nullptr;
+ }
+
+ if (fileMap) {
+ Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
+ fileMap = nullptr;
+ }
+
+ if (fd) {
+ Unused << NS_WARN_IF(PR_Close(fd) != PR_SUCCESS);
+ fd = nullptr;
+ }
+}
+
+nsresult AutoMemMap::init(nsIFile* file, int flags, int mode,
+ PRFileMapProtect prot) {
+ MOZ_ASSERT(!fd);
+ MOZ_ASSERT(!fileMap);
+ MOZ_ASSERT(!addr);
+
+ nsresult rv;
+ int64_t inputFileSize;
+ rv = file->GetFileSize(&inputFileSize);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // Check if the file is too big to memmap.
+ if (inputFileSize > int64_t(UINT32_MAX)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ fileSize = uint32_t(inputFileSize);
+
+ rv = file->OpenNSPRFileDesc(flags, mode, &fd);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!fd) return NS_ERROR_UNEXPECTED;
+
+ fileMap = PR_CreateFileMap(fd, inputFileSize, prot);
+ if (!fileMap) return NS_ERROR_UNEXPECTED;
+
+ addr = PR_MemMap(fileMap, 0, fileSize);
+ if (!addr) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/AutoMemMap.h b/devtools/shared/heapsnapshot/AutoMemMap.h
new file mode 100644
index 0000000000..f67dacf0df
--- /dev/null
+++ b/devtools/shared/heapsnapshot/AutoMemMap.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_AutoMemMap_h
+#define mozilla_devtools_AutoMemMap_h
+
+#include <prio.h>
+#include "nsIFile.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "ErrorList.h"
+
+namespace mozilla {
+namespace devtools {
+
+// # AutoMemMap
+//
+// AutoMemMap is an RAII class to manage mapping a file to memory. It is a
+// wrapper around managing opening and closing a file and calling PR_MemMap and
+// PR_MemUnmap.
+//
+// Example usage:
+//
+// {
+// AutoMemMap mm;
+// if (NS_FAILED(mm.init("/path/to/desired/file"))) {
+// // Handle the error however you see fit.
+// return false;
+// }
+//
+// doStuffWithMappedMemory(mm.address());
+// }
+// // The memory is automatically unmapped when the AutoMemMap leaves scope.
+class MOZ_RAII AutoMemMap {
+ // At the time of this writing, this class supports file imports up to
+ // UINT32_MAX bytes due to limitations in the underlying function PR_MemMap.
+ uint32_t fileSize;
+ PRFileDesc* fd;
+ PRFileMap* fileMap;
+ void* addr;
+
+ AutoMemMap(const AutoMemMap& aOther) = delete;
+ void operator=(const AutoMemMap& aOther) = delete;
+
+ public:
+ explicit AutoMemMap()
+ : fileSize(0), fd(nullptr), fileMap(nullptr), addr(nullptr){};
+ ~AutoMemMap();
+
+ // Initialize this AutoMemMap.
+ nsresult init(nsIFile* file, int flags = PR_RDONLY, int mode = 0,
+ PRFileMapProtect prot = PR_PROT_READONLY);
+
+ // Get the size of the memory mapped file.
+ uint32_t size() const {
+ MOZ_ASSERT(fd, "Should only call size() if init() succeeded.");
+ return fileSize;
+ }
+
+ // Get the mapped memory.
+ void* address() {
+ MOZ_ASSERT(addr);
+ return addr;
+ }
+ const void* address() const {
+ MOZ_ASSERT(addr);
+ return addr;
+ }
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_AutoMemMap_h
diff --git a/devtools/shared/heapsnapshot/CensusUtils.js b/devtools/shared/heapsnapshot/CensusUtils.js
new file mode 100644
index 0000000000..3af68d3b57
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -0,0 +1,502 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ flatten,
+} = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
+
+/** * Visitor ****************************************************************/
+
+/**
+ * A Visitor visits each node and edge of a census report tree as the census
+ * report is being traversed by `walk`.
+ */
+function Visitor() {}
+exports.Visitor = Visitor;
+
+/**
+ * The `enter` method is called when a new sub-report is entered in traversal.
+ *
+ * @param {Object} breakdown
+ * The breakdown for the sub-report that is being entered by traversal.
+ *
+ * @param {Object} report
+ * The report generated by the given breakdown.
+ *
+ * @param {any} edge
+ * The edge leading to this sub-report. The edge is null if (but not iff!
+ * eg, null allocation stack edges) we are entering the root report.
+ */
+Visitor.prototype.enter = function (breakdown, report, edge) {};
+
+/**
+ * The `exit` method is called when traversal of a sub-report has finished.
+ *
+ * @param {Object} breakdown
+ * The breakdown for the sub-report whose traversal has finished.
+ *
+ * @param {Object} report
+ * The report generated by the given breakdown.
+ *
+ * @param {any} edge
+ * The edge leading to this sub-report. The edge is null if (but not iff!
+ * eg, null allocation stack edges) we are entering the root report.
+ */
+Visitor.prototype.exit = function (breakdown, report, edge) {};
+
+/**
+ * The `count` method is called when leaf nodes (reports whose breakdown is
+ * by: "count") in the report tree are encountered.
+ *
+ * @param {Object} breakdown
+ * The count breakdown for this report.
+ *
+ * @param {Object} report
+ * The report generated by a breakdown by "count".
+ *
+ * @param {any|null} edge
+ * The edge leading to this count report. The edge is null if we are
+ * entering the root report.
+ */
+Visitor.prototype.count = function (breakdown, report, edge) {};
+
+/** * getReportEdges *********************************************************/
+
+const EDGES = Object.create(null);
+
+EDGES.count = function (breakdown, report) {
+ return [];
+};
+
+EDGES.bucket = function (breakdown, report) {
+ return [];
+};
+
+EDGES.internalType = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: breakdown.then,
+ }));
+};
+
+EDGES.descriptiveType = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: breakdown.then,
+ }));
+};
+
+EDGES.objectClass = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: key === "other" ? breakdown.other : breakdown.then,
+ }));
+};
+
+EDGES.coarseType = function (breakdown, report) {
+ return [
+ { edge: "objects", referent: report.objects, breakdown: breakdown.objects },
+ { edge: "scripts", referent: report.scripts, breakdown: breakdown.scripts },
+ { edge: "strings", referent: report.strings, breakdown: breakdown.strings },
+ { edge: "other", referent: report.other, breakdown: breakdown.other },
+ { edge: "domNode", referent: report.domNode, breakdown: breakdown.domNode },
+ ];
+};
+
+EDGES.allocationStack = function (breakdown, report) {
+ const edges = [];
+ report.forEach((value, key) => {
+ edges.push({
+ edge: key,
+ referent: value,
+ breakdown: key === "noStack" ? breakdown.noStack : breakdown.then,
+ });
+ });
+ return edges;
+};
+
+EDGES.filename = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: key === "noFilename" ? breakdown.noFilename : breakdown.then,
+ }));
+};
+
+/**
+ * Get the set of outgoing edges from `report` as specified by the given
+ * breakdown.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ */
+function getReportEdges(breakdown, report) {
+ return EDGES[breakdown.by](breakdown, report);
+}
+exports.getReportEdges = getReportEdges;
+
+/** * walk *******************************************************************/
+
+function recursiveWalk(breakdown, edge, report, visitor) {
+ if (breakdown.by === "count") {
+ visitor.enter(breakdown, report, edge);
+ visitor.count(breakdown, report, edge);
+ visitor.exit(breakdown, report, edge);
+ } else {
+ visitor.enter(breakdown, report, edge);
+ for (const {
+ edge: ed,
+ referent,
+ breakdown: subBreakdown,
+ } of getReportEdges(breakdown, report)) {
+ recursiveWalk(subBreakdown, ed, referent, visitor);
+ }
+ visitor.exit(breakdown, report, edge);
+ }
+}
+
+/**
+ * Walk the given `report` that was generated by taking a census with the
+ * specified `breakdown`.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {Visitor} visitor
+ * The Visitor instance to call into while traversing.
+ */
+function walk(breakdown, report, visitor) {
+ recursiveWalk(breakdown, null, report, visitor);
+}
+exports.walk = walk;
+
+/** * diff *******************************************************************/
+
+/**
+ * Return true if the object is a Map, false otherwise. Works with Map objects
+ * from other globals, unlike `instanceof`.
+ *
+ * @returns {Boolean}
+ */
+function isMap(obj) {
+ return Object.prototype.toString.call(obj) === "[object Map]";
+}
+
+/**
+ * A Visitor for computing the difference between the census report being
+ * traversed and the given other census.
+ *
+ * @param {Object} otherCensus
+ * The other census report.
+ */
+function DiffVisitor(otherCensus) {
+ // The other census we are comparing against.
+ this._otherCensus = otherCensus;
+
+ // The total bytes and count of the basis census we are traversing.
+ this._totalBytes = 0;
+ this._totalCount = 0;
+
+ // Stack maintaining the current corresponding sub-report for the other
+ // census we are comparing against.
+ this._otherCensusStack = [];
+
+ // Stack maintaining the set of edges visited at each sub-report.
+ this._edgesVisited = [new Set()];
+
+ // The final delta census. Valid only after traversal.
+ this._results = null;
+
+ // Stack maintaining the results corresponding to each sub-report we are
+ // currently traversing.
+ this._resultsStack = [];
+}
+
+DiffVisitor.prototype = Object.create(Visitor.prototype);
+
+/**
+ * Given a report and an outgoing edge, get the edge's referent.
+ */
+DiffVisitor.prototype._get = function (report, edge) {
+ if (!report) {
+ return undefined;
+ }
+ return isMap(report) ? report.get(edge) : report[edge];
+};
+
+/**
+ * Given a report, an outgoing edge, and a value, set the edge's referent to
+ * the given value.
+ */
+DiffVisitor.prototype._set = function (report, edge, val) {
+ if (isMap(report)) {
+ report.set(edge, val);
+ } else {
+ report[edge] = val;
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+DiffVisitor.prototype.enter = function (breakdown, report, edge) {
+ const newResults = breakdown.by === "allocationStack" ? new Map() : {};
+ let newOther;
+
+ if (!this._results) {
+ // This is the first time we have entered a sub-report.
+ this._results = newResults;
+ newOther = this._otherCensus;
+ } else {
+ const topResults = this._resultsStack[this._resultsStack.length - 1];
+ this._set(topResults, edge, newResults);
+
+ const topOther = this._otherCensusStack[this._otherCensusStack.length - 1];
+ newOther = this._get(topOther, edge);
+ }
+
+ this._resultsStack.push(newResults);
+ this._otherCensusStack.push(newOther);
+
+ const visited = this._edgesVisited[this._edgesVisited.length - 1];
+ visited.add(edge);
+ this._edgesVisited.push(new Set());
+};
+
+/**
+ * @overrides Visitor.prototype.exit
+ */
+DiffVisitor.prototype.exit = function (breakdown, report, edge) {
+ // Find all the edges in the other census report that were not traversed and
+ // add them to the results directly.
+ const other = this._otherCensusStack[this._otherCensusStack.length - 1];
+ if (other) {
+ const visited = this._edgesVisited[this._edgesVisited.length - 1];
+ const unvisited = getReportEdges(breakdown, other)
+ .map(e => e.edge)
+ .filter(e => !visited.has(e));
+ const results = this._resultsStack[this._resultsStack.length - 1];
+ for (const edg of unvisited) {
+ this._set(results, edg, this._get(other, edg));
+ }
+ }
+
+ this._otherCensusStack.pop();
+ this._resultsStack.pop();
+ this._edgesVisited.pop();
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+DiffVisitor.prototype.count = function (breakdown, report, edge) {
+ const other = this._otherCensusStack[this._otherCensusStack.length - 1];
+ const results = this._resultsStack[this._resultsStack.length - 1];
+
+ if (breakdown.count) {
+ this._totalCount += report.count;
+ }
+ if (breakdown.bytes) {
+ this._totalBytes += report.bytes;
+ }
+
+ if (other) {
+ if (breakdown.count) {
+ results.count = other.count - report.count;
+ }
+ if (breakdown.bytes) {
+ results.bytes = other.bytes - report.bytes;
+ }
+ } else {
+ if (breakdown.count) {
+ results.count = -report.count;
+ }
+ if (breakdown.bytes) {
+ results.bytes = -report.bytes;
+ }
+ }
+};
+
+const basisTotalBytes = (exports.basisTotalBytes = Symbol("basisTotalBytes"));
+const basisTotalCount = (exports.basisTotalCount = Symbol("basisTotalCount"));
+
+/**
+ * Get the resulting report of the difference between the traversed census
+ * report and the other census report.
+ *
+ * @returns {Object}
+ * The delta census report.
+ */
+DiffVisitor.prototype.results = function () {
+ if (!this._results) {
+ throw new Error("Attempt to get results before computing diff!");
+ }
+
+ if (this._resultsStack.length) {
+ throw new Error("Attempt to get results while still computing diff!");
+ }
+
+ this._results[basisTotalBytes] = this._totalBytes;
+ this._results[basisTotalCount] = this._totalCount;
+
+ return this._results;
+};
+
+/**
+ * Take the difference between two censuses. The resulting delta report
+ * contains the number/size of things that are in the `endCensus` that are not
+ * in the `startCensus`.
+ *
+ * @param {Object} breakdown
+ * The breakdown used to generate both census reports.
+ *
+ * @param {Object} startCensus
+ * The first census report.
+ *
+ * @param {Object} endCensus
+ * The second census report.
+ *
+ * @returns {Object}
+ * A delta report mirroring the structure of the two census reports (as
+ * specified by the given breakdown). Has two additional properties:
+ * - {Number} basisTotalBytes: the total number of bytes in the start
+ * census.
+ * - {Number} basisTotalCount: the total count in the start census.
+ */
+function diff(breakdown, startCensus, endCensus) {
+ const visitor = new DiffVisitor(endCensus);
+ walk(breakdown, startCensus, visitor);
+ return visitor.results();
+}
+exports.diff = diff;
+
+/**
+ * Creates a hash map mapping node IDs to its parent node.
+ *
+ * @param {CensusTreeNode} node
+ * @param {Object<number, TreeNode>} aggregator
+ *
+ * @return {Object<number, TreeNode>}
+ */
+const createParentMap = function (node, getId = n => n.id, aggregator = {}) {
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ const child = node.children[i];
+ aggregator[getId(child)] = node;
+ createParentMap(child, getId, aggregator);
+ }
+ }
+ return aggregator;
+};
+exports.createParentMap = createParentMap;
+
+const BUCKET = Object.freeze({ by: "bucket" });
+
+/**
+ * Convert a breakdown whose leaves are { by: "count" } to an identical
+ * breakdown, except with { by: "bucket" } leaves.
+ *
+ * @param {Object} breakdown
+ * @returns {Object}
+ */
+exports.countToBucketBreakdown = function (breakdown) {
+ if (typeof breakdown !== "object" || !breakdown) {
+ return breakdown;
+ }
+
+ if (breakdown.by === "count") {
+ return BUCKET;
+ }
+
+ const keys = Object.keys(breakdown);
+ const vals = keys.reduce((vs, k) => {
+ vs.push(exports.countToBucketBreakdown(breakdown[k]));
+ return vs;
+ }, []);
+
+ const result = {};
+ for (let i = 0, length = keys.length; i < length; i++) {
+ result[keys[i]] = vals[i];
+ }
+
+ return Object.freeze(result);
+};
+
+/**
+ * A Visitor for finding report leaves by their DFS index.
+ */
+function GetLeavesVisitor(targetIndices) {
+ this._index = -1;
+ this._targetIndices = targetIndices;
+ this._leaves = [];
+}
+
+GetLeavesVisitor.prototype = Object.create(Visitor.prototype);
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+GetLeavesVisitor.prototype.enter = function (breakdown, report, edge) {
+ this._index++;
+ if (this._targetIndices.has(this._index)) {
+ this._leaves.push(report);
+ }
+};
+
+/**
+ * Get the accumulated report leaves after traversal.
+ */
+GetLeavesVisitor.prototype.leaves = function () {
+ if (this._index === -1) {
+ throw new Error("Attempt to call `leaves` before traversing report!");
+ }
+ return this._leaves;
+};
+
+/**
+ * Given a set of indices of leaves in a pre-order depth-first traversal of the
+ * given census report, return the leaves.
+ *
+ * @param {Set<Number>} indices
+ * @param {Object} breakdown
+ * @param {Object} report
+ *
+ * @returns {Array<Object>}
+ */
+exports.getReportLeaves = function (indices, breakdown, report) {
+ const visitor = new GetLeavesVisitor(indices);
+ walk(breakdown, report, visitor);
+ return visitor.leaves();
+};
+
+/**
+ * Get a list of the individual node IDs that belong to the census report leaves
+ * of the given indices.
+ *
+ * @param {Set<Number>} indices
+ * @param {Object} breakdown
+ * @param {HeapSnapshot} snapshot
+ *
+ * @returns {Array<NodeId>}
+ */
+exports.getCensusIndividuals = function (indices, countBreakdown, snapshot) {
+ const bucketBreakdown = exports.countToBucketBreakdown(countBreakdown);
+ const bucketReport = snapshot.takeCensus({ breakdown: bucketBreakdown });
+ const buckets = exports.getReportLeaves(
+ indices,
+ bucketBreakdown,
+ bucketReport
+ );
+ return flatten(buckets);
+};
diff --git a/devtools/shared/heapsnapshot/CoreDump.pb.cc b/devtools/shared/heapsnapshot/CoreDump.pb.cc
new file mode 100644
index 0000000000..9283733771
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.cc
@@ -0,0 +1,2242 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: CoreDump.proto
+
+#include "CoreDump.pb.h"
+
+#include <algorithm>
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/wire_format_lite.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+// @@protoc_insertion_point(includes)
+#include <google/protobuf/port_def.inc>
+
+PROTOBUF_PRAGMA_INIT_SEG
+
+namespace _pb = ::PROTOBUF_NAMESPACE_ID;
+namespace _pbi = _pb::internal;
+
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+PROTOBUF_CONSTEXPR Metadata::Metadata(
+ ::_pbi::ConstantInitialized): _impl_{
+ /*decltype(_impl_._has_bits_)*/{}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_.timestamp_)*/uint64_t{0u}} {}
+struct MetadataDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR MetadataDefaultTypeInternal()
+ : _instance(::_pbi::ConstantInitialized{}) {}
+ ~MetadataDefaultTypeInternal() {}
+ union {
+ Metadata _instance;
+ };
+};
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 MetadataDefaultTypeInternal _Metadata_default_instance_;
+PROTOBUF_CONSTEXPR StackFrame_Data::StackFrame_Data(
+ ::_pbi::ConstantInitialized): _impl_{
+ /*decltype(_impl_._has_bits_)*/{}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_.parent_)*/nullptr
+ , /*decltype(_impl_.id_)*/uint64_t{0u}
+ , /*decltype(_impl_.line_)*/0u
+ , /*decltype(_impl_.column_)*/0u
+ , /*decltype(_impl_.issystem_)*/false
+ , /*decltype(_impl_.isselfhosted_)*/false
+ , /*decltype(_impl_.SourceOrRef_)*/{}
+ , /*decltype(_impl_.FunctionDisplayNameOrRef_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}} {}
+struct StackFrame_DataDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR StackFrame_DataDefaultTypeInternal()
+ : _instance(::_pbi::ConstantInitialized{}) {}
+ ~StackFrame_DataDefaultTypeInternal() {}
+ union {
+ StackFrame_Data _instance;
+ };
+};
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 StackFrame_DataDefaultTypeInternal _StackFrame_Data_default_instance_;
+PROTOBUF_CONSTEXPR StackFrame::StackFrame(
+ ::_pbi::ConstantInitialized): _impl_{
+ /*decltype(_impl_.StackFrameType_)*/{}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}} {}
+struct StackFrameDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR StackFrameDefaultTypeInternal()
+ : _instance(::_pbi::ConstantInitialized{}) {}
+ ~StackFrameDefaultTypeInternal() {}
+ union {
+ StackFrame _instance;
+ };
+};
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 StackFrameDefaultTypeInternal _StackFrame_default_instance_;
+PROTOBUF_CONSTEXPR Node::Node(
+ ::_pbi::ConstantInitialized): _impl_{
+ /*decltype(_impl_._has_bits_)*/{}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_.edges_)*/{}
+ , /*decltype(_impl_.allocationstack_)*/nullptr
+ , /*decltype(_impl_.id_)*/uint64_t{0u}
+ , /*decltype(_impl_.size_)*/uint64_t{0u}
+ , /*decltype(_impl_.coarsetype_)*/0u
+ , /*decltype(_impl_.TypeNameOrRef_)*/{}
+ , /*decltype(_impl_.JSObjectClassNameOrRef_)*/{}
+ , /*decltype(_impl_.ScriptFilenameOrRef_)*/{}
+ , /*decltype(_impl_.descriptiveTypeNameOrRef_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}} {}
+struct NodeDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR NodeDefaultTypeInternal()
+ : _instance(::_pbi::ConstantInitialized{}) {}
+ ~NodeDefaultTypeInternal() {}
+ union {
+ Node _instance;
+ };
+};
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 NodeDefaultTypeInternal _Node_default_instance_;
+PROTOBUF_CONSTEXPR Edge::Edge(
+ ::_pbi::ConstantInitialized): _impl_{
+ /*decltype(_impl_._has_bits_)*/{}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_.referent_)*/uint64_t{0u}
+ , /*decltype(_impl_.EdgeNameOrRef_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}} {}
+struct EdgeDefaultTypeInternal {
+ PROTOBUF_CONSTEXPR EdgeDefaultTypeInternal()
+ : _instance(::_pbi::ConstantInitialized{}) {}
+ ~EdgeDefaultTypeInternal() {}
+ union {
+ Edge _instance;
+ };
+};
+PROTOBUF_ATTRIBUTE_NO_DESTROY PROTOBUF_CONSTINIT PROTOBUF_ATTRIBUTE_INIT_PRIORITY1 EdgeDefaultTypeInternal _Edge_default_instance_;
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+// ===================================================================
+
+class Metadata::_Internal {
+ public:
+ using HasBits = decltype(std::declval<Metadata>()._impl_._has_bits_);
+ static void set_has_timestamp(HasBits* has_bits) {
+ (*has_bits)[0] |= 1u;
+ }
+};
+
+Metadata::Metadata(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) {
+ SharedCtor(arena, is_message_owned);
+ // @@protoc_insertion_point(arena_constructor:mozilla.devtools.protobuf.Metadata)
+}
+Metadata::Metadata(const Metadata& from)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite() {
+ Metadata* const _this = this; (void)_this;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){from._impl_._has_bits_}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.timestamp_){}};
+
+ _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+ _this->_impl_.timestamp_ = from._impl_.timestamp_;
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Metadata)
+}
+
+inline void Metadata::SharedCtor(
+ ::_pb::Arena* arena, bool is_message_owned) {
+ (void)arena;
+ (void)is_message_owned;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.timestamp_){uint64_t{0u}}
+ };
+}
+
+Metadata::~Metadata() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Metadata)
+ if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) {
+ (void)arena;
+ return;
+ }
+ SharedDtor();
+}
+
+inline void Metadata::SharedDtor() {
+ GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
+}
+
+void Metadata::SetCachedSize(int size) const {
+ _impl_._cached_size_.Set(size);
+}
+
+void Metadata::Clear() {
+// @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.Metadata)
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ _impl_.timestamp_ = uint64_t{0u};
+ _impl_._has_bits_.Clear();
+ _internal_metadata_.Clear<std::string>();
+}
+
+const char* Metadata::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+ _Internal::HasBits has_bits{};
+ while (!ctx->Done(&ptr)) {
+ uint32_t tag;
+ ptr = ::_pbi::ReadTag(ptr, &tag);
+ switch (tag >> 3) {
+ // optional uint64 timeStamp = 1;
+ case 1:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) {
+ _Internal::set_has_timestamp(&has_bits);
+ _impl_.timestamp_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ default:
+ goto handle_unusual;
+ } // switch
+ handle_unusual:
+ if ((tag == 0) || ((tag & 7) == 4)) {
+ CHK_(ptr);
+ ctx->SetLastTag(tag);
+ goto message_done;
+ }
+ ptr = UnknownFieldParse(
+ tag,
+ _internal_metadata_.mutable_unknown_fields<std::string>(),
+ ptr, ctx);
+ CHK_(ptr != nullptr);
+ } // while
+message_done:
+ _impl_._has_bits_.Or(has_bits);
+ return ptr;
+failure:
+ ptr = nullptr;
+ goto message_done;
+#undef CHK_
+}
+
+uint8_t* Metadata::_InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Metadata)
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ // optional uint64 timeStamp = 1;
+ if (cached_has_bits & 0x00000001u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_timestamp(), target);
+ }
+
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
+ static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Metadata)
+ return target;
+}
+
+size_t Metadata::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.Metadata)
+ size_t total_size = 0;
+
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ // optional uint64 timeStamp = 1;
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_timestamp());
+ }
+
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size();
+ }
+ int cached_size = ::_pbi::ToCachedSize(total_size);
+ SetCachedSize(cached_size);
+ return total_size;
+}
+
+void Metadata::CheckTypeAndMergeFrom(
+ const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) {
+ MergeFrom(*::_pbi::DownCast<const Metadata*>(
+ &from));
+}
+
+void Metadata::MergeFrom(const Metadata& from) {
+ Metadata* const _this = this;
+ // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.devtools.protobuf.Metadata)
+ GOOGLE_DCHECK_NE(&from, _this);
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ if (from._internal_has_timestamp()) {
+ _this->_internal_set_timestamp(from._internal_timestamp());
+ }
+ _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+}
+
+void Metadata::CopyFrom(const Metadata& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.Metadata)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Metadata::IsInitialized() const {
+ return true;
+}
+
+void Metadata::InternalSwap(Metadata* other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]);
+ swap(_impl_.timestamp_, other->_impl_.timestamp_);
+}
+
+std::string Metadata::GetTypeName() const {
+ return "mozilla.devtools.protobuf.Metadata";
+}
+
+
+// ===================================================================
+
+class StackFrame_Data::_Internal {
+ public:
+ using HasBits = decltype(std::declval<StackFrame_Data>()._impl_._has_bits_);
+ static void set_has_id(HasBits* has_bits) {
+ (*has_bits)[0] |= 2u;
+ }
+ static const ::mozilla::devtools::protobuf::StackFrame& parent(const StackFrame_Data* msg);
+ static void set_has_parent(HasBits* has_bits) {
+ (*has_bits)[0] |= 1u;
+ }
+ static void set_has_line(HasBits* has_bits) {
+ (*has_bits)[0] |= 4u;
+ }
+ static void set_has_column(HasBits* has_bits) {
+ (*has_bits)[0] |= 8u;
+ }
+ static void set_has_issystem(HasBits* has_bits) {
+ (*has_bits)[0] |= 16u;
+ }
+ static void set_has_isselfhosted(HasBits* has_bits) {
+ (*has_bits)[0] |= 32u;
+ }
+};
+
+const ::mozilla::devtools::protobuf::StackFrame&
+StackFrame_Data::_Internal::parent(const StackFrame_Data* msg) {
+ return *msg->_impl_.parent_;
+}
+StackFrame_Data::StackFrame_Data(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) {
+ SharedCtor(arena, is_message_owned);
+ // @@protoc_insertion_point(arena_constructor:mozilla.devtools.protobuf.StackFrame.Data)
+}
+StackFrame_Data::StackFrame_Data(const StackFrame_Data& from)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite() {
+ StackFrame_Data* const _this = this; (void)_this;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){from._impl_._has_bits_}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.parent_){nullptr}
+ , decltype(_impl_.id_){}
+ , decltype(_impl_.line_){}
+ , decltype(_impl_.column_){}
+ , decltype(_impl_.issystem_){}
+ , decltype(_impl_.isselfhosted_){}
+ , decltype(_impl_.SourceOrRef_){}
+ , decltype(_impl_.FunctionDisplayNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}};
+
+ _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+ if (from._internal_has_parent()) {
+ _this->_impl_.parent_ = new ::mozilla::devtools::protobuf::StackFrame(*from._impl_.parent_);
+ }
+ ::memcpy(&_impl_.id_, &from._impl_.id_,
+ static_cast<size_t>(reinterpret_cast<char*>(&_impl_.isselfhosted_) -
+ reinterpret_cast<char*>(&_impl_.id_)) + sizeof(_impl_.isselfhosted_));
+ clear_has_SourceOrRef();
+ switch (from.SourceOrRef_case()) {
+ case kSource: {
+ _this->_internal_set_source(from._internal_source());
+ break;
+ }
+ case kSourceRef: {
+ _this->_internal_set_sourceref(from._internal_sourceref());
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ clear_has_FunctionDisplayNameOrRef();
+ switch (from.FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ _this->_internal_set_functiondisplayname(from._internal_functiondisplayname());
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ _this->_internal_set_functiondisplaynameref(from._internal_functiondisplaynameref());
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.StackFrame.Data)
+}
+
+inline void StackFrame_Data::SharedCtor(
+ ::_pb::Arena* arena, bool is_message_owned) {
+ (void)arena;
+ (void)is_message_owned;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.parent_){nullptr}
+ , decltype(_impl_.id_){uint64_t{0u}}
+ , decltype(_impl_.line_){0u}
+ , decltype(_impl_.column_){0u}
+ , decltype(_impl_.issystem_){false}
+ , decltype(_impl_.isselfhosted_){false}
+ , decltype(_impl_.SourceOrRef_){}
+ , decltype(_impl_.FunctionDisplayNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}
+ };
+ clear_has_SourceOrRef();
+ clear_has_FunctionDisplayNameOrRef();
+}
+
+StackFrame_Data::~StackFrame_Data() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.StackFrame.Data)
+ if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) {
+ (void)arena;
+ return;
+ }
+ SharedDtor();
+}
+
+inline void StackFrame_Data::SharedDtor() {
+ GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
+ if (this != internal_default_instance()) delete _impl_.parent_;
+ if (has_SourceOrRef()) {
+ clear_SourceOrRef();
+ }
+ if (has_FunctionDisplayNameOrRef()) {
+ clear_FunctionDisplayNameOrRef();
+ }
+}
+
+void StackFrame_Data::SetCachedSize(int size) const {
+ _impl_._cached_size_.Set(size);
+}
+
+void StackFrame_Data::clear_SourceOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.StackFrame.Data)
+ switch (SourceOrRef_case()) {
+ case kSource: {
+ _impl_.SourceOrRef_.source_.Destroy();
+ break;
+ }
+ case kSourceRef: {
+ // No need to clear
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[0] = SOURCEORREF_NOT_SET;
+}
+
+void StackFrame_Data::clear_FunctionDisplayNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.StackFrame.Data)
+ switch (FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Destroy();
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ // No need to clear
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[1] = FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+
+
+void StackFrame_Data::Clear() {
+// @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.StackFrame.Data)
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ GOOGLE_DCHECK(_impl_.parent_ != nullptr);
+ _impl_.parent_->Clear();
+ }
+ if (cached_has_bits & 0x0000003eu) {
+ ::memset(&_impl_.id_, 0, static_cast<size_t>(
+ reinterpret_cast<char*>(&_impl_.isselfhosted_) -
+ reinterpret_cast<char*>(&_impl_.id_)) + sizeof(_impl_.isselfhosted_));
+ }
+ clear_SourceOrRef();
+ clear_FunctionDisplayNameOrRef();
+ _impl_._has_bits_.Clear();
+ _internal_metadata_.Clear<std::string>();
+}
+
+const char* StackFrame_Data::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+ _Internal::HasBits has_bits{};
+ while (!ctx->Done(&ptr)) {
+ uint32_t tag;
+ ptr = ::_pbi::ReadTag(ptr, &tag);
+ switch (tag >> 3) {
+ // optional uint64 id = 1;
+ case 1:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) {
+ _Internal::set_has_id(&has_bits);
+ _impl_.id_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ case 2:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) {
+ ptr = ctx->ParseMessage(_internal_mutable_parent(), ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional uint32 line = 3;
+ case 3:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) {
+ _Internal::set_has_line(&has_bits);
+ _impl_.line_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional uint32 column = 4;
+ case 4:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 32)) {
+ _Internal::set_has_column(&has_bits);
+ _impl_.column_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes source = 5;
+ case 5:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) {
+ auto str = _internal_mutable_source();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 sourceRef = 6;
+ case 6:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 48)) {
+ _internal_set_sourceref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes functionDisplayName = 7;
+ case 7:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 58)) {
+ auto str = _internal_mutable_functiondisplayname();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 functionDisplayNameRef = 8;
+ case 8:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 64)) {
+ _internal_set_functiondisplaynameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional bool isSystem = 9;
+ case 9:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 72)) {
+ _Internal::set_has_issystem(&has_bits);
+ _impl_.issystem_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional bool isSelfHosted = 10;
+ case 10:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 80)) {
+ _Internal::set_has_isselfhosted(&has_bits);
+ _impl_.isselfhosted_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ default:
+ goto handle_unusual;
+ } // switch
+ handle_unusual:
+ if ((tag == 0) || ((tag & 7) == 4)) {
+ CHK_(ptr);
+ ctx->SetLastTag(tag);
+ goto message_done;
+ }
+ ptr = UnknownFieldParse(
+ tag,
+ _internal_metadata_.mutable_unknown_fields<std::string>(),
+ ptr, ctx);
+ CHK_(ptr != nullptr);
+ } // while
+message_done:
+ _impl_._has_bits_.Or(has_bits);
+ return ptr;
+failure:
+ ptr = nullptr;
+ goto message_done;
+#undef CHK_
+}
+
+uint8_t* StackFrame_Data::_InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.StackFrame.Data)
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ // optional uint64 id = 1;
+ if (cached_has_bits & 0x00000002u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_id(), target);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ if (cached_has_bits & 0x00000001u) {
+ target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+ InternalWriteMessage(2, _Internal::parent(this),
+ _Internal::parent(this).GetCachedSize(), target, stream);
+ }
+
+ // optional uint32 line = 3;
+ if (cached_has_bits & 0x00000004u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt32ToArray(3, this->_internal_line(), target);
+ }
+
+ // optional uint32 column = 4;
+ if (cached_has_bits & 0x00000008u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt32ToArray(4, this->_internal_column(), target);
+ }
+
+ switch (SourceOrRef_case()) {
+ case kSource: {
+ target = stream->WriteBytesMaybeAliased(
+ 5, this->_internal_source(), target);
+ break;
+ }
+ case kSourceRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(6, this->_internal_sourceref(), target);
+ break;
+ }
+ default: ;
+ }
+ switch (FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ target = stream->WriteBytesMaybeAliased(
+ 7, this->_internal_functiondisplayname(), target);
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(8, this->_internal_functiondisplaynameref(), target);
+ break;
+ }
+ default: ;
+ }
+ // optional bool isSystem = 9;
+ if (cached_has_bits & 0x00000010u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteBoolToArray(9, this->_internal_issystem(), target);
+ }
+
+ // optional bool isSelfHosted = 10;
+ if (cached_has_bits & 0x00000020u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteBoolToArray(10, this->_internal_isselfhosted(), target);
+ }
+
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
+ static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.StackFrame.Data)
+ return target;
+}
+
+size_t StackFrame_Data::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.StackFrame.Data)
+ size_t total_size = 0;
+
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x0000003fu) {
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ if (cached_has_bits & 0x00000001u) {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+ *_impl_.parent_);
+ }
+
+ // optional uint64 id = 1;
+ if (cached_has_bits & 0x00000002u) {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id());
+ }
+
+ // optional uint32 line = 3;
+ if (cached_has_bits & 0x00000004u) {
+ total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_line());
+ }
+
+ // optional uint32 column = 4;
+ if (cached_has_bits & 0x00000008u) {
+ total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_column());
+ }
+
+ // optional bool isSystem = 9;
+ if (cached_has_bits & 0x00000010u) {
+ total_size += 1 + 1;
+ }
+
+ // optional bool isSelfHosted = 10;
+ if (cached_has_bits & 0x00000020u) {
+ total_size += 1 + 1;
+ }
+
+ }
+ switch (SourceOrRef_case()) {
+ // bytes source = 5;
+ case kSource: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_source());
+ break;
+ }
+ // uint64 sourceRef = 6;
+ case kSourceRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_sourceref());
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (FunctionDisplayNameOrRef_case()) {
+ // bytes functionDisplayName = 7;
+ case kFunctionDisplayName: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_functiondisplayname());
+ break;
+ }
+ // uint64 functionDisplayNameRef = 8;
+ case kFunctionDisplayNameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_functiondisplaynameref());
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size();
+ }
+ int cached_size = ::_pbi::ToCachedSize(total_size);
+ SetCachedSize(cached_size);
+ return total_size;
+}
+
+void StackFrame_Data::CheckTypeAndMergeFrom(
+ const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) {
+ MergeFrom(*::_pbi::DownCast<const StackFrame_Data*>(
+ &from));
+}
+
+void StackFrame_Data::MergeFrom(const StackFrame_Data& from) {
+ StackFrame_Data* const _this = this;
+ // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.devtools.protobuf.StackFrame.Data)
+ GOOGLE_DCHECK_NE(&from, _this);
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ cached_has_bits = from._impl_._has_bits_[0];
+ if (cached_has_bits & 0x0000003fu) {
+ if (cached_has_bits & 0x00000001u) {
+ _this->_internal_mutable_parent()->::mozilla::devtools::protobuf::StackFrame::MergeFrom(
+ from._internal_parent());
+ }
+ if (cached_has_bits & 0x00000002u) {
+ _this->_impl_.id_ = from._impl_.id_;
+ }
+ if (cached_has_bits & 0x00000004u) {
+ _this->_impl_.line_ = from._impl_.line_;
+ }
+ if (cached_has_bits & 0x00000008u) {
+ _this->_impl_.column_ = from._impl_.column_;
+ }
+ if (cached_has_bits & 0x00000010u) {
+ _this->_impl_.issystem_ = from._impl_.issystem_;
+ }
+ if (cached_has_bits & 0x00000020u) {
+ _this->_impl_.isselfhosted_ = from._impl_.isselfhosted_;
+ }
+ _this->_impl_._has_bits_[0] |= cached_has_bits;
+ }
+ switch (from.SourceOrRef_case()) {
+ case kSource: {
+ _this->_internal_set_source(from._internal_source());
+ break;
+ }
+ case kSourceRef: {
+ _this->_internal_set_sourceref(from._internal_sourceref());
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ _this->_internal_set_functiondisplayname(from._internal_functiondisplayname());
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ _this->_internal_set_functiondisplaynameref(from._internal_functiondisplaynameref());
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+}
+
+void StackFrame_Data::CopyFrom(const StackFrame_Data& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.StackFrame.Data)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool StackFrame_Data::IsInitialized() const {
+ return true;
+}
+
+void StackFrame_Data::InternalSwap(StackFrame_Data* other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]);
+ ::PROTOBUF_NAMESPACE_ID::internal::memswap<
+ PROTOBUF_FIELD_OFFSET(StackFrame_Data, _impl_.isselfhosted_)
+ + sizeof(StackFrame_Data::_impl_.isselfhosted_)
+ - PROTOBUF_FIELD_OFFSET(StackFrame_Data, _impl_.parent_)>(
+ reinterpret_cast<char*>(&_impl_.parent_),
+ reinterpret_cast<char*>(&other->_impl_.parent_));
+ swap(_impl_.SourceOrRef_, other->_impl_.SourceOrRef_);
+ swap(_impl_.FunctionDisplayNameOrRef_, other->_impl_.FunctionDisplayNameOrRef_);
+ swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
+ swap(_impl_._oneof_case_[1], other->_impl_._oneof_case_[1]);
+}
+
+std::string StackFrame_Data::GetTypeName() const {
+ return "mozilla.devtools.protobuf.StackFrame.Data";
+}
+
+
+// ===================================================================
+
+class StackFrame::_Internal {
+ public:
+ static const ::mozilla::devtools::protobuf::StackFrame_Data& data(const StackFrame* msg);
+};
+
+const ::mozilla::devtools::protobuf::StackFrame_Data&
+StackFrame::_Internal::data(const StackFrame* msg) {
+ return *msg->_impl_.StackFrameType_.data_;
+}
+void StackFrame::set_allocated_data(::mozilla::devtools::protobuf::StackFrame_Data* data) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation();
+ clear_StackFrameType();
+ if (data) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena =
+ ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(data);
+ if (message_arena != submessage_arena) {
+ data = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+ message_arena, data, submessage_arena);
+ }
+ set_has_data();
+ _impl_.StackFrameType_.data_ = data;
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.StackFrame.data)
+}
+StackFrame::StackFrame(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) {
+ SharedCtor(arena, is_message_owned);
+ // @@protoc_insertion_point(arena_constructor:mozilla.devtools.protobuf.StackFrame)
+}
+StackFrame::StackFrame(const StackFrame& from)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite() {
+ StackFrame* const _this = this; (void)_this;
+ new (&_impl_) Impl_{
+ decltype(_impl_.StackFrameType_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}};
+
+ _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+ clear_has_StackFrameType();
+ switch (from.StackFrameType_case()) {
+ case kData: {
+ _this->_internal_mutable_data()->::mozilla::devtools::protobuf::StackFrame_Data::MergeFrom(
+ from._internal_data());
+ break;
+ }
+ case kRef: {
+ _this->_internal_set_ref(from._internal_ref());
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.StackFrame)
+}
+
+inline void StackFrame::SharedCtor(
+ ::_pb::Arena* arena, bool is_message_owned) {
+ (void)arena;
+ (void)is_message_owned;
+ new (&_impl_) Impl_{
+ decltype(_impl_.StackFrameType_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , /*decltype(_impl_._oneof_case_)*/{}
+ };
+ clear_has_StackFrameType();
+}
+
+StackFrame::~StackFrame() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.StackFrame)
+ if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) {
+ (void)arena;
+ return;
+ }
+ SharedDtor();
+}
+
+inline void StackFrame::SharedDtor() {
+ GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
+ if (has_StackFrameType()) {
+ clear_StackFrameType();
+ }
+}
+
+void StackFrame::SetCachedSize(int size) const {
+ _impl_._cached_size_.Set(size);
+}
+
+void StackFrame::clear_StackFrameType() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.StackFrame)
+ switch (StackFrameType_case()) {
+ case kData: {
+ if (GetArenaForAllocation() == nullptr) {
+ delete _impl_.StackFrameType_.data_;
+ }
+ break;
+ }
+ case kRef: {
+ // No need to clear
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[0] = STACKFRAMETYPE_NOT_SET;
+}
+
+
+void StackFrame::Clear() {
+// @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.StackFrame)
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ clear_StackFrameType();
+ _internal_metadata_.Clear<std::string>();
+}
+
+const char* StackFrame::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+ while (!ctx->Done(&ptr)) {
+ uint32_t tag;
+ ptr = ::_pbi::ReadTag(ptr, &tag);
+ switch (tag >> 3) {
+ // .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ case 1:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 10)) {
+ ptr = ctx->ParseMessage(_internal_mutable_data(), ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 ref = 2;
+ case 2:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 16)) {
+ _internal_set_ref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ default:
+ goto handle_unusual;
+ } // switch
+ handle_unusual:
+ if ((tag == 0) || ((tag & 7) == 4)) {
+ CHK_(ptr);
+ ctx->SetLastTag(tag);
+ goto message_done;
+ }
+ ptr = UnknownFieldParse(
+ tag,
+ _internal_metadata_.mutable_unknown_fields<std::string>(),
+ ptr, ctx);
+ CHK_(ptr != nullptr);
+ } // while
+message_done:
+ return ptr;
+failure:
+ ptr = nullptr;
+ goto message_done;
+#undef CHK_
+}
+
+uint8_t* StackFrame::_InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.StackFrame)
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ switch (StackFrameType_case()) {
+ case kData: {
+ target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+ InternalWriteMessage(1, _Internal::data(this),
+ _Internal::data(this).GetCachedSize(), target, stream);
+ break;
+ }
+ case kRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(2, this->_internal_ref(), target);
+ break;
+ }
+ default: ;
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
+ static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.StackFrame)
+ return target;
+}
+
+size_t StackFrame::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.StackFrame)
+ size_t total_size = 0;
+
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ switch (StackFrameType_case()) {
+ // .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ case kData: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+ *_impl_.StackFrameType_.data_);
+ break;
+ }
+ // uint64 ref = 2;
+ case kRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_ref());
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size();
+ }
+ int cached_size = ::_pbi::ToCachedSize(total_size);
+ SetCachedSize(cached_size);
+ return total_size;
+}
+
+void StackFrame::CheckTypeAndMergeFrom(
+ const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) {
+ MergeFrom(*::_pbi::DownCast<const StackFrame*>(
+ &from));
+}
+
+void StackFrame::MergeFrom(const StackFrame& from) {
+ StackFrame* const _this = this;
+ // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.devtools.protobuf.StackFrame)
+ GOOGLE_DCHECK_NE(&from, _this);
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ switch (from.StackFrameType_case()) {
+ case kData: {
+ _this->_internal_mutable_data()->::mozilla::devtools::protobuf::StackFrame_Data::MergeFrom(
+ from._internal_data());
+ break;
+ }
+ case kRef: {
+ _this->_internal_set_ref(from._internal_ref());
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+}
+
+void StackFrame::CopyFrom(const StackFrame& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.StackFrame)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool StackFrame::IsInitialized() const {
+ return true;
+}
+
+void StackFrame::InternalSwap(StackFrame* other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_.StackFrameType_, other->_impl_.StackFrameType_);
+ swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
+}
+
+std::string StackFrame::GetTypeName() const {
+ return "mozilla.devtools.protobuf.StackFrame";
+}
+
+
+// ===================================================================
+
+class Node::_Internal {
+ public:
+ using HasBits = decltype(std::declval<Node>()._impl_._has_bits_);
+ static void set_has_id(HasBits* has_bits) {
+ (*has_bits)[0] |= 2u;
+ }
+ static void set_has_size(HasBits* has_bits) {
+ (*has_bits)[0] |= 4u;
+ }
+ static const ::mozilla::devtools::protobuf::StackFrame& allocationstack(const Node* msg);
+ static void set_has_allocationstack(HasBits* has_bits) {
+ (*has_bits)[0] |= 1u;
+ }
+ static void set_has_coarsetype(HasBits* has_bits) {
+ (*has_bits)[0] |= 8u;
+ }
+};
+
+const ::mozilla::devtools::protobuf::StackFrame&
+Node::_Internal::allocationstack(const Node* msg) {
+ return *msg->_impl_.allocationstack_;
+}
+Node::Node(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) {
+ SharedCtor(arena, is_message_owned);
+ // @@protoc_insertion_point(arena_constructor:mozilla.devtools.protobuf.Node)
+}
+Node::Node(const Node& from)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite() {
+ Node* const _this = this; (void)_this;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){from._impl_._has_bits_}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.edges_){from._impl_.edges_}
+ , decltype(_impl_.allocationstack_){nullptr}
+ , decltype(_impl_.id_){}
+ , decltype(_impl_.size_){}
+ , decltype(_impl_.coarsetype_){}
+ , decltype(_impl_.TypeNameOrRef_){}
+ , decltype(_impl_.JSObjectClassNameOrRef_){}
+ , decltype(_impl_.ScriptFilenameOrRef_){}
+ , decltype(_impl_.descriptiveTypeNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}};
+
+ _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+ if (from._internal_has_allocationstack()) {
+ _this->_impl_.allocationstack_ = new ::mozilla::devtools::protobuf::StackFrame(*from._impl_.allocationstack_);
+ }
+ ::memcpy(&_impl_.id_, &from._impl_.id_,
+ static_cast<size_t>(reinterpret_cast<char*>(&_impl_.coarsetype_) -
+ reinterpret_cast<char*>(&_impl_.id_)) + sizeof(_impl_.coarsetype_));
+ clear_has_TypeNameOrRef();
+ switch (from.TypeNameOrRef_case()) {
+ case kTypeName: {
+ _this->_internal_set_typename_(from._internal_typename_());
+ break;
+ }
+ case kTypeNameRef: {
+ _this->_internal_set_typenameref(from._internal_typenameref());
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ clear_has_JSObjectClassNameOrRef();
+ switch (from.JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ _this->_internal_set_jsobjectclassname(from._internal_jsobjectclassname());
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ _this->_internal_set_jsobjectclassnameref(from._internal_jsobjectclassnameref());
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ clear_has_ScriptFilenameOrRef();
+ switch (from.ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ _this->_internal_set_scriptfilename(from._internal_scriptfilename());
+ break;
+ }
+ case kScriptFilenameRef: {
+ _this->_internal_set_scriptfilenameref(from._internal_scriptfilenameref());
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ clear_has_descriptiveTypeNameOrRef();
+ switch (from.descriptiveTypeNameOrRef_case()) {
+ case kDescriptiveTypeName: {
+ _this->_internal_set_descriptivetypename(from._internal_descriptivetypename());
+ break;
+ }
+ case kDescriptiveTypeNameRef: {
+ _this->_internal_set_descriptivetypenameref(from._internal_descriptivetypenameref());
+ break;
+ }
+ case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Node)
+}
+
+inline void Node::SharedCtor(
+ ::_pb::Arena* arena, bool is_message_owned) {
+ (void)arena;
+ (void)is_message_owned;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.edges_){arena}
+ , decltype(_impl_.allocationstack_){nullptr}
+ , decltype(_impl_.id_){uint64_t{0u}}
+ , decltype(_impl_.size_){uint64_t{0u}}
+ , decltype(_impl_.coarsetype_){0u}
+ , decltype(_impl_.TypeNameOrRef_){}
+ , decltype(_impl_.JSObjectClassNameOrRef_){}
+ , decltype(_impl_.ScriptFilenameOrRef_){}
+ , decltype(_impl_.descriptiveTypeNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}
+ };
+ clear_has_TypeNameOrRef();
+ clear_has_JSObjectClassNameOrRef();
+ clear_has_ScriptFilenameOrRef();
+ clear_has_descriptiveTypeNameOrRef();
+}
+
+Node::~Node() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Node)
+ if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) {
+ (void)arena;
+ return;
+ }
+ SharedDtor();
+}
+
+inline void Node::SharedDtor() {
+ GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
+ _impl_.edges_.~RepeatedPtrField();
+ if (this != internal_default_instance()) delete _impl_.allocationstack_;
+ if (has_TypeNameOrRef()) {
+ clear_TypeNameOrRef();
+ }
+ if (has_JSObjectClassNameOrRef()) {
+ clear_JSObjectClassNameOrRef();
+ }
+ if (has_ScriptFilenameOrRef()) {
+ clear_ScriptFilenameOrRef();
+ }
+ if (has_descriptiveTypeNameOrRef()) {
+ clear_descriptiveTypeNameOrRef();
+ }
+}
+
+void Node::SetCachedSize(int size) const {
+ _impl_._cached_size_.Set(size);
+}
+
+void Node::clear_TypeNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Node)
+ switch (TypeNameOrRef_case()) {
+ case kTypeName: {
+ _impl_.TypeNameOrRef_.typename__.Destroy();
+ break;
+ }
+ case kTypeNameRef: {
+ // No need to clear
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[0] = TYPENAMEORREF_NOT_SET;
+}
+
+void Node::clear_JSObjectClassNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Node)
+ switch (JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Destroy();
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ // No need to clear
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+
+void Node::clear_ScriptFilenameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Node)
+ switch (ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.Destroy();
+ break;
+ }
+ case kScriptFilenameRef: {
+ // No need to clear
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
+
+void Node::clear_descriptiveTypeNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Node)
+ switch (descriptiveTypeNameOrRef_case()) {
+ case kDescriptiveTypeName: {
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Destroy();
+ break;
+ }
+ case kDescriptiveTypeNameRef: {
+ // No need to clear
+ break;
+ }
+ case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[3] = DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+
+
+void Node::Clear() {
+// @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.Node)
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ _impl_.edges_.Clear();
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ GOOGLE_DCHECK(_impl_.allocationstack_ != nullptr);
+ _impl_.allocationstack_->Clear();
+ }
+ if (cached_has_bits & 0x0000000eu) {
+ ::memset(&_impl_.id_, 0, static_cast<size_t>(
+ reinterpret_cast<char*>(&_impl_.coarsetype_) -
+ reinterpret_cast<char*>(&_impl_.id_)) + sizeof(_impl_.coarsetype_));
+ }
+ clear_TypeNameOrRef();
+ clear_JSObjectClassNameOrRef();
+ clear_ScriptFilenameOrRef();
+ clear_descriptiveTypeNameOrRef();
+ _impl_._has_bits_.Clear();
+ _internal_metadata_.Clear<std::string>();
+}
+
+const char* Node::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+ _Internal::HasBits has_bits{};
+ while (!ctx->Done(&ptr)) {
+ uint32_t tag;
+ ptr = ::_pbi::ReadTag(ptr, &tag);
+ switch (tag >> 3) {
+ // optional uint64 id = 1;
+ case 1:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) {
+ _Internal::set_has_id(&has_bits);
+ _impl_.id_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes typeName = 2;
+ case 2:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) {
+ auto str = _internal_mutable_typename_();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 typeNameRef = 3;
+ case 3:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) {
+ _internal_set_typenameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional uint64 size = 4;
+ case 4:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 32)) {
+ _Internal::set_has_size(&has_bits);
+ _impl_.size_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ case 5:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 42)) {
+ ptr -= 1;
+ do {
+ ptr += 1;
+ ptr = ctx->ParseMessage(_internal_add_edges(), ptr);
+ CHK_(ptr);
+ if (!ctx->DataAvailable(ptr)) break;
+ } while (::PROTOBUF_NAMESPACE_ID::internal::ExpectTag<42>(ptr));
+ } else
+ goto handle_unusual;
+ continue;
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ case 6:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 50)) {
+ ptr = ctx->ParseMessage(_internal_mutable_allocationstack(), ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes jsObjectClassName = 7;
+ case 7:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 58)) {
+ auto str = _internal_mutable_jsobjectclassname();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 jsObjectClassNameRef = 8;
+ case 8:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 64)) {
+ _internal_set_jsobjectclassnameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // optional uint32 coarseType = 9 [default = 0];
+ case 9:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 72)) {
+ _Internal::set_has_coarsetype(&has_bits);
+ _impl_.coarsetype_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint32(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes scriptFilename = 10;
+ case 10:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 82)) {
+ auto str = _internal_mutable_scriptfilename();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 scriptFilenameRef = 11;
+ case 11:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 88)) {
+ _internal_set_scriptfilenameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes descriptiveTypeName = 12;
+ case 12:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 98)) {
+ auto str = _internal_mutable_descriptivetypename();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 descriptiveTypeNameRef = 13;
+ case 13:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 104)) {
+ _internal_set_descriptivetypenameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ default:
+ goto handle_unusual;
+ } // switch
+ handle_unusual:
+ if ((tag == 0) || ((tag & 7) == 4)) {
+ CHK_(ptr);
+ ctx->SetLastTag(tag);
+ goto message_done;
+ }
+ ptr = UnknownFieldParse(
+ tag,
+ _internal_metadata_.mutable_unknown_fields<std::string>(),
+ ptr, ctx);
+ CHK_(ptr != nullptr);
+ } // while
+message_done:
+ _impl_._has_bits_.Or(has_bits);
+ return ptr;
+failure:
+ ptr = nullptr;
+ goto message_done;
+#undef CHK_
+}
+
+uint8_t* Node::_InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Node)
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ // optional uint64 id = 1;
+ if (cached_has_bits & 0x00000002u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_id(), target);
+ }
+
+ switch (TypeNameOrRef_case()) {
+ case kTypeName: {
+ target = stream->WriteBytesMaybeAliased(
+ 2, this->_internal_typename_(), target);
+ break;
+ }
+ case kTypeNameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(3, this->_internal_typenameref(), target);
+ break;
+ }
+ default: ;
+ }
+ // optional uint64 size = 4;
+ if (cached_has_bits & 0x00000004u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(4, this->_internal_size(), target);
+ }
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ for (unsigned i = 0,
+ n = static_cast<unsigned>(this->_internal_edges_size()); i < n; i++) {
+ const auto& repfield = this->_internal_edges(i);
+ target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+ InternalWriteMessage(5, repfield, repfield.GetCachedSize(), target, stream);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ if (cached_has_bits & 0x00000001u) {
+ target = ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::
+ InternalWriteMessage(6, _Internal::allocationstack(this),
+ _Internal::allocationstack(this).GetCachedSize(), target, stream);
+ }
+
+ switch (JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ target = stream->WriteBytesMaybeAliased(
+ 7, this->_internal_jsobjectclassname(), target);
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(8, this->_internal_jsobjectclassnameref(), target);
+ break;
+ }
+ default: ;
+ }
+ // optional uint32 coarseType = 9 [default = 0];
+ if (cached_has_bits & 0x00000008u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt32ToArray(9, this->_internal_coarsetype(), target);
+ }
+
+ switch (ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ target = stream->WriteBytesMaybeAliased(
+ 10, this->_internal_scriptfilename(), target);
+ break;
+ }
+ case kScriptFilenameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(11, this->_internal_scriptfilenameref(), target);
+ break;
+ }
+ default: ;
+ }
+ switch (descriptiveTypeNameOrRef_case()) {
+ case kDescriptiveTypeName: {
+ target = stream->WriteBytesMaybeAliased(
+ 12, this->_internal_descriptivetypename(), target);
+ break;
+ }
+ case kDescriptiveTypeNameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(13, this->_internal_descriptivetypenameref(), target);
+ break;
+ }
+ default: ;
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
+ static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Node)
+ return target;
+}
+
+size_t Node::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.Node)
+ size_t total_size = 0;
+
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ total_size += 1UL * this->_internal_edges_size();
+ for (const auto& msg : this->_impl_.edges_) {
+ total_size +=
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(msg);
+ }
+
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x0000000fu) {
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ if (cached_has_bits & 0x00000001u) {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::MessageSize(
+ *_impl_.allocationstack_);
+ }
+
+ // optional uint64 id = 1;
+ if (cached_has_bits & 0x00000002u) {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_id());
+ }
+
+ // optional uint64 size = 4;
+ if (cached_has_bits & 0x00000004u) {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_size());
+ }
+
+ // optional uint32 coarseType = 9 [default = 0];
+ if (cached_has_bits & 0x00000008u) {
+ total_size += ::_pbi::WireFormatLite::UInt32SizePlusOne(this->_internal_coarsetype());
+ }
+
+ }
+ switch (TypeNameOrRef_case()) {
+ // bytes typeName = 2;
+ case kTypeName: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_typename_());
+ break;
+ }
+ // uint64 typeNameRef = 3;
+ case kTypeNameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_typenameref());
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (JSObjectClassNameOrRef_case()) {
+ // bytes jsObjectClassName = 7;
+ case kJsObjectClassName: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_jsobjectclassname());
+ break;
+ }
+ // uint64 jsObjectClassNameRef = 8;
+ case kJsObjectClassNameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_jsobjectclassnameref());
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (ScriptFilenameOrRef_case()) {
+ // bytes scriptFilename = 10;
+ case kScriptFilename: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_scriptfilename());
+ break;
+ }
+ // uint64 scriptFilenameRef = 11;
+ case kScriptFilenameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_scriptfilenameref());
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (descriptiveTypeNameOrRef_case()) {
+ // bytes descriptiveTypeName = 12;
+ case kDescriptiveTypeName: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_descriptivetypename());
+ break;
+ }
+ // uint64 descriptiveTypeNameRef = 13;
+ case kDescriptiveTypeNameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_descriptivetypenameref());
+ break;
+ }
+ case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size();
+ }
+ int cached_size = ::_pbi::ToCachedSize(total_size);
+ SetCachedSize(cached_size);
+ return total_size;
+}
+
+void Node::CheckTypeAndMergeFrom(
+ const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) {
+ MergeFrom(*::_pbi::DownCast<const Node*>(
+ &from));
+}
+
+void Node::MergeFrom(const Node& from) {
+ Node* const _this = this;
+ // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.devtools.protobuf.Node)
+ GOOGLE_DCHECK_NE(&from, _this);
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ _this->_impl_.edges_.MergeFrom(from._impl_.edges_);
+ cached_has_bits = from._impl_._has_bits_[0];
+ if (cached_has_bits & 0x0000000fu) {
+ if (cached_has_bits & 0x00000001u) {
+ _this->_internal_mutable_allocationstack()->::mozilla::devtools::protobuf::StackFrame::MergeFrom(
+ from._internal_allocationstack());
+ }
+ if (cached_has_bits & 0x00000002u) {
+ _this->_impl_.id_ = from._impl_.id_;
+ }
+ if (cached_has_bits & 0x00000004u) {
+ _this->_impl_.size_ = from._impl_.size_;
+ }
+ if (cached_has_bits & 0x00000008u) {
+ _this->_impl_.coarsetype_ = from._impl_.coarsetype_;
+ }
+ _this->_impl_._has_bits_[0] |= cached_has_bits;
+ }
+ switch (from.TypeNameOrRef_case()) {
+ case kTypeName: {
+ _this->_internal_set_typename_(from._internal_typename_());
+ break;
+ }
+ case kTypeNameRef: {
+ _this->_internal_set_typenameref(from._internal_typenameref());
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ _this->_internal_set_jsobjectclassname(from._internal_jsobjectclassname());
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ _this->_internal_set_jsobjectclassnameref(from._internal_jsobjectclassnameref());
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ _this->_internal_set_scriptfilename(from._internal_scriptfilename());
+ break;
+ }
+ case kScriptFilenameRef: {
+ _this->_internal_set_scriptfilenameref(from._internal_scriptfilenameref());
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.descriptiveTypeNameOrRef_case()) {
+ case kDescriptiveTypeName: {
+ _this->_internal_set_descriptivetypename(from._internal_descriptivetypename());
+ break;
+ }
+ case kDescriptiveTypeNameRef: {
+ _this->_internal_set_descriptivetypenameref(from._internal_descriptivetypenameref());
+ break;
+ }
+ case DESCRIPTIVETYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+}
+
+void Node::CopyFrom(const Node& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.Node)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Node::IsInitialized() const {
+ return true;
+}
+
+void Node::InternalSwap(Node* other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]);
+ _impl_.edges_.InternalSwap(&other->_impl_.edges_);
+ ::PROTOBUF_NAMESPACE_ID::internal::memswap<
+ PROTOBUF_FIELD_OFFSET(Node, _impl_.coarsetype_)
+ + sizeof(Node::_impl_.coarsetype_)
+ - PROTOBUF_FIELD_OFFSET(Node, _impl_.allocationstack_)>(
+ reinterpret_cast<char*>(&_impl_.allocationstack_),
+ reinterpret_cast<char*>(&other->_impl_.allocationstack_));
+ swap(_impl_.TypeNameOrRef_, other->_impl_.TypeNameOrRef_);
+ swap(_impl_.JSObjectClassNameOrRef_, other->_impl_.JSObjectClassNameOrRef_);
+ swap(_impl_.ScriptFilenameOrRef_, other->_impl_.ScriptFilenameOrRef_);
+ swap(_impl_.descriptiveTypeNameOrRef_, other->_impl_.descriptiveTypeNameOrRef_);
+ swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
+ swap(_impl_._oneof_case_[1], other->_impl_._oneof_case_[1]);
+ swap(_impl_._oneof_case_[2], other->_impl_._oneof_case_[2]);
+ swap(_impl_._oneof_case_[3], other->_impl_._oneof_case_[3]);
+}
+
+std::string Node::GetTypeName() const {
+ return "mozilla.devtools.protobuf.Node";
+}
+
+
+// ===================================================================
+
+class Edge::_Internal {
+ public:
+ using HasBits = decltype(std::declval<Edge>()._impl_._has_bits_);
+ static void set_has_referent(HasBits* has_bits) {
+ (*has_bits)[0] |= 1u;
+ }
+};
+
+Edge::Edge(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite(arena, is_message_owned) {
+ SharedCtor(arena, is_message_owned);
+ // @@protoc_insertion_point(arena_constructor:mozilla.devtools.protobuf.Edge)
+}
+Edge::Edge(const Edge& from)
+ : ::PROTOBUF_NAMESPACE_ID::MessageLite() {
+ Edge* const _this = this; (void)_this;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){from._impl_._has_bits_}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.referent_){}
+ , decltype(_impl_.EdgeNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}};
+
+ _internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+ _this->_impl_.referent_ = from._impl_.referent_;
+ clear_has_EdgeNameOrRef();
+ switch (from.EdgeNameOrRef_case()) {
+ case kName: {
+ _this->_internal_set_name(from._internal_name());
+ break;
+ }
+ case kNameRef: {
+ _this->_internal_set_nameref(from._internal_nameref());
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Edge)
+}
+
+inline void Edge::SharedCtor(
+ ::_pb::Arena* arena, bool is_message_owned) {
+ (void)arena;
+ (void)is_message_owned;
+ new (&_impl_) Impl_{
+ decltype(_impl_._has_bits_){}
+ , /*decltype(_impl_._cached_size_)*/{}
+ , decltype(_impl_.referent_){uint64_t{0u}}
+ , decltype(_impl_.EdgeNameOrRef_){}
+ , /*decltype(_impl_._oneof_case_)*/{}
+ };
+ clear_has_EdgeNameOrRef();
+}
+
+Edge::~Edge() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Edge)
+ if (auto *arena = _internal_metadata_.DeleteReturnArena<std::string>()) {
+ (void)arena;
+ return;
+ }
+ SharedDtor();
+}
+
+inline void Edge::SharedDtor() {
+ GOOGLE_DCHECK(GetArenaForAllocation() == nullptr);
+ if (has_EdgeNameOrRef()) {
+ clear_EdgeNameOrRef();
+ }
+}
+
+void Edge::SetCachedSize(int size) const {
+ _impl_._cached_size_.Set(size);
+}
+
+void Edge::clear_EdgeNameOrRef() {
+// @@protoc_insertion_point(one_of_clear_start:mozilla.devtools.protobuf.Edge)
+ switch (EdgeNameOrRef_case()) {
+ case kName: {
+ _impl_.EdgeNameOrRef_.name_.Destroy();
+ break;
+ }
+ case kNameRef: {
+ // No need to clear
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _impl_._oneof_case_[0] = EDGENAMEORREF_NOT_SET;
+}
+
+
+void Edge::Clear() {
+// @@protoc_insertion_point(message_clear_start:mozilla.devtools.protobuf.Edge)
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ _impl_.referent_ = uint64_t{0u};
+ clear_EdgeNameOrRef();
+ _impl_._has_bits_.Clear();
+ _internal_metadata_.Clear<std::string>();
+}
+
+const char* Edge::_InternalParse(const char* ptr, ::_pbi::ParseContext* ctx) {
+#define CHK_(x) if (PROTOBUF_PREDICT_FALSE(!(x))) goto failure
+ _Internal::HasBits has_bits{};
+ while (!ctx->Done(&ptr)) {
+ uint32_t tag;
+ ptr = ::_pbi::ReadTag(ptr, &tag);
+ switch (tag >> 3) {
+ // optional uint64 referent = 1;
+ case 1:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 8)) {
+ _Internal::set_has_referent(&has_bits);
+ _impl_.referent_ = ::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // bytes name = 2;
+ case 2:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 18)) {
+ auto str = _internal_mutable_name();
+ ptr = ::_pbi::InlineGreedyStringParser(str, ptr, ctx);
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ // uint64 nameRef = 3;
+ case 3:
+ if (PROTOBUF_PREDICT_TRUE(static_cast<uint8_t>(tag) == 24)) {
+ _internal_set_nameref(::PROTOBUF_NAMESPACE_ID::internal::ReadVarint64(&ptr));
+ CHK_(ptr);
+ } else
+ goto handle_unusual;
+ continue;
+ default:
+ goto handle_unusual;
+ } // switch
+ handle_unusual:
+ if ((tag == 0) || ((tag & 7) == 4)) {
+ CHK_(ptr);
+ ctx->SetLastTag(tag);
+ goto message_done;
+ }
+ ptr = UnknownFieldParse(
+ tag,
+ _internal_metadata_.mutable_unknown_fields<std::string>(),
+ ptr, ctx);
+ CHK_(ptr != nullptr);
+ } // while
+message_done:
+ _impl_._has_bits_.Or(has_bits);
+ return ptr;
+failure:
+ ptr = nullptr;
+ goto message_done;
+#undef CHK_
+}
+
+uint8_t* Edge::_InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Edge)
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ cached_has_bits = _impl_._has_bits_[0];
+ // optional uint64 referent = 1;
+ if (cached_has_bits & 0x00000001u) {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(1, this->_internal_referent(), target);
+ }
+
+ switch (EdgeNameOrRef_case()) {
+ case kName: {
+ target = stream->WriteBytesMaybeAliased(
+ 2, this->_internal_name(), target);
+ break;
+ }
+ case kNameRef: {
+ target = stream->EnsureSpace(target);
+ target = ::_pbi::WireFormatLite::WriteUInt64ToArray(3, this->_internal_nameref(), target);
+ break;
+ }
+ default: ;
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ target = stream->WriteRaw(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).data(),
+ static_cast<int>(_internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size()), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Edge)
+ return target;
+}
+
+size_t Edge::ByteSizeLong() const {
+// @@protoc_insertion_point(message_byte_size_start:mozilla.devtools.protobuf.Edge)
+ size_t total_size = 0;
+
+ uint32_t cached_has_bits = 0;
+ // Prevent compiler warnings about cached_has_bits being unused
+ (void) cached_has_bits;
+
+ // optional uint64 referent = 1;
+ cached_has_bits = _impl_._has_bits_[0];
+ if (cached_has_bits & 0x00000001u) {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_referent());
+ }
+
+ switch (EdgeNameOrRef_case()) {
+ // bytes name = 2;
+ case kName: {
+ total_size += 1 +
+ ::PROTOBUF_NAMESPACE_ID::internal::WireFormatLite::BytesSize(
+ this->_internal_name());
+ break;
+ }
+ // uint64 nameRef = 3;
+ case kNameRef: {
+ total_size += ::_pbi::WireFormatLite::UInt64SizePlusOne(this->_internal_nameref());
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (PROTOBUF_PREDICT_FALSE(_internal_metadata_.have_unknown_fields())) {
+ total_size += _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString).size();
+ }
+ int cached_size = ::_pbi::ToCachedSize(total_size);
+ SetCachedSize(cached_size);
+ return total_size;
+}
+
+void Edge::CheckTypeAndMergeFrom(
+ const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) {
+ MergeFrom(*::_pbi::DownCast<const Edge*>(
+ &from));
+}
+
+void Edge::MergeFrom(const Edge& from) {
+ Edge* const _this = this;
+ // @@protoc_insertion_point(class_specific_merge_from_start:mozilla.devtools.protobuf.Edge)
+ GOOGLE_DCHECK_NE(&from, _this);
+ uint32_t cached_has_bits = 0;
+ (void) cached_has_bits;
+
+ if (from._internal_has_referent()) {
+ _this->_internal_set_referent(from._internal_referent());
+ }
+ switch (from.EdgeNameOrRef_case()) {
+ case kName: {
+ _this->_internal_set_name(from._internal_name());
+ break;
+ }
+ case kNameRef: {
+ _this->_internal_set_nameref(from._internal_nameref());
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _this->_internal_metadata_.MergeFrom<std::string>(from._internal_metadata_);
+}
+
+void Edge::CopyFrom(const Edge& from) {
+// @@protoc_insertion_point(class_specific_copy_from_start:mozilla.devtools.protobuf.Edge)
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Edge::IsInitialized() const {
+ return true;
+}
+
+void Edge::InternalSwap(Edge* other) {
+ using std::swap;
+ _internal_metadata_.InternalSwap(&other->_internal_metadata_);
+ swap(_impl_._has_bits_[0], other->_impl_._has_bits_[0]);
+ swap(_impl_.referent_, other->_impl_.referent_);
+ swap(_impl_.EdgeNameOrRef_, other->_impl_.EdgeNameOrRef_);
+ swap(_impl_._oneof_case_[0], other->_impl_._oneof_case_[0]);
+}
+
+std::string Edge::GetTypeName() const {
+ return "mozilla.devtools.protobuf.Edge";
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+PROTOBUF_NAMESPACE_OPEN
+template<> PROTOBUF_NOINLINE ::mozilla::devtools::protobuf::Metadata*
+Arena::CreateMaybeMessage< ::mozilla::devtools::protobuf::Metadata >(Arena* arena) {
+ return Arena::CreateMessageInternal< ::mozilla::devtools::protobuf::Metadata >(arena);
+}
+template<> PROTOBUF_NOINLINE ::mozilla::devtools::protobuf::StackFrame_Data*
+Arena::CreateMaybeMessage< ::mozilla::devtools::protobuf::StackFrame_Data >(Arena* arena) {
+ return Arena::CreateMessageInternal< ::mozilla::devtools::protobuf::StackFrame_Data >(arena);
+}
+template<> PROTOBUF_NOINLINE ::mozilla::devtools::protobuf::StackFrame*
+Arena::CreateMaybeMessage< ::mozilla::devtools::protobuf::StackFrame >(Arena* arena) {
+ return Arena::CreateMessageInternal< ::mozilla::devtools::protobuf::StackFrame >(arena);
+}
+template<> PROTOBUF_NOINLINE ::mozilla::devtools::protobuf::Node*
+Arena::CreateMaybeMessage< ::mozilla::devtools::protobuf::Node >(Arena* arena) {
+ return Arena::CreateMessageInternal< ::mozilla::devtools::protobuf::Node >(arena);
+}
+template<> PROTOBUF_NOINLINE ::mozilla::devtools::protobuf::Edge*
+Arena::CreateMaybeMessage< ::mozilla::devtools::protobuf::Edge >(Arena* arena) {
+ return Arena::CreateMessageInternal< ::mozilla::devtools::protobuf::Edge >(arena);
+}
+PROTOBUF_NAMESPACE_CLOSE
+
+// @@protoc_insertion_point(global_scope)
+#include <google/protobuf/port_undef.inc>
diff --git a/devtools/shared/heapsnapshot/CoreDump.pb.h b/devtools/shared/heapsnapshot/CoreDump.pb.h
new file mode 100644
index 0000000000..734c38c017
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.h
@@ -0,0 +1,2883 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: CoreDump.proto
+
+#ifndef GOOGLE_PROTOBUF_INCLUDED_CoreDump_2eproto
+#define GOOGLE_PROTOBUF_INCLUDED_CoreDump_2eproto
+
+#include <limits>
+#include <string>
+
+#include <google/protobuf/port_def.inc>
+#if PROTOBUF_VERSION < 3021000
+#error This file was generated by a newer version of protoc which is
+#error incompatible with your Protocol Buffer headers. Please update
+#error your headers.
+#endif
+#if 3021006 < PROTOBUF_MIN_PROTOC_VERSION
+#error This file was generated by an older version of protoc which is
+#error incompatible with your Protocol Buffer headers. Please
+#error regenerate this file with a newer version of protoc.
+#endif
+
+#include <google/protobuf/port_undef.inc>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/arena.h>
+#include <google/protobuf/arenastring.h>
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/metadata_lite.h>
+#include <google/protobuf/message_lite.h>
+#include <google/protobuf/repeated_field.h> // IWYU pragma: export
+#include <google/protobuf/extension_set.h> // IWYU pragma: export
+// @@protoc_insertion_point(includes)
+#include <google/protobuf/port_def.inc>
+#define PROTOBUF_INTERNAL_EXPORT_CoreDump_2eproto
+PROTOBUF_NAMESPACE_OPEN
+namespace internal {
+class AnyMetadata;
+} // namespace internal
+PROTOBUF_NAMESPACE_CLOSE
+
+// Internal implementation detail -- do not use these members.
+struct TableStruct_CoreDump_2eproto {
+ static const uint32_t offsets[];
+};
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+class Edge;
+struct EdgeDefaultTypeInternal;
+extern EdgeDefaultTypeInternal _Edge_default_instance_;
+class Metadata;
+struct MetadataDefaultTypeInternal;
+extern MetadataDefaultTypeInternal _Metadata_default_instance_;
+class Node;
+struct NodeDefaultTypeInternal;
+extern NodeDefaultTypeInternal _Node_default_instance_;
+class StackFrame;
+struct StackFrameDefaultTypeInternal;
+extern StackFrameDefaultTypeInternal _StackFrame_default_instance_;
+class StackFrame_Data;
+struct StackFrame_DataDefaultTypeInternal;
+extern StackFrame_DataDefaultTypeInternal _StackFrame_Data_default_instance_;
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+PROTOBUF_NAMESPACE_OPEN
+template<> ::mozilla::devtools::protobuf::Edge* Arena::CreateMaybeMessage<::mozilla::devtools::protobuf::Edge>(Arena*);
+template<> ::mozilla::devtools::protobuf::Metadata* Arena::CreateMaybeMessage<::mozilla::devtools::protobuf::Metadata>(Arena*);
+template<> ::mozilla::devtools::protobuf::Node* Arena::CreateMaybeMessage<::mozilla::devtools::protobuf::Node>(Arena*);
+template<> ::mozilla::devtools::protobuf::StackFrame* Arena::CreateMaybeMessage<::mozilla::devtools::protobuf::StackFrame>(Arena*);
+template<> ::mozilla::devtools::protobuf::StackFrame_Data* Arena::CreateMaybeMessage<::mozilla::devtools::protobuf::StackFrame_Data>(Arena*);
+PROTOBUF_NAMESPACE_CLOSE
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+// ===================================================================
+
+class Metadata final :
+ public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.Metadata) */ {
+ public:
+ inline Metadata() : Metadata(nullptr) {}
+ ~Metadata() override;
+ explicit PROTOBUF_CONSTEXPR Metadata(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);
+
+ Metadata(const Metadata& from);
+ Metadata(Metadata&& from) noexcept
+ : Metadata() {
+ *this = ::std::move(from);
+ }
+
+ inline Metadata& operator=(const Metadata& from) {
+ CopyFrom(from);
+ return *this;
+ }
+ inline Metadata& operator=(Metadata&& from) noexcept {
+ if (this == &from) return *this;
+ if (GetOwningArena() == from.GetOwningArena()
+ #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
+ && GetOwningArena() != nullptr
+ #endif // !PROTOBUF_FORCE_COPY_IN_MOVE
+ ) {
+ InternalSwap(&from);
+ } else {
+ CopyFrom(from);
+ }
+ return *this;
+ }
+
+ inline const std::string& unknown_fields() const {
+ return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString);
+ }
+ inline std::string* mutable_unknown_fields() {
+ return _internal_metadata_.mutable_unknown_fields<std::string>();
+ }
+
+ static const Metadata& default_instance() {
+ return *internal_default_instance();
+ }
+ static inline const Metadata* internal_default_instance() {
+ return reinterpret_cast<const Metadata*>(
+ &_Metadata_default_instance_);
+ }
+ static constexpr int kIndexInFileMessages =
+ 0;
+
+ friend void swap(Metadata& a, Metadata& b) {
+ a.Swap(&b);
+ }
+ inline void Swap(Metadata* other) {
+ if (other == this) return;
+ #ifdef PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() != nullptr &&
+ GetOwningArena() == other->GetOwningArena()) {
+ #else // PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() == other->GetOwningArena()) {
+ #endif // !PROTOBUF_FORCE_COPY_IN_SWAP
+ InternalSwap(other);
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other);
+ }
+ }
+ void UnsafeArenaSwap(Metadata* other) {
+ if (other == this) return;
+ GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena());
+ InternalSwap(other);
+ }
+
+ // implements Message ----------------------------------------------
+
+ Metadata* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final {
+ return CreateMaybeMessage<Metadata>(arena);
+ }
+ void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final;
+ void CopyFrom(const Metadata& from);
+ void MergeFrom(const Metadata& from);
+ PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+ bool IsInitialized() const final;
+
+ size_t ByteSizeLong() const final;
+ const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+ uint8_t* _InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+ int GetCachedSize() const final { return _impl_._cached_size_.Get(); }
+
+ private:
+ void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned);
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ void InternalSwap(Metadata* other);
+
+ private:
+ friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+ static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+ return "mozilla.devtools.protobuf.Metadata";
+ }
+ protected:
+ explicit Metadata(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned = false);
+ public:
+
+ std::string GetTypeName() const final;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ enum : int {
+ kTimeStampFieldNumber = 1,
+ };
+ // optional uint64 timeStamp = 1;
+ bool has_timestamp() const;
+ private:
+ bool _internal_has_timestamp() const;
+ public:
+ void clear_timestamp();
+ uint64_t timestamp() const;
+ void set_timestamp(uint64_t value);
+ private:
+ uint64_t _internal_timestamp() const;
+ void _internal_set_timestamp(uint64_t value);
+ public:
+
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Metadata)
+ private:
+ class _Internal;
+
+ template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
+ typedef void InternalArenaConstructable_;
+ typedef void DestructorSkippable_;
+ struct Impl_ {
+ ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_;
+ mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+ uint64_t timestamp_;
+ };
+ union { Impl_ _impl_; };
+ friend struct ::TableStruct_CoreDump_2eproto;
+};
+// -------------------------------------------------------------------
+
+class StackFrame_Data final :
+ public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.StackFrame.Data) */ {
+ public:
+ inline StackFrame_Data() : StackFrame_Data(nullptr) {}
+ ~StackFrame_Data() override;
+ explicit PROTOBUF_CONSTEXPR StackFrame_Data(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);
+
+ StackFrame_Data(const StackFrame_Data& from);
+ StackFrame_Data(StackFrame_Data&& from) noexcept
+ : StackFrame_Data() {
+ *this = ::std::move(from);
+ }
+
+ inline StackFrame_Data& operator=(const StackFrame_Data& from) {
+ CopyFrom(from);
+ return *this;
+ }
+ inline StackFrame_Data& operator=(StackFrame_Data&& from) noexcept {
+ if (this == &from) return *this;
+ if (GetOwningArena() == from.GetOwningArena()
+ #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
+ && GetOwningArena() != nullptr
+ #endif // !PROTOBUF_FORCE_COPY_IN_MOVE
+ ) {
+ InternalSwap(&from);
+ } else {
+ CopyFrom(from);
+ }
+ return *this;
+ }
+
+ inline const std::string& unknown_fields() const {
+ return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString);
+ }
+ inline std::string* mutable_unknown_fields() {
+ return _internal_metadata_.mutable_unknown_fields<std::string>();
+ }
+
+ static const StackFrame_Data& default_instance() {
+ return *internal_default_instance();
+ }
+ enum SourceOrRefCase {
+ kSource = 5,
+ kSourceRef = 6,
+ SOURCEORREF_NOT_SET = 0,
+ };
+
+ enum FunctionDisplayNameOrRefCase {
+ kFunctionDisplayName = 7,
+ kFunctionDisplayNameRef = 8,
+ FUNCTIONDISPLAYNAMEORREF_NOT_SET = 0,
+ };
+
+ static inline const StackFrame_Data* internal_default_instance() {
+ return reinterpret_cast<const StackFrame_Data*>(
+ &_StackFrame_Data_default_instance_);
+ }
+ static constexpr int kIndexInFileMessages =
+ 1;
+
+ friend void swap(StackFrame_Data& a, StackFrame_Data& b) {
+ a.Swap(&b);
+ }
+ inline void Swap(StackFrame_Data* other) {
+ if (other == this) return;
+ #ifdef PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() != nullptr &&
+ GetOwningArena() == other->GetOwningArena()) {
+ #else // PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() == other->GetOwningArena()) {
+ #endif // !PROTOBUF_FORCE_COPY_IN_SWAP
+ InternalSwap(other);
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other);
+ }
+ }
+ void UnsafeArenaSwap(StackFrame_Data* other) {
+ if (other == this) return;
+ GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena());
+ InternalSwap(other);
+ }
+
+ // implements Message ----------------------------------------------
+
+ StackFrame_Data* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final {
+ return CreateMaybeMessage<StackFrame_Data>(arena);
+ }
+ void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final;
+ void CopyFrom(const StackFrame_Data& from);
+ void MergeFrom(const StackFrame_Data& from);
+ PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+ bool IsInitialized() const final;
+
+ size_t ByteSizeLong() const final;
+ const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+ uint8_t* _InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+ int GetCachedSize() const final { return _impl_._cached_size_.Get(); }
+
+ private:
+ void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned);
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ void InternalSwap(StackFrame_Data* other);
+
+ private:
+ friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+ static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+ return "mozilla.devtools.protobuf.StackFrame.Data";
+ }
+ protected:
+ explicit StackFrame_Data(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned = false);
+ public:
+
+ std::string GetTypeName() const final;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ enum : int {
+ kParentFieldNumber = 2,
+ kIdFieldNumber = 1,
+ kLineFieldNumber = 3,
+ kColumnFieldNumber = 4,
+ kIsSystemFieldNumber = 9,
+ kIsSelfHostedFieldNumber = 10,
+ kSourceFieldNumber = 5,
+ kSourceRefFieldNumber = 6,
+ kFunctionDisplayNameFieldNumber = 7,
+ kFunctionDisplayNameRefFieldNumber = 8,
+ };
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ bool has_parent() const;
+ private:
+ bool _internal_has_parent() const;
+ public:
+ void clear_parent();
+ const ::mozilla::devtools::protobuf::StackFrame& parent() const;
+ PROTOBUF_NODISCARD ::mozilla::devtools::protobuf::StackFrame* release_parent();
+ ::mozilla::devtools::protobuf::StackFrame* mutable_parent();
+ void set_allocated_parent(::mozilla::devtools::protobuf::StackFrame* parent);
+ private:
+ const ::mozilla::devtools::protobuf::StackFrame& _internal_parent() const;
+ ::mozilla::devtools::protobuf::StackFrame* _internal_mutable_parent();
+ public:
+ void unsafe_arena_set_allocated_parent(
+ ::mozilla::devtools::protobuf::StackFrame* parent);
+ ::mozilla::devtools::protobuf::StackFrame* unsafe_arena_release_parent();
+
+ // optional uint64 id = 1;
+ bool has_id() const;
+ private:
+ bool _internal_has_id() const;
+ public:
+ void clear_id();
+ uint64_t id() const;
+ void set_id(uint64_t value);
+ private:
+ uint64_t _internal_id() const;
+ void _internal_set_id(uint64_t value);
+ public:
+
+ // optional uint32 line = 3;
+ bool has_line() const;
+ private:
+ bool _internal_has_line() const;
+ public:
+ void clear_line();
+ uint32_t line() const;
+ void set_line(uint32_t value);
+ private:
+ uint32_t _internal_line() const;
+ void _internal_set_line(uint32_t value);
+ public:
+
+ // optional uint32 column = 4;
+ bool has_column() const;
+ private:
+ bool _internal_has_column() const;
+ public:
+ void clear_column();
+ uint32_t column() const;
+ void set_column(uint32_t value);
+ private:
+ uint32_t _internal_column() const;
+ void _internal_set_column(uint32_t value);
+ public:
+
+ // optional bool isSystem = 9;
+ bool has_issystem() const;
+ private:
+ bool _internal_has_issystem() const;
+ public:
+ void clear_issystem();
+ bool issystem() const;
+ void set_issystem(bool value);
+ private:
+ bool _internal_issystem() const;
+ void _internal_set_issystem(bool value);
+ public:
+
+ // optional bool isSelfHosted = 10;
+ bool has_isselfhosted() const;
+ private:
+ bool _internal_has_isselfhosted() const;
+ public:
+ void clear_isselfhosted();
+ bool isselfhosted() const;
+ void set_isselfhosted(bool value);
+ private:
+ bool _internal_isselfhosted() const;
+ void _internal_set_isselfhosted(bool value);
+ public:
+
+ // bytes source = 5;
+ bool has_source() const;
+ private:
+ bool _internal_has_source() const;
+ public:
+ void clear_source();
+ const std::string& source() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_source(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_source();
+ PROTOBUF_NODISCARD std::string* release_source();
+ void set_allocated_source(std::string* source);
+ private:
+ const std::string& _internal_source() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_source(const std::string& value);
+ std::string* _internal_mutable_source();
+ public:
+
+ // uint64 sourceRef = 6;
+ bool has_sourceref() const;
+ private:
+ bool _internal_has_sourceref() const;
+ public:
+ void clear_sourceref();
+ uint64_t sourceref() const;
+ void set_sourceref(uint64_t value);
+ private:
+ uint64_t _internal_sourceref() const;
+ void _internal_set_sourceref(uint64_t value);
+ public:
+
+ // bytes functionDisplayName = 7;
+ bool has_functiondisplayname() const;
+ private:
+ bool _internal_has_functiondisplayname() const;
+ public:
+ void clear_functiondisplayname();
+ const std::string& functiondisplayname() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_functiondisplayname(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_functiondisplayname();
+ PROTOBUF_NODISCARD std::string* release_functiondisplayname();
+ void set_allocated_functiondisplayname(std::string* functiondisplayname);
+ private:
+ const std::string& _internal_functiondisplayname() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_functiondisplayname(const std::string& value);
+ std::string* _internal_mutable_functiondisplayname();
+ public:
+
+ // uint64 functionDisplayNameRef = 8;
+ bool has_functiondisplaynameref() const;
+ private:
+ bool _internal_has_functiondisplaynameref() const;
+ public:
+ void clear_functiondisplaynameref();
+ uint64_t functiondisplaynameref() const;
+ void set_functiondisplaynameref(uint64_t value);
+ private:
+ uint64_t _internal_functiondisplaynameref() const;
+ void _internal_set_functiondisplaynameref(uint64_t value);
+ public:
+
+ void clear_SourceOrRef();
+ SourceOrRefCase SourceOrRef_case() const;
+ void clear_FunctionDisplayNameOrRef();
+ FunctionDisplayNameOrRefCase FunctionDisplayNameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.StackFrame.Data)
+ private:
+ class _Internal;
+ void set_has_source();
+ void set_has_sourceref();
+ void set_has_functiondisplayname();
+ void set_has_functiondisplaynameref();
+
+ inline bool has_SourceOrRef() const;
+ inline void clear_has_SourceOrRef();
+
+ inline bool has_FunctionDisplayNameOrRef() const;
+ inline void clear_has_FunctionDisplayNameOrRef();
+
+ template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
+ typedef void InternalArenaConstructable_;
+ typedef void DestructorSkippable_;
+ struct Impl_ {
+ ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_;
+ mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+ ::mozilla::devtools::protobuf::StackFrame* parent_;
+ uint64_t id_;
+ uint32_t line_;
+ uint32_t column_;
+ bool issystem_;
+ bool isselfhosted_;
+ union SourceOrRefUnion {
+ constexpr SourceOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr source_;
+ uint64_t sourceref_;
+ } SourceOrRef_;
+ union FunctionDisplayNameOrRefUnion {
+ constexpr FunctionDisplayNameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr functiondisplayname_;
+ uint64_t functiondisplaynameref_;
+ } FunctionDisplayNameOrRef_;
+ uint32_t _oneof_case_[2];
+
+ };
+ union { Impl_ _impl_; };
+ friend struct ::TableStruct_CoreDump_2eproto;
+};
+// -------------------------------------------------------------------
+
+class StackFrame final :
+ public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.StackFrame) */ {
+ public:
+ inline StackFrame() : StackFrame(nullptr) {}
+ ~StackFrame() override;
+ explicit PROTOBUF_CONSTEXPR StackFrame(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);
+
+ StackFrame(const StackFrame& from);
+ StackFrame(StackFrame&& from) noexcept
+ : StackFrame() {
+ *this = ::std::move(from);
+ }
+
+ inline StackFrame& operator=(const StackFrame& from) {
+ CopyFrom(from);
+ return *this;
+ }
+ inline StackFrame& operator=(StackFrame&& from) noexcept {
+ if (this == &from) return *this;
+ if (GetOwningArena() == from.GetOwningArena()
+ #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
+ && GetOwningArena() != nullptr
+ #endif // !PROTOBUF_FORCE_COPY_IN_MOVE
+ ) {
+ InternalSwap(&from);
+ } else {
+ CopyFrom(from);
+ }
+ return *this;
+ }
+
+ inline const std::string& unknown_fields() const {
+ return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString);
+ }
+ inline std::string* mutable_unknown_fields() {
+ return _internal_metadata_.mutable_unknown_fields<std::string>();
+ }
+
+ static const StackFrame& default_instance() {
+ return *internal_default_instance();
+ }
+ enum StackFrameTypeCase {
+ kData = 1,
+ kRef = 2,
+ STACKFRAMETYPE_NOT_SET = 0,
+ };
+
+ static inline const StackFrame* internal_default_instance() {
+ return reinterpret_cast<const StackFrame*>(
+ &_StackFrame_default_instance_);
+ }
+ static constexpr int kIndexInFileMessages =
+ 2;
+
+ friend void swap(StackFrame& a, StackFrame& b) {
+ a.Swap(&b);
+ }
+ inline void Swap(StackFrame* other) {
+ if (other == this) return;
+ #ifdef PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() != nullptr &&
+ GetOwningArena() == other->GetOwningArena()) {
+ #else // PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() == other->GetOwningArena()) {
+ #endif // !PROTOBUF_FORCE_COPY_IN_SWAP
+ InternalSwap(other);
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other);
+ }
+ }
+ void UnsafeArenaSwap(StackFrame* other) {
+ if (other == this) return;
+ GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena());
+ InternalSwap(other);
+ }
+
+ // implements Message ----------------------------------------------
+
+ StackFrame* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final {
+ return CreateMaybeMessage<StackFrame>(arena);
+ }
+ void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final;
+ void CopyFrom(const StackFrame& from);
+ void MergeFrom(const StackFrame& from);
+ PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+ bool IsInitialized() const final;
+
+ size_t ByteSizeLong() const final;
+ const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+ uint8_t* _InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+ int GetCachedSize() const final { return _impl_._cached_size_.Get(); }
+
+ private:
+ void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned);
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ void InternalSwap(StackFrame* other);
+
+ private:
+ friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+ static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+ return "mozilla.devtools.protobuf.StackFrame";
+ }
+ protected:
+ explicit StackFrame(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned = false);
+ public:
+
+ std::string GetTypeName() const final;
+
+ // nested types ----------------------------------------------------
+
+ typedef StackFrame_Data Data;
+
+ // accessors -------------------------------------------------------
+
+ enum : int {
+ kDataFieldNumber = 1,
+ kRefFieldNumber = 2,
+ };
+ // .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ bool has_data() const;
+ private:
+ bool _internal_has_data() const;
+ public:
+ void clear_data();
+ const ::mozilla::devtools::protobuf::StackFrame_Data& data() const;
+ PROTOBUF_NODISCARD ::mozilla::devtools::protobuf::StackFrame_Data* release_data();
+ ::mozilla::devtools::protobuf::StackFrame_Data* mutable_data();
+ void set_allocated_data(::mozilla::devtools::protobuf::StackFrame_Data* data);
+ private:
+ const ::mozilla::devtools::protobuf::StackFrame_Data& _internal_data() const;
+ ::mozilla::devtools::protobuf::StackFrame_Data* _internal_mutable_data();
+ public:
+ void unsafe_arena_set_allocated_data(
+ ::mozilla::devtools::protobuf::StackFrame_Data* data);
+ ::mozilla::devtools::protobuf::StackFrame_Data* unsafe_arena_release_data();
+
+ // uint64 ref = 2;
+ bool has_ref() const;
+ private:
+ bool _internal_has_ref() const;
+ public:
+ void clear_ref();
+ uint64_t ref() const;
+ void set_ref(uint64_t value);
+ private:
+ uint64_t _internal_ref() const;
+ void _internal_set_ref(uint64_t value);
+ public:
+
+ void clear_StackFrameType();
+ StackFrameTypeCase StackFrameType_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.StackFrame)
+ private:
+ class _Internal;
+ void set_has_data();
+ void set_has_ref();
+
+ inline bool has_StackFrameType() const;
+ inline void clear_has_StackFrameType();
+
+ template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
+ typedef void InternalArenaConstructable_;
+ typedef void DestructorSkippable_;
+ struct Impl_ {
+ union StackFrameTypeUnion {
+ constexpr StackFrameTypeUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::mozilla::devtools::protobuf::StackFrame_Data* data_;
+ uint64_t ref_;
+ } StackFrameType_;
+ mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+ uint32_t _oneof_case_[1];
+
+ };
+ union { Impl_ _impl_; };
+ friend struct ::TableStruct_CoreDump_2eproto;
+};
+// -------------------------------------------------------------------
+
+class Node final :
+ public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.Node) */ {
+ public:
+ inline Node() : Node(nullptr) {}
+ ~Node() override;
+ explicit PROTOBUF_CONSTEXPR Node(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);
+
+ Node(const Node& from);
+ Node(Node&& from) noexcept
+ : Node() {
+ *this = ::std::move(from);
+ }
+
+ inline Node& operator=(const Node& from) {
+ CopyFrom(from);
+ return *this;
+ }
+ inline Node& operator=(Node&& from) noexcept {
+ if (this == &from) return *this;
+ if (GetOwningArena() == from.GetOwningArena()
+ #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
+ && GetOwningArena() != nullptr
+ #endif // !PROTOBUF_FORCE_COPY_IN_MOVE
+ ) {
+ InternalSwap(&from);
+ } else {
+ CopyFrom(from);
+ }
+ return *this;
+ }
+
+ inline const std::string& unknown_fields() const {
+ return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString);
+ }
+ inline std::string* mutable_unknown_fields() {
+ return _internal_metadata_.mutable_unknown_fields<std::string>();
+ }
+
+ static const Node& default_instance() {
+ return *internal_default_instance();
+ }
+ enum TypeNameOrRefCase {
+ kTypeName = 2,
+ kTypeNameRef = 3,
+ TYPENAMEORREF_NOT_SET = 0,
+ };
+
+ enum JSObjectClassNameOrRefCase {
+ kJsObjectClassName = 7,
+ kJsObjectClassNameRef = 8,
+ JSOBJECTCLASSNAMEORREF_NOT_SET = 0,
+ };
+
+ enum ScriptFilenameOrRefCase {
+ kScriptFilename = 10,
+ kScriptFilenameRef = 11,
+ SCRIPTFILENAMEORREF_NOT_SET = 0,
+ };
+
+ enum DescriptiveTypeNameOrRefCase {
+ kDescriptiveTypeName = 12,
+ kDescriptiveTypeNameRef = 13,
+ DESCRIPTIVETYPENAMEORREF_NOT_SET = 0,
+ };
+
+ static inline const Node* internal_default_instance() {
+ return reinterpret_cast<const Node*>(
+ &_Node_default_instance_);
+ }
+ static constexpr int kIndexInFileMessages =
+ 3;
+
+ friend void swap(Node& a, Node& b) {
+ a.Swap(&b);
+ }
+ inline void Swap(Node* other) {
+ if (other == this) return;
+ #ifdef PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() != nullptr &&
+ GetOwningArena() == other->GetOwningArena()) {
+ #else // PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() == other->GetOwningArena()) {
+ #endif // !PROTOBUF_FORCE_COPY_IN_SWAP
+ InternalSwap(other);
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other);
+ }
+ }
+ void UnsafeArenaSwap(Node* other) {
+ if (other == this) return;
+ GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena());
+ InternalSwap(other);
+ }
+
+ // implements Message ----------------------------------------------
+
+ Node* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final {
+ return CreateMaybeMessage<Node>(arena);
+ }
+ void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final;
+ void CopyFrom(const Node& from);
+ void MergeFrom(const Node& from);
+ PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+ bool IsInitialized() const final;
+
+ size_t ByteSizeLong() const final;
+ const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+ uint8_t* _InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+ int GetCachedSize() const final { return _impl_._cached_size_.Get(); }
+
+ private:
+ void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned);
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ void InternalSwap(Node* other);
+
+ private:
+ friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+ static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+ return "mozilla.devtools.protobuf.Node";
+ }
+ protected:
+ explicit Node(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned = false);
+ public:
+
+ std::string GetTypeName() const final;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ enum : int {
+ kEdgesFieldNumber = 5,
+ kAllocationStackFieldNumber = 6,
+ kIdFieldNumber = 1,
+ kSizeFieldNumber = 4,
+ kCoarseTypeFieldNumber = 9,
+ kTypeNameFieldNumber = 2,
+ kTypeNameRefFieldNumber = 3,
+ kJsObjectClassNameFieldNumber = 7,
+ kJsObjectClassNameRefFieldNumber = 8,
+ kScriptFilenameFieldNumber = 10,
+ kScriptFilenameRefFieldNumber = 11,
+ kDescriptiveTypeNameFieldNumber = 12,
+ kDescriptiveTypeNameRefFieldNumber = 13,
+ };
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ int edges_size() const;
+ private:
+ int _internal_edges_size() const;
+ public:
+ void clear_edges();
+ ::mozilla::devtools::protobuf::Edge* mutable_edges(int index);
+ ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+ mutable_edges();
+ private:
+ const ::mozilla::devtools::protobuf::Edge& _internal_edges(int index) const;
+ ::mozilla::devtools::protobuf::Edge* _internal_add_edges();
+ public:
+ const ::mozilla::devtools::protobuf::Edge& edges(int index) const;
+ ::mozilla::devtools::protobuf::Edge* add_edges();
+ const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+ edges() const;
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ bool has_allocationstack() const;
+ private:
+ bool _internal_has_allocationstack() const;
+ public:
+ void clear_allocationstack();
+ const ::mozilla::devtools::protobuf::StackFrame& allocationstack() const;
+ PROTOBUF_NODISCARD ::mozilla::devtools::protobuf::StackFrame* release_allocationstack();
+ ::mozilla::devtools::protobuf::StackFrame* mutable_allocationstack();
+ void set_allocated_allocationstack(::mozilla::devtools::protobuf::StackFrame* allocationstack);
+ private:
+ const ::mozilla::devtools::protobuf::StackFrame& _internal_allocationstack() const;
+ ::mozilla::devtools::protobuf::StackFrame* _internal_mutable_allocationstack();
+ public:
+ void unsafe_arena_set_allocated_allocationstack(
+ ::mozilla::devtools::protobuf::StackFrame* allocationstack);
+ ::mozilla::devtools::protobuf::StackFrame* unsafe_arena_release_allocationstack();
+
+ // optional uint64 id = 1;
+ bool has_id() const;
+ private:
+ bool _internal_has_id() const;
+ public:
+ void clear_id();
+ uint64_t id() const;
+ void set_id(uint64_t value);
+ private:
+ uint64_t _internal_id() const;
+ void _internal_set_id(uint64_t value);
+ public:
+
+ // optional uint64 size = 4;
+ bool has_size() const;
+ private:
+ bool _internal_has_size() const;
+ public:
+ void clear_size();
+ uint64_t size() const;
+ void set_size(uint64_t value);
+ private:
+ uint64_t _internal_size() const;
+ void _internal_set_size(uint64_t value);
+ public:
+
+ // optional uint32 coarseType = 9 [default = 0];
+ bool has_coarsetype() const;
+ private:
+ bool _internal_has_coarsetype() const;
+ public:
+ void clear_coarsetype();
+ uint32_t coarsetype() const;
+ void set_coarsetype(uint32_t value);
+ private:
+ uint32_t _internal_coarsetype() const;
+ void _internal_set_coarsetype(uint32_t value);
+ public:
+
+ // bytes typeName = 2;
+ bool has_typename_() const;
+ private:
+ bool _internal_has_typename_() const;
+ public:
+ void clear_typename_();
+ const std::string& typename_() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_typename_(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_typename_();
+ PROTOBUF_NODISCARD std::string* release_typename_();
+ void set_allocated_typename_(std::string* typename_);
+ private:
+ const std::string& _internal_typename_() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_typename_(const std::string& value);
+ std::string* _internal_mutable_typename_();
+ public:
+
+ // uint64 typeNameRef = 3;
+ bool has_typenameref() const;
+ private:
+ bool _internal_has_typenameref() const;
+ public:
+ void clear_typenameref();
+ uint64_t typenameref() const;
+ void set_typenameref(uint64_t value);
+ private:
+ uint64_t _internal_typenameref() const;
+ void _internal_set_typenameref(uint64_t value);
+ public:
+
+ // bytes jsObjectClassName = 7;
+ bool has_jsobjectclassname() const;
+ private:
+ bool _internal_has_jsobjectclassname() const;
+ public:
+ void clear_jsobjectclassname();
+ const std::string& jsobjectclassname() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_jsobjectclassname(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_jsobjectclassname();
+ PROTOBUF_NODISCARD std::string* release_jsobjectclassname();
+ void set_allocated_jsobjectclassname(std::string* jsobjectclassname);
+ private:
+ const std::string& _internal_jsobjectclassname() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_jsobjectclassname(const std::string& value);
+ std::string* _internal_mutable_jsobjectclassname();
+ public:
+
+ // uint64 jsObjectClassNameRef = 8;
+ bool has_jsobjectclassnameref() const;
+ private:
+ bool _internal_has_jsobjectclassnameref() const;
+ public:
+ void clear_jsobjectclassnameref();
+ uint64_t jsobjectclassnameref() const;
+ void set_jsobjectclassnameref(uint64_t value);
+ private:
+ uint64_t _internal_jsobjectclassnameref() const;
+ void _internal_set_jsobjectclassnameref(uint64_t value);
+ public:
+
+ // bytes scriptFilename = 10;
+ bool has_scriptfilename() const;
+ private:
+ bool _internal_has_scriptfilename() const;
+ public:
+ void clear_scriptfilename();
+ const std::string& scriptfilename() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_scriptfilename(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_scriptfilename();
+ PROTOBUF_NODISCARD std::string* release_scriptfilename();
+ void set_allocated_scriptfilename(std::string* scriptfilename);
+ private:
+ const std::string& _internal_scriptfilename() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_scriptfilename(const std::string& value);
+ std::string* _internal_mutable_scriptfilename();
+ public:
+
+ // uint64 scriptFilenameRef = 11;
+ bool has_scriptfilenameref() const;
+ private:
+ bool _internal_has_scriptfilenameref() const;
+ public:
+ void clear_scriptfilenameref();
+ uint64_t scriptfilenameref() const;
+ void set_scriptfilenameref(uint64_t value);
+ private:
+ uint64_t _internal_scriptfilenameref() const;
+ void _internal_set_scriptfilenameref(uint64_t value);
+ public:
+
+ // bytes descriptiveTypeName = 12;
+ bool has_descriptivetypename() const;
+ private:
+ bool _internal_has_descriptivetypename() const;
+ public:
+ void clear_descriptivetypename();
+ const std::string& descriptivetypename() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_descriptivetypename(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_descriptivetypename();
+ PROTOBUF_NODISCARD std::string* release_descriptivetypename();
+ void set_allocated_descriptivetypename(std::string* descriptivetypename);
+ private:
+ const std::string& _internal_descriptivetypename() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_descriptivetypename(const std::string& value);
+ std::string* _internal_mutable_descriptivetypename();
+ public:
+
+ // uint64 descriptiveTypeNameRef = 13;
+ bool has_descriptivetypenameref() const;
+ private:
+ bool _internal_has_descriptivetypenameref() const;
+ public:
+ void clear_descriptivetypenameref();
+ uint64_t descriptivetypenameref() const;
+ void set_descriptivetypenameref(uint64_t value);
+ private:
+ uint64_t _internal_descriptivetypenameref() const;
+ void _internal_set_descriptivetypenameref(uint64_t value);
+ public:
+
+ void clear_TypeNameOrRef();
+ TypeNameOrRefCase TypeNameOrRef_case() const;
+ void clear_JSObjectClassNameOrRef();
+ JSObjectClassNameOrRefCase JSObjectClassNameOrRef_case() const;
+ void clear_ScriptFilenameOrRef();
+ ScriptFilenameOrRefCase ScriptFilenameOrRef_case() const;
+ void clear_descriptiveTypeNameOrRef();
+ DescriptiveTypeNameOrRefCase descriptiveTypeNameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node)
+ private:
+ class _Internal;
+ void set_has_typename_();
+ void set_has_typenameref();
+ void set_has_jsobjectclassname();
+ void set_has_jsobjectclassnameref();
+ void set_has_scriptfilename();
+ void set_has_scriptfilenameref();
+ void set_has_descriptivetypename();
+ void set_has_descriptivetypenameref();
+
+ inline bool has_TypeNameOrRef() const;
+ inline void clear_has_TypeNameOrRef();
+
+ inline bool has_JSObjectClassNameOrRef() const;
+ inline void clear_has_JSObjectClassNameOrRef();
+
+ inline bool has_ScriptFilenameOrRef() const;
+ inline void clear_has_ScriptFilenameOrRef();
+
+ inline bool has_descriptiveTypeNameOrRef() const;
+ inline void clear_has_descriptiveTypeNameOrRef();
+
+ template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
+ typedef void InternalArenaConstructable_;
+ typedef void DestructorSkippable_;
+ struct Impl_ {
+ ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_;
+ mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+ ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_;
+ ::mozilla::devtools::protobuf::StackFrame* allocationstack_;
+ uint64_t id_;
+ uint64_t size_;
+ uint32_t coarsetype_;
+ union TypeNameOrRefUnion {
+ constexpr TypeNameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr typename__;
+ uint64_t typenameref_;
+ } TypeNameOrRef_;
+ union JSObjectClassNameOrRefUnion {
+ constexpr JSObjectClassNameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr jsobjectclassname_;
+ uint64_t jsobjectclassnameref_;
+ } JSObjectClassNameOrRef_;
+ union ScriptFilenameOrRefUnion {
+ constexpr ScriptFilenameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr scriptfilename_;
+ uint64_t scriptfilenameref_;
+ } ScriptFilenameOrRef_;
+ union DescriptiveTypeNameOrRefUnion {
+ constexpr DescriptiveTypeNameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr descriptivetypename_;
+ uint64_t descriptivetypenameref_;
+ } descriptiveTypeNameOrRef_;
+ uint32_t _oneof_case_[4];
+
+ };
+ union { Impl_ _impl_; };
+ friend struct ::TableStruct_CoreDump_2eproto;
+};
+// -------------------------------------------------------------------
+
+class Edge final :
+ public ::PROTOBUF_NAMESPACE_ID::MessageLite /* @@protoc_insertion_point(class_definition:mozilla.devtools.protobuf.Edge) */ {
+ public:
+ inline Edge() : Edge(nullptr) {}
+ ~Edge() override;
+ explicit PROTOBUF_CONSTEXPR Edge(::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized);
+
+ Edge(const Edge& from);
+ Edge(Edge&& from) noexcept
+ : Edge() {
+ *this = ::std::move(from);
+ }
+
+ inline Edge& operator=(const Edge& from) {
+ CopyFrom(from);
+ return *this;
+ }
+ inline Edge& operator=(Edge&& from) noexcept {
+ if (this == &from) return *this;
+ if (GetOwningArena() == from.GetOwningArena()
+ #ifdef PROTOBUF_FORCE_COPY_IN_MOVE
+ && GetOwningArena() != nullptr
+ #endif // !PROTOBUF_FORCE_COPY_IN_MOVE
+ ) {
+ InternalSwap(&from);
+ } else {
+ CopyFrom(from);
+ }
+ return *this;
+ }
+
+ inline const std::string& unknown_fields() const {
+ return _internal_metadata_.unknown_fields<std::string>(::PROTOBUF_NAMESPACE_ID::internal::GetEmptyString);
+ }
+ inline std::string* mutable_unknown_fields() {
+ return _internal_metadata_.mutable_unknown_fields<std::string>();
+ }
+
+ static const Edge& default_instance() {
+ return *internal_default_instance();
+ }
+ enum EdgeNameOrRefCase {
+ kName = 2,
+ kNameRef = 3,
+ EDGENAMEORREF_NOT_SET = 0,
+ };
+
+ static inline const Edge* internal_default_instance() {
+ return reinterpret_cast<const Edge*>(
+ &_Edge_default_instance_);
+ }
+ static constexpr int kIndexInFileMessages =
+ 4;
+
+ friend void swap(Edge& a, Edge& b) {
+ a.Swap(&b);
+ }
+ inline void Swap(Edge* other) {
+ if (other == this) return;
+ #ifdef PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() != nullptr &&
+ GetOwningArena() == other->GetOwningArena()) {
+ #else // PROTOBUF_FORCE_COPY_IN_SWAP
+ if (GetOwningArena() == other->GetOwningArena()) {
+ #endif // !PROTOBUF_FORCE_COPY_IN_SWAP
+ InternalSwap(other);
+ } else {
+ ::PROTOBUF_NAMESPACE_ID::internal::GenericSwap(this, other);
+ }
+ }
+ void UnsafeArenaSwap(Edge* other) {
+ if (other == this) return;
+ GOOGLE_DCHECK(GetOwningArena() == other->GetOwningArena());
+ InternalSwap(other);
+ }
+
+ // implements Message ----------------------------------------------
+
+ Edge* New(::PROTOBUF_NAMESPACE_ID::Arena* arena = nullptr) const final {
+ return CreateMaybeMessage<Edge>(arena);
+ }
+ void CheckTypeAndMergeFrom(const ::PROTOBUF_NAMESPACE_ID::MessageLite& from) final;
+ void CopyFrom(const Edge& from);
+ void MergeFrom(const Edge& from);
+ PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final;
+ bool IsInitialized() const final;
+
+ size_t ByteSizeLong() const final;
+ const char* _InternalParse(const char* ptr, ::PROTOBUF_NAMESPACE_ID::internal::ParseContext* ctx) final;
+ uint8_t* _InternalSerialize(
+ uint8_t* target, ::PROTOBUF_NAMESPACE_ID::io::EpsCopyOutputStream* stream) const final;
+ int GetCachedSize() const final { return _impl_._cached_size_.Get(); }
+
+ private:
+ void SharedCtor(::PROTOBUF_NAMESPACE_ID::Arena* arena, bool is_message_owned);
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ void InternalSwap(Edge* other);
+
+ private:
+ friend class ::PROTOBUF_NAMESPACE_ID::internal::AnyMetadata;
+ static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
+ return "mozilla.devtools.protobuf.Edge";
+ }
+ protected:
+ explicit Edge(::PROTOBUF_NAMESPACE_ID::Arena* arena,
+ bool is_message_owned = false);
+ public:
+
+ std::string GetTypeName() const final;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ enum : int {
+ kReferentFieldNumber = 1,
+ kNameFieldNumber = 2,
+ kNameRefFieldNumber = 3,
+ };
+ // optional uint64 referent = 1;
+ bool has_referent() const;
+ private:
+ bool _internal_has_referent() const;
+ public:
+ void clear_referent();
+ uint64_t referent() const;
+ void set_referent(uint64_t value);
+ private:
+ uint64_t _internal_referent() const;
+ void _internal_set_referent(uint64_t value);
+ public:
+
+ // bytes name = 2;
+ bool has_name() const;
+ private:
+ bool _internal_has_name() const;
+ public:
+ void clear_name();
+ const std::string& name() const;
+ template <typename ArgT0 = const std::string&, typename... ArgT>
+ void set_name(ArgT0&& arg0, ArgT... args);
+ std::string* mutable_name();
+ PROTOBUF_NODISCARD std::string* release_name();
+ void set_allocated_name(std::string* name);
+ private:
+ const std::string& _internal_name() const;
+ inline PROTOBUF_ALWAYS_INLINE void _internal_set_name(const std::string& value);
+ std::string* _internal_mutable_name();
+ public:
+
+ // uint64 nameRef = 3;
+ bool has_nameref() const;
+ private:
+ bool _internal_has_nameref() const;
+ public:
+ void clear_nameref();
+ uint64_t nameref() const;
+ void set_nameref(uint64_t value);
+ private:
+ uint64_t _internal_nameref() const;
+ void _internal_set_nameref(uint64_t value);
+ public:
+
+ void clear_EdgeNameOrRef();
+ EdgeNameOrRefCase EdgeNameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Edge)
+ private:
+ class _Internal;
+ void set_has_name();
+ void set_has_nameref();
+
+ inline bool has_EdgeNameOrRef() const;
+ inline void clear_has_EdgeNameOrRef();
+
+ template <typename T> friend class ::PROTOBUF_NAMESPACE_ID::Arena::InternalHelper;
+ typedef void InternalArenaConstructable_;
+ typedef void DestructorSkippable_;
+ struct Impl_ {
+ ::PROTOBUF_NAMESPACE_ID::internal::HasBits<1> _has_bits_;
+ mutable ::PROTOBUF_NAMESPACE_ID::internal::CachedSize _cached_size_;
+ uint64_t referent_;
+ union EdgeNameOrRefUnion {
+ constexpr EdgeNameOrRefUnion() : _constinit_{} {}
+ ::PROTOBUF_NAMESPACE_ID::internal::ConstantInitialized _constinit_;
+ ::PROTOBUF_NAMESPACE_ID::internal::ArenaStringPtr name_;
+ uint64_t nameref_;
+ } EdgeNameOrRef_;
+ uint32_t _oneof_case_[1];
+
+ };
+ union { Impl_ _impl_; };
+ friend struct ::TableStruct_CoreDump_2eproto;
+};
+// ===================================================================
+
+
+// ===================================================================
+
+#ifdef __GNUC__
+ #pragma GCC diagnostic push
+ #pragma GCC diagnostic ignored "-Wstrict-aliasing"
+#endif // __GNUC__
+// Metadata
+
+// optional uint64 timeStamp = 1;
+inline bool Metadata::_internal_has_timestamp() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0;
+ return value;
+}
+inline bool Metadata::has_timestamp() const {
+ return _internal_has_timestamp();
+}
+inline void Metadata::clear_timestamp() {
+ _impl_.timestamp_ = uint64_t{0u};
+ _impl_._has_bits_[0] &= ~0x00000001u;
+}
+inline uint64_t Metadata::_internal_timestamp() const {
+ return _impl_.timestamp_;
+}
+inline uint64_t Metadata::timestamp() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Metadata.timeStamp)
+ return _internal_timestamp();
+}
+inline void Metadata::_internal_set_timestamp(uint64_t value) {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ _impl_.timestamp_ = value;
+}
+inline void Metadata::set_timestamp(uint64_t value) {
+ _internal_set_timestamp(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Metadata.timeStamp)
+}
+
+// -------------------------------------------------------------------
+
+// StackFrame_Data
+
+// optional uint64 id = 1;
+inline bool StackFrame_Data::_internal_has_id() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0;
+ return value;
+}
+inline bool StackFrame_Data::has_id() const {
+ return _internal_has_id();
+}
+inline void StackFrame_Data::clear_id() {
+ _impl_.id_ = uint64_t{0u};
+ _impl_._has_bits_[0] &= ~0x00000002u;
+}
+inline uint64_t StackFrame_Data::_internal_id() const {
+ return _impl_.id_;
+}
+inline uint64_t StackFrame_Data::id() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.id)
+ return _internal_id();
+}
+inline void StackFrame_Data::_internal_set_id(uint64_t value) {
+ _impl_._has_bits_[0] |= 0x00000002u;
+ _impl_.id_ = value;
+}
+inline void StackFrame_Data::set_id(uint64_t value) {
+ _internal_set_id(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.id)
+}
+
+// optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+inline bool StackFrame_Data::_internal_has_parent() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0;
+ PROTOBUF_ASSUME(!value || _impl_.parent_ != nullptr);
+ return value;
+}
+inline bool StackFrame_Data::has_parent() const {
+ return _internal_has_parent();
+}
+inline void StackFrame_Data::clear_parent() {
+ if (_impl_.parent_ != nullptr) _impl_.parent_->Clear();
+ _impl_._has_bits_[0] &= ~0x00000001u;
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& StackFrame_Data::_internal_parent() const {
+ const ::mozilla::devtools::protobuf::StackFrame* p = _impl_.parent_;
+ return p != nullptr ? *p : reinterpret_cast<const ::mozilla::devtools::protobuf::StackFrame&>(
+ ::mozilla::devtools::protobuf::_StackFrame_default_instance_);
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& StackFrame_Data::parent() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.parent)
+ return _internal_parent();
+}
+inline void StackFrame_Data::unsafe_arena_set_allocated_parent(
+ ::mozilla::devtools::protobuf::StackFrame* parent) {
+ if (GetArenaForAllocation() == nullptr) {
+ delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.parent_);
+ }
+ _impl_.parent_ = parent;
+ if (parent) {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ } else {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ }
+ // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.devtools.protobuf.StackFrame.Data.parent)
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::release_parent() {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ ::mozilla::devtools::protobuf::StackFrame* temp = _impl_.parent_;
+ _impl_.parent_ = nullptr;
+#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE
+ auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp);
+ temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp);
+ if (GetArenaForAllocation() == nullptr) { delete old; }
+#else // PROTOBUF_FORCE_COPY_IN_RELEASE
+ if (GetArenaForAllocation() != nullptr) {
+ temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp);
+ }
+#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE
+ return temp;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::unsafe_arena_release_parent() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.StackFrame.Data.parent)
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ ::mozilla::devtools::protobuf::StackFrame* temp = _impl_.parent_;
+ _impl_.parent_ = nullptr;
+ return temp;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::_internal_mutable_parent() {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ if (_impl_.parent_ == nullptr) {
+ auto* p = CreateMaybeMessage<::mozilla::devtools::protobuf::StackFrame>(GetArenaForAllocation());
+ _impl_.parent_ = p;
+ }
+ return _impl_.parent_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::mutable_parent() {
+ ::mozilla::devtools::protobuf::StackFrame* _msg = _internal_mutable_parent();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.StackFrame.Data.parent)
+ return _msg;
+}
+inline void StackFrame_Data::set_allocated_parent(::mozilla::devtools::protobuf::StackFrame* parent) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation();
+ if (message_arena == nullptr) {
+ delete _impl_.parent_;
+ }
+ if (parent) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena =
+ ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(parent);
+ if (message_arena != submessage_arena) {
+ parent = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+ message_arena, parent, submessage_arena);
+ }
+ _impl_._has_bits_[0] |= 0x00000001u;
+ } else {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ }
+ _impl_.parent_ = parent;
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.StackFrame.Data.parent)
+}
+
+// optional uint32 line = 3;
+inline bool StackFrame_Data::_internal_has_line() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0;
+ return value;
+}
+inline bool StackFrame_Data::has_line() const {
+ return _internal_has_line();
+}
+inline void StackFrame_Data::clear_line() {
+ _impl_.line_ = 0u;
+ _impl_._has_bits_[0] &= ~0x00000004u;
+}
+inline uint32_t StackFrame_Data::_internal_line() const {
+ return _impl_.line_;
+}
+inline uint32_t StackFrame_Data::line() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.line)
+ return _internal_line();
+}
+inline void StackFrame_Data::_internal_set_line(uint32_t value) {
+ _impl_._has_bits_[0] |= 0x00000004u;
+ _impl_.line_ = value;
+}
+inline void StackFrame_Data::set_line(uint32_t value) {
+ _internal_set_line(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.line)
+}
+
+// optional uint32 column = 4;
+inline bool StackFrame_Data::_internal_has_column() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0;
+ return value;
+}
+inline bool StackFrame_Data::has_column() const {
+ return _internal_has_column();
+}
+inline void StackFrame_Data::clear_column() {
+ _impl_.column_ = 0u;
+ _impl_._has_bits_[0] &= ~0x00000008u;
+}
+inline uint32_t StackFrame_Data::_internal_column() const {
+ return _impl_.column_;
+}
+inline uint32_t StackFrame_Data::column() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.column)
+ return _internal_column();
+}
+inline void StackFrame_Data::_internal_set_column(uint32_t value) {
+ _impl_._has_bits_[0] |= 0x00000008u;
+ _impl_.column_ = value;
+}
+inline void StackFrame_Data::set_column(uint32_t value) {
+ _internal_set_column(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.column)
+}
+
+// bytes source = 5;
+inline bool StackFrame_Data::_internal_has_source() const {
+ return SourceOrRef_case() == kSource;
+}
+inline bool StackFrame_Data::has_source() const {
+ return _internal_has_source();
+}
+inline void StackFrame_Data::set_has_source() {
+ _impl_._oneof_case_[0] = kSource;
+}
+inline void StackFrame_Data::clear_source() {
+ if (_internal_has_source()) {
+ _impl_.SourceOrRef_.source_.Destroy();
+ clear_has_SourceOrRef();
+ }
+}
+inline const std::string& StackFrame_Data::source() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.source)
+ return _internal_source();
+}
+template <typename ArgT0, typename... ArgT>
+inline void StackFrame_Data::set_source(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ _impl_.SourceOrRef_.source_.InitDefault();
+ }
+ _impl_.SourceOrRef_.source_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.source)
+}
+inline std::string* StackFrame_Data::mutable_source() {
+ std::string* _s = _internal_mutable_source();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.StackFrame.Data.source)
+ return _s;
+}
+inline const std::string& StackFrame_Data::_internal_source() const {
+ if (_internal_has_source()) {
+ return _impl_.SourceOrRef_.source_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void StackFrame_Data::_internal_set_source(const std::string& value) {
+ if (!_internal_has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ _impl_.SourceOrRef_.source_.InitDefault();
+ }
+ _impl_.SourceOrRef_.source_.Set(value, GetArenaForAllocation());
+}
+inline std::string* StackFrame_Data::_internal_mutable_source() {
+ if (!_internal_has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ _impl_.SourceOrRef_.source_.InitDefault();
+ }
+ return _impl_.SourceOrRef_.source_.Mutable( GetArenaForAllocation());
+}
+inline std::string* StackFrame_Data::release_source() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.StackFrame.Data.source)
+ if (_internal_has_source()) {
+ clear_has_SourceOrRef();
+ return _impl_.SourceOrRef_.source_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void StackFrame_Data::set_allocated_source(std::string* source) {
+ if (has_SourceOrRef()) {
+ clear_SourceOrRef();
+ }
+ if (source != nullptr) {
+ set_has_source();
+ _impl_.SourceOrRef_.source_.InitAllocated(source, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.StackFrame.Data.source)
+}
+
+// uint64 sourceRef = 6;
+inline bool StackFrame_Data::_internal_has_sourceref() const {
+ return SourceOrRef_case() == kSourceRef;
+}
+inline bool StackFrame_Data::has_sourceref() const {
+ return _internal_has_sourceref();
+}
+inline void StackFrame_Data::set_has_sourceref() {
+ _impl_._oneof_case_[0] = kSourceRef;
+}
+inline void StackFrame_Data::clear_sourceref() {
+ if (_internal_has_sourceref()) {
+ _impl_.SourceOrRef_.sourceref_ = uint64_t{0u};
+ clear_has_SourceOrRef();
+ }
+}
+inline uint64_t StackFrame_Data::_internal_sourceref() const {
+ if (_internal_has_sourceref()) {
+ return _impl_.SourceOrRef_.sourceref_;
+ }
+ return uint64_t{0u};
+}
+inline void StackFrame_Data::_internal_set_sourceref(uint64_t value) {
+ if (!_internal_has_sourceref()) {
+ clear_SourceOrRef();
+ set_has_sourceref();
+ }
+ _impl_.SourceOrRef_.sourceref_ = value;
+}
+inline uint64_t StackFrame_Data::sourceref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.sourceRef)
+ return _internal_sourceref();
+}
+inline void StackFrame_Data::set_sourceref(uint64_t value) {
+ _internal_set_sourceref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.sourceRef)
+}
+
+// bytes functionDisplayName = 7;
+inline bool StackFrame_Data::_internal_has_functiondisplayname() const {
+ return FunctionDisplayNameOrRef_case() == kFunctionDisplayName;
+}
+inline bool StackFrame_Data::has_functiondisplayname() const {
+ return _internal_has_functiondisplayname();
+}
+inline void StackFrame_Data::set_has_functiondisplayname() {
+ _impl_._oneof_case_[1] = kFunctionDisplayName;
+}
+inline void StackFrame_Data::clear_functiondisplayname() {
+ if (_internal_has_functiondisplayname()) {
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Destroy();
+ clear_has_FunctionDisplayNameOrRef();
+ }
+}
+inline const std::string& StackFrame_Data::functiondisplayname() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayName)
+ return _internal_functiondisplayname();
+}
+template <typename ArgT0, typename... ArgT>
+inline void StackFrame_Data::set_functiondisplayname(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.InitDefault();
+ }
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayName)
+}
+inline std::string* StackFrame_Data::mutable_functiondisplayname() {
+ std::string* _s = _internal_mutable_functiondisplayname();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayName)
+ return _s;
+}
+inline const std::string& StackFrame_Data::_internal_functiondisplayname() const {
+ if (_internal_has_functiondisplayname()) {
+ return _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void StackFrame_Data::_internal_set_functiondisplayname(const std::string& value) {
+ if (!_internal_has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.InitDefault();
+ }
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Set(value, GetArenaForAllocation());
+}
+inline std::string* StackFrame_Data::_internal_mutable_functiondisplayname() {
+ if (!_internal_has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.InitDefault();
+ }
+ return _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Mutable( GetArenaForAllocation());
+}
+inline std::string* StackFrame_Data::release_functiondisplayname() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayName)
+ if (_internal_has_functiondisplayname()) {
+ clear_has_FunctionDisplayNameOrRef();
+ return _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void StackFrame_Data::set_allocated_functiondisplayname(std::string* functiondisplayname) {
+ if (has_FunctionDisplayNameOrRef()) {
+ clear_FunctionDisplayNameOrRef();
+ }
+ if (functiondisplayname != nullptr) {
+ set_has_functiondisplayname();
+ _impl_.FunctionDisplayNameOrRef_.functiondisplayname_.InitAllocated(functiondisplayname, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayName)
+}
+
+// uint64 functionDisplayNameRef = 8;
+inline bool StackFrame_Data::_internal_has_functiondisplaynameref() const {
+ return FunctionDisplayNameOrRef_case() == kFunctionDisplayNameRef;
+}
+inline bool StackFrame_Data::has_functiondisplaynameref() const {
+ return _internal_has_functiondisplaynameref();
+}
+inline void StackFrame_Data::set_has_functiondisplaynameref() {
+ _impl_._oneof_case_[1] = kFunctionDisplayNameRef;
+}
+inline void StackFrame_Data::clear_functiondisplaynameref() {
+ if (_internal_has_functiondisplaynameref()) {
+ _impl_.FunctionDisplayNameOrRef_.functiondisplaynameref_ = uint64_t{0u};
+ clear_has_FunctionDisplayNameOrRef();
+ }
+}
+inline uint64_t StackFrame_Data::_internal_functiondisplaynameref() const {
+ if (_internal_has_functiondisplaynameref()) {
+ return _impl_.FunctionDisplayNameOrRef_.functiondisplaynameref_;
+ }
+ return uint64_t{0u};
+}
+inline void StackFrame_Data::_internal_set_functiondisplaynameref(uint64_t value) {
+ if (!_internal_has_functiondisplaynameref()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplaynameref();
+ }
+ _impl_.FunctionDisplayNameOrRef_.functiondisplaynameref_ = value;
+}
+inline uint64_t StackFrame_Data::functiondisplaynameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayNameRef)
+ return _internal_functiondisplaynameref();
+}
+inline void StackFrame_Data::set_functiondisplaynameref(uint64_t value) {
+ _internal_set_functiondisplaynameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.functionDisplayNameRef)
+}
+
+// optional bool isSystem = 9;
+inline bool StackFrame_Data::_internal_has_issystem() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000010u) != 0;
+ return value;
+}
+inline bool StackFrame_Data::has_issystem() const {
+ return _internal_has_issystem();
+}
+inline void StackFrame_Data::clear_issystem() {
+ _impl_.issystem_ = false;
+ _impl_._has_bits_[0] &= ~0x00000010u;
+}
+inline bool StackFrame_Data::_internal_issystem() const {
+ return _impl_.issystem_;
+}
+inline bool StackFrame_Data::issystem() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.isSystem)
+ return _internal_issystem();
+}
+inline void StackFrame_Data::_internal_set_issystem(bool value) {
+ _impl_._has_bits_[0] |= 0x00000010u;
+ _impl_.issystem_ = value;
+}
+inline void StackFrame_Data::set_issystem(bool value) {
+ _internal_set_issystem(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.isSystem)
+}
+
+// optional bool isSelfHosted = 10;
+inline bool StackFrame_Data::_internal_has_isselfhosted() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000020u) != 0;
+ return value;
+}
+inline bool StackFrame_Data::has_isselfhosted() const {
+ return _internal_has_isselfhosted();
+}
+inline void StackFrame_Data::clear_isselfhosted() {
+ _impl_.isselfhosted_ = false;
+ _impl_._has_bits_[0] &= ~0x00000020u;
+}
+inline bool StackFrame_Data::_internal_isselfhosted() const {
+ return _impl_.isselfhosted_;
+}
+inline bool StackFrame_Data::isselfhosted() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.isSelfHosted)
+ return _internal_isselfhosted();
+}
+inline void StackFrame_Data::_internal_set_isselfhosted(bool value) {
+ _impl_._has_bits_[0] |= 0x00000020u;
+ _impl_.isselfhosted_ = value;
+}
+inline void StackFrame_Data::set_isselfhosted(bool value) {
+ _internal_set_isselfhosted(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.isSelfHosted)
+}
+
+inline bool StackFrame_Data::has_SourceOrRef() const {
+ return SourceOrRef_case() != SOURCEORREF_NOT_SET;
+}
+inline void StackFrame_Data::clear_has_SourceOrRef() {
+ _impl_._oneof_case_[0] = SOURCEORREF_NOT_SET;
+}
+inline bool StackFrame_Data::has_FunctionDisplayNameOrRef() const {
+ return FunctionDisplayNameOrRef_case() != FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+inline void StackFrame_Data::clear_has_FunctionDisplayNameOrRef() {
+ _impl_._oneof_case_[1] = FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+inline StackFrame_Data::SourceOrRefCase StackFrame_Data::SourceOrRef_case() const {
+ return StackFrame_Data::SourceOrRefCase(_impl_._oneof_case_[0]);
+}
+inline StackFrame_Data::FunctionDisplayNameOrRefCase StackFrame_Data::FunctionDisplayNameOrRef_case() const {
+ return StackFrame_Data::FunctionDisplayNameOrRefCase(_impl_._oneof_case_[1]);
+}
+// -------------------------------------------------------------------
+
+// StackFrame
+
+// .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+inline bool StackFrame::_internal_has_data() const {
+ return StackFrameType_case() == kData;
+}
+inline bool StackFrame::has_data() const {
+ return _internal_has_data();
+}
+inline void StackFrame::set_has_data() {
+ _impl_._oneof_case_[0] = kData;
+}
+inline void StackFrame::clear_data() {
+ if (_internal_has_data()) {
+ if (GetArenaForAllocation() == nullptr) {
+ delete _impl_.StackFrameType_.data_;
+ }
+ clear_has_StackFrameType();
+ }
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::release_data() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.StackFrame.data)
+ if (_internal_has_data()) {
+ clear_has_StackFrameType();
+ ::mozilla::devtools::protobuf::StackFrame_Data* temp = _impl_.StackFrameType_.data_;
+ if (GetArenaForAllocation() != nullptr) {
+ temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp);
+ }
+ _impl_.StackFrameType_.data_ = nullptr;
+ return temp;
+ } else {
+ return nullptr;
+ }
+}
+inline const ::mozilla::devtools::protobuf::StackFrame_Data& StackFrame::_internal_data() const {
+ return _internal_has_data()
+ ? *_impl_.StackFrameType_.data_
+ : reinterpret_cast< ::mozilla::devtools::protobuf::StackFrame_Data&>(::mozilla::devtools::protobuf::_StackFrame_Data_default_instance_);
+}
+inline const ::mozilla::devtools::protobuf::StackFrame_Data& StackFrame::data() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.data)
+ return _internal_data();
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::unsafe_arena_release_data() {
+ // @@protoc_insertion_point(field_unsafe_arena_release:mozilla.devtools.protobuf.StackFrame.data)
+ if (_internal_has_data()) {
+ clear_has_StackFrameType();
+ ::mozilla::devtools::protobuf::StackFrame_Data* temp = _impl_.StackFrameType_.data_;
+ _impl_.StackFrameType_.data_ = nullptr;
+ return temp;
+ } else {
+ return nullptr;
+ }
+}
+inline void StackFrame::unsafe_arena_set_allocated_data(::mozilla::devtools::protobuf::StackFrame_Data* data) {
+ clear_StackFrameType();
+ if (data) {
+ set_has_data();
+ _impl_.StackFrameType_.data_ = data;
+ }
+ // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.devtools.protobuf.StackFrame.data)
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::_internal_mutable_data() {
+ if (!_internal_has_data()) {
+ clear_StackFrameType();
+ set_has_data();
+ _impl_.StackFrameType_.data_ = CreateMaybeMessage< ::mozilla::devtools::protobuf::StackFrame_Data >(GetArenaForAllocation());
+ }
+ return _impl_.StackFrameType_.data_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::mutable_data() {
+ ::mozilla::devtools::protobuf::StackFrame_Data* _msg = _internal_mutable_data();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.StackFrame.data)
+ return _msg;
+}
+
+// uint64 ref = 2;
+inline bool StackFrame::_internal_has_ref() const {
+ return StackFrameType_case() == kRef;
+}
+inline bool StackFrame::has_ref() const {
+ return _internal_has_ref();
+}
+inline void StackFrame::set_has_ref() {
+ _impl_._oneof_case_[0] = kRef;
+}
+inline void StackFrame::clear_ref() {
+ if (_internal_has_ref()) {
+ _impl_.StackFrameType_.ref_ = uint64_t{0u};
+ clear_has_StackFrameType();
+ }
+}
+inline uint64_t StackFrame::_internal_ref() const {
+ if (_internal_has_ref()) {
+ return _impl_.StackFrameType_.ref_;
+ }
+ return uint64_t{0u};
+}
+inline void StackFrame::_internal_set_ref(uint64_t value) {
+ if (!_internal_has_ref()) {
+ clear_StackFrameType();
+ set_has_ref();
+ }
+ _impl_.StackFrameType_.ref_ = value;
+}
+inline uint64_t StackFrame::ref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.ref)
+ return _internal_ref();
+}
+inline void StackFrame::set_ref(uint64_t value) {
+ _internal_set_ref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.ref)
+}
+
+inline bool StackFrame::has_StackFrameType() const {
+ return StackFrameType_case() != STACKFRAMETYPE_NOT_SET;
+}
+inline void StackFrame::clear_has_StackFrameType() {
+ _impl_._oneof_case_[0] = STACKFRAMETYPE_NOT_SET;
+}
+inline StackFrame::StackFrameTypeCase StackFrame::StackFrameType_case() const {
+ return StackFrame::StackFrameTypeCase(_impl_._oneof_case_[0]);
+}
+// -------------------------------------------------------------------
+
+// Node
+
+// optional uint64 id = 1;
+inline bool Node::_internal_has_id() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000002u) != 0;
+ return value;
+}
+inline bool Node::has_id() const {
+ return _internal_has_id();
+}
+inline void Node::clear_id() {
+ _impl_.id_ = uint64_t{0u};
+ _impl_._has_bits_[0] &= ~0x00000002u;
+}
+inline uint64_t Node::_internal_id() const {
+ return _impl_.id_;
+}
+inline uint64_t Node::id() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.id)
+ return _internal_id();
+}
+inline void Node::_internal_set_id(uint64_t value) {
+ _impl_._has_bits_[0] |= 0x00000002u;
+ _impl_.id_ = value;
+}
+inline void Node::set_id(uint64_t value) {
+ _internal_set_id(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.id)
+}
+
+// bytes typeName = 2;
+inline bool Node::_internal_has_typename_() const {
+ return TypeNameOrRef_case() == kTypeName;
+}
+inline bool Node::has_typename_() const {
+ return _internal_has_typename_();
+}
+inline void Node::set_has_typename_() {
+ _impl_._oneof_case_[0] = kTypeName;
+}
+inline void Node::clear_typename_() {
+ if (_internal_has_typename_()) {
+ _impl_.TypeNameOrRef_.typename__.Destroy();
+ clear_has_TypeNameOrRef();
+ }
+}
+inline const std::string& Node::typename_() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.typeName)
+ return _internal_typename_();
+}
+template <typename ArgT0, typename... ArgT>
+inline void Node::set_typename_(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ _impl_.TypeNameOrRef_.typename__.InitDefault();
+ }
+ _impl_.TypeNameOrRef_.typename__.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.typeName)
+}
+inline std::string* Node::mutable_typename_() {
+ std::string* _s = _internal_mutable_typename_();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.typeName)
+ return _s;
+}
+inline const std::string& Node::_internal_typename_() const {
+ if (_internal_has_typename_()) {
+ return _impl_.TypeNameOrRef_.typename__.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::_internal_set_typename_(const std::string& value) {
+ if (!_internal_has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ _impl_.TypeNameOrRef_.typename__.InitDefault();
+ }
+ _impl_.TypeNameOrRef_.typename__.Set(value, GetArenaForAllocation());
+}
+inline std::string* Node::_internal_mutable_typename_() {
+ if (!_internal_has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ _impl_.TypeNameOrRef_.typename__.InitDefault();
+ }
+ return _impl_.TypeNameOrRef_.typename__.Mutable( GetArenaForAllocation());
+}
+inline std::string* Node::release_typename_() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.typeName)
+ if (_internal_has_typename_()) {
+ clear_has_TypeNameOrRef();
+ return _impl_.TypeNameOrRef_.typename__.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void Node::set_allocated_typename_(std::string* typename_) {
+ if (has_TypeNameOrRef()) {
+ clear_TypeNameOrRef();
+ }
+ if (typename_ != nullptr) {
+ set_has_typename_();
+ _impl_.TypeNameOrRef_.typename__.InitAllocated(typename_, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.typeName)
+}
+
+// uint64 typeNameRef = 3;
+inline bool Node::_internal_has_typenameref() const {
+ return TypeNameOrRef_case() == kTypeNameRef;
+}
+inline bool Node::has_typenameref() const {
+ return _internal_has_typenameref();
+}
+inline void Node::set_has_typenameref() {
+ _impl_._oneof_case_[0] = kTypeNameRef;
+}
+inline void Node::clear_typenameref() {
+ if (_internal_has_typenameref()) {
+ _impl_.TypeNameOrRef_.typenameref_ = uint64_t{0u};
+ clear_has_TypeNameOrRef();
+ }
+}
+inline uint64_t Node::_internal_typenameref() const {
+ if (_internal_has_typenameref()) {
+ return _impl_.TypeNameOrRef_.typenameref_;
+ }
+ return uint64_t{0u};
+}
+inline void Node::_internal_set_typenameref(uint64_t value) {
+ if (!_internal_has_typenameref()) {
+ clear_TypeNameOrRef();
+ set_has_typenameref();
+ }
+ _impl_.TypeNameOrRef_.typenameref_ = value;
+}
+inline uint64_t Node::typenameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.typeNameRef)
+ return _internal_typenameref();
+}
+inline void Node::set_typenameref(uint64_t value) {
+ _internal_set_typenameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.typeNameRef)
+}
+
+// optional uint64 size = 4;
+inline bool Node::_internal_has_size() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000004u) != 0;
+ return value;
+}
+inline bool Node::has_size() const {
+ return _internal_has_size();
+}
+inline void Node::clear_size() {
+ _impl_.size_ = uint64_t{0u};
+ _impl_._has_bits_[0] &= ~0x00000004u;
+}
+inline uint64_t Node::_internal_size() const {
+ return _impl_.size_;
+}
+inline uint64_t Node::size() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.size)
+ return _internal_size();
+}
+inline void Node::_internal_set_size(uint64_t value) {
+ _impl_._has_bits_[0] |= 0x00000004u;
+ _impl_.size_ = value;
+}
+inline void Node::set_size(uint64_t value) {
+ _internal_set_size(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.size)
+}
+
+// repeated .mozilla.devtools.protobuf.Edge edges = 5;
+inline int Node::_internal_edges_size() const {
+ return _impl_.edges_.size();
+}
+inline int Node::edges_size() const {
+ return _internal_edges_size();
+}
+inline void Node::clear_edges() {
+ _impl_.edges_.Clear();
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::mutable_edges(int index) {
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.edges)
+ return _impl_.edges_.Mutable(index);
+}
+inline ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+Node::mutable_edges() {
+ // @@protoc_insertion_point(field_mutable_list:mozilla.devtools.protobuf.Node.edges)
+ return &_impl_.edges_;
+}
+inline const ::mozilla::devtools::protobuf::Edge& Node::_internal_edges(int index) const {
+ return _impl_.edges_.Get(index);
+}
+inline const ::mozilla::devtools::protobuf::Edge& Node::edges(int index) const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.edges)
+ return _internal_edges(index);
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::_internal_add_edges() {
+ return _impl_.edges_.Add();
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::add_edges() {
+ ::mozilla::devtools::protobuf::Edge* _add = _internal_add_edges();
+ // @@protoc_insertion_point(field_add:mozilla.devtools.protobuf.Node.edges)
+ return _add;
+}
+inline const ::PROTOBUF_NAMESPACE_ID::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+Node::edges() const {
+ // @@protoc_insertion_point(field_list:mozilla.devtools.protobuf.Node.edges)
+ return _impl_.edges_;
+}
+
+// optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+inline bool Node::_internal_has_allocationstack() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0;
+ PROTOBUF_ASSUME(!value || _impl_.allocationstack_ != nullptr);
+ return value;
+}
+inline bool Node::has_allocationstack() const {
+ return _internal_has_allocationstack();
+}
+inline void Node::clear_allocationstack() {
+ if (_impl_.allocationstack_ != nullptr) _impl_.allocationstack_->Clear();
+ _impl_._has_bits_[0] &= ~0x00000001u;
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& Node::_internal_allocationstack() const {
+ const ::mozilla::devtools::protobuf::StackFrame* p = _impl_.allocationstack_;
+ return p != nullptr ? *p : reinterpret_cast<const ::mozilla::devtools::protobuf::StackFrame&>(
+ ::mozilla::devtools::protobuf::_StackFrame_default_instance_);
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& Node::allocationstack() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.allocationStack)
+ return _internal_allocationstack();
+}
+inline void Node::unsafe_arena_set_allocated_allocationstack(
+ ::mozilla::devtools::protobuf::StackFrame* allocationstack) {
+ if (GetArenaForAllocation() == nullptr) {
+ delete reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(_impl_.allocationstack_);
+ }
+ _impl_.allocationstack_ = allocationstack;
+ if (allocationstack) {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ } else {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ }
+ // @@protoc_insertion_point(field_unsafe_arena_set_allocated:mozilla.devtools.protobuf.Node.allocationStack)
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::release_allocationstack() {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ ::mozilla::devtools::protobuf::StackFrame* temp = _impl_.allocationstack_;
+ _impl_.allocationstack_ = nullptr;
+#ifdef PROTOBUF_FORCE_COPY_IN_RELEASE
+ auto* old = reinterpret_cast<::PROTOBUF_NAMESPACE_ID::MessageLite*>(temp);
+ temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp);
+ if (GetArenaForAllocation() == nullptr) { delete old; }
+#else // PROTOBUF_FORCE_COPY_IN_RELEASE
+ if (GetArenaForAllocation() != nullptr) {
+ temp = ::PROTOBUF_NAMESPACE_ID::internal::DuplicateIfNonNull(temp);
+ }
+#endif // !PROTOBUF_FORCE_COPY_IN_RELEASE
+ return temp;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::unsafe_arena_release_allocationstack() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.allocationStack)
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ ::mozilla::devtools::protobuf::StackFrame* temp = _impl_.allocationstack_;
+ _impl_.allocationstack_ = nullptr;
+ return temp;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::_internal_mutable_allocationstack() {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ if (_impl_.allocationstack_ == nullptr) {
+ auto* p = CreateMaybeMessage<::mozilla::devtools::protobuf::StackFrame>(GetArenaForAllocation());
+ _impl_.allocationstack_ = p;
+ }
+ return _impl_.allocationstack_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::mutable_allocationstack() {
+ ::mozilla::devtools::protobuf::StackFrame* _msg = _internal_mutable_allocationstack();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.allocationStack)
+ return _msg;
+}
+inline void Node::set_allocated_allocationstack(::mozilla::devtools::protobuf::StackFrame* allocationstack) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* message_arena = GetArenaForAllocation();
+ if (message_arena == nullptr) {
+ delete _impl_.allocationstack_;
+ }
+ if (allocationstack) {
+ ::PROTOBUF_NAMESPACE_ID::Arena* submessage_arena =
+ ::PROTOBUF_NAMESPACE_ID::Arena::InternalGetOwningArena(allocationstack);
+ if (message_arena != submessage_arena) {
+ allocationstack = ::PROTOBUF_NAMESPACE_ID::internal::GetOwnedMessage(
+ message_arena, allocationstack, submessage_arena);
+ }
+ _impl_._has_bits_[0] |= 0x00000001u;
+ } else {
+ _impl_._has_bits_[0] &= ~0x00000001u;
+ }
+ _impl_.allocationstack_ = allocationstack;
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.allocationStack)
+}
+
+// bytes jsObjectClassName = 7;
+inline bool Node::_internal_has_jsobjectclassname() const {
+ return JSObjectClassNameOrRef_case() == kJsObjectClassName;
+}
+inline bool Node::has_jsobjectclassname() const {
+ return _internal_has_jsobjectclassname();
+}
+inline void Node::set_has_jsobjectclassname() {
+ _impl_._oneof_case_[1] = kJsObjectClassName;
+}
+inline void Node::clear_jsobjectclassname() {
+ if (_internal_has_jsobjectclassname()) {
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Destroy();
+ clear_has_JSObjectClassNameOrRef();
+ }
+}
+inline const std::string& Node::jsobjectclassname() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.jsObjectClassName)
+ return _internal_jsobjectclassname();
+}
+template <typename ArgT0, typename... ArgT>
+inline void Node::set_jsobjectclassname(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.InitDefault();
+ }
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.jsObjectClassName)
+}
+inline std::string* Node::mutable_jsobjectclassname() {
+ std::string* _s = _internal_mutable_jsobjectclassname();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.jsObjectClassName)
+ return _s;
+}
+inline const std::string& Node::_internal_jsobjectclassname() const {
+ if (_internal_has_jsobjectclassname()) {
+ return _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::_internal_set_jsobjectclassname(const std::string& value) {
+ if (!_internal_has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.InitDefault();
+ }
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Set(value, GetArenaForAllocation());
+}
+inline std::string* Node::_internal_mutable_jsobjectclassname() {
+ if (!_internal_has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.InitDefault();
+ }
+ return _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Mutable( GetArenaForAllocation());
+}
+inline std::string* Node::release_jsobjectclassname() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.jsObjectClassName)
+ if (_internal_has_jsobjectclassname()) {
+ clear_has_JSObjectClassNameOrRef();
+ return _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void Node::set_allocated_jsobjectclassname(std::string* jsobjectclassname) {
+ if (has_JSObjectClassNameOrRef()) {
+ clear_JSObjectClassNameOrRef();
+ }
+ if (jsobjectclassname != nullptr) {
+ set_has_jsobjectclassname();
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassname_.InitAllocated(jsobjectclassname, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.jsObjectClassName)
+}
+
+// uint64 jsObjectClassNameRef = 8;
+inline bool Node::_internal_has_jsobjectclassnameref() const {
+ return JSObjectClassNameOrRef_case() == kJsObjectClassNameRef;
+}
+inline bool Node::has_jsobjectclassnameref() const {
+ return _internal_has_jsobjectclassnameref();
+}
+inline void Node::set_has_jsobjectclassnameref() {
+ _impl_._oneof_case_[1] = kJsObjectClassNameRef;
+}
+inline void Node::clear_jsobjectclassnameref() {
+ if (_internal_has_jsobjectclassnameref()) {
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassnameref_ = uint64_t{0u};
+ clear_has_JSObjectClassNameOrRef();
+ }
+}
+inline uint64_t Node::_internal_jsobjectclassnameref() const {
+ if (_internal_has_jsobjectclassnameref()) {
+ return _impl_.JSObjectClassNameOrRef_.jsobjectclassnameref_;
+ }
+ return uint64_t{0u};
+}
+inline void Node::_internal_set_jsobjectclassnameref(uint64_t value) {
+ if (!_internal_has_jsobjectclassnameref()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassnameref();
+ }
+ _impl_.JSObjectClassNameOrRef_.jsobjectclassnameref_ = value;
+}
+inline uint64_t Node::jsobjectclassnameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.jsObjectClassNameRef)
+ return _internal_jsobjectclassnameref();
+}
+inline void Node::set_jsobjectclassnameref(uint64_t value) {
+ _internal_set_jsobjectclassnameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.jsObjectClassNameRef)
+}
+
+// optional uint32 coarseType = 9 [default = 0];
+inline bool Node::_internal_has_coarsetype() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000008u) != 0;
+ return value;
+}
+inline bool Node::has_coarsetype() const {
+ return _internal_has_coarsetype();
+}
+inline void Node::clear_coarsetype() {
+ _impl_.coarsetype_ = 0u;
+ _impl_._has_bits_[0] &= ~0x00000008u;
+}
+inline uint32_t Node::_internal_coarsetype() const {
+ return _impl_.coarsetype_;
+}
+inline uint32_t Node::coarsetype() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.coarseType)
+ return _internal_coarsetype();
+}
+inline void Node::_internal_set_coarsetype(uint32_t value) {
+ _impl_._has_bits_[0] |= 0x00000008u;
+ _impl_.coarsetype_ = value;
+}
+inline void Node::set_coarsetype(uint32_t value) {
+ _internal_set_coarsetype(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.coarseType)
+}
+
+// bytes scriptFilename = 10;
+inline bool Node::_internal_has_scriptfilename() const {
+ return ScriptFilenameOrRef_case() == kScriptFilename;
+}
+inline bool Node::has_scriptfilename() const {
+ return _internal_has_scriptfilename();
+}
+inline void Node::set_has_scriptfilename() {
+ _impl_._oneof_case_[2] = kScriptFilename;
+}
+inline void Node::clear_scriptfilename() {
+ if (_internal_has_scriptfilename()) {
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.Destroy();
+ clear_has_ScriptFilenameOrRef();
+ }
+}
+inline const std::string& Node::scriptfilename() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.scriptFilename)
+ return _internal_scriptfilename();
+}
+template <typename ArgT0, typename... ArgT>
+inline void Node::set_scriptfilename(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.InitDefault();
+ }
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.scriptFilename)
+}
+inline std::string* Node::mutable_scriptfilename() {
+ std::string* _s = _internal_mutable_scriptfilename();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.scriptFilename)
+ return _s;
+}
+inline const std::string& Node::_internal_scriptfilename() const {
+ if (_internal_has_scriptfilename()) {
+ return _impl_.ScriptFilenameOrRef_.scriptfilename_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::_internal_set_scriptfilename(const std::string& value) {
+ if (!_internal_has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.InitDefault();
+ }
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.Set(value, GetArenaForAllocation());
+}
+inline std::string* Node::_internal_mutable_scriptfilename() {
+ if (!_internal_has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.InitDefault();
+ }
+ return _impl_.ScriptFilenameOrRef_.scriptfilename_.Mutable( GetArenaForAllocation());
+}
+inline std::string* Node::release_scriptfilename() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.scriptFilename)
+ if (_internal_has_scriptfilename()) {
+ clear_has_ScriptFilenameOrRef();
+ return _impl_.ScriptFilenameOrRef_.scriptfilename_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void Node::set_allocated_scriptfilename(std::string* scriptfilename) {
+ if (has_ScriptFilenameOrRef()) {
+ clear_ScriptFilenameOrRef();
+ }
+ if (scriptfilename != nullptr) {
+ set_has_scriptfilename();
+ _impl_.ScriptFilenameOrRef_.scriptfilename_.InitAllocated(scriptfilename, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.scriptFilename)
+}
+
+// uint64 scriptFilenameRef = 11;
+inline bool Node::_internal_has_scriptfilenameref() const {
+ return ScriptFilenameOrRef_case() == kScriptFilenameRef;
+}
+inline bool Node::has_scriptfilenameref() const {
+ return _internal_has_scriptfilenameref();
+}
+inline void Node::set_has_scriptfilenameref() {
+ _impl_._oneof_case_[2] = kScriptFilenameRef;
+}
+inline void Node::clear_scriptfilenameref() {
+ if (_internal_has_scriptfilenameref()) {
+ _impl_.ScriptFilenameOrRef_.scriptfilenameref_ = uint64_t{0u};
+ clear_has_ScriptFilenameOrRef();
+ }
+}
+inline uint64_t Node::_internal_scriptfilenameref() const {
+ if (_internal_has_scriptfilenameref()) {
+ return _impl_.ScriptFilenameOrRef_.scriptfilenameref_;
+ }
+ return uint64_t{0u};
+}
+inline void Node::_internal_set_scriptfilenameref(uint64_t value) {
+ if (!_internal_has_scriptfilenameref()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilenameref();
+ }
+ _impl_.ScriptFilenameOrRef_.scriptfilenameref_ = value;
+}
+inline uint64_t Node::scriptfilenameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.scriptFilenameRef)
+ return _internal_scriptfilenameref();
+}
+inline void Node::set_scriptfilenameref(uint64_t value) {
+ _internal_set_scriptfilenameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.scriptFilenameRef)
+}
+
+// bytes descriptiveTypeName = 12;
+inline bool Node::_internal_has_descriptivetypename() const {
+ return descriptiveTypeNameOrRef_case() == kDescriptiveTypeName;
+}
+inline bool Node::has_descriptivetypename() const {
+ return _internal_has_descriptivetypename();
+}
+inline void Node::set_has_descriptivetypename() {
+ _impl_._oneof_case_[3] = kDescriptiveTypeName;
+}
+inline void Node::clear_descriptivetypename() {
+ if (_internal_has_descriptivetypename()) {
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Destroy();
+ clear_has_descriptiveTypeNameOrRef();
+ }
+}
+inline const std::string& Node::descriptivetypename() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+ return _internal_descriptivetypename();
+}
+template <typename ArgT0, typename... ArgT>
+inline void Node::set_descriptivetypename(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_descriptivetypename()) {
+ clear_descriptiveTypeNameOrRef();
+ set_has_descriptivetypename();
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.InitDefault();
+ }
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+inline std::string* Node::mutable_descriptivetypename() {
+ std::string* _s = _internal_mutable_descriptivetypename();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+ return _s;
+}
+inline const std::string& Node::_internal_descriptivetypename() const {
+ if (_internal_has_descriptivetypename()) {
+ return _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::_internal_set_descriptivetypename(const std::string& value) {
+ if (!_internal_has_descriptivetypename()) {
+ clear_descriptiveTypeNameOrRef();
+ set_has_descriptivetypename();
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.InitDefault();
+ }
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Set(value, GetArenaForAllocation());
+}
+inline std::string* Node::_internal_mutable_descriptivetypename() {
+ if (!_internal_has_descriptivetypename()) {
+ clear_descriptiveTypeNameOrRef();
+ set_has_descriptivetypename();
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.InitDefault();
+ }
+ return _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Mutable( GetArenaForAllocation());
+}
+inline std::string* Node::release_descriptivetypename() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+ if (_internal_has_descriptivetypename()) {
+ clear_has_descriptiveTypeNameOrRef();
+ return _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void Node::set_allocated_descriptivetypename(std::string* descriptivetypename) {
+ if (has_descriptiveTypeNameOrRef()) {
+ clear_descriptiveTypeNameOrRef();
+ }
+ if (descriptivetypename != nullptr) {
+ set_has_descriptivetypename();
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypename_.InitAllocated(descriptivetypename, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.descriptiveTypeName)
+}
+
+// uint64 descriptiveTypeNameRef = 13;
+inline bool Node::_internal_has_descriptivetypenameref() const {
+ return descriptiveTypeNameOrRef_case() == kDescriptiveTypeNameRef;
+}
+inline bool Node::has_descriptivetypenameref() const {
+ return _internal_has_descriptivetypenameref();
+}
+inline void Node::set_has_descriptivetypenameref() {
+ _impl_._oneof_case_[3] = kDescriptiveTypeNameRef;
+}
+inline void Node::clear_descriptivetypenameref() {
+ if (_internal_has_descriptivetypenameref()) {
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypenameref_ = uint64_t{0u};
+ clear_has_descriptiveTypeNameOrRef();
+ }
+}
+inline uint64_t Node::_internal_descriptivetypenameref() const {
+ if (_internal_has_descriptivetypenameref()) {
+ return _impl_.descriptiveTypeNameOrRef_.descriptivetypenameref_;
+ }
+ return uint64_t{0u};
+}
+inline void Node::_internal_set_descriptivetypenameref(uint64_t value) {
+ if (!_internal_has_descriptivetypenameref()) {
+ clear_descriptiveTypeNameOrRef();
+ set_has_descriptivetypenameref();
+ }
+ _impl_.descriptiveTypeNameOrRef_.descriptivetypenameref_ = value;
+}
+inline uint64_t Node::descriptivetypenameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+ return _internal_descriptivetypenameref();
+}
+inline void Node::set_descriptivetypenameref(uint64_t value) {
+ _internal_set_descriptivetypenameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.descriptiveTypeNameRef)
+}
+
+inline bool Node::has_TypeNameOrRef() const {
+ return TypeNameOrRef_case() != TYPENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_TypeNameOrRef() {
+ _impl_._oneof_case_[0] = TYPENAMEORREF_NOT_SET;
+}
+inline bool Node::has_JSObjectClassNameOrRef() const {
+ return JSObjectClassNameOrRef_case() != JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_JSObjectClassNameOrRef() {
+ _impl_._oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+inline bool Node::has_ScriptFilenameOrRef() const {
+ return ScriptFilenameOrRef_case() != SCRIPTFILENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_ScriptFilenameOrRef() {
+ _impl_._oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
+inline bool Node::has_descriptiveTypeNameOrRef() const {
+ return descriptiveTypeNameOrRef_case() != DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_descriptiveTypeNameOrRef() {
+ _impl_._oneof_case_[3] = DESCRIPTIVETYPENAMEORREF_NOT_SET;
+}
+inline Node::TypeNameOrRefCase Node::TypeNameOrRef_case() const {
+ return Node::TypeNameOrRefCase(_impl_._oneof_case_[0]);
+}
+inline Node::JSObjectClassNameOrRefCase Node::JSObjectClassNameOrRef_case() const {
+ return Node::JSObjectClassNameOrRefCase(_impl_._oneof_case_[1]);
+}
+inline Node::ScriptFilenameOrRefCase Node::ScriptFilenameOrRef_case() const {
+ return Node::ScriptFilenameOrRefCase(_impl_._oneof_case_[2]);
+}
+inline Node::DescriptiveTypeNameOrRefCase Node::descriptiveTypeNameOrRef_case() const {
+ return Node::DescriptiveTypeNameOrRefCase(_impl_._oneof_case_[3]);
+}
+// -------------------------------------------------------------------
+
+// Edge
+
+// optional uint64 referent = 1;
+inline bool Edge::_internal_has_referent() const {
+ bool value = (_impl_._has_bits_[0] & 0x00000001u) != 0;
+ return value;
+}
+inline bool Edge::has_referent() const {
+ return _internal_has_referent();
+}
+inline void Edge::clear_referent() {
+ _impl_.referent_ = uint64_t{0u};
+ _impl_._has_bits_[0] &= ~0x00000001u;
+}
+inline uint64_t Edge::_internal_referent() const {
+ return _impl_.referent_;
+}
+inline uint64_t Edge::referent() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.referent)
+ return _internal_referent();
+}
+inline void Edge::_internal_set_referent(uint64_t value) {
+ _impl_._has_bits_[0] |= 0x00000001u;
+ _impl_.referent_ = value;
+}
+inline void Edge::set_referent(uint64_t value) {
+ _internal_set_referent(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.referent)
+}
+
+// bytes name = 2;
+inline bool Edge::_internal_has_name() const {
+ return EdgeNameOrRef_case() == kName;
+}
+inline bool Edge::has_name() const {
+ return _internal_has_name();
+}
+inline void Edge::set_has_name() {
+ _impl_._oneof_case_[0] = kName;
+}
+inline void Edge::clear_name() {
+ if (_internal_has_name()) {
+ _impl_.EdgeNameOrRef_.name_.Destroy();
+ clear_has_EdgeNameOrRef();
+ }
+}
+inline const std::string& Edge::name() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.name)
+ return _internal_name();
+}
+template <typename ArgT0, typename... ArgT>
+inline void Edge::set_name(ArgT0&& arg0, ArgT... args) {
+ if (!_internal_has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ _impl_.EdgeNameOrRef_.name_.InitDefault();
+ }
+ _impl_.EdgeNameOrRef_.name_.SetBytes( static_cast<ArgT0 &&>(arg0), args..., GetArenaForAllocation());
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.name)
+}
+inline std::string* Edge::mutable_name() {
+ std::string* _s = _internal_mutable_name();
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Edge.name)
+ return _s;
+}
+inline const std::string& Edge::_internal_name() const {
+ if (_internal_has_name()) {
+ return _impl_.EdgeNameOrRef_.name_.Get();
+ }
+ return ::PROTOBUF_NAMESPACE_ID::internal::GetEmptyStringAlreadyInited();
+}
+inline void Edge::_internal_set_name(const std::string& value) {
+ if (!_internal_has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ _impl_.EdgeNameOrRef_.name_.InitDefault();
+ }
+ _impl_.EdgeNameOrRef_.name_.Set(value, GetArenaForAllocation());
+}
+inline std::string* Edge::_internal_mutable_name() {
+ if (!_internal_has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ _impl_.EdgeNameOrRef_.name_.InitDefault();
+ }
+ return _impl_.EdgeNameOrRef_.name_.Mutable( GetArenaForAllocation());
+}
+inline std::string* Edge::release_name() {
+ // @@protoc_insertion_point(field_release:mozilla.devtools.protobuf.Edge.name)
+ if (_internal_has_name()) {
+ clear_has_EdgeNameOrRef();
+ return _impl_.EdgeNameOrRef_.name_.Release();
+ } else {
+ return nullptr;
+ }
+}
+inline void Edge::set_allocated_name(std::string* name) {
+ if (has_EdgeNameOrRef()) {
+ clear_EdgeNameOrRef();
+ }
+ if (name != nullptr) {
+ set_has_name();
+ _impl_.EdgeNameOrRef_.name_.InitAllocated(name, GetArenaForAllocation());
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Edge.name)
+}
+
+// uint64 nameRef = 3;
+inline bool Edge::_internal_has_nameref() const {
+ return EdgeNameOrRef_case() == kNameRef;
+}
+inline bool Edge::has_nameref() const {
+ return _internal_has_nameref();
+}
+inline void Edge::set_has_nameref() {
+ _impl_._oneof_case_[0] = kNameRef;
+}
+inline void Edge::clear_nameref() {
+ if (_internal_has_nameref()) {
+ _impl_.EdgeNameOrRef_.nameref_ = uint64_t{0u};
+ clear_has_EdgeNameOrRef();
+ }
+}
+inline uint64_t Edge::_internal_nameref() const {
+ if (_internal_has_nameref()) {
+ return _impl_.EdgeNameOrRef_.nameref_;
+ }
+ return uint64_t{0u};
+}
+inline void Edge::_internal_set_nameref(uint64_t value) {
+ if (!_internal_has_nameref()) {
+ clear_EdgeNameOrRef();
+ set_has_nameref();
+ }
+ _impl_.EdgeNameOrRef_.nameref_ = value;
+}
+inline uint64_t Edge::nameref() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.nameRef)
+ return _internal_nameref();
+}
+inline void Edge::set_nameref(uint64_t value) {
+ _internal_set_nameref(value);
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.nameRef)
+}
+
+inline bool Edge::has_EdgeNameOrRef() const {
+ return EdgeNameOrRef_case() != EDGENAMEORREF_NOT_SET;
+}
+inline void Edge::clear_has_EdgeNameOrRef() {
+ _impl_._oneof_case_[0] = EDGENAMEORREF_NOT_SET;
+}
+inline Edge::EdgeNameOrRefCase Edge::EdgeNameOrRef_case() const {
+ return Edge::EdgeNameOrRefCase(_impl_._oneof_case_[0]);
+}
+#ifdef __GNUC__
+ #pragma GCC diagnostic pop
+#endif // __GNUC__
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+
+// -------------------------------------------------------------------
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+
+// @@protoc_insertion_point(global_scope)
+
+#include <google/protobuf/port_undef.inc>
+#endif // GOOGLE_PROTOBUF_INCLUDED_GOOGLE_PROTOBUF_INCLUDED_CoreDump_2eproto
diff --git a/devtools/shared/heapsnapshot/CoreDump.proto b/devtools/shared/heapsnapshot/CoreDump.proto
new file mode 100644
index 0000000000..4d0bf27f01
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.proto
@@ -0,0 +1,152 @@
+/* -*- Mode: protobuf; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// # Core Dumps
+//
+// A core dump is a serialized snapshot of the heap graph. We serialize the heap
+// as a series of protobuf messages with each message prefixed by its Varint32
+// byte size so we can delimit individual protobuf messages (protobuf parsers
+// cannot determine where a message ends on their own).
+//
+// The first protobuf message is an instance of the `Metadata` message. All
+// subsequent messages will be instances of the `Node` message. The first of
+// these `Node` messages is the root node of the serialized heap graph. Here is
+// a diagram of our core dump format:
+//
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of following `Metadata` message. |
+// +-----------------------------------------------------------------------+
+// | message: The core dump `Metadata` message. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: The first `Node` message. This is the root node. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: A `Node` message. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: A `Node` message. |
+// +-----------------------------------------------------------------------+
+// | . |
+// | . |
+// | . |
+// +-----------------------------------------------------------------------+
+//
+// Core dumps should always be written with a
+// `google::protobuf::io::GzipOutputStream` and read from a
+// `google::protobuf::io::GzipInputStream`.
+//
+// Note that all strings are de-duplicated. The first time the N^th unique
+// string is encountered, the full string is serialized. Subsequent times that
+// same string is encountered, it is referenced by N. This de-duplication
+// happens across string properties, not on a per-property basis. For example,
+// if the same K^th unique string is first used as an Edge::EdgeNameOrRef and
+// then as a StackFrame::Data::FunctionDisplayNameOrRef, the first will be the
+// actual string as the functionDisplayName oneof property, and the second will
+// be a reference to the first as the edgeNameRef oneof property whose value is
+// K.
+//
+// We would ordinarily abstract these de-duplicated strings with messages of
+// their own, but unfortunately, the protobuf compiler does not have a way to
+// inline a messsage within another message and the child message must be
+// referenced by pointer. This leads to extra mallocs that we wish to avoid.
+
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package mozilla.devtools.protobuf;
+
+// A collection of metadata about this core dump.
+message Metadata {
+ // Number of microseconds since midnight (00:00:00) 1 January 1970 UTC.
+ optional uint64 timeStamp = 1;
+}
+
+// A serialized version of `JS::ubi::StackFrame`. Older parent frame tails are
+// de-duplicated to cut down on [de]serialization and size costs.
+message StackFrame {
+ oneof StackFrameType {
+ // This is the first time this stack frame has been serialized, and so
+ // here is all of its data.
+ Data data = 1;
+ // A reference to a stack frame that has already been serialized and has
+ // the given number as its id.
+ uint64 ref = 2;
+ }
+
+ message Data {
+ optional uint64 id = 1;
+ optional StackFrame parent = 2;
+ optional uint32 line = 3;
+ optional uint32 column = 4;
+
+ // De-duplicated two-byte string.
+ oneof SourceOrRef {
+ bytes source = 5;
+ uint64 sourceRef = 6;
+ }
+
+ // De-duplicated two-byte string.
+ oneof FunctionDisplayNameOrRef {
+ bytes functionDisplayName = 7;
+ uint64 functionDisplayNameRef = 8;
+ }
+
+ optional bool isSystem = 9;
+ optional bool isSelfHosted = 10;
+ }
+}
+
+// A serialized version of `JS::ubi::Node` and its outgoing edges.
+message Node {
+ optional uint64 id = 1;
+
+ // De-duplicated two-byte string.
+ oneof TypeNameOrRef {
+ bytes typeName = 2;
+ uint64 typeNameRef = 3;
+ }
+
+ optional uint64 size = 4;
+ repeated Edge edges = 5;
+ optional StackFrame allocationStack = 6;
+
+ // De-duplicated one-byte string.
+ oneof JSObjectClassNameOrRef {
+ bytes jsObjectClassName = 7;
+ uint64 jsObjectClassNameRef = 8;
+ }
+
+ // JS::ubi::CoarseType. Defaults to Other.
+ optional uint32 coarseType = 9 [default = 0];
+
+ // De-duplicated one-byte string.
+ oneof ScriptFilenameOrRef {
+ bytes scriptFilename = 10;
+ uint64 scriptFilenameRef = 11;
+ }
+
+ // De-duplicated one-byte string.
+ oneof descriptiveTypeNameOrRef {
+ bytes descriptiveTypeName = 12;
+ uint64 descriptiveTypeNameRef = 13;
+ }
+}
+
+// A serialized edge from the heap graph.
+message Edge {
+ optional uint64 referent = 1;
+
+ // De-duplicated two-byte string.
+ oneof EdgeNameOrRef {
+ bytes name = 2;
+ uint64 nameRef = 3;
+ }
+}
diff --git a/devtools/shared/heapsnapshot/DeserializedNode.cpp b/devtools/shared/heapsnapshot/DeserializedNode.cpp
new file mode 100644
index 0000000000..7de7d17faa
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -0,0 +1,124 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "nsCRTGlue.h"
+
+namespace mozilla {
+namespace devtools {
+
+DeserializedEdge::DeserializedEdge(DeserializedEdge&& rhs) {
+ referent = rhs.referent;
+ name = rhs.name;
+}
+
+DeserializedEdge& DeserializedEdge::operator=(DeserializedEdge&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~DeserializedEdge();
+ new (this) DeserializedEdge(std::move(rhs));
+ return *this;
+}
+
+JS::ubi::Node DeserializedNode::getEdgeReferent(const DeserializedEdge& edge) {
+ auto ptr = owner->nodes.lookup(edge.referent);
+ MOZ_ASSERT(ptr);
+
+ // `HashSets` only provide const access to their values, because mutating a
+ // value might change its hash, rendering it unfindable in the set.
+ // Unfortunately, the `ubi::Node` constructor requires a non-const pointer to
+ // its referent. However, the only aspect of a `DeserializedNode` we hash on
+ // is its id, which can't be changed via `ubi::Node`, so this cast can't cause
+ // the trouble `HashSet` is concerned a non-const reference would cause.
+ return JS::ubi::Node(const_cast<DeserializedNode*>(&*ptr));
+}
+
+JS::ubi::StackFrame DeserializedStackFrame::getParentStackFrame() const {
+ MOZ_ASSERT(parent.isSome());
+ auto ptr = owner->frames.lookup(parent.ref());
+ MOZ_ASSERT(ptr);
+ // See above comment in DeserializedNode::getEdgeReferent about why this
+ // const_cast is needed and safe.
+ return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+namespace JS {
+namespace ubi {
+
+const char16_t Concrete<DeserializedNode>::concreteTypeName[] =
+ u"mozilla::devtools::DeserializedNode";
+
+const char16_t* Concrete<DeserializedNode>::typeName() const {
+ return get().typeName;
+}
+
+Node::Size Concrete<DeserializedNode>::size(
+ mozilla::MallocSizeOf mallocSizeof) const {
+ return get().size;
+}
+
+class DeserializedEdgeRange : public EdgeRange {
+ DeserializedNode* node;
+ Edge currentEdge;
+ size_t i;
+
+ void settle() {
+ if (i >= node->edges.length()) {
+ front_ = nullptr;
+ return;
+ }
+
+ auto& edge = node->edges[i];
+ auto referent = node->getEdgeReferent(edge);
+ currentEdge = Edge(edge.name ? NS_xstrdup(edge.name) : nullptr, referent);
+ front_ = &currentEdge;
+ }
+
+ public:
+ explicit DeserializedEdgeRange(DeserializedNode& node) : node(&node), i(0) {
+ settle();
+ }
+
+ void popFront() override {
+ i++;
+ settle();
+ }
+};
+
+StackFrame Concrete<DeserializedNode>::allocationStack() const {
+ MOZ_ASSERT(hasAllocationStack());
+ auto id = get().allocationStack.ref();
+ auto ptr = get().owner->frames.lookup(id);
+ MOZ_ASSERT(ptr);
+ // See above comment in DeserializedNode::getEdgeReferent about why this
+ // const_cast is needed and safe.
+ return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
+}
+
+js::UniquePtr<EdgeRange> Concrete<DeserializedNode>::edges(JSContext* cx,
+ bool) const {
+ js::UniquePtr<DeserializedEdgeRange> range(
+ js_new<DeserializedEdgeRange>(get()));
+
+ if (!range) return nullptr;
+
+ return js::UniquePtr<EdgeRange>(range.release());
+}
+
+StackFrame ConcreteStackFrame<DeserializedStackFrame>::parent() const {
+ return get().parent.isNothing() ? StackFrame() : get().getParentStackFrame();
+}
+
+bool ConcreteStackFrame<DeserializedStackFrame>::constructSavedFrameStack(
+ JSContext* cx, JS::MutableHandle<JSObject*> outSavedFrameStack) const {
+ StackFrame f(&get());
+ return ConstructSavedFrameStackSlow(cx, f, outSavedFrameStack);
+}
+
+} // namespace ubi
+} // namespace JS
diff --git a/devtools/shared/heapsnapshot/DeserializedNode.h b/devtools/shared/heapsnapshot/DeserializedNode.h
new file mode 100644
index 0000000000..cc831b4dc5
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -0,0 +1,308 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_DeserializedNode__
+#define mozilla_devtools_DeserializedNode__
+
+#include <utility>
+
+#include "js/ColumnNumber.h" // JS::TaggedColumnNumberOneOrigin
+#include "js/UbiNode.h"
+#include "js/UniquePtr.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Vector.h"
+#include "mozilla/devtools/CoreDump.pb.h"
+
+// `Deserialized{Node,Edge}` translate protobuf messages from our core dump
+// format into structures we can rely upon for implementing `JS::ubi::Node`
+// specializations on top of. All of the properties of the protobuf messages are
+// optional for future compatibility, and this is the layer where we validate
+// that the properties that do actually exist in any given message fulfill our
+// semantic requirements.
+//
+// Both `DeserializedNode` and `DeserializedEdge` are always owned by a
+// `HeapSnapshot` instance, and their lifetimes must not extend after that of
+// their owning `HeapSnapshot`.
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshot;
+
+using NodeId = uint64_t;
+using StackFrameId = uint64_t;
+
+// A `DeserializedEdge` represents an edge in the heap graph pointing to the
+// node with id equal to `DeserializedEdge::referent` that we deserialized from
+// a core dump.
+struct DeserializedEdge {
+ NodeId referent;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char16_t* name;
+
+ explicit DeserializedEdge(NodeId referent, const char16_t* edgeName = nullptr)
+ : referent(referent), name(edgeName) {}
+ DeserializedEdge(DeserializedEdge&& rhs);
+ DeserializedEdge& operator=(DeserializedEdge&& rhs);
+
+ private:
+ DeserializedEdge(const DeserializedEdge&) = delete;
+ DeserializedEdge& operator=(const DeserializedEdge&) = delete;
+};
+
+// A `DeserializedNode` is a node in the heap graph that we deserialized from a
+// core dump.
+struct DeserializedNode {
+ using EdgeVector = Vector<DeserializedEdge>;
+ using UniqueStringPtr = UniquePtr<char16_t[]>;
+
+ NodeId id;
+ JS::ubi::CoarseType coarseType;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char16_t* typeName;
+ uint64_t size;
+ EdgeVector edges;
+ Maybe<StackFrameId> allocationStack;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char* jsObjectClassName;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char* scriptFilename;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char16_t* descriptiveTypeName;
+ // A weak pointer to this node's owning `HeapSnapshot`. Safe without
+ // AddRef'ing because this node's lifetime is equal to that of its owner.
+ HeapSnapshot* owner;
+
+ DeserializedNode(NodeId id, JS::ubi::CoarseType coarseType,
+ const char16_t* typeName, uint64_t size, EdgeVector&& edges,
+ const Maybe<StackFrameId>& allocationStack,
+ const char* className, const char* filename,
+ const char16_t* descriptiveName, HeapSnapshot& owner)
+ : id(id),
+ coarseType(coarseType),
+ typeName(typeName),
+ size(size),
+ edges(std::move(edges)),
+ allocationStack(allocationStack),
+ jsObjectClassName(className),
+ scriptFilename(filename),
+ descriptiveTypeName(descriptiveName),
+ owner(&owner) {}
+ virtual ~DeserializedNode() {}
+
+ DeserializedNode(DeserializedNode&& rhs)
+ : id(rhs.id),
+ coarseType(rhs.coarseType),
+ typeName(rhs.typeName),
+ size(rhs.size),
+ edges(std::move(rhs.edges)),
+ allocationStack(rhs.allocationStack),
+ jsObjectClassName(rhs.jsObjectClassName),
+ scriptFilename(rhs.scriptFilename),
+ descriptiveTypeName(rhs.descriptiveTypeName),
+ owner(rhs.owner) {}
+
+ DeserializedNode& operator=(DeserializedNode&& rhs) {
+ MOZ_ASSERT(&rhs != this);
+ this->~DeserializedNode();
+ new (this) DeserializedNode(std::move(rhs));
+ return *this;
+ }
+
+ // Get a borrowed reference to the given edge's referent. This method is
+ // virtual to provide a hook for gmock and gtest.
+ virtual JS::ubi::Node getEdgeReferent(const DeserializedEdge& edge);
+
+ struct HashPolicy;
+
+ protected:
+ // This is only for use with `MockDeserializedNode` in testing.
+ DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size)
+ : id(id),
+ coarseType(JS::ubi::CoarseType::Other),
+ typeName(typeName),
+ size(size),
+ edges(),
+ allocationStack(Nothing()),
+ jsObjectClassName(nullptr),
+ scriptFilename(nullptr),
+ descriptiveTypeName(nullptr),
+ owner(nullptr) {}
+
+ private:
+ DeserializedNode(const DeserializedNode&) = delete;
+ DeserializedNode& operator=(const DeserializedNode&) = delete;
+};
+
+static inline js::HashNumber hashIdDerivedFromPtr(uint64_t id) {
+ return mozilla::HashGeneric(id);
+}
+
+struct DeserializedNode::HashPolicy {
+ using Lookup = NodeId;
+
+ static js::HashNumber hash(const Lookup& lookup) {
+ return hashIdDerivedFromPtr(lookup);
+ }
+
+ static bool match(const DeserializedNode& existing, const Lookup& lookup) {
+ return existing.id == lookup;
+ }
+};
+
+// A `DeserializedStackFrame` is a stack frame referred to by a thing in the
+// heap graph that we deserialized from a core dump.
+struct DeserializedStackFrame {
+ StackFrameId id;
+ Maybe<StackFrameId> parent;
+ uint32_t line;
+ JS::TaggedColumnNumberOneOrigin column;
+ // Borrowed references to strings owned by this DeserializedStackFrame's
+ // owning HeapSnapshot.
+ const char16_t* source;
+ const char16_t* functionDisplayName;
+ bool isSystem;
+ bool isSelfHosted;
+ // A weak pointer to this frame's owning `HeapSnapshot`. Safe without
+ // AddRef'ing because this frame's lifetime is equal to that of its owner.
+ HeapSnapshot* owner;
+
+ explicit DeserializedStackFrame(
+ StackFrameId id, const Maybe<StackFrameId>& parent, uint32_t line,
+ JS::TaggedColumnNumberOneOrigin column, const char16_t* source,
+ const char16_t* functionDisplayName, bool isSystem, bool isSelfHosted,
+ HeapSnapshot& owner)
+ : id(id),
+ parent(parent),
+ line(line),
+ column(column),
+ source(source),
+ functionDisplayName(functionDisplayName),
+ isSystem(isSystem),
+ isSelfHosted(isSelfHosted),
+ owner(&owner) {
+ MOZ_ASSERT(source);
+ }
+
+ JS::ubi::StackFrame getParentStackFrame() const;
+
+ struct HashPolicy;
+
+ protected:
+ // This is exposed only for MockDeserializedStackFrame in the gtests.
+ explicit DeserializedStackFrame()
+ : id(0),
+ parent(Nothing()),
+ line(0),
+ source(nullptr),
+ functionDisplayName(nullptr),
+ isSystem(false),
+ isSelfHosted(false),
+ owner(nullptr){};
+};
+
+struct DeserializedStackFrame::HashPolicy {
+ using Lookup = StackFrameId;
+
+ static js::HashNumber hash(const Lookup& lookup) {
+ return hashIdDerivedFromPtr(lookup);
+ }
+
+ static bool match(const DeserializedStackFrame& existing,
+ const Lookup& lookup) {
+ return existing.id == lookup;
+ }
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+namespace JS {
+namespace ubi {
+
+using mozilla::devtools::DeserializedNode;
+using mozilla::devtools::DeserializedStackFrame;
+
+template <>
+class Concrete<DeserializedNode> : public Base {
+ protected:
+ explicit Concrete(DeserializedNode* ptr) : Base(ptr) {}
+ DeserializedNode& get() const { return *static_cast<DeserializedNode*>(ptr); }
+
+ public:
+ static void construct(void* storage, DeserializedNode* ptr) {
+ new (storage) Concrete(ptr);
+ }
+
+ CoarseType coarseType() const final { return get().coarseType; }
+ Id identifier() const override { return get().id; }
+ bool isLive() const override { return false; }
+ const char16_t* typeName() const override;
+ Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override;
+ const char* jsObjectClassName() const override {
+ return get().jsObjectClassName;
+ }
+ const char* scriptFilename() const final { return get().scriptFilename; }
+ const char16_t* descriptiveTypeName() const override {
+ return get().descriptiveTypeName;
+ }
+
+ bool hasAllocationStack() const override {
+ return get().allocationStack.isSome();
+ }
+ StackFrame allocationStack() const override;
+
+ // We ignore the `bool wantNames` parameter because we can't control whether
+ // the core dump was serialized with edge names or not.
+ js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
+
+ static const char16_t concreteTypeName[];
+};
+
+template <>
+class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame {
+ protected:
+ explicit ConcreteStackFrame(DeserializedStackFrame* ptr)
+ : BaseStackFrame(ptr) {}
+
+ DeserializedStackFrame& get() const {
+ return *static_cast<DeserializedStackFrame*>(ptr);
+ }
+
+ public:
+ static void construct(void* storage, DeserializedStackFrame* ptr) {
+ new (storage) ConcreteStackFrame(ptr);
+ }
+
+ uint64_t identifier() const override { return get().id; }
+ uint32_t line() const override { return get().line; }
+ JS::TaggedColumnNumberOneOrigin column() const override {
+ return get().column;
+ }
+ bool isSystem() const override { return get().isSystem; }
+ bool isSelfHosted(JSContext* cx) const override { return get().isSelfHosted; }
+ void trace(JSTracer* trc) override {}
+ AtomOrTwoByteChars source() const override {
+ return AtomOrTwoByteChars(get().source);
+ }
+ uint32_t sourceId() const override {
+ // Source IDs are local to their host process and are not serialized.
+ return 0;
+ }
+ AtomOrTwoByteChars functionDisplayName() const override {
+ return AtomOrTwoByteChars(get().functionDisplayName);
+ }
+
+ StackFrame parent() const override;
+ bool constructSavedFrameStack(
+ JSContext* cx,
+ JS::MutableHandle<JSObject*> outSavedFrameStack) const override;
+};
+
+} // namespace ubi
+} // namespace JS
+
+#endif // mozilla_devtools_DeserializedNode__
diff --git a/devtools/shared/heapsnapshot/DominatorTree.cpp b/devtools/shared/heapsnapshot/DominatorTree.cpp
new file mode 100644
index 0000000000..065db13576
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTree.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/DominatorTree.h"
+#include "mozilla/dom/DominatorTreeBinding.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla {
+namespace devtools {
+
+dom::Nullable<uint64_t> DominatorTree::GetRetainedSize(uint64_t aNodeId,
+ ErrorResult& aRv) {
+ JS::ubi::Node::Id id(aNodeId);
+ auto node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing()) return dom::Nullable<uint64_t>();
+
+ auto mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ JS::ubi::Node::Size size = 0;
+ if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return dom::Nullable<uint64_t>();
+ }
+
+ MOZ_ASSERT(size != 0,
+ "The node should not have been unknown since we got it from the "
+ "heap snapshot.");
+ return dom::Nullable<uint64_t>(size);
+}
+
+struct NodeAndRetainedSize {
+ JS::ubi::Node mNode;
+ JS::ubi::Node::Size mSize;
+
+ NodeAndRetainedSize(const JS::ubi::Node& aNode, JS::ubi::Node::Size aSize)
+ : mNode(aNode), mSize(aSize) {}
+
+ struct Comparator {
+ static bool Equals(const NodeAndRetainedSize& aLhs,
+ const NodeAndRetainedSize& aRhs) {
+ return aLhs.mSize == aRhs.mSize;
+ }
+
+ static bool LessThan(const NodeAndRetainedSize& aLhs,
+ const NodeAndRetainedSize& aRhs) {
+ // Use > because we want to sort from greatest to least retained size.
+ return aLhs.mSize > aRhs.mSize;
+ }
+ };
+};
+
+void DominatorTree::GetImmediatelyDominated(
+ uint64_t aNodeId, dom::Nullable<nsTArray<uint64_t>>& aOutResult,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aOutResult.IsNull());
+
+ JS::ubi::Node::Id id(aNodeId);
+ Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing()) return;
+
+ // Get all immediately dominated nodes and their retained sizes.
+ MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ Maybe<JS::ubi::DominatorTree::DominatedSetRange> range =
+ mDominatorTree.getDominatedSet(*node);
+ MOZ_ASSERT(
+ range.isSome(),
+ "The node should be known, since we got it from the heap snapshot.");
+ size_t length = range->length();
+ nsTArray<NodeAndRetainedSize> dominatedNodes(length);
+ for (const JS::ubi::Node& dominatedNode : *range) {
+ JS::ubi::Node::Size retainedSize = 0;
+ if (NS_WARN_IF(!mDominatorTree.getRetainedSize(dominatedNode, mallocSizeOf,
+ retainedSize))) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ MOZ_ASSERT(retainedSize != 0,
+ "retainedSize should not be zero since we know the node is in "
+ "the dominator tree.");
+
+ dominatedNodes.AppendElement(
+ NodeAndRetainedSize(dominatedNode, retainedSize));
+ }
+
+ // Sort them by retained size.
+ NodeAndRetainedSize::Comparator comparator;
+ dominatedNodes.Sort(comparator);
+
+ // Fill the result with the nodes' ids.
+ JS::ubi::Node root = mDominatorTree.root();
+ aOutResult.SetValue(nsTArray<uint64_t>(length));
+ for (const NodeAndRetainedSize& entry : dominatedNodes) {
+ // The root dominates itself, but we don't want to expose that to JS.
+ if (entry.mNode == root) continue;
+
+ aOutResult.Value().AppendElement(entry.mNode.identifier());
+ }
+}
+
+dom::Nullable<uint64_t> DominatorTree::GetImmediateDominator(
+ uint64_t aNodeId) const {
+ JS::ubi::Node::Id id(aNodeId);
+ Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing()) return dom::Nullable<uint64_t>();
+
+ JS::ubi::Node dominator = mDominatorTree.getImmediateDominator(*node);
+ if (!dominator || dominator == *node) return dom::Nullable<uint64_t>();
+
+ return dom::Nullable<uint64_t>(dominator.identifier());
+}
+
+/*** Cycle Collection Boilerplate
+ * *****************************************************************/
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent, mHeapSnapshot)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DominatorTree)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DominatorTree)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DominatorTree)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* virtual */
+JSObject* DominatorTree::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return dom::DominatorTree_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/DominatorTree.h b/devtools/shared/heapsnapshot/DominatorTree.h
new file mode 100644
index 0000000000..fb3275cf23
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTree.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_DominatorTree__
+#define mozilla_devtools_DominatorTree__
+
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/RefCounted.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace devtools {
+
+class DominatorTree final : public nsISupports, public nsWrapperCache {
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+
+ virtual ~DominatorTree() {}
+
+ private:
+ JS::ubi::DominatorTree mDominatorTree;
+ RefPtr<HeapSnapshot> mHeapSnapshot;
+
+ public:
+ explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree,
+ HeapSnapshot* aHeapSnapshot, nsISupports* aParent)
+ : mParent(aParent),
+ mDominatorTree(std::move(aDominatorTree)),
+ mHeapSnapshot(aHeapSnapshot) {
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aHeapSnapshot);
+ };
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(DominatorTree);
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // readonly attribute NodeId root
+ uint64_t Root() const { return mDominatorTree.root().identifier(); }
+
+ // [Throws] NodeSize getRetainedSize(NodeId node)
+ dom::Nullable<uint64_t> GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv);
+
+ // [Throws] sequence<NodeId>? getImmediatelyDominated(NodeId node);
+ void GetImmediatelyDominated(uint64_t aNodeId,
+ dom::Nullable<nsTArray<uint64_t>>& aOutDominated,
+ ErrorResult& aRv);
+
+ // NodeId? getImmediateDominator(NodeId node);
+ dom::Nullable<uint64_t> GetImmediateDominator(uint64_t aNodeId) const;
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_DominatorTree__
diff --git a/devtools/shared/heapsnapshot/DominatorTreeNode.js b/devtools/shared/heapsnapshot/DominatorTreeNode.js
new file mode 100644
index 0000000000..9fe5cf1032
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTreeNode.js
@@ -0,0 +1,378 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ immutableUpdate,
+} = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
+const {
+ Visitor,
+ walk,
+} = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+const {
+ deduplicatePaths,
+} = require("resource://devtools/shared/heapsnapshot/shortest-paths.js");
+
+const DEFAULT_MAX_DEPTH = 4;
+const DEFAULT_MAX_SIBLINGS = 15;
+const DEFAULT_MAX_NUM_PATHS = 5;
+
+/**
+ * A single node in a dominator tree.
+ *
+ * @param {NodeId} nodeId
+ * @param {NodeSize} retainedSize
+ */
+function DominatorTreeNode(nodeId, label, shallowSize, retainedSize) {
+ // The id of this node.
+ this.nodeId = nodeId;
+
+ // The label structure generated by describing the given node.
+ this.label = label;
+
+ // The shallow size of this node.
+ this.shallowSize = shallowSize;
+
+ // The retained size of this node.
+ this.retainedSize = retainedSize;
+
+ // The id of this node's parent or undefined if this node is the root.
+ this.parentId = undefined;
+
+ // An array of immediately dominated child `DominatorTreeNode`s, or undefined.
+ this.children = undefined;
+
+ // An object of the form returned by `deduplicatePaths`, encoding the set of
+ // the N shortest retaining paths for this node as a graph.
+ this.shortestPaths = undefined;
+
+ // True iff the `children` property does not contain every immediately
+ // dominated node.
+ //
+ // * If children is an array and this property is true: the array does not
+ // contain the complete set of immediately dominated children.
+ // * If children is an array and this property is false: the array contains
+ // the complete set of immediately dominated children.
+ // * If children is undefined and this property is true: there exist
+ // immediately dominated children for this node, but they have not been
+ // loaded yet.
+ // * If children is undefined and this property is false: this node does not
+ // dominate any others and therefore has no children.
+ this.moreChildrenAvailable = true;
+}
+
+DominatorTreeNode.prototype = null;
+
+module.exports = DominatorTreeNode;
+
+/**
+ * Add `child` to the `parent`'s set of children.
+ *
+ * @param {DominatorTreeNode} parent
+ * @param {DominatorTreeNode} child
+ */
+DominatorTreeNode.addChild = function (parent, child) {
+ if (parent.children === undefined) {
+ parent.children = [];
+ }
+
+ parent.children.push(child);
+ child.parentId = parent.nodeId;
+};
+
+/**
+ * A Visitor that is used to generate a label for a node in the heap snapshot
+ * and get its shallow size as well while we are at it.
+ */
+function LabelAndShallowSizeVisitor() {
+ // As we walk the description, we accumulate edges in this array.
+ this._labelPieces = [];
+
+ // Once we reach the non-zero count leaf node in the description, we move the
+ // labelPieces here to signify that we no longer need to accumulate edges.
+ this._label = undefined;
+
+ // Once we reach the non-zero count leaf node in the description, we grab the
+ // shallow size and place it here.
+ this._shallowSize = 0;
+}
+
+DominatorTreeNode.LabelAndShallowSizeVisitor = LabelAndShallowSizeVisitor;
+
+LabelAndShallowSizeVisitor.prototype = Object.create(Visitor);
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+LabelAndShallowSizeVisitor.prototype.enter = function (
+ breakdown,
+ report,
+ edge
+) {
+ if (this._labelPieces && edge) {
+ this._labelPieces.push(edge);
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.exit
+ */
+LabelAndShallowSizeVisitor.prototype.exit = function (breakdown, report, edge) {
+ if (this._labelPieces && edge) {
+ this._labelPieces.pop();
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+LabelAndShallowSizeVisitor.prototype.count = function (
+ breakdown,
+ report,
+ edge
+) {
+ if (report.count === 0) {
+ return;
+ }
+
+ this._label = this._labelPieces;
+ this._labelPieces = undefined;
+
+ this._shallowSize = report.bytes;
+};
+
+/**
+ * Get the generated label structure accumulated by this visitor.
+ *
+ * @returns {Object}
+ */
+LabelAndShallowSizeVisitor.prototype.label = function () {
+ return this._label;
+};
+
+/**
+ * Get the shallow size of the node this visitor visited.
+ *
+ * @returns {Number}
+ */
+LabelAndShallowSizeVisitor.prototype.shallowSize = function () {
+ return this._shallowSize;
+};
+
+/**
+ * Generate a label structure for the node with the given id and grab its
+ * shallow size.
+ *
+ * What is a "label" structure? HeapSnapshot.describeNode essentially takes a
+ * census of a single node rather than the whole heap graph. The resulting
+ * report has only one count leaf that is non-zero. The label structure is the
+ * path in this report from the root to the non-zero count leaf.
+ *
+ * @param {Number} nodeId
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ *
+ * @returns {Object}
+ * An object with the following properties:
+ * - {Number} shallowSize
+ * - {Object} label
+ */
+DominatorTreeNode.getLabelAndShallowSize = function (
+ nodeId,
+ snapshot,
+ breakdown
+) {
+ const description = snapshot.describeNode(breakdown, nodeId);
+
+ const visitor = new LabelAndShallowSizeVisitor();
+ walk(breakdown, description, visitor);
+
+ return {
+ label: visitor.label(),
+ shallowSize: visitor.shallowSize(),
+ };
+};
+
+/**
+ * Do a partial traversal of the given dominator tree and convert it into a tree
+ * of `DominatorTreeNode`s. Dominator trees have a node for every node in the
+ * snapshot's heap graph, so we must not allocate a JS object for every node. It
+ * would be way too many and the node count is effectively unbounded.
+ *
+ * Go no deeper down the tree than `maxDepth` and only consider at most
+ * `maxSiblings` within any single node's children.
+ *
+ * @param {DominatorTree} dominatorTree
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ * @param {Number} maxDepth
+ * @param {Number} maxSiblings
+ *
+ * @returns {DominatorTreeNode}
+ */
+DominatorTreeNode.partialTraversal = function (
+ dominatorTree,
+ snapshot,
+ breakdown,
+ maxDepth = DEFAULT_MAX_DEPTH,
+ maxSiblings = DEFAULT_MAX_SIBLINGS
+) {
+ function dfs(nodeId, depth) {
+ const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize(
+ nodeId,
+ snapshot,
+ breakdown
+ );
+ const retainedSize = dominatorTree.getRetainedSize(nodeId);
+ const node = new DominatorTreeNode(
+ nodeId,
+ label,
+ shallowSize,
+ retainedSize
+ );
+ const childNodeIds = dominatorTree.getImmediatelyDominated(nodeId);
+
+ const newDepth = depth + 1;
+ if (newDepth < maxDepth) {
+ const endIdx = Math.min(childNodeIds.length, maxSiblings);
+ for (let i = 0; i < endIdx; i++) {
+ DominatorTreeNode.addChild(node, dfs(childNodeIds[i], newDepth));
+ }
+ node.moreChildrenAvailable = endIdx < childNodeIds.length;
+ } else {
+ node.moreChildrenAvailable = !!childNodeIds.length;
+ }
+
+ return node;
+ }
+
+ return dfs(dominatorTree.root, 0);
+};
+
+/**
+ * Insert more children into the given (partially complete) dominator tree.
+ *
+ * The tree is updated in an immutable and persistent manner: a new tree is
+ * returned, but all unmodified subtrees (which is most) are shared with the
+ * original tree. Only the modified nodes are re-allocated.
+ *
+ * @param {DominatorTreeNode} tree
+ * @param {Array<NodeId>} path
+ * @param {Array<DominatorTreeNode>} newChildren
+ * @param {Boolean} moreChildrenAvailable
+ *
+ * @returns {DominatorTreeNode}
+ */
+DominatorTreeNode.insert = function (
+ nodeTree,
+ path,
+ newChildren,
+ moreChildrenAvailable
+) {
+ function insert(tree, i) {
+ if (tree.nodeId !== path[i]) {
+ return tree;
+ }
+
+ if (i == path.length - 1) {
+ return immutableUpdate(tree, {
+ children: (tree.children || []).concat(newChildren),
+ moreChildrenAvailable,
+ });
+ }
+
+ return tree.children
+ ? immutableUpdate(tree, {
+ children: tree.children.map(c => insert(c, i + 1)),
+ })
+ : tree;
+ }
+
+ return insert(nodeTree, 0);
+};
+
+/**
+ * Get the new canonical node with the given `id` in `tree` that exists along
+ * `path`. If there is no such node along `path`, return null.
+ *
+ * This is useful if we have a reference to a now-outdated DominatorTreeNode due
+ * to a recent call to DominatorTreeNode.insert and want to get the up-to-date
+ * version. We don't have to walk the whole tree: if there is an updated version
+ * of the node then it *must* be along the path.
+ *
+ * @param {NodeId} id
+ * @param {DominatorTreeNode} tree
+ * @param {Array<NodeId>} path
+ *
+ * @returns {DominatorTreeNode|null}
+ */
+DominatorTreeNode.getNodeByIdAlongPath = function (id, tree, path) {
+ function find(node, i) {
+ if (!node || node.nodeId !== path[i]) {
+ return null;
+ }
+
+ if (node.nodeId === id) {
+ return node;
+ }
+
+ if (i === path.length - 1 || !node.children) {
+ return null;
+ }
+
+ const nextId = path[i + 1];
+ return find(
+ node.children.find(c => c.nodeId === nextId),
+ i + 1
+ );
+ }
+
+ return find(tree, 0);
+};
+
+/**
+ * Find the shortest retaining paths for the given set of DominatorTreeNodes,
+ * and populate each node's `shortestPaths` property with them in place.
+ *
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ * @param {NodeId} start
+ * @param {Array<DominatorTreeNode>} treeNodes
+ * @param {Number} maxNumPaths
+ */
+DominatorTreeNode.attachShortestPaths = function (
+ snapshot,
+ breakdown,
+ start,
+ treeNodes,
+ maxNumPaths = DEFAULT_MAX_NUM_PATHS
+) {
+ const idToTreeNode = new Map();
+ const targets = [];
+ for (const node of treeNodes) {
+ const id = node.nodeId;
+ idToTreeNode.set(id, node);
+ targets.push(id);
+ }
+
+ const shortestPaths = snapshot.computeShortestPaths(
+ start,
+ targets,
+ maxNumPaths
+ );
+
+ for (const [target, paths] of shortestPaths) {
+ const deduped = deduplicatePaths(target, paths);
+ deduped.nodes = deduped.nodes.map(id => {
+ const { label } = DominatorTreeNode.getLabelAndShallowSize(
+ id,
+ snapshot,
+ breakdown
+ );
+ return { id, label };
+ });
+
+ idToTreeNode.get(target).shortestPaths = deduped;
+ }
+};
diff --git a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
new file mode 100644
index 0000000000..651d7b9496
--- /dev/null
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/FileDescriptorOutputStream.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace devtools {
+
+/* static */
+already_AddRefed<FileDescriptorOutputStream> FileDescriptorOutputStream::Create(
+ const ipc::FileDescriptor& fileDescriptor) {
+ if (NS_WARN_IF(!fileDescriptor.IsValid())) return nullptr;
+
+ auto rawFD = fileDescriptor.ClonePlatformHandle();
+ PRFileDesc* prfd = PR_ImportFile(PROsfd(rawFD.release()));
+ if (NS_WARN_IF(!prfd)) return nullptr;
+
+ RefPtr<FileDescriptorOutputStream> stream =
+ new FileDescriptorOutputStream(prfd);
+ return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS(FileDescriptorOutputStream, nsIOutputStream);
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Close() {
+ // Repeatedly closing is idempotent.
+ if (!fd) return NS_OK;
+
+ if (PR_Close(fd) != PR_SUCCESS) return NS_ERROR_FAILURE;
+ fd = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::StreamStatus() {
+ return fd ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Write(const char* buf, uint32_t count,
+ uint32_t* retval) {
+ if (NS_WARN_IF(!fd)) return NS_ERROR_FAILURE;
+
+ auto written = PR_Write(fd, buf, count);
+ if (written < 0) return NS_ERROR_FAILURE;
+ *retval = written;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Flush() {
+ if (NS_WARN_IF(!fd)) return NS_ERROR_FAILURE;
+
+ return PR_Sync(fd) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::WriteFrom(nsIInputStream* fromStream,
+ uint32_t count, uint32_t* retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::WriteSegments(nsReadSegmentFun reader,
+ void* closure, uint32_t count,
+ uint32_t* retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::IsNonBlocking(bool* retval) {
+ *retval = false;
+ return NS_OK;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h
new file mode 100644
index 0000000000..5d8f31424c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_FileDescriptorOutputStream_h
+#define mozilla_devtools_FileDescriptorOutputStream_h
+
+#include <prio.h>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace devtools {
+
+class FileDescriptorOutputStream final : public nsIOutputStream {
+ private:
+ PRFileDesc* fd;
+
+ public:
+ static already_AddRefed<FileDescriptorOutputStream> Create(
+ const ipc::FileDescriptor& fileDescriptor);
+
+ private:
+ explicit FileDescriptorOutputStream(PRFileDesc* prfd) : fd(prfd) {}
+
+ virtual ~FileDescriptorOutputStream() {}
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_FileDescriptorOutputStream_h
diff --git a/devtools/shared/heapsnapshot/HeapAnalyses.worker.js b/devtools/shared/heapsnapshot/HeapAnalyses.worker.js
new file mode 100644
index 0000000000..0f61d99a94
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapAnalyses.worker.js
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This is a worker which reads offline heap snapshots into memory and performs
+// heavyweight analyses on them without blocking the main thread. A
+// HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
+// instance. See HeapAnalysesClient.js.
+
+"use strict";
+
+/* import-globals-from /toolkit/components/workerloader/require.js */
+importScripts("resource://gre/modules/workers/require.js");
+/* import-globals-from ../worker/helper.js */
+importScripts("resource://devtools/shared/worker/helper.js");
+const {
+ censusReportToCensusTreeNode,
+} = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
+const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
+const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+
+const DEFAULT_START_INDEX = 0;
+const DEFAULT_MAX_COUNT = 50;
+
+/**
+ * The set of HeapSnapshot instances this worker has read into memory. Keyed by
+ * snapshot file path.
+ */
+const snapshots = Object.create(null);
+
+/**
+ * The set of `DominatorTree`s that have been computed, mapped by their id (aka
+ * the index into this array).
+ *
+ * @see /dom/webidl/DominatorTree.webidl
+ */
+const dominatorTrees = [];
+
+/**
+ * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th
+ * dominator tree in `dominatorTrees` above. This lets us map from a dominator
+ * tree id to the snapshot it came from.
+ */
+const dominatorTreeSnapshots = [];
+
+/**
+ * @see HeapAnalysesClient.prototype.readHeapSnapshot
+ */
+workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
+ snapshots[snapshotFilePath] = ChromeUtils.readHeapSnapshot(snapshotFilePath);
+ return true;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.deleteHeapSnapshot
+ */
+workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => {
+ const snapshot = snapshots[snapshotFilePath];
+ if (!snapshot) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ snapshots[snapshotFilePath] = undefined;
+
+ const dominatorTreeId = dominatorTreeSnapshots.indexOf(snapshot);
+ if (dominatorTreeId != -1) {
+ dominatorTreeSnapshots[dominatorTreeId] = undefined;
+ dominatorTrees[dominatorTreeId] = undefined;
+ }
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.takeCensus
+ */
+workerHelper.createTask(
+ self,
+ "takeCensus",
+ ({ snapshotFilePath, censusOptions, requestOptions }) => {
+ if (!snapshots[snapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
+ let parentMap;
+
+ if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
+ const opts = { filter: requestOptions.filter || null };
+ if (requestOptions.asInvertedTreeNode) {
+ opts.invert = true;
+ }
+ report = censusReportToCensusTreeNode(
+ censusOptions.breakdown,
+ report,
+ opts
+ );
+ parentMap = CensusUtils.createParentMap(report);
+ }
+
+ return { report, parentMap };
+ }
+);
+
+/**
+ * @see HeapAnalysesClient.prototype.getCensusIndividuals
+ */
+workerHelper.createTask(self, "getCensusIndividuals", request => {
+ const {
+ dominatorTreeId,
+ indices,
+ censusBreakdown,
+ labelBreakdown,
+ maxRetainingPaths,
+ maxIndividuals,
+ } = request;
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ if (!dominatorTree) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`
+ );
+ }
+
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+ const nodeIds = CensusUtils.getCensusIndividuals(
+ indices,
+ censusBreakdown,
+ snapshot
+ );
+
+ const nodes = nodeIds
+ .sort(
+ (a, b) =>
+ dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a)
+ )
+ .slice(0, maxIndividuals)
+ .map(id => {
+ const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize(
+ id,
+ snapshot,
+ labelBreakdown
+ );
+ const retainedSize = dominatorTree.getRetainedSize(id);
+ const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
+ node.moreChildrenAvailable = false;
+ return node;
+ });
+
+ DominatorTreeNode.attachShortestPaths(
+ snapshot,
+ labelBreakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths
+ );
+
+ return { nodes };
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.takeCensusDiff
+ */
+workerHelper.createTask(self, "takeCensusDiff", request => {
+ const {
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions,
+ } = request;
+
+ if (!snapshots[firstSnapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`);
+ }
+
+ if (!snapshots[secondSnapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
+ }
+
+ const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
+ const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
+ let delta = CensusUtils.diff(censusOptions.breakdown, first, second);
+ let parentMap;
+
+ if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
+ const opts = { filter: requestOptions.filter || null };
+ if (requestOptions.asInvertedTreeNode) {
+ opts.invert = true;
+ }
+ delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts);
+ parentMap = CensusUtils.createParentMap(delta);
+ }
+
+ return { delta, parentMap };
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getCreationTime
+ */
+workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
+ if (!snapshots[snapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+ return snapshots[snapshotFilePath].creationTime;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.computeDominatorTree
+ */
+workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => {
+ const snapshot = snapshots[snapshotFilePath];
+ if (!snapshot) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ const id = dominatorTrees.length;
+ dominatorTrees.push(snapshot.computeDominatorTree());
+ dominatorTreeSnapshots.push(snapshot);
+ return id;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getDominatorTree
+ */
+workerHelper.createTask(self, "getDominatorTree", request => {
+ const {
+ dominatorTreeId,
+ breakdown,
+ maxDepth,
+ maxSiblings,
+ maxRetainingPaths,
+ } = request;
+
+ if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`
+ );
+ }
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+
+ const tree = DominatorTreeNode.partialTraversal(
+ dominatorTree,
+ snapshot,
+ breakdown,
+ maxDepth,
+ maxSiblings
+ );
+
+ const nodes = [];
+ (function getNodes(node) {
+ nodes.push(node);
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ getNodes(node.children[i]);
+ }
+ }
+ })(tree);
+
+ DominatorTreeNode.attachShortestPaths(
+ snapshot,
+ breakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths
+ );
+
+ return tree;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getImmediatelyDominated
+ */
+workerHelper.createTask(self, "getImmediatelyDominated", request => {
+ const {
+ dominatorTreeId,
+ nodeId,
+ breakdown,
+ startIndex,
+ maxCount,
+ maxRetainingPaths,
+ } = request;
+
+ if (!(dominatorTreeId >= 0 && dominatorTreeId < dominatorTrees.length)) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`
+ );
+ }
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+
+ const childIds = dominatorTree.getImmediatelyDominated(nodeId);
+ if (!childIds) {
+ throw new Error(`${nodeId} is not a node id in the dominator tree`);
+ }
+
+ const start = startIndex || DEFAULT_START_INDEX;
+ const count = maxCount || DEFAULT_MAX_COUNT;
+ const end = start + count;
+
+ const nodes = childIds.slice(start, end).map(id => {
+ const { label, shallowSize } = DominatorTreeNode.getLabelAndShallowSize(
+ id,
+ snapshot,
+ breakdown
+ );
+ const retainedSize = dominatorTree.getRetainedSize(id);
+ const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
+ node.parentId = nodeId;
+ // DominatorTree.getImmediatelyDominated will always return non-null here
+ // because we got the id directly from the dominator tree.
+ node.moreChildrenAvailable =
+ !!dominatorTree.getImmediatelyDominated(id).length;
+ return node;
+ });
+
+ const path = [];
+ let id = nodeId;
+ do {
+ path.push(id);
+ id = dominatorTree.getImmediateDominator(id);
+ } while (id !== null);
+ path.reverse();
+
+ const moreChildrenAvailable = childIds.length > end;
+
+ DominatorTreeNode.attachShortestPaths(
+ snapshot,
+ breakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths
+ );
+
+ return { nodes, moreChildrenAvailable, path };
+});
diff --git a/devtools/shared/heapsnapshot/HeapAnalysesClient.js b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
new file mode 100644
index 0000000000..a77d9397a5
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -0,0 +1,285 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
+const {
+ DevToolsWorker,
+} = require("resource://devtools/shared/worker/worker.js");
+
+const WORKER_URL =
+ "resource://devtools/shared/heapsnapshot/HeapAnalyses.worker.js";
+var workerCounter = 0;
+
+/**
+ * A HeapAnalysesClient instance provides a developer-friendly interface for
+ * interacting with a HeapAnalysesWorker. This enables users to be ignorant of
+ * the message passing protocol used to communicate with the worker. The
+ * HeapAnalysesClient owns the worker, and terminating the worker is done by
+ * terminating the client (see the `destroy` method).
+ */
+const HeapAnalysesClient = (module.exports = function () {
+ this._worker = new DevToolsWorker(WORKER_URL, {
+ name: `HeapAnalyses-${workerCounter++}`,
+ verbose: DevToolsUtils.dumpv.wantVerbose,
+ });
+});
+
+/**
+ * Destroy the worker, causing it to release its resources (such as heap
+ * snapshots it has deserialized and read into memory). The client is no longer
+ * usable after calling this method.
+ */
+HeapAnalysesClient.prototype.destroy = function () {
+ this._worker.destroy();
+ this._worker = null;
+};
+
+/**
+ * Tell the worker to read into memory the heap snapshot at the given file
+ * path. This is a prerequisite for asking the worker to perform various
+ * analyses on a heap snapshot.
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @returns Promise
+ * The promise is fulfilled if the heap snapshot is successfully
+ * deserialized and read into memory. The promise is rejected if that
+ * does not happen, eg due to a bad file path or malformed heap
+ * snapshot file.
+ */
+HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
+ return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
+};
+
+/**
+ * Tell the worker to delete all references to the snapshot and dominator trees
+ * linked to the provided snapshot file path.
+ *
+ * @param {String} snapshotFilePath
+ * @return Promise<undefined>
+ */
+HeapAnalysesClient.prototype.deleteHeapSnapshot = function (snapshotFilePath) {
+ return this._worker.performTask("deleteHeapSnapshot", { snapshotFilePath });
+};
+
+/**
+ * Request the creation time given a snapshot file path. Returns `null`
+ * if snapshot does not exist.
+ *
+ * @param {String} snapshotFilePath
+ * The path to the snapshot.
+ * @return {Number?}
+ * The unix timestamp of the creation time of the snapshot, or null if
+ * snapshot does not exist.
+ */
+HeapAnalysesClient.prototype.getCreationTime = function (snapshotFilePath) {
+ return this._worker.performTask("getCreationTime", snapshotFilePath);
+};
+
+/** * Censuses *****************************************************************/
+
+/**
+ * Ask the worker to perform a census analysis on the heap snapshot with the
+ * given path. The heap snapshot at the given path must have already been read
+ * into memory by the worker (see `readHeapSnapshot`).
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @param {Object} censusOptions
+ * A structured-cloneable object specifying the requested census's
+ * breakdown. See the "takeCensus" section of
+ * `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
+ *
+ * @param {Object} requestOptions
+ * An object specifying options of this worker request.
+ * - {Boolean} asTreeNode
+ * Whether or not the census is returned as a CensusTreeNode,
+ * or just a breakdown report. Defaults to false.
+ * @see `devtools/shared/heapsnapshot/census-tree-node.js`
+ * - {Boolean} asInvertedTreeNode
+ * Whether or not the census is returned as an inverted
+ * CensusTreeNode. Defaults to false.
+ * - {String} filter
+ * A filter string to prune the resulting tree with. Only applies if
+ * either asTreeNode or asInvertedTreeNode is true.
+ *
+ * @returns Promise<Object>
+ * An object with the following properties:
+ * - report:
+ * The report generated by the given census breakdown, or a
+ * CensusTreeNode generated by the given census breakdown if
+ * `asTreeNode` is true.
+ * - parentMap:
+ * The result of calling CensusUtils.createParentMap on the generated
+ * report. Only exists if asTreeNode or asInvertedTreeNode are set.
+ */
+HeapAnalysesClient.prototype.takeCensus = function (
+ snapshotFilePath,
+ censusOptions,
+ requestOptions = {}
+) {
+ return this._worker.performTask("takeCensus", {
+ snapshotFilePath,
+ censusOptions,
+ requestOptions,
+ });
+};
+
+/**
+ * Get the individual nodes that correspond to the given census report leaf
+ * indices.
+ *
+ * @param {Object} opts
+ * An object with the following properties:
+ * - {DominatorTreeId} dominatorTreeId: The id of the dominator tree.
+ * - {Set<Number>} indices: The indices of the census report leaves we
+ * would like to get the individuals for.
+ * - {Object} censusBreakdown: The breakdown used to generate the census.
+ * - {Object} labelBreakdown: The breakdown we would like to use when
+ * labeling the resulting nodes.
+ * - {Number} maxRetainingPaths: The maximum number of retaining paths to
+ * compute for each node.
+ * - {Number} maxIndividuals: The maximum number of individual nodes to
+ * return.
+ *
+ * @returns {Promise<Object>}
+ * A promise of an object with the following properties:
+ * - {Array<DominatorTreeNode>} nodes: An array of `DominatorTreeNode`s
+ * with their shortest paths attached, and without any dominator tree
+ * child/parent information attached. The results are sorted by
+ * retained size.
+ *
+ */
+HeapAnalysesClient.prototype.getCensusIndividuals = function (opts) {
+ return this._worker.performTask("getCensusIndividuals", opts);
+};
+
+/**
+ * Request that the worker take a census on the heap snapshots with the given
+ * paths and then return the difference between them. Both heap snapshots must
+ * have already been read into memory by the worker (see `readHeapSnapshot`).
+ *
+ * @param {String} firstSnapshotFilePath
+ * The first snapshot file path.
+ *
+ * @param {String} secondSnapshotFilePath
+ * The second snapshot file path.
+ *
+ * @param {Object} censusOptions
+ * A structured-cloneable object specifying the requested census's
+ * breakdown. See the "takeCensus" section of
+ * `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
+ *
+ * @param {Object} requestOptions
+ * An object specifying options for this request.
+ * - {Boolean} asTreeNode
+ * Whether the resulting delta report should be converted to a census
+ * tree node before returned. Defaults to false.
+ * - {Boolean} asInvertedTreeNode
+ * Whether or not the census is returned as an inverted
+ * CensusTreeNode. Defaults to false.
+ * - {String} filter
+ * A filter string to prune the resulting tree with. Only applies if
+ * either asTreeNode or asInvertedTreeNode is true.
+ *
+ * @returns Promise<Object>
+ * - delta:
+ * The delta report generated by diffing the two census reports, or a
+ * CensusTreeNode generated from the delta report if
+ * `requestOptions.asTreeNode` was true.
+ * - parentMap:
+ * The result of calling CensusUtils.createParentMap on the generated
+ * delta. Only exists if asTreeNode or asInvertedTreeNode are set.
+ */
+HeapAnalysesClient.prototype.takeCensusDiff = function (
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions = {}
+) {
+ return this._worker.performTask("takeCensusDiff", {
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions,
+ });
+};
+
+/** * Dominator Trees **********************************************************/
+
+/**
+ * Compute the dominator tree of the heap snapshot loaded from the given file
+ * path. Returns the id of the computed dominator tree.
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @returns {Promise<DominatorTreeId>}
+ */
+HeapAnalysesClient.prototype.computeDominatorTree = function (
+ snapshotFilePath
+) {
+ return this._worker.performTask("computeDominatorTree", snapshotFilePath);
+};
+
+/**
+ * Get the initial, partial view of the dominator tree with the given id.
+ *
+ * @param {Object} opts
+ * An object specifying options for this request.
+ * - {DominatorTreeId} dominatorTreeId
+ * The id of the dominator tree.
+ * - {Object} breakdown
+ * The breakdown used to generate node labels.
+ * - {Number} maxDepth
+ * The maximum depth to traverse down the tree to create this initial
+ * view.
+ * - {Number} maxSiblings
+ * The maximum number of siblings to visit within each traversed node's
+ * children.
+ * - {Number} maxRetainingPaths
+ * The maximum number of retaining paths to find for each node.
+ *
+ * @returns {Promise<DominatorTreeNode>}
+ */
+HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
+ return this._worker.performTask("getDominatorTree", opts);
+};
+
+/**
+ * Get a subset of a nodes children in the dominator tree.
+ *
+ * @param {Object} opts
+ * An object specifying options for this request.
+ * - {DominatorTreeId} dominatorTreeId
+ * The id of the dominator tree.
+ * - {NodeId} nodeId
+ * The id of the node whose children are being found.
+ * - {Object} breakdown
+ * The breakdown used to generate node labels.
+ * - {Number} startIndex
+ * The starting index within the full set of immediately dominated
+ * children of the children being requested. Children are always sorted
+ * by greatest to least retained size.
+ * - {Number} maxCount
+ * The maximum number of children to return.
+ * - {Number} maxRetainingPaths
+ * The maximum number of retaining paths to find for each node.
+ *
+ * @returns {Promise<Object>}
+ * A promise of an object with the following properties:
+ * - {Array<DominatorTreeNode>} nodes
+ * The requested nodes that are immediately dominated by the node
+ * identified by `opts.nodeId`.
+ * - {Boolean} moreChildrenAvailable
+ * True iff there are more children available after the returned
+ * nodes.
+ * - {Array<NodeId>} path
+ * The path through the tree from the root to these node's parent, eg
+ * [root's id, child of root's id, child of child of root's id, ..., `nodeId`].
+ */
+HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
+ return this._worker.performTask("getImmediatelyDominated", opts);
+};
diff --git a/devtools/shared/heapsnapshot/HeapSnapshot.cpp b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
new file mode 100644
index 0000000000..ce0eec2d5c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -0,0 +1,1581 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeapSnapshot.h"
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/gzip_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+#include "js/Array.h" // JS::NewArrayObject
+#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
+#include "js/Debug.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/TypeDecls.h"
+#include "js/UbiNodeBreadthFirst.h"
+#include "js/UbiNodeCensus.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/devtools/AutoMemMap.h"
+#include "mozilla/devtools/CoreDump.pb.h"
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/DominatorTree.h"
+#include "mozilla/devtools/FileDescriptorOutputStream.h"
+#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HeapSnapshotBinding.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "js/MapAndSet.h"
+#include "js/Object.h" // JS::GetCompartment
+#include "nsComponentManagerUtils.h" // do_CreateInstance
+#include "nsCycleCollectionParticipant.h"
+#include "nsCRTGlue.h"
+#include "nsIFile.h"
+#include "nsIOutputStream.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prtypes.h"
+#include "SpecialSystemDirectory.h"
+
+namespace mozilla {
+namespace devtools {
+
+using namespace JS;
+using namespace dom;
+
+using ::google::protobuf::io::ArrayInputStream;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::GzipInputStream;
+using ::google::protobuf::io::ZeroCopyInputStream;
+
+using JS::ubi::AtomOrTwoByteChars;
+using JS::ubi::ShortestPaths;
+
+MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf() {
+ auto ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx);
+ auto cx = ccjscx->Context();
+ MOZ_ASSERT(cx);
+ auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(cx);
+ MOZ_ASSERT(mallocSizeOf);
+ return mallocSizeOf;
+}
+
+/*** Cycle Collection Boilerplate *********************************************/
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* virtual */
+JSObject* HeapSnapshot::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return HeapSnapshot_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+/*** Reading Heap Snapshots ***************************************************/
+
+/* static */
+already_AddRefed<HeapSnapshot> HeapSnapshot::Create(JSContext* cx,
+ GlobalObject& global,
+ const uint8_t* buffer,
+ uint32_t size,
+ ErrorResult& rv) {
+ RefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
+ if (!snapshot->init(cx, buffer, size)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return snapshot.forget();
+}
+
+template <typename MessageType>
+static bool parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage,
+ MessageType& message) {
+ // We need to create a new `CodedInputStream` for each message so that the
+ // 64MB limit is applied per-message rather than to the whole stream.
+ CodedInputStream codedStream(&stream);
+
+ // The protobuf message nesting that core dumps exhibit is dominated by
+ // allocation stacks' frames. In the most deeply nested case, each frame has
+ // two messages: a StackFrame message and a StackFrame::Data message. These
+ // frames are on top of a small constant of other messages. There are a
+ // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for
+ // the two messages per frame plus some head room for the constant number of
+ // non-dominating messages.
+ codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
+
+ auto limit = codedStream.PushLimit(sizeOfMessage);
+ if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
+ NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
+ NS_WARN_IF(codedStream.BytesUntilLimit() != 0)) {
+ return false;
+ }
+
+ codedStream.PopLimit(limit);
+ return true;
+}
+
+template <typename CharT, typename InternedStringSet>
+struct GetOrInternStringMatcher {
+ InternedStringSet& internedStrings;
+
+ explicit GetOrInternStringMatcher(InternedStringSet& strings)
+ : internedStrings(strings) {}
+
+ const CharT* operator()(const std::string* str) {
+ MOZ_ASSERT(str);
+ size_t length = str->length() / sizeof(CharT);
+ auto tempString = reinterpret_cast<const CharT*>(str->data());
+
+ UniqueFreePtr<CharT[]> owned(NS_xstrndup(tempString, length));
+ if (!internedStrings.append(std::move(owned))) return nullptr;
+
+ return internedStrings.back().get();
+ }
+
+ const CharT* operator()(uint64_t ref) {
+ if (MOZ_LIKELY(ref < internedStrings.length())) {
+ auto& string = internedStrings[ref];
+ MOZ_ASSERT(string);
+ return string.get();
+ }
+
+ return nullptr;
+ }
+};
+
+template <
+ // Either char or char16_t.
+ typename CharT,
+ // A reference to either `internedOneByteStrings` or
+ // `internedTwoByteStrings` if CharT is char or char16_t respectively.
+ typename InternedStringSet>
+const CharT* HeapSnapshot::getOrInternString(
+ InternedStringSet& internedStrings, Maybe<StringOrRef>& maybeStrOrRef) {
+ // Incomplete message: has neither a string nor a reference to an already
+ // interned string.
+ if (MOZ_UNLIKELY(maybeStrOrRef.isNothing())) return nullptr;
+
+ GetOrInternStringMatcher<CharT, InternedStringSet> m(internedStrings);
+ return maybeStrOrRef->match(m);
+}
+
+// Get a de-duplicated string as a Maybe<StringOrRef> from the given `msg`.
+#define GET_STRING_OR_REF_WITH_PROP_NAMES(msg, strPropertyName, \
+ refPropertyName) \
+ (msg.has_##refPropertyName() ? Some(StringOrRef(msg.refPropertyName())) \
+ : msg.has_##strPropertyName() ? Some(StringOrRef(&msg.strPropertyName())) \
+ : Nothing())
+
+#define GET_STRING_OR_REF(msg, property) \
+ (msg.has_##property##ref() ? Some(StringOrRef(msg.property##ref())) \
+ : msg.has_##property() ? Some(StringOrRef(&msg.property())) \
+ : Nothing())
+
+bool HeapSnapshot::saveNode(const protobuf::Node& node,
+ NodeIdSet& edgeReferents) {
+ // NB: de-duplicated string properties must be read back and interned in the
+ // same order here as they are written and serialized in
+ // `CoreDumpWriter::writeNode` or else indices in references to already
+ // serialized strings will be off.
+
+ if (NS_WARN_IF(!node.has_id())) return false;
+ NodeId id = node.id();
+
+ // NodeIds are derived from pointers (at most 48 bits) and we rely on them
+ // fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit
+ // integers) despite storing them on disk as 64 bit integers.
+ if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id))) return false;
+
+ // Should only deserialize each node once.
+ if (NS_WARN_IF(nodes.has(id))) return false;
+
+ if (NS_WARN_IF(!JS::ubi::Uint32IsValidCoarseType(node.coarsetype())))
+ return false;
+ auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype());
+
+ Maybe<StringOrRef> typeNameOrRef =
+ GET_STRING_OR_REF_WITH_PROP_NAMES(node, typename_, typenameref);
+ auto typeName =
+ getOrInternString<char16_t>(internedTwoByteStrings, typeNameOrRef);
+ if (NS_WARN_IF(!typeName)) return false;
+
+ if (NS_WARN_IF(!node.has_size())) return false;
+ uint64_t size = node.size();
+
+ auto edgesLength = node.edges_size();
+ DeserializedNode::EdgeVector edges;
+ if (NS_WARN_IF(!edges.reserve(edgesLength))) return false;
+ for (decltype(edgesLength) i = 0; i < edgesLength; i++) {
+ auto& protoEdge = node.edges(i);
+
+ if (NS_WARN_IF(!protoEdge.has_referent())) return false;
+ NodeId referent = protoEdge.referent();
+
+ if (NS_WARN_IF(!edgeReferents.put(referent))) return false;
+
+ const char16_t* edgeName = nullptr;
+ if (protoEdge.EdgeNameOrRef_case() !=
+ protobuf::Edge::EDGENAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> edgeNameOrRef = GET_STRING_OR_REF(protoEdge, name);
+ edgeName =
+ getOrInternString<char16_t>(internedTwoByteStrings, edgeNameOrRef);
+ if (NS_WARN_IF(!edgeName)) return false;
+ }
+
+ edges.infallibleAppend(DeserializedEdge(referent, edgeName));
+ }
+
+ Maybe<StackFrameId> allocationStack;
+ if (node.has_allocationstack()) {
+ StackFrameId id = 0;
+ if (NS_WARN_IF(!saveStackFrame(node.allocationstack(), id))) return false;
+ allocationStack.emplace(id);
+ }
+ MOZ_ASSERT(allocationStack.isSome() == node.has_allocationstack());
+
+ const char* jsObjectClassName = nullptr;
+ if (node.JSObjectClassNameOrRef_case() !=
+ protobuf::Node::JSOBJECTCLASSNAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> clsNameOrRef =
+ GET_STRING_OR_REF(node, jsobjectclassname);
+ jsObjectClassName =
+ getOrInternString<char>(internedOneByteStrings, clsNameOrRef);
+ if (NS_WARN_IF(!jsObjectClassName)) return false;
+ }
+
+ const char* scriptFilename = nullptr;
+ if (node.ScriptFilenameOrRef_case() !=
+ protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> scriptFilenameOrRef =
+ GET_STRING_OR_REF(node, scriptfilename);
+ scriptFilename =
+ getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
+ if (NS_WARN_IF(!scriptFilename)) return false;
+ }
+
+ const char16_t* descriptiveTypeName = nullptr;
+ if (node.descriptiveTypeNameOrRef_case() !=
+ protobuf::Node::DESCRIPTIVETYPENAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> descriptiveTypeNameOrRef =
+ GET_STRING_OR_REF(node, descriptivetypename);
+ descriptiveTypeName = getOrInternString<char16_t>(internedTwoByteStrings,
+ descriptiveTypeNameOrRef);
+ if (NS_WARN_IF(!descriptiveTypeName)) return false;
+ }
+
+ if (NS_WARN_IF(!nodes.putNew(
+ id, DeserializedNode(id, coarseType, typeName, size, std::move(edges),
+ allocationStack, jsObjectClassName,
+ scriptFilename, descriptiveTypeName, *this)))) {
+ return false;
+ };
+
+ return true;
+}
+
+bool HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
+ StackFrameId& outFrameId) {
+ // NB: de-duplicated string properties must be read in the same order here as
+ // they are written in `CoreDumpWriter::getProtobufStackFrame` or else indices
+ // in references to already serialized strings will be off.
+
+ if (frame.has_ref()) {
+ // We should only get a reference to the previous frame if we have already
+ // seen the previous frame.
+ if (!frames.has(frame.ref())) return false;
+
+ outFrameId = frame.ref();
+ return true;
+ }
+
+ // Incomplete message.
+ if (!frame.has_data()) return false;
+
+ auto data = frame.data();
+
+ if (!data.has_id()) return false;
+ StackFrameId id = data.id();
+
+ // This should be the first and only time we see this frame.
+ if (frames.has(id)) return false;
+
+ if (!data.has_line()) return false;
+ uint32_t line = data.line();
+
+ if (!data.has_column()) return false;
+ JS::TaggedColumnNumberOneOrigin column(
+ JS::LimitedColumnNumberOneOrigin(data.column()));
+
+ if (!data.has_issystem()) return false;
+ bool isSystem = data.issystem();
+
+ if (!data.has_isselfhosted()) return false;
+ bool isSelfHosted = data.isselfhosted();
+
+ Maybe<StringOrRef> sourceOrRef = GET_STRING_OR_REF(data, source);
+ auto source =
+ getOrInternString<char16_t>(internedTwoByteStrings, sourceOrRef);
+ if (!source) return false;
+
+ const char16_t* functionDisplayName = nullptr;
+ if (data.FunctionDisplayNameOrRef_case() !=
+ protobuf::StackFrame_Data::FUNCTIONDISPLAYNAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> nameOrRef = GET_STRING_OR_REF(data, functiondisplayname);
+ functionDisplayName =
+ getOrInternString<char16_t>(internedTwoByteStrings, nameOrRef);
+ if (!functionDisplayName) return false;
+ }
+
+ Maybe<StackFrameId> parent;
+ if (data.has_parent()) {
+ StackFrameId parentId = 0;
+ if (!saveStackFrame(data.parent(), parentId)) return false;
+ parent = Some(parentId);
+ }
+
+ if (!frames.putNew(id,
+ DeserializedStackFrame(id, parent, line, column, source,
+ functionDisplayName, isSystem,
+ isSelfHosted, *this))) {
+ return false;
+ }
+
+ outFrameId = id;
+ return true;
+}
+
+#undef GET_STRING_OR_REF_WITH_PROP_NAMES
+#undef GET_STRING_OR_REF
+
+// Because protobuf messages aren't self-delimiting, we serialize each message
+// preceded by its size in bytes. When deserializing, we read this size and then
+// limit reading from the stream to the given byte size. If we didn't, then the
+// first message would consume the entire stream.
+static bool readSizeOfNextMessage(ZeroCopyInputStream& stream,
+ uint32_t* sizep) {
+ MOZ_ASSERT(sizep);
+ CodedInputStream codedStream(&stream);
+ return codedStream.ReadVarint32(sizep) && *sizep > 0;
+}
+
+bool HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size) {
+ ArrayInputStream stream(buffer, size);
+ GzipInputStream gzipStream(&stream);
+ uint32_t sizeOfMessage = 0;
+
+ // First is the metadata.
+
+ protobuf::Metadata metadata;
+ if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
+ return false;
+ if (!parseMessage(gzipStream, sizeOfMessage, metadata)) return false;
+ if (metadata.has_timestamp()) timestamp.emplace(metadata.timestamp());
+
+ // Next is the root node.
+
+ protobuf::Node root;
+ if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
+ return false;
+ if (!parseMessage(gzipStream, sizeOfMessage, root)) return false;
+
+ // Although the id is optional in the protobuf format for future proofing, we
+ // can't currently do anything without it.
+ if (NS_WARN_IF(!root.has_id())) return false;
+ rootId = root.id();
+
+ // The set of all node ids we've found edges pointing to.
+ NodeIdSet edgeReferents(cx);
+
+ if (NS_WARN_IF(!saveNode(root, edgeReferents))) return false;
+
+ // Finally, the rest of the nodes in the core dump.
+
+ // Test for the end of the stream. The protobuf library gives no way to tell
+ // the difference between an underlying read error and the stream being
+ // done. All we can do is attempt to read the size of the next message and
+ // extrapolate guestimations from the result of that operation.
+ while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
+ protobuf::Node node;
+ if (!parseMessage(gzipStream, sizeOfMessage, node)) return false;
+ if (NS_WARN_IF(!saveNode(node, edgeReferents))) return false;
+ }
+
+ // Check the set of node ids referred to by edges we found and ensure that we
+ // have the node corresponding to each id. If we don't have all of them, it is
+ // unsafe to perform analyses of this heap snapshot.
+ for (auto iter = edgeReferents.iter(); !iter.done(); iter.next()) {
+ if (NS_WARN_IF(!nodes.has(iter.get()))) return false;
+ }
+
+ return true;
+}
+
+/*** Heap Snapshot Analyses ***************************************************/
+
+void HeapSnapshot::TakeCensus(JSContext* cx, JS::Handle<JSObject*> options,
+ JS::MutableHandle<JS::Value> rval,
+ ErrorResult& rv) {
+ JS::ubi::Census census(cx);
+
+ JS::ubi::CountTypePtr rootType;
+ if (NS_WARN_IF(!JS::ubi::ParseCensusOptions(cx, census, options, rootType))) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
+ if (NS_WARN_IF(!rootCount)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::ubi::CensusHandler handler(census, rootCount,
+ GetCurrentThreadDebuggerMallocSizeOf());
+
+ {
+ JS::AutoCheckCannotGC nogc;
+
+ JS::ubi::CensusTraversal traversal(cx, handler, nogc);
+
+ if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (NS_WARN_IF(!traversal.traverse())) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ if (NS_WARN_IF(!handler.report(cx, rval))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void HeapSnapshot::DescribeNode(JSContext* cx, JS::Handle<JSObject*> breakdown,
+ uint64_t nodeId,
+ JS::MutableHandle<JS::Value> rval,
+ ErrorResult& rv) {
+ MOZ_ASSERT(breakdown);
+ JS::Rooted<JS::Value> breakdownVal(cx, JS::ObjectValue(*breakdown));
+ JS::ubi::CountTypePtr rootType = JS::ubi::ParseBreakdown(cx, breakdownVal);
+ if (NS_WARN_IF(!rootType)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
+ if (NS_WARN_IF(!rootCount)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::ubi::Node::Id id(nodeId);
+ Maybe<JS::ubi::Node> node = getNodeById(id);
+ if (NS_WARN_IF(node.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ if (NS_WARN_IF(!rootCount->count(mallocSizeOf, *node))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (NS_WARN_IF(!rootCount->report(cx, rval))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+already_AddRefed<DominatorTree> HeapSnapshot::ComputeDominatorTree(
+ ErrorResult& rv) {
+ Maybe<JS::ubi::DominatorTree> maybeTree;
+ {
+ auto ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx);
+ auto cx = ccjscx->Context();
+ MOZ_ASSERT(cx);
+ JS::AutoCheckCannotGC nogc(cx);
+ maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
+ }
+
+ if (NS_WARN_IF(maybeTree.isNothing())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ return MakeAndAddRef<DominatorTree>(std::move(*maybeTree), this, mParent);
+}
+
+void HeapSnapshot::ComputeShortestPaths(JSContext* cx, uint64_t start,
+ const Sequence<uint64_t>& targets,
+ uint64_t maxNumPaths,
+ JS::MutableHandle<JSObject*> results,
+ ErrorResult& rv) {
+ // First ensure that our inputs are valid.
+
+ if (NS_WARN_IF(maxNumPaths == 0)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Maybe<JS::ubi::Node> startNode = getNodeById(start);
+ if (NS_WARN_IF(startNode.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (NS_WARN_IF(targets.Length() == 0)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ // Aggregate the targets into a set and make sure that they exist in the heap
+ // snapshot.
+
+ JS::ubi::NodeSet targetsSet;
+
+ for (const auto& target : targets) {
+ Maybe<JS::ubi::Node> targetNode = getNodeById(target);
+ if (NS_WARN_IF(targetNode.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (NS_WARN_IF(!targetsSet.put(*targetNode))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ // Walk the heap graph and find the shortest paths.
+
+ Maybe<ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC nogc(cx);
+ maybeShortestPaths = ShortestPaths::Create(
+ cx, nogc, maxNumPaths, *startNode, std::move(targetsSet));
+ }
+
+ if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto& shortestPaths = *maybeShortestPaths;
+
+ // Convert the results into a Map object mapping target node IDs to arrays of
+ // paths found.
+
+ JS::Rooted<JSObject*> resultsMap(cx, JS::NewMapObject(cx));
+ if (NS_WARN_IF(!resultsMap)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ for (auto iter = shortestPaths.targetIter(); !iter.done(); iter.next()) {
+ JS::Rooted<JS::Value> key(cx, JS::NumberValue(iter.get().identifier()));
+ JS::RootedVector<JS::Value> paths(cx);
+
+ bool ok = shortestPaths.forEachPath(iter.get(), [&](JS::ubi::Path& path) {
+ JS::RootedVector<JS::Value> pathValues(cx);
+
+ for (JS::ubi::BackEdge* edge : path) {
+ JS::Rooted<JSObject*> pathPart(cx, JS_NewPlainObject(cx));
+ if (!pathPart) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> predecessor(
+ cx, NumberValue(edge->predecessor().identifier()));
+ if (!JS_DefineProperty(cx, pathPart, "predecessor", predecessor,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ JS::Rooted<JS::Value> edgeNameVal(cx, NullValue());
+ if (edge->name()) {
+ JS::Rooted<JSString*> edgeName(
+ cx, JS_AtomizeUCString(cx, edge->name().get()));
+ if (!edgeName) {
+ return false;
+ }
+ edgeNameVal = StringValue(edgeName);
+ }
+
+ if (!JS_DefineProperty(cx, pathPart, "edge", edgeNameVal,
+ JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (!pathValues.append(ObjectValue(*pathPart))) {
+ return false;
+ }
+ }
+
+ JS::Rooted<JSObject*> pathObj(cx, JS::NewArrayObject(cx, pathValues));
+ return pathObj && paths.append(ObjectValue(*pathObj));
+ });
+
+ if (NS_WARN_IF(!ok)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::Rooted<JSObject*> pathsArray(cx, JS::NewArrayObject(cx, paths));
+ if (NS_WARN_IF(!pathsArray)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::Rooted<JS::Value> pathsVal(cx, ObjectValue(*pathsArray));
+ if (NS_WARN_IF(!JS::MapSet(cx, resultsMap, key, pathsVal))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ results.set(resultsMap);
+}
+
+/*** Saving Heap Snapshots ****************************************************/
+
+// If we are only taking a snapshot of the heap affected by the given set of
+// globals, find the set of compartments the globals are allocated
+// within. Returns false on OOM failure.
+static bool PopulateCompartmentsWithGlobals(
+ CompartmentSet& compartments, JS::HandleVector<JSObject*> globals) {
+ unsigned length = globals.length();
+ for (unsigned i = 0; i < length; i++) {
+ if (!compartments.put(JS::GetCompartment(globals[i]))) return false;
+ }
+
+ return true;
+}
+
+// Add the given set of globals as explicit roots in the given roots
+// list. Returns false on OOM failure.
+static bool AddGlobalsAsRoots(JS::HandleVector<JSObject*> globals,
+ ubi::RootList& roots) {
+ unsigned length = globals.length();
+ for (unsigned i = 0; i < length; i++) {
+ if (!roots.addRoot(ubi::Node(globals[i].get()), u"heap snapshot global")) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
+// the set of nodes within the boundaries that are referred to by nodes
+// outside. If `boundaries` does not include all JS compartments, initialize
+// `compartments` to the set of included compartments; otherwise, leave
+// `compartments` uninitialized. (You can use compartments.initialized() to
+// check.)
+//
+// If `boundaries` is incoherent, or we encounter an error while trying to
+// handle it, or we run out of memory, set `rv` appropriately and return
+// `false`.
+//
+// Return value is a pair of the status and an AutoCheckCannotGC token,
+// forwarded from ubi::RootList::init(), to ensure that the caller does
+// not GC while the RootList is live and initialized.
+static std::pair<bool, AutoCheckCannotGC> EstablishBoundaries(
+ JSContext* cx, ErrorResult& rv, const HeapSnapshotBoundaries& boundaries,
+ ubi::RootList& roots, CompartmentSet& compartments) {
+ MOZ_ASSERT(!roots.initialized());
+ MOZ_ASSERT(compartments.empty());
+
+ bool foundBoundaryProperty = false;
+
+ if (boundaries.mRuntime.WasPassed()) {
+ foundBoundaryProperty = true;
+
+ if (!boundaries.mRuntime.Value()) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+
+ auto [ok, nogc] = roots.init();
+ if (!ok) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return {false, nogc};
+ }
+ }
+
+ if (boundaries.mDebugger.WasPassed()) {
+ if (foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ foundBoundaryProperty = true;
+
+ JSObject* dbgObj = boundaries.mDebugger.Value();
+ if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+
+ JS::RootedVector<JSObject*> globals(cx);
+ if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, &globals) ||
+ !PopulateCompartmentsWithGlobals(compartments, globals) ||
+ !roots.init(compartments).first || !AddGlobalsAsRoots(globals, roots)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ }
+
+ if (boundaries.mGlobals.WasPassed()) {
+ if (foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ foundBoundaryProperty = true;
+
+ uint32_t length = boundaries.mGlobals.Value().Length();
+ if (length == 0) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+
+ JS::RootedVector<JSObject*> globals(cx);
+ for (uint32_t i = 0; i < length; i++) {
+ JSObject* global = boundaries.mGlobals.Value().ElementAt(i);
+ if (!JS_IsGlobalObject(global)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ if (!globals.append(global)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ }
+
+ if (!PopulateCompartmentsWithGlobals(compartments, globals) ||
+ !roots.init(compartments).first || !AddGlobalsAsRoots(globals, roots)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return {false, AutoCheckCannotGC(cx)};
+ }
+ }
+ AutoCheckCannotGC nogc(cx);
+
+ if (!foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return {false, nogc};
+ }
+
+ MOZ_ASSERT(roots.initialized());
+ return {true, nogc};
+}
+
+// A variant covering all the various two-byte strings that we can get from the
+// ubi::Node API.
+class TwoByteString
+ : public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName> {
+ using Base = Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>;
+
+ struct CopyToBufferMatcher {
+ RangedPtr<char16_t> destination;
+ size_t maxLength;
+
+ CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
+ : destination(destination), maxLength(maxLength) {}
+
+ size_t operator()(JS::ubi::EdgeName& ptr) {
+ return ptr ? operator()(ptr.get()) : 0;
+ }
+
+ size_t operator()(JSAtom* atom) {
+ MOZ_ASSERT(atom);
+ JS::ubi::AtomOrTwoByteChars s(atom);
+ return s.copyToBuffer(destination, maxLength);
+ }
+
+ size_t operator()(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+ JS::ubi::AtomOrTwoByteChars s(chars);
+ return s.copyToBuffer(destination, maxLength);
+ }
+ };
+
+ public:
+ template <typename T>
+ MOZ_IMPLICIT TwoByteString(T&& rhs) : Base(std::forward<T>(rhs)) {}
+
+ template <typename T>
+ TwoByteString& operator=(T&& rhs) {
+ MOZ_ASSERT(this != &rhs, "self-move disallowed");
+ this->~TwoByteString();
+ new (this) TwoByteString(std::forward<T>(rhs));
+ return *this;
+ }
+
+ TwoByteString(const TwoByteString&) = delete;
+ TwoByteString& operator=(const TwoByteString&) = delete;
+
+ // Rewrap the inner value of a JS::ubi::AtomOrTwoByteChars as a TwoByteString.
+ static TwoByteString from(JS::ubi::AtomOrTwoByteChars&& s) {
+ return s.match([](auto* a) { return TwoByteString(a); });
+ }
+
+ // Returns true if the given TwoByteString is non-null, false otherwise.
+ bool isNonNull() const {
+ return match([](auto& t) { return t != nullptr; });
+ }
+
+ // Return the length of the string, 0 if it is null.
+ size_t length() const {
+ return match(
+ [](JSAtom* atom) -> size_t {
+ MOZ_ASSERT(atom);
+ JS::ubi::AtomOrTwoByteChars s(atom);
+ return s.length();
+ },
+ [](const char16_t* chars) -> size_t {
+ MOZ_ASSERT(chars);
+ return NS_strlen(chars);
+ },
+ [](const JS::ubi::EdgeName& ptr) -> size_t {
+ MOZ_ASSERT(ptr);
+ return NS_strlen(ptr.get());
+ });
+ }
+
+ // Copy the contents of a TwoByteString into the provided buffer. The buffer
+ // is NOT null terminated. The number of characters written is returned.
+ size_t copyToBuffer(RangedPtr<char16_t> destination, size_t maxLength) {
+ CopyToBufferMatcher m(destination, maxLength);
+ return match(m);
+ }
+
+ struct HashPolicy;
+};
+
+// A hashing policy for TwoByteString.
+//
+// Atoms are pointer hashed and use pointer equality, which means that we
+// tolerate some duplication across atoms and the other two types of two-byte
+// strings. In practice, we expect the amount of this duplication to be very low
+// because each type is generally a different semantic thing in addition to
+// having a slightly different representation. For example, the set of edge
+// names and the set stack frames' source names naturally tend not to overlap
+// very much if at all.
+struct TwoByteString::HashPolicy {
+ using Lookup = TwoByteString;
+
+ static js::HashNumber hash(const Lookup& l) {
+ return l.match(
+ [](const JSAtom* atom) {
+ return js::DefaultHasher<const JSAtom*>::hash(atom);
+ },
+ [](const char16_t* chars) {
+ MOZ_ASSERT(chars);
+ auto length = NS_strlen(chars);
+ return HashString(chars, length);
+ },
+ [](const JS::ubi::EdgeName& ptr) {
+ const char16_t* chars = ptr.get();
+ MOZ_ASSERT(chars);
+ auto length = NS_strlen(chars);
+ return HashString(chars, length);
+ });
+ }
+
+ struct EqualityMatcher {
+ const TwoByteString& rhs;
+ explicit EqualityMatcher(const TwoByteString& rhs) : rhs(rhs) {}
+
+ bool operator()(const JSAtom* atom) {
+ return rhs.is<JSAtom*>() && rhs.as<JSAtom*>() == atom;
+ }
+
+ bool operator()(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+
+ const char16_t* rhsChars = nullptr;
+ if (rhs.is<const char16_t*>())
+ rhsChars = rhs.as<const char16_t*>();
+ else if (rhs.is<JS::ubi::EdgeName>())
+ rhsChars = rhs.as<JS::ubi::EdgeName>().get();
+ else
+ return false;
+ MOZ_ASSERT(rhsChars);
+
+ auto length = NS_strlen(chars);
+ if (NS_strlen(rhsChars) != length) return false;
+
+ return memcmp(chars, rhsChars, length * sizeof(char16_t)) == 0;
+ }
+
+ bool operator()(const JS::ubi::EdgeName& ptr) {
+ MOZ_ASSERT(ptr);
+ return operator()(ptr.get());
+ }
+ };
+
+ static bool match(const TwoByteString& k, const Lookup& l) {
+ EqualityMatcher eq(l);
+ return k.match(eq);
+ }
+
+ static void rekey(TwoByteString& k, TwoByteString&& newKey) {
+ k = std::move(newKey);
+ }
+};
+
+// Returns whether `edge` should be included in a heap snapshot of
+// `compartments`. The optional `policy` out-param is set to INCLUDE_EDGES
+// if we want to include the referent's edges, or EXCLUDE_EDGES if we don't
+// want to include them.
+static bool ShouldIncludeEdge(JS::CompartmentSet* compartments,
+ const ubi::Node& origin, const ubi::Edge& edge,
+ CoreDumpWriter::EdgePolicy* policy = nullptr) {
+ if (policy) {
+ *policy = CoreDumpWriter::INCLUDE_EDGES;
+ }
+
+ if (!compartments) {
+ // We aren't targeting a particular set of compartments, so serialize all
+ // the things!
+ return true;
+ }
+
+ // We are targeting a particular set of compartments. If this node is in our
+ // target set, serialize it and all of its edges. If this node is _not_ in our
+ // target set, we also serialize under the assumption that it is a shared
+ // resource being used by something in our target compartments since we
+ // reached it by traversing the heap graph. However, we do not serialize its
+ // outgoing edges and we abandon further traversal from this node.
+ //
+ // If the node does not belong to any compartment, we also serialize its
+ // outgoing edges. This case is relevant for Shapes: they don't belong to a
+ // specific compartment and contain edges to parent/kids Shapes we want to
+ // include. Note that these Shapes may contain pointers into our target
+ // compartment (the Shape's getter/setter JSObjects). However, we do not
+ // serialize nodes in other compartments that are reachable from these
+ // non-compartment nodes.
+
+ JS::Compartment* compartment = edge.referent.compartment();
+
+ if (!compartment || compartments->has(compartment)) {
+ return true;
+ }
+
+ if (policy) {
+ *policy = CoreDumpWriter::EXCLUDE_EDGES;
+ }
+
+ return !!origin.compartment();
+}
+
+// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
+// given `ZeroCopyOutputStream`.
+class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter {
+ using FrameSet = js::HashSet<uint64_t>;
+ using TwoByteStringMap =
+ js::HashMap<TwoByteString, uint64_t, TwoByteString::HashPolicy>;
+ using OneByteStringMap = js::HashMap<const char*, uint64_t>;
+
+ JSContext* cx;
+ bool wantNames;
+ // The set of |JS::ubi::StackFrame::identifier()|s that have already been
+ // serialized and written to the core dump.
+ FrameSet framesAlreadySerialized;
+ // The set of two-byte strings that have already been serialized and written
+ // to the core dump.
+ TwoByteStringMap twoByteStringsAlreadySerialized;
+ // The set of one-byte strings that have already been serialized and written
+ // to the core dump.
+ OneByteStringMap oneByteStringsAlreadySerialized;
+
+ ::google::protobuf::io::ZeroCopyOutputStream& stream;
+
+ JS::CompartmentSet* compartments;
+
+ bool writeMessage(const ::google::protobuf::MessageLite& message) {
+ // We have to create a new CodedOutputStream when writing each message so
+ // that the 64MB size limit used by Coded{Output,Input}Stream to prevent
+ // integer overflow is enforced per message rather than on the whole stream.
+ ::google::protobuf::io::CodedOutputStream codedStream(&stream);
+ codedStream.WriteVarint32(message.ByteSizeLong());
+ message.SerializeWithCachedSizes(&codedStream);
+ return !codedStream.HadError();
+ }
+
+ // Attach the full two-byte string or a reference to a two-byte string that
+ // has already been serialized to a protobuf message.
+ template <typename SetStringFunction, typename SetRefFunction>
+ bool attachTwoByteString(TwoByteString& string, SetStringFunction setString,
+ SetRefFunction setRef) {
+ auto ptr = twoByteStringsAlreadySerialized.lookupForAdd(string);
+ if (ptr) {
+ setRef(ptr->value());
+ return true;
+ }
+
+ auto length = string.length();
+ auto stringData = MakeUnique<std::string>(length * sizeof(char16_t), '\0');
+ if (!stringData) return false;
+
+ auto buf = const_cast<char16_t*>(
+ reinterpret_cast<const char16_t*>(stringData->data()));
+ string.copyToBuffer(RangedPtr<char16_t>(buf, length), length);
+
+ uint64_t ref = twoByteStringsAlreadySerialized.count();
+ if (!twoByteStringsAlreadySerialized.add(ptr, std::move(string), ref))
+ return false;
+
+ setString(stringData.release());
+ return true;
+ }
+
+ // Attach the full one-byte string or a reference to a one-byte string that
+ // has already been serialized to a protobuf message.
+ template <typename SetStringFunction, typename SetRefFunction>
+ bool attachOneByteString(const char* string, SetStringFunction setString,
+ SetRefFunction setRef) {
+ auto ptr = oneByteStringsAlreadySerialized.lookupForAdd(string);
+ if (ptr) {
+ setRef(ptr->value());
+ return true;
+ }
+
+ auto length = strlen(string);
+ auto stringData = MakeUnique<std::string>(string, length);
+ if (!stringData) return false;
+
+ uint64_t ref = oneByteStringsAlreadySerialized.count();
+ if (!oneByteStringsAlreadySerialized.add(ptr, string, ref)) return false;
+
+ setString(stringData.release());
+ return true;
+ }
+
+ protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
+ size_t depth = 1) {
+ // NB: de-duplicated string properties must be written in the same order
+ // here as they are read in `HeapSnapshot::saveStackFrame` or else indices
+ // in references to already serialized strings will be off.
+
+ MOZ_ASSERT(frame,
+ "null frames should be represented as the lack of a serialized "
+ "stack frame");
+
+ auto id = frame.identifier();
+ auto protobufStackFrame = MakeUnique<protobuf::StackFrame>();
+ if (!protobufStackFrame) return nullptr;
+
+ if (framesAlreadySerialized.has(id)) {
+ protobufStackFrame->set_ref(id);
+ return protobufStackFrame.release();
+ }
+
+ auto data = MakeUnique<protobuf::StackFrame_Data>();
+ if (!data) return nullptr;
+
+ data->set_id(id);
+ data->set_line(frame.line());
+ data->set_column(frame.column().oneOriginValue());
+ data->set_issystem(frame.isSystem());
+ data->set_isselfhosted(frame.isSelfHosted(cx));
+
+ auto dupeSource = TwoByteString::from(frame.source());
+ if (!attachTwoByteString(
+ dupeSource,
+ [&](std::string* source) { data->set_allocated_source(source); },
+ [&](uint64_t ref) { data->set_sourceref(ref); })) {
+ return nullptr;
+ }
+
+ auto dupeName = TwoByteString::from(frame.functionDisplayName());
+ if (dupeName.isNonNull()) {
+ if (!attachTwoByteString(
+ dupeName,
+ [&](std::string* name) {
+ data->set_allocated_functiondisplayname(name);
+ },
+ [&](uint64_t ref) { data->set_functiondisplaynameref(ref); })) {
+ return nullptr;
+ }
+ }
+
+ auto parent = frame.parent();
+ if (parent && depth < HeapSnapshot::MAX_STACK_DEPTH) {
+ auto protobufParent = getProtobufStackFrame(parent, depth + 1);
+ if (!protobufParent) return nullptr;
+ data->set_allocated_parent(protobufParent);
+ }
+
+ protobufStackFrame->set_allocated_data(data.release());
+
+ if (!framesAlreadySerialized.put(id)) return nullptr;
+
+ return protobufStackFrame.release();
+ }
+
+ public:
+ StreamWriter(JSContext* cx,
+ ::google::protobuf::io::ZeroCopyOutputStream& stream,
+ bool wantNames, JS::CompartmentSet* compartments)
+ : cx(cx),
+ wantNames(wantNames),
+ framesAlreadySerialized(cx),
+ twoByteStringsAlreadySerialized(cx),
+ oneByteStringsAlreadySerialized(cx),
+ stream(stream),
+ compartments(compartments) {}
+
+ ~StreamWriter() override {}
+
+ bool writeMetadata(uint64_t timestamp) final {
+ protobuf::Metadata metadata;
+ metadata.set_timestamp(timestamp);
+ return writeMessage(metadata);
+ }
+
+ bool writeNode(const JS::ubi::Node& ubiNode, EdgePolicy includeEdges) final {
+ // NB: de-duplicated string properties must be written in the same order
+ // here as they are read in `HeapSnapshot::saveNode` or else indices in
+ // references to already serialized strings will be off.
+
+ protobuf::Node protobufNode;
+ protobufNode.set_id(ubiNode.identifier());
+
+ protobufNode.set_coarsetype(
+ JS::ubi::CoarseTypeToUint32(ubiNode.coarseType()));
+
+ auto typeName = TwoByteString(ubiNode.typeName());
+ if (NS_WARN_IF(!attachTwoByteString(
+ typeName,
+ [&](std::string* name) {
+ protobufNode.set_allocated_typename_(name);
+ },
+ [&](uint64_t ref) { protobufNode.set_typenameref(ref); }))) {
+ return false;
+ }
+
+ mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
+ MOZ_ASSERT(mallocSizeOf);
+ protobufNode.set_size(ubiNode.size(mallocSizeOf));
+
+ if (includeEdges) {
+ auto edges = ubiNode.edges(cx, wantNames);
+ if (NS_WARN_IF(!edges)) return false;
+
+ for (; !edges->empty(); edges->popFront()) {
+ ubi::Edge& ubiEdge = edges->front();
+ if (!ShouldIncludeEdge(compartments, ubiNode, ubiEdge)) {
+ continue;
+ }
+
+ protobuf::Edge* protobufEdge = protobufNode.add_edges();
+ if (NS_WARN_IF(!protobufEdge)) {
+ return false;
+ }
+
+ protobufEdge->set_referent(ubiEdge.referent.identifier());
+
+ if (wantNames && ubiEdge.name) {
+ TwoByteString edgeName(std::move(ubiEdge.name));
+ if (NS_WARN_IF(!attachTwoByteString(
+ edgeName,
+ [&](std::string* name) {
+ protobufEdge->set_allocated_name(name);
+ },
+ [&](uint64_t ref) { protobufEdge->set_nameref(ref); }))) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (ubiNode.hasAllocationStack()) {
+ auto ubiStackFrame = ubiNode.allocationStack();
+ auto protoStackFrame = getProtobufStackFrame(ubiStackFrame);
+ if (NS_WARN_IF(!protoStackFrame)) return false;
+ protobufNode.set_allocated_allocationstack(protoStackFrame);
+ }
+
+ if (auto className = ubiNode.jsObjectClassName()) {
+ if (NS_WARN_IF(!attachOneByteString(
+ className,
+ [&](std::string* name) {
+ protobufNode.set_allocated_jsobjectclassname(name);
+ },
+ [&](uint64_t ref) {
+ protobufNode.set_jsobjectclassnameref(ref);
+ }))) {
+ return false;
+ }
+ }
+
+ if (auto scriptFilename = ubiNode.scriptFilename()) {
+ if (NS_WARN_IF(!attachOneByteString(
+ scriptFilename,
+ [&](std::string* name) {
+ protobufNode.set_allocated_scriptfilename(name);
+ },
+ [&](uint64_t ref) {
+ protobufNode.set_scriptfilenameref(ref);
+ }))) {
+ return false;
+ }
+ }
+
+ if (ubiNode.descriptiveTypeName()) {
+ auto descriptiveTypeName = TwoByteString(ubiNode.descriptiveTypeName());
+ if (NS_WARN_IF(!attachTwoByteString(
+ descriptiveTypeName,
+ [&](std::string* name) {
+ protobufNode.set_allocated_descriptivetypename(name);
+ },
+ [&](uint64_t ref) {
+ protobufNode.set_descriptivetypenameref(ref);
+ }))) {
+ return false;
+ }
+ }
+
+ return writeMessage(protobufNode);
+ }
+};
+
+// A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
+// core dump.
+class MOZ_STACK_CLASS HeapSnapshotHandler {
+ CoreDumpWriter& writer;
+ JS::CompartmentSet* compartments;
+
+ public:
+ // For telemetry.
+ uint32_t nodeCount;
+ uint32_t edgeCount;
+
+ HeapSnapshotHandler(CoreDumpWriter& writer, JS::CompartmentSet* compartments)
+ : writer(writer),
+ compartments(compartments),
+ nodeCount(0),
+ edgeCount(0) {}
+
+ // JS::ubi::BreadthFirst handler interface.
+
+ class NodeData {};
+ typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
+ bool operator()(Traversal& traversal, JS::ubi::Node origin,
+ const JS::ubi::Edge& edge, NodeData*, bool first) {
+ edgeCount++;
+
+ // We're only interested in the first time we reach edge.referent, not in
+ // every edge arriving at that node. "But, don't we want to serialize every
+ // edge in the heap graph?" you ask. Don't worry! This edge is still
+ // serialized into the core dump. Serializing a node also serializes each of
+ // its edges, and if we are traversing a given edge, we must have already
+ // visited and serialized the origin node and its edges.
+ if (!first) return true;
+
+ CoreDumpWriter::EdgePolicy policy;
+ if (!ShouldIncludeEdge(compartments, origin, edge, &policy)) {
+ // Because ShouldIncludeEdge considers the |origin| node as well, we don't
+ // want to consider this node 'visited' until we write it to the core
+ // dump.
+ traversal.doNotMarkReferentAsVisited();
+ return true;
+ }
+
+ nodeCount++;
+
+ if (policy == CoreDumpWriter::EXCLUDE_EDGES) traversal.abandonReferent();
+
+ return writer.writeNode(edge.referent, policy);
+ }
+};
+
+bool WriteHeapGraph(JSContext* cx, const JS::ubi::Node& node,
+ CoreDumpWriter& writer, bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC, uint32_t& outNodeCount,
+ uint32_t& outEdgeCount) {
+ // Serialize the starting node to the core dump.
+
+ if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
+ return false;
+ }
+
+ // Walk the heap graph starting from the given node and serialize it into the
+ // core dump.
+
+ HeapSnapshotHandler handler(writer, compartments);
+ HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
+ traversal.wantNames = wantNames;
+
+ bool ok = traversal.addStartVisited(node) && traversal.traverse();
+
+ if (ok) {
+ outNodeCount = handler.nodeCount;
+ outEdgeCount = handler.edgeCount;
+ }
+
+ return ok;
+}
+
+static unsigned long msSinceProcessCreation(const TimeStamp& now) {
+ auto duration = now - TimeStamp::ProcessCreation();
+ return (unsigned long)duration.ToMilliseconds();
+}
+
+/* static */
+already_AddRefed<nsIFile> HeapSnapshot::CreateUniqueCoreDumpFile(
+ ErrorResult& rv, const TimeStamp& now, nsAString& outFilePath,
+ nsAString& outSnapshotId) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ nsCOMPtr<nsIFile> file;
+ rv = GetSpecialSystemDirectory(OS_TemporaryDirectory, getter_AddRefs(file));
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ nsAutoString tempPath;
+ rv = file->GetPath(tempPath);
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ auto ms = msSinceProcessCreation(now);
+ rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ rv = file->GetPath(outFilePath);
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ // The snapshot ID must be computed in the process that created the
+ // temp file, because TmpD may not be the same in all processes.
+ outSnapshotId.Assign(Substring(
+ outFilePath, tempPath.Length() + 1,
+ outFilePath.Length() - tempPath.Length() - sizeof(".fxsnapshot")));
+
+ return file.forget();
+}
+
+// Deletion policy for cleaning up PHeapSnapshotTempFileHelperChild pointers.
+class DeleteHeapSnapshotTempFileHelperChild {
+ public:
+ constexpr DeleteHeapSnapshotTempFileHelperChild() {}
+
+ void operator()(PHeapSnapshotTempFileHelperChild* ptr) const {
+ Unused << NS_WARN_IF(!HeapSnapshotTempFileHelperChild::Send__delete__(ptr));
+ }
+};
+
+// A UniquePtr alias to automatically manage PHeapSnapshotTempFileHelperChild
+// pointers.
+using UniqueHeapSnapshotTempFileHelperChild =
+ UniquePtr<PHeapSnapshotTempFileHelperChild,
+ DeleteHeapSnapshotTempFileHelperChild>;
+
+// Get an nsIOutputStream that we can write the heap snapshot to. In non-e10s
+// and in the e10s parent process, open a file directly and create an output
+// stream for it. In e10s child processes, we are sandboxed without access to
+// the filesystem. Use IPDL to request a file descriptor from the parent
+// process.
+static already_AddRefed<nsIOutputStream> getCoreDumpOutputStream(
+ ErrorResult& rv, TimeStamp& start, nsAString& outFilePath,
+ nsAString& outSnapshotId) {
+ if (XRE_IsParentProcess()) {
+ // Create the file and open the output stream directly.
+
+ nsCOMPtr<nsIFile> file = HeapSnapshot::CreateUniqueCoreDumpFile(
+ rv, start, outFilePath, outSnapshotId);
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
+ PR_WRONLY, -1, 0);
+ if (NS_WARN_IF(rv.Failed())) return nullptr;
+
+ return outputStream.forget();
+ }
+ // Request a file descriptor from the parent process over IPDL.
+
+ auto cc = ContentChild::GetSingleton();
+ if (!cc) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ UniqueHeapSnapshotTempFileHelperChild helper(
+ cc->SendPHeapSnapshotTempFileHelperConstructor());
+ if (NS_WARN_IF(!helper)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ OpenHeapSnapshotTempFileResponse response;
+ if (!helper->SendOpenHeapSnapshotTempFile(&response)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ if (response.type() == OpenHeapSnapshotTempFileResponse::Tnsresult) {
+ rv.Throw(response.get_nsresult());
+ return nullptr;
+ }
+
+ auto opened = response.get_OpenedFile();
+ outFilePath = opened.path();
+ outSnapshotId = opened.snapshotId();
+ nsCOMPtr<nsIOutputStream> outputStream =
+ FileDescriptorOutputStream::Create(opened.descriptor());
+ if (NS_WARN_IF(!outputStream)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return outputStream.forget();
+}
+
+} // namespace devtools
+
+namespace dom {
+
+using namespace JS;
+using namespace devtools;
+
+/* static */
+void ChromeUtils::SaveHeapSnapshotShared(
+ GlobalObject& global, const HeapSnapshotBoundaries& boundaries,
+ nsAString& outFilePath, nsAString& outSnapshotId, ErrorResult& rv) {
+ auto start = TimeStamp::Now();
+
+ bool wantNames = true;
+ CompartmentSet compartments;
+ uint32_t nodeCount = 0;
+ uint32_t edgeCount = 0;
+
+ nsCOMPtr<nsIOutputStream> outputStream =
+ getCoreDumpOutputStream(rv, start, outFilePath, outSnapshotId);
+ if (NS_WARN_IF(rv.Failed())) return;
+
+ ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
+ ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream);
+
+ JSContext* cx = global.Context();
+
+ {
+ ubi::RootList rootList(cx, wantNames);
+ auto [ok, nogc] =
+ EstablishBoundaries(cx, rv, boundaries, rootList, compartments);
+ if (!ok) {
+ return;
+ }
+
+ StreamWriter writer(cx, gzipStream, wantNames,
+ !compartments.empty() ? &compartments : nullptr);
+
+ ubi::Node roots(&rootList);
+
+ // Serialize the initial heap snapshot metadata to the core dump.
+ if (!writer.writeMetadata(PR_Now()) ||
+ // Serialize the heap graph to the core dump, starting from our list of
+ // roots.
+ !WriteHeapGraph(cx, roots, writer, wantNames,
+ !compartments.empty() ? &compartments : nullptr, nogc,
+ nodeCount, edgeCount)) {
+ rv.Throw(zeroCopyStream.failed() ? zeroCopyStream.result()
+ : NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_SAVE_HEAP_SNAPSHOT_MS,
+ start);
+ Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_NODE_COUNT,
+ nodeCount);
+ Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_EDGE_COUNT,
+ edgeCount);
+}
+
+/* static */
+uint64_t ChromeUtils::GetObjectNodeId(GlobalObject& global,
+ JS::Handle<JSObject*> val) {
+ JS::Rooted<JSObject*> obj(global.Context(), val);
+
+ JS::ubi::Node node(obj);
+ return node.identifier();
+}
+
+/* static */
+void ChromeUtils::SaveHeapSnapshot(GlobalObject& global,
+ const HeapSnapshotBoundaries& boundaries,
+ nsAString& outFilePath, ErrorResult& rv) {
+ nsAutoString snapshotId;
+ SaveHeapSnapshotShared(global, boundaries, outFilePath, snapshotId, rv);
+}
+
+/* static */
+void ChromeUtils::SaveHeapSnapshotGetId(
+ GlobalObject& global, const HeapSnapshotBoundaries& boundaries,
+ nsAString& outSnapshotId, ErrorResult& rv) {
+ nsAutoString filePath;
+ SaveHeapSnapshotShared(global, boundaries, filePath, outSnapshotId, rv);
+}
+
+/* static */
+already_AddRefed<HeapSnapshot> ChromeUtils::ReadHeapSnapshot(
+ GlobalObject& global, const nsAString& filePath, ErrorResult& rv) {
+ auto start = TimeStamp::Now();
+
+ nsresult nsrv;
+ nsCOMPtr<nsIFile> snapshotFile =
+ do_CreateInstance("@mozilla.org/file/local;1", &nsrv);
+
+ if (NS_FAILED(nsrv)) {
+ rv = nsrv;
+ return nullptr;
+ }
+
+ rv = snapshotFile->InitWithPath(filePath);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ AutoMemMap mm;
+ rv = mm.init(snapshotFile);
+ if (rv.Failed()) return nullptr;
+
+ RefPtr<HeapSnapshot> snapshot = HeapSnapshot::Create(
+ global.Context(), global, reinterpret_cast<const uint8_t*>(mm.address()),
+ mm.size(), rv);
+
+ if (!rv.Failed())
+ Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_READ_HEAP_SNAPSHOT_MS,
+ start);
+
+ return snapshot.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/HeapSnapshot.h b/devtools/shared/heapsnapshot/HeapSnapshot.h
new file mode 100644
index 0000000000..a23fdc2c86
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -0,0 +1,216 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshot__
+#define mozilla_devtools_HeapSnapshot__
+
+#include "js/HashTable.h"
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+#include "CoreDump.pb.h"
+#include "nsCOMPtr.h"
+#include "nsCRTGlue.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "nsXPCOM.h"
+
+namespace mozilla {
+class ErrorResult;
+
+namespace devtools {
+
+class DominatorTree;
+
+using UniqueTwoByteString = UniqueFreePtr<char16_t[]>;
+using UniqueOneByteString = UniqueFreePtr<char[]>;
+
+class HeapSnapshot final : public nsISupports, public nsWrapperCache {
+ friend struct DeserializedNode;
+ friend struct DeserializedEdge;
+ friend struct DeserializedStackFrame;
+ friend class JS::ubi::Concrete<JS::ubi::DeserializedNode>;
+
+ explicit HeapSnapshot(JSContext* cx, nsISupports* aParent)
+ : timestamp(Nothing()),
+ rootId(0),
+ nodes(cx),
+ frames(cx),
+ mParent(aParent) {
+ MOZ_ASSERT(aParent);
+ };
+
+ // Initialize this HeapSnapshot from the given buffer that contains a
+ // serialized core dump. Do NOT take ownership of the buffer, only borrow it
+ // for the duration of the call. Return false on failure.
+ bool init(JSContext* cx, const uint8_t* buffer, uint32_t size);
+
+ using NodeIdSet = js::HashSet<NodeId>;
+
+ // Save the given `protobuf::Node` message in this `HeapSnapshot` as a
+ // `DeserializedNode`.
+ bool saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents);
+
+ // Save the given `protobuf::StackFrame` message in this `HeapSnapshot` as a
+ // `DeserializedStackFrame`. The saved stack frame's id is returned via the
+ // out parameter.
+ bool saveStackFrame(const protobuf::StackFrame& frame,
+ StackFrameId& outFrameId);
+
+ public:
+ // The maximum number of stack frames that we will serialize into a core
+ // dump. This helps prevent over-recursion in the protobuf library when
+ // deserializing stacks.
+ static const size_t MAX_STACK_DEPTH = 60;
+
+ private:
+ // If present, a timestamp in the same units that `PR_Now` gives.
+ Maybe<uint64_t> timestamp;
+
+ // The id of the root node for this deserialized heap graph.
+ NodeId rootId;
+
+ // The set of nodes in this deserialized heap graph, keyed by id.
+ using NodeSet = js::HashSet<DeserializedNode, DeserializedNode::HashPolicy>;
+ NodeSet nodes;
+
+ // The set of stack frames in this deserialized heap graph, keyed by id.
+ using FrameSet =
+ js::HashSet<DeserializedStackFrame, DeserializedStackFrame::HashPolicy>;
+ FrameSet frames;
+
+ Vector<UniqueTwoByteString> internedTwoByteStrings;
+ Vector<UniqueOneByteString> internedOneByteStrings;
+
+ using StringOrRef = Variant<const std::string*, uint64_t>;
+
+ template <typename CharT, typename InternedStringSet>
+ const CharT* getOrInternString(InternedStringSet& internedStrings,
+ Maybe<StringOrRef>& maybeStrOrRef);
+
+ protected:
+ nsCOMPtr<nsISupports> mParent;
+
+ virtual ~HeapSnapshot() {}
+
+ public:
+ // Create a `HeapSnapshot` from the given buffer that contains a serialized
+ // core dump. Do NOT take ownership of the buffer, only borrow it for the
+ // duration of the call.
+ static already_AddRefed<HeapSnapshot> Create(JSContext* cx,
+ dom::GlobalObject& global,
+ const uint8_t* buffer,
+ uint32_t size, ErrorResult& rv);
+
+ // Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap
+ // snapshots are serialized into.
+ static already_AddRefed<nsIFile> CreateUniqueCoreDumpFile(
+ ErrorResult& rv, const TimeStamp& now, nsAString& outFilePath,
+ nsAString& outSnapshotId);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(HeapSnapshot)
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(HeapSnapshot)
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ const char16_t* borrowUniqueString(const char16_t* duplicateString,
+ size_t length);
+
+ // Get the root node of this heap snapshot's graph.
+ JS::ubi::Node getRoot() {
+ auto p = nodes.lookup(rootId);
+ MOZ_ASSERT(p);
+ const DeserializedNode& node = *p;
+ return JS::ubi::Node(const_cast<DeserializedNode*>(&node));
+ }
+
+ Maybe<JS::ubi::Node> getNodeById(JS::ubi::Node::Id nodeId) {
+ auto p = nodes.lookup(nodeId);
+ if (!p) return Nothing();
+ return Some(JS::ubi::Node(const_cast<DeserializedNode*>(&*p)));
+ }
+
+ void TakeCensus(JSContext* cx, JS::Handle<JSObject*> options,
+ JS::MutableHandle<JS::Value> rval, ErrorResult& rv);
+
+ void DescribeNode(JSContext* cx, JS::Handle<JSObject*> breakdown,
+ uint64_t nodeId, JS::MutableHandle<JS::Value> rval,
+ ErrorResult& rv);
+
+ already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
+
+ void ComputeShortestPaths(JSContext* cx, uint64_t start,
+ const dom::Sequence<uint64_t>& targets,
+ uint64_t maxNumPaths,
+ JS::MutableHandle<JSObject*> results,
+ ErrorResult& rv);
+
+ dom::Nullable<uint64_t> GetCreationTime() {
+ static const uint64_t maxTime = uint64_t(1) << 53;
+ if (timestamp.isSome() && timestamp.ref() <= maxTime) {
+ return dom::Nullable<uint64_t>(timestamp.ref());
+ }
+
+ return dom::Nullable<uint64_t>();
+ }
+};
+
+// A `CoreDumpWriter` is given the data we wish to save in a core dump and
+// serializes it to disk, or memory, or a socket, etc.
+class CoreDumpWriter {
+ public:
+ virtual ~CoreDumpWriter(){};
+
+ // Write the given bits of metadata we would like to associate with this core
+ // dump.
+ virtual bool writeMetadata(uint64_t timestamp) = 0;
+
+ enum EdgePolicy : bool { INCLUDE_EDGES = true, EXCLUDE_EDGES = false };
+
+ // Write the given `JS::ubi::Node` to the core dump. The given `EdgePolicy`
+ // dictates whether its outgoing edges should also be written to the core
+ // dump, or excluded.
+ virtual bool writeNode(const JS::ubi::Node& node,
+ EdgePolicy includeEdges) = 0;
+};
+
+// Serialize the heap graph as seen from `node` with the given `CoreDumpWriter`.
+// If `wantNames` is true, capture edge names. If `zones` is non-null, only
+// capture the sub-graph within the zone set, otherwise capture the whole heap
+// graph. Returns false on failure.
+bool WriteHeapGraph(JSContext* cx, const JS::ubi::Node& node,
+ CoreDumpWriter& writer, bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC, uint32_t& outNodeCount,
+ uint32_t& outEdgeCount);
+inline bool WriteHeapGraph(JSContext* cx, const JS::ubi::Node& node,
+ CoreDumpWriter& writer, bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC) {
+ uint32_t ignoreNodeCount;
+ uint32_t ignoreEdgeCount;
+ return WriteHeapGraph(cx, node, writer, wantNames, compartments, noGC,
+ ignoreNodeCount, ignoreEdgeCount);
+}
+
+// Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
+MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshot__
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js b/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js
new file mode 100644
index 0000000000..ab804eded1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Heap snapshots are always saved in the temp directory, and have a regular
+// naming convention. This module provides helpers for working with heap
+// snapshot files in a safe manner. Because we attempt to avoid unnecessary
+// copies of the heap snapshot files by checking the local filesystem for a heap
+// snapshot file with the given snapshot id, we want to ensure that we are only
+// attempting to open heap snapshot files and not `~/.ssh/id_rsa`, for
+// example. Therefore, the RDP only talks about snapshot ids, or transfering the
+// bulk file data. A file path can be recovered from a snapshot id, which allows
+// one to check for the presence of the heap snapshot file on the local file
+// system, but we don't have to worry about opening arbitrary files.
+//
+// The heap snapshot file path conventions permits the following forms:
+//
+// $TEMP_DIRECTORY/XXXXXXXXXX.fxsnapshot
+// $TEMP_DIRECTORY/XXXXXXXXXX-XXXXX.fxsnapshot
+//
+// Where the strings of "X" are zero or more digits.
+
+"use strict";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
+});
+
+function getHeapSnapshotFileTemplate() {
+ return PathUtils.join(PathUtils.tempDir, `${Date.now()}.fxsnapshot`);
+}
+
+/**
+ * Get a unique temp file path for a new heap snapshot. The file is guaranteed
+ * not to exist before this call.
+ *
+ * @returns String
+ */
+exports.getNewUniqueHeapSnapshotTempFilePath = function () {
+ const file = new lazy.FileUtils.File(getHeapSnapshotFileTemplate());
+ // The call to createUnique will append "-N" after the leaf name (but before
+ // the extension) until a new file is found and create it. This guarantees we
+ // won't accidentally choose the same file twice.
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ return file.path;
+};
+
+function isValidSnapshotFileId(snapshotId) {
+ return /^\d+(\-\d+)?$/.test(snapshotId);
+}
+
+/**
+ * Get the file path for the given snapshot id.
+ *
+ * @param {String} snapshotId
+ *
+ * @returns String | null
+ */
+exports.getHeapSnapshotTempFilePath = function (snapshotId) {
+ // Don't want anyone sneaking "../../../.." strings into the snapshot id and
+ // trying to make us open arbitrary files.
+ if (!isValidSnapshotFileId(snapshotId)) {
+ return null;
+ }
+ return PathUtils.join(PathUtils.tempDir, snapshotId + ".fxsnapshot");
+};
+
+/**
+ * Return true if we have the heap snapshot file for the given snapshot id on
+ * the local file system. False is returned otherwise.
+ *
+ * @returns Promise<Boolean>
+ */
+exports.haveHeapSnapshotTempFile = function (snapshotId) {
+ const path = exports.getHeapSnapshotTempFilePath(snapshotId);
+ if (!path) {
+ return Promise.resolve(false);
+ }
+
+ return IOUtils.stat(path).then(
+ () => true,
+ () => false
+ );
+};
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h
new file mode 100644
index 0000000000..becf00eb85
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshotTempFileHelperChild_h
+#define mozilla_devtools_HeapSnapshotTempFileHelperChild_h
+
+#include "mozilla/devtools/PHeapSnapshotTempFileHelperChild.h"
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshotTempFileHelperChild
+ : public PHeapSnapshotTempFileHelperChild {
+ explicit HeapSnapshotTempFileHelperChild() {}
+
+ public:
+ static inline PHeapSnapshotTempFileHelperChild* Create();
+};
+
+/* static */ inline PHeapSnapshotTempFileHelperChild*
+HeapSnapshotTempFileHelperChild::Create() {
+ return new HeapSnapshotTempFileHelperChild();
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshotTempFileHelperChild_h
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp
new file mode 100644
index 0000000000..ea1cf8378a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
+#include "mozilla/ErrorResult.h"
+#include "private/pprio.h"
+
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace devtools {
+
+static bool openFileFailure(ErrorResult& rv,
+ OpenHeapSnapshotTempFileResponse* outResponse) {
+ *outResponse = rv.StealNSResult();
+ return true;
+}
+
+mozilla::ipc::IPCResult
+HeapSnapshotTempFileHelperParent::RecvOpenHeapSnapshotTempFile(
+ OpenHeapSnapshotTempFileResponse* outResponse) {
+ auto start = TimeStamp::Now();
+ ErrorResult rv;
+ nsAutoString filePath;
+ nsAutoString snapshotId;
+ nsCOMPtr<nsIFile> file =
+ HeapSnapshot::CreateUniqueCoreDumpFile(rv, start, filePath, snapshotId);
+ if (NS_WARN_IF(rv.Failed())) {
+ if (!openFileFailure(rv, outResponse)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+ }
+
+ PRFileDesc* prfd;
+ rv = file->OpenNSPRFileDesc(PR_WRONLY, 0, &prfd);
+ if (NS_WARN_IF(rv.Failed())) {
+ if (!openFileFailure(rv, outResponse)) {
+ return IPC_FAIL_NO_REASON(this);
+ }
+ return IPC_OK();
+ }
+
+ FileDescriptor::PlatformHandleType handle =
+ FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prfd));
+ FileDescriptor fd(handle);
+ *outResponse = OpenedFile(filePath, snapshotId, fd);
+ return IPC_OK();
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h
new file mode 100644
index 0000000000..058f0c6eea
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set sw=2 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshotTempFileHelperParent_h
+#define mozilla_devtools_HeapSnapshotTempFileHelperParent_h
+
+#include "mozilla/devtools/PHeapSnapshotTempFileHelperParent.h"
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshotTempFileHelperParent
+ : public PHeapSnapshotTempFileHelperParent {
+ friend class PHeapSnapshotTempFileHelperParent;
+
+ explicit HeapSnapshotTempFileHelperParent() {}
+ void ActorDestroy(ActorDestroyReason why) override {}
+ mozilla::ipc::IPCResult RecvOpenHeapSnapshotTempFile(
+ OpenHeapSnapshotTempFileResponse* outResponse);
+
+ public:
+ static inline PHeapSnapshotTempFileHelperParent* Create();
+};
+
+/* static */ inline PHeapSnapshotTempFileHelperParent*
+HeapSnapshotTempFileHelperParent::Create() {
+ return new HeapSnapshotTempFileHelperParent();
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshotTempFileHelperParent_h
diff --git a/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl b/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl
new file mode 100644
index 0000000000..7257e43088
--- /dev/null
+++ b/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace devtools {
+
+struct OpenedFile
+{
+ nsString path;
+ nsString snapshotId;
+ FileDescriptor descriptor;
+};
+
+union OpenHeapSnapshotTempFileResponse
+{
+ nsresult;
+ OpenedFile;
+};
+
+[ManualDealloc]
+sync protocol PHeapSnapshotTempFileHelper
+{
+ manager PContent;
+
+parent:
+ sync OpenHeapSnapshotTempFile() returns (OpenHeapSnapshotTempFileResponse response);
+
+ async __delete__();
+};
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
new file mode 100644
index 0000000000..8edde20635
--- /dev/null
+++ b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace devtools {
+
+ZeroCopyNSIOutputStream::ZeroCopyNSIOutputStream(nsCOMPtr<nsIOutputStream>& out)
+ : out(out), result_(NS_OK), amountUsed(0), writtenCount(0) {
+ DebugOnly<bool> nonBlocking = false;
+ MOZ_ASSERT(out->IsNonBlocking(&nonBlocking) == NS_OK);
+ MOZ_ASSERT(!nonBlocking);
+}
+
+ZeroCopyNSIOutputStream::~ZeroCopyNSIOutputStream() {
+ if (!failed()) Unused << NS_WARN_IF(NS_FAILED(writeBuffer()));
+}
+
+nsresult ZeroCopyNSIOutputStream::writeBuffer() {
+ if (failed()) return result_;
+
+ if (amountUsed == 0) return NS_OK;
+
+ int32_t amountWritten = 0;
+ while (amountWritten < amountUsed) {
+ uint32_t justWritten = 0;
+
+ result_ = out->Write(buffer + amountWritten, amountUsed - amountWritten,
+ &justWritten);
+ if (NS_WARN_IF(NS_FAILED(result_))) return result_;
+
+ amountWritten += justWritten;
+ }
+
+ writtenCount += amountUsed;
+ amountUsed = 0;
+ return NS_OK;
+}
+
+// ZeroCopyOutputStream Interface
+
+bool ZeroCopyNSIOutputStream::Next(void** data, int* size) {
+ MOZ_ASSERT(data != nullptr);
+ MOZ_ASSERT(size != nullptr);
+
+ if (failed()) return false;
+
+ if (amountUsed == BUFFER_SIZE) {
+ if (NS_FAILED(writeBuffer())) return false;
+ }
+
+ *data = buffer + amountUsed;
+ *size = BUFFER_SIZE - amountUsed;
+ amountUsed = BUFFER_SIZE;
+ return true;
+}
+
+void ZeroCopyNSIOutputStream::BackUp(int count) {
+ MOZ_ASSERT(count >= 0, "Cannot back up a negative amount of bytes.");
+ MOZ_ASSERT(amountUsed == BUFFER_SIZE,
+ "Can only call BackUp directly after calling Next.");
+ MOZ_ASSERT(count <= amountUsed,
+ "Can't back up further than we've given out.");
+
+ amountUsed -= count;
+}
+
+::google::protobuf::int64 ZeroCopyNSIOutputStream::ByteCount() const {
+ return writtenCount + amountUsed;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h
new file mode 100644
index 0000000000..96158e389e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_ZeroCopyNSIOutputStream__
+#define mozilla_devtools_ZeroCopyNSIOutputStream__
+
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/stubs/common.h>
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace devtools {
+
+// A `google::protobuf::io::ZeroCopyOutputStream` implementation that uses an
+// `nsIOutputStream` under the covers.
+//
+// This class will automatically write and flush its data to the
+// `nsIOutputStream` in its destructor, but if you care whether that call
+// succeeds or fails, then you should call the `flush` method yourself. Errors
+// will be logged, however.
+class MOZ_STACK_CLASS ZeroCopyNSIOutputStream
+ : public ::google::protobuf::io::ZeroCopyOutputStream {
+ static const int BUFFER_SIZE = 8192;
+
+ // The nsIOutputStream we are streaming to.
+ nsCOMPtr<nsIOutputStream>& out;
+
+ // The buffer we write data to before passing it to the output stream.
+ char buffer[BUFFER_SIZE];
+
+ // The status of writing to the underlying output stream.
+ nsresult result_;
+
+ // The number of bytes in the buffer that have been used thus far.
+ int amountUsed;
+
+ // Excluding the amount of the buffer currently used (which hasn't been
+ // written and flushed yet), this is the number of bytes written to the output
+ // stream.
+ int64_t writtenCount;
+
+ // Write the internal buffer to the output stream and flush it.
+ nsresult writeBuffer();
+
+ public:
+ explicit ZeroCopyNSIOutputStream(nsCOMPtr<nsIOutputStream>& out);
+
+ nsresult flush() { return writeBuffer(); }
+
+ // Return true if writing to the underlying output stream ever failed.
+ bool failed() const { return NS_FAILED(result_); }
+
+ nsresult result() const { return result_; }
+
+ // ZeroCopyOutputStream Interface
+ virtual ~ZeroCopyNSIOutputStream() override;
+ virtual bool Next(void** data, int* size) override;
+ virtual void BackUp(int count) override;
+ virtual ::google::protobuf::int64 ByteCount() const override;
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_ZeroCopyNSIOutputStream__
diff --git a/devtools/shared/heapsnapshot/census-tree-node.js b/devtools/shared/heapsnapshot/census-tree-node.js
new file mode 100644
index 0000000000..82f5929370
--- /dev/null
+++ b/devtools/shared/heapsnapshot/census-tree-node.js
@@ -0,0 +1,764 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// CensusTreeNode is an intermediate representation of a census report that
+// exists between after a report is generated by taking a census and before the
+// report is rendered in the DOM. It must be dead simple to render, with no
+// further data processing or massaging needed before rendering DOM nodes. Our
+// goal is to do the census report to CensusTreeNode transformation in the
+// HeapAnalysesWorker, and ensure that the **only** work that the main thread
+// has to do is strictly DOM rendering work.
+
+const {
+ Visitor,
+ walk,
+ basisTotalBytes,
+ basisTotalCount,
+} = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+
+// Monotonically increasing integer for CensusTreeNode `id`s.
+let censusTreeNodeIdCounter = 0;
+
+/**
+ * Return true if the given object is a SavedFrame stack object, false otherwise.
+ *
+ * @param {any} obj
+ * @returns {Boolean}
+ */
+function isSavedFrame(obj) {
+ return Object.prototype.toString.call(obj) === "[object SavedFrame]";
+}
+
+/**
+ * A CensusTreeNodeCache maps from SavedFrames to CensusTreeNodes. It is used when
+ * aggregating multiple SavedFrame allocation stack keys into a tree of many
+ * CensusTreeNodes. Each stack may share older frames, and we want to preserve
+ * 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;
+
+/**
+ * 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.
+ */
+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
+ */
+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
+ */
+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}
+ */
+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}
+ */
+CensusTreeNodeCache.lookupNode = function (cache, node) {
+ return isSavedFrame(node.name)
+ ? CensusTreeNodeCache.lookupFrame(cache, node.name)
+ : cache[CensusTreeNodeCache.hashNode(node)];
+};
+
+/**
+ * Add `child` to `parent`'s set of children and store the parent ID
+ * on the child.
+ *
+ * @param {CensusTreeNode} parent
+ * @param {CensusTreeNode} child
+ */
+function addChild(parent, child) {
+ if (!parent.children) {
+ parent.children = [];
+ }
+ child.parent = parent.id;
+ parent.children.push(child);
+}
+
+/**
+ * Get an array of each frame in the provided stack.
+ *
+ * @param {SavedFrame} stack
+ * @returns {Array<SavedFrame>}
+ */
+function getArrayOfFrames(stack) {
+ const frames = [];
+ let frame = stack;
+ while (frame) {
+ frames.push(frame);
+ frame = frame.parent;
+ }
+ frames.reverse();
+ return frames;
+}
+
+/**
+ * Given an `edge` to a sub-`report` whose structure is described by
+ * `breakdown`, create a CensusTreeNode tree.
+ *
+ * @param {Object} breakdown
+ * The breakdown specifying the structure of the given report.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {null|String|SavedFrame} edge
+ * The edge leading to this report from the parent report.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * The cache of CensusTreeNodes we have already made for the siblings of
+ * the node being created. The existing nodes are reused when possible.
+ *
+ * @param {Object} outParams
+ * The return values are attached to this object after this function
+ * returns. Because we create a CensusTreeNode for each frame in a
+ * SavedFrame stack edge, there may multiple nodes per sub-report.
+ *
+ * - top: The deepest node in the CensusTreeNode subtree created.
+ *
+ * - bottom: The shallowest node in the CensusTreeNode subtree created.
+ * This is null if the shallowest node in the subtree was
+ * found in the `cache` and reused.
+ *
+ * Note that top and bottom are not necessarily different. In the case
+ * where there is a 1:1 correspondence between an edge in the report and
+ * a CensusTreeNode, top and bottom refer to the same node.
+ */
+function makeCensusTreeNodeSubTree(breakdown, report, edge, cache, outParams) {
+ if (!isSavedFrame(edge)) {
+ const node = new CensusTreeNode(edge);
+ outParams.top = outParams.bottom = node;
+ return;
+ }
+
+ const frames = getArrayOfFrames(edge);
+ let currentCache = cache;
+ let prevNode;
+ for (let i = 0, length = frames.length; i < length; i++) {
+ const frame = frames[i];
+
+ // Get or create the CensusTreeNodeCacheValue for this frame. If we already
+ // have a CensusTreeNodeCacheValue (and hence a CensusTreeNode) for this
+ // frame, we don't need to add the node to the previous node's children as
+ // we have already done that. If we don't have a CensusTreeNodeCacheValue
+ // and CensusTreeNode for this frame, then create one and make sure to hook
+ // it up as a child of the previous node.
+ let isNewNode = false;
+ let val = CensusTreeNodeCache.lookupFrame(currentCache, frame);
+ if (!val) {
+ isNewNode = true;
+ val = new CensusTreeNodeCacheValue();
+ val.node = new CensusTreeNode(frame);
+
+ CensusTreeNodeCache.insertFrame(currentCache, val);
+ if (prevNode) {
+ addChild(prevNode, val.node);
+ }
+ }
+
+ if (i === 0) {
+ outParams.bottom = isNewNode ? val.node : null;
+ }
+ if (i === length - 1) {
+ outParams.top = val.node;
+ }
+
+ prevNode = val.node;
+
+ if (i !== length - 1 && !val.children) {
+ // This is not the last frame and therefore this node will have
+ // children, which we must cache.
+ val.children = new CensusTreeNodeCache();
+ }
+
+ currentCache = val.children;
+ }
+}
+
+/**
+ * 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);
+ }
+
+ this._cacheStack.push(new CensusTreeNodeCache());
+ this._nodeStack.push(top);
+};
+
+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
+ );
+}
+
+/**
+ * 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 (breakdown, report, edge) {
+ // 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;
+
+ if (node.children) {
+ // Prune empty leaves.
+ node.children = node.children.filter(isNonEmpty);
+
+ 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;
+ }
+ }
+ }
+
+ const top = this._nodeStack.pop();
+ const cache = this._cacheStack.pop();
+ dfs(top, cache);
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+CensusTreeNodeVisitor.prototype.count = function (breakdown, report, edge) {
+ const node = this._nodeStack[this._nodeStack.length - 1];
+ node.reportLeafIndex = this._index;
+
+ if (breakdown.count) {
+ node.count = report.count;
+ }
+
+ if (breakdown.bytes) {
+ node.bytes = report.bytes;
+ }
+};
+
+/**
+ * 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!");
+ }
+
+ return this._root;
+};
+
+/**
+ * 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;
+}
+
+CensusTreeNode.prototype = null;
+
+/**
+ * Compare the given nodes by their `totalBytes` properties, and breaking ties
+ * with the `totalCount`, `bytes`, and `count` properties (in that order).
+ *
+ * @param {CensusTreeNode} node1
+ * @param {CensusTreeNode} node2
+ *
+ * @returns {Number}
+ * A number suitable for using with Array.prototype.sort.
+ */
+function compareByTotal(node1, node2) {
+ return (
+ Math.abs(node2.totalBytes) - Math.abs(node1.totalBytes) ||
+ Math.abs(node2.totalCount) - Math.abs(node1.totalCount) ||
+ Math.abs(node2.bytes) - Math.abs(node1.bytes) ||
+ Math.abs(node2.count) - Math.abs(node1.count)
+ );
+}
+
+/**
+ * Compare the given nodes by their `bytes` properties, and breaking ties with
+ * the `count`, `totalBytes`, and `totalCount` properties (in that order).
+ *
+ * @param {CensusTreeNode} node1
+ * @param {CensusTreeNode} node2
+ *
+ * @returns {Number}
+ * A number suitable for using with Array.prototype.sort.
+ */
+function compareBySelf(node1, node2) {
+ return (
+ Math.abs(node2.bytes) - Math.abs(node1.bytes) ||
+ Math.abs(node2.count) - Math.abs(node1.count) ||
+ Math.abs(node2.totalBytes) - Math.abs(node1.totalBytes) ||
+ Math.abs(node2.totalCount) - Math.abs(node1.totalCount)
+ );
+}
+
+/**
+ * Given a parent cache value from a tree we are building and a child node from
+ * a tree we are basing the new tree off of, if we already have a corresponding
+ * node in the parent's children cache, merge this node's counts with
+ * it. Otherwise, create the corresponding node, add it to the parent's children
+ * cache, and create the parent->child edge.
+ *
+ * @param {CensusTreeNodeCacheValue} parentCachevalue
+ * @param {CensusTreeNode} node
+ *
+ * @returns {CensusTreeNodeCacheValue}
+ * The new or extant child node's corresponding cache value.
+ */
+function insertOrMergeNode(parentCacheValue, node) {
+ if (!parentCacheValue.children) {
+ parentCacheValue.children = new CensusTreeNodeCache();
+ }
+
+ let val = CensusTreeNodeCache.lookupNode(parentCacheValue.children, node);
+
+ if (val) {
+ // When inverting, it is possible that multiple leaves in the census report
+ // get merged into a single CensusTreeNode node. When this occurs, switch
+ // from a single index to a set of indices.
+ if (
+ val.node.reportLeafIndex !== undefined &&
+ val.node.reportLeafIndex !== node.reportLeafIndex
+ ) {
+ if (typeof val.node.reportLeafIndex === "number") {
+ const oldIndex = val.node.reportLeafIndex;
+ val.node.reportLeafIndex = new Set();
+ val.node.reportLeafIndex.add(oldIndex);
+ val.node.reportLeafIndex.add(node.reportLeafIndex);
+ } else {
+ val.node.reportLeafIndex.add(node.reportLeafIndex);
+ }
+ }
+
+ val.node.count += node.count;
+ val.node.bytes += node.bytes;
+ } else {
+ val = new CensusTreeNodeCacheValue();
+
+ val.node = new CensusTreeNode(node.name);
+ val.node.reportLeafIndex = node.reportLeafIndex;
+ val.node.count = node.count;
+ val.node.totalCount = node.totalCount;
+ val.node.bytes = node.bytes;
+ val.node.totalBytes = node.totalBytes;
+
+ addChild(parentCacheValue.node, val.node);
+ CensusTreeNodeCache.insertNode(parentCacheValue.children, val);
+ }
+
+ return val;
+}
+
+/**
+ * Given an un-inverted CensusTreeNode tree, return the corresponding inverted
+ * CensusTreeNode tree. The input tree is not modified. The resulting inverted
+ * tree is sorted by self bytes rather than by total bytes.
+ *
+ * @param {CensusTreeNode} tree
+ * The un-inverted tree.
+ *
+ * @returns {CensusTreeNode}
+ * The corresponding inverted tree.
+ */
+function invert(tree) {
+ const inverted = new CensusTreeNodeCacheValue();
+ inverted.node = new CensusTreeNode(null);
+
+ // Do a depth-first search of the un-inverted tree. As we reach each leaf,
+ // take the path from the old root to the leaf, reverse that path, and add it
+ // to the new, inverted tree's root.
+
+ const path = [];
+ (function addInvertedPaths(node) {
+ path.push(node);
+
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ addInvertedPaths(node.children[i]);
+ }
+ } else {
+ // We found a leaf node, add the reverse path to the inverted tree.
+ let currentCacheValue = inverted;
+ for (let i = path.length - 1; i >= 0; i--) {
+ currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
+ }
+ }
+
+ path.pop();
+ })(tree);
+
+ // Ensure that the root node always has the totals.
+ inverted.node.totalBytes = tree.totalBytes;
+ inverted.node.totalCount = tree.totalCount;
+
+ return inverted.node;
+}
+
+/**
+ * Given a CensusTreeNode tree and predicate function, create the tree
+ * containing only the nodes in any path `(node_0, node_1, ..., node_n-1)` in
+ * the given tree where `predicate(node_j)` is true for `0 <= j < n`, `node_0`
+ * is the given tree's root, and `node_n-1` is a leaf in the given tree. The
+ * given tree is left unmodified.
+ *
+ * @param {CensusTreeNode} tree
+ * @param {Function} predicate
+ *
+ * @returns {CensusTreeNode}
+ */
+function filter(tree, predicate) {
+ const filtered = new CensusTreeNodeCacheValue();
+ filtered.node = new CensusTreeNode(null);
+
+ // Do a DFS over the given tree. If the predicate returns true for any node,
+ // add that node and its whole subtree to the filtered tree.
+
+ const path = [];
+ let match = false;
+
+ function addMatchingNodes(node) {
+ path.push(node);
+
+ const oldMatch = match;
+ if (!match && predicate(node)) {
+ match = true;
+ }
+
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ addMatchingNodes(node.children[i]);
+ }
+ } else if (match) {
+ // We found a matching leaf node, add it to the filtered tree.
+ let currentCacheValue = filtered;
+ for (let i = 0, length = path.length; i < length; i++) {
+ currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
+ }
+ }
+
+ match = oldMatch;
+ path.pop();
+ }
+
+ if (tree.children) {
+ for (let i = 0, length = tree.children.length; i < length; i++) {
+ addMatchingNodes(tree.children[i]);
+ }
+ }
+
+ filtered.node.count = tree.count;
+ filtered.node.totalCount = tree.totalCount;
+ filtered.node.bytes = tree.bytes;
+ filtered.node.totalBytes = tree.totalBytes;
+
+ return filtered.node;
+}
+
+/**
+ * Given a filter string, return a predicate function that takes a node and
+ * returns true iff the node matches the filter.
+ *
+ * @param {String} filterString
+ * @returns {Function}
+ */
+function makeFilterPredicate(filterString) {
+ return function (node) {
+ if (!node.name) {
+ return false;
+ }
+
+ if (isSavedFrame(node.name)) {
+ return (
+ node.name.source.includes(filterString) ||
+ (node.name.functionDisplayName || "").includes(filterString) ||
+ (node.name.asyncCause || "").includes(filterString)
+ );
+ }
+
+ return String(node.name).includes(filterString);
+ };
+}
+
+/**
+ * Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
+ * used to generate the census and returns a structure used to render
+ * a tree to display the data.
+ *
+ * Returns a recursive "CensusTreeNode" object, looking like:
+ *
+ * CensusTreeNode = {
+ * // `children` if it exists, is sorted by `bytes`, if they are leaf nodes.
+ * children: ?[<CensusTreeNode...>],
+ * name: <?String>
+ * count: <?Number>
+ * bytes: <?Number>
+ * id: <?Number>
+ * parent: <?Number>
+ * }
+ *
+ * @param {Object} breakdown
+ * The breakdown used to generate the census report.
+ *
+ * @param {Object} report
+ * The census report generated with the specified breakdown.
+ *
+ * @param {Object} options
+ * Configuration options.
+ * - invert: Whether to invert the resulting tree or not. Defaults to
+ * false, ie uninverted.
+ *
+ * @returns {CensusTreeNode}
+ */
+exports.censusReportToCensusTreeNode = function (
+ breakdown,
+ report,
+ options = {
+ invert: false,
+ filter: null,
+ }
+) {
+ // Reset the counter so that turning the same census report into a
+ // CensusTreeNode tree repeatedly is idempotent.
+ censusTreeNodeIdCounter = 0;
+
+ const visitor = new CensusTreeNodeVisitor();
+ walk(breakdown, report, visitor);
+ let result = visitor.root();
+
+ if (options.invert) {
+ result = invert(result);
+ }
+
+ if (typeof options.filter === "string") {
+ result = filter(result, makeFilterPredicate(options.filter));
+ }
+
+ // If the report is a delta report that was generated by diffing two other
+ // reports, make sure to use the basis totals rather than the totals of the
+ // difference.
+ if (typeof report[basisTotalBytes] === "number") {
+ result.totalBytes = report[basisTotalBytes];
+ result.totalCount = report[basisTotalCount];
+ }
+
+ // Inverting and filtering could have messed up the sort order, so do a
+ // depth-first search of the tree and ensure that siblings are sorted.
+ const comparator = options.invert ? compareBySelf : compareByTotal;
+ (function ensureSorted(node) {
+ if (node.children) {
+ node.children.sort(comparator);
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ ensureSorted(node.children[i]);
+ }
+ }
+ })(result);
+
+ return result;
+};
diff --git a/devtools/shared/heapsnapshot/generate-core-dump-sources.sh b/devtools/shared/heapsnapshot/generate-core-dump-sources.sh
new file mode 100755
index 0000000000..97e492ff05
--- /dev/null
+++ b/devtools/shared/heapsnapshot/generate-core-dump-sources.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+# A script to generate devtools/server/CoreDump.pb.{h,cc} from
+# devtools/server/CoreDump.proto. This script assumes you have
+# downloaded and installed the protocol buffer compiler, and that it is either
+# on your $PATH or located at $PROTOC_PATH.
+#
+# These files were last compiled with libprotoc 2.4.1.
+
+set -e
+
+cd $(dirname $0)
+
+if [ -n $PROTOC_PATH ]; then
+ PROTOC_PATH=`which protoc`
+fi
+
+if [ ! -e $PROTOC_PATH ]; then
+ echo You must install the protocol compiler from
+ echo https://code.google.com/p/protobuf/downloads/list
+ exit 1
+fi
+
+echo Using $PROTOC_PATH as the protocol compiler
+
+$PROTOC_PATH --cpp_out="." CoreDump.proto
diff --git a/devtools/shared/heapsnapshot/moz.build b/devtools/shared/heapsnapshot/moz.build
new file mode 100644
index 0000000000..41bc0fb4aa
--- /dev/null
+++ b/devtools/shared/heapsnapshot/moz.build
@@ -0,0 +1,59 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("DevTools", "Memory")
+
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += ["tests/gtest"]
+
+XPCSHELL_TESTS_MANIFESTS += ["tests/xpcshell/xpcshell.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
+BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"]
+
+EXPORTS.mozilla.devtools += [
+ "AutoMemMap.h",
+ "CoreDump.pb.h",
+ "DeserializedNode.h",
+ "DominatorTree.h",
+ "FileDescriptorOutputStream.h",
+ "HeapSnapshot.h",
+ "HeapSnapshotTempFileHelperChild.h",
+ "HeapSnapshotTempFileHelperParent.h",
+ "ZeroCopyNSIOutputStream.h",
+]
+
+IPDL_SOURCES += [
+ "PHeapSnapshotTempFileHelper.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+SOURCES += [
+ "AutoMemMap.cpp",
+ "CoreDump.pb.cc",
+ "DeserializedNode.cpp",
+ "DominatorTree.cpp",
+ "FileDescriptorOutputStream.cpp",
+ "HeapSnapshot.cpp",
+ "HeapSnapshotTempFileHelperParent.cpp",
+ "ZeroCopyNSIOutputStream.cpp",
+]
+
+# Disable RTTI in google protocol buffer
+DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
+
+FINAL_LIBRARY = "xul"
+
+DevToolsModules(
+ "census-tree-node.js",
+ "CensusUtils.js",
+ "DominatorTreeNode.js",
+ "HeapAnalyses.worker.js",
+ "HeapAnalysesClient.js",
+ "HeapSnapshotFileUtils.js",
+ "shortest-paths.js",
+)
diff --git a/devtools/shared/heapsnapshot/shortest-paths.js b/devtools/shared/heapsnapshot/shortest-paths.js
new file mode 100644
index 0000000000..eca1ab9735
--- /dev/null
+++ b/devtools/shared/heapsnapshot/shortest-paths.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * Compress a set of paths leading to `target` into a single graph, returned as
+ * a set of nodes and a set of edges.
+ *
+ * @param {NodeId} target
+ * The target node passed to `HeapSnapshot.computeShortestPaths`.
+ *
+ * @param {Array<Path>} paths
+ * An array of paths to `target`, as returned by
+ * `HeapSnapshot.computeShortestPaths`.
+ *
+ * @returns {Object}
+ * An object with two properties:
+ * - edges: An array of unique objects of the form:
+ * {
+ * from: <node ID>,
+ * to: <node ID>,
+ * name: <string or null>
+ * }
+ * - nodes: An array of unique node IDs. Every `from` and `to` id is
+ * guaranteed to be in this array exactly once.
+ */
+exports.deduplicatePaths = function (target, paths) {
+ // Use this structure to de-duplicate edges among many retaining paths from
+ // start to target.
+ //
+ // Map<FromNodeId, Map<ToNodeId, Set<EdgeName>>>
+ const deduped = new Map();
+
+ function insert(from, to, name) {
+ let toMap = deduped.get(from);
+ if (!toMap) {
+ toMap = new Map();
+ deduped.set(from, toMap);
+ }
+
+ let nameSet = toMap.get(to);
+ if (!nameSet) {
+ nameSet = new Set();
+ toMap.set(to, nameSet);
+ }
+
+ nameSet.add(name);
+ }
+
+ // eslint-disable-next-line no-labels
+ outer: for (const path of paths) {
+ const pathLength = path.length;
+
+ // Check for duplicate predecessors in the path, and skip paths that contain
+ // them.
+ const predecessorsSeen = new Set();
+ predecessorsSeen.add(target);
+ for (let i = 0; i < pathLength; i++) {
+ if (predecessorsSeen.has(path[i].predecessor)) {
+ // eslint-disable-next-line no-labels
+ continue outer;
+ }
+ predecessorsSeen.add(path[i].predecessor);
+ }
+
+ for (let i = 0; i < pathLength - 1; i++) {
+ insert(path[i].predecessor, path[i + 1].predecessor, path[i].edge);
+ }
+
+ insert(path[pathLength - 1].predecessor, target, path[pathLength - 1].edge);
+ }
+
+ const nodes = [target];
+ const edges = [];
+
+ for (const [from, toMap] of deduped) {
+ // If the second/third/etc shortest path contains the `target` anywhere
+ // other than the very last node, we could accidentally put the `target` in
+ // `nodes` more than once.
+ if (from !== target) {
+ nodes.push(from);
+ }
+
+ for (const [to, edgeNameSet] of toMap) {
+ for (const name of edgeNameSet) {
+ edges.push({ from, to, name });
+ }
+ }
+ }
+
+ return { nodes, edges };
+};
diff --git a/devtools/shared/heapsnapshot/tests/browser/browser.toml b/devtools/shared/heapsnapshot/tests/browser/browser.toml
new file mode 100644
index 0000000000..dc0042b957
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/browser/browser.toml
@@ -0,0 +1,9 @@
+[DEFAULT]
+tags = "devtools"
+subsuite = "devtools"
+support-files = [
+ "!/devtools/client/shared/test/shared-head.js",
+ "!/devtools/client/shared/test/telemetry-test-helpers.js",
+]
+
+["browser_saveHeapSnapshot_e10s_01.js"]
diff --git a/devtools/shared/heapsnapshot/tests/browser/browser_saveHeapSnapshot_e10s_01.js b/devtools/shared/heapsnapshot/tests/browser/browser_saveHeapSnapshot_e10s_01.js
new file mode 100644
index 0000000000..1fc25341b8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/browser/browser_saveHeapSnapshot_e10s_01.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1201597 - Test to verify that we can take a heap snapshot in an e10s child process.
+ */
+
+"use strict";
+
+add_task(async function () {
+ // Create a minimal browser
+ const browser = document.createXULElement("browser");
+ browser.setAttribute("type", "content");
+ document.body.appendChild(browser);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ info("Save heap snapshot");
+ const result = await SpecialPowers.spawn(browser, [], () => {
+ try {
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ } catch (err) {
+ return err.toString();
+ }
+
+ return "";
+ });
+ is(result, "", "result of saveHeapSnapshot");
+
+ browser.remove();
+});
diff --git a/devtools/shared/heapsnapshot/tests/chrome/chrome.toml b/devtools/shared/heapsnapshot/tests/chrome/chrome.toml
new file mode 100644
index 0000000000..afe21d17fa
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/chrome/chrome.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = "devtools devtools-memory"
+skip-if = ["os == 'android'"]
+
+["test_DominatorTree_01.html"]
+
+["test_SaveHeapSnapshot.html"]
diff --git a/devtools/shared/heapsnapshot/tests/chrome/test_DominatorTree_01.html b/devtools/shared/heapsnapshot/tests/chrome/test_DominatorTree_01.html
new file mode 100644
index 0000000000..61e60ae209
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/chrome/test_DominatorTree_01.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Sanity test that we can compute dominator trees from a heap snapshot in a web window.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>ChromeUtils.saveHeapSnapshot test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+/* global window, ChromeUtils, DominatorTree */
+
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(DominatorTree.isInstance(dominatorTree));
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/shared/heapsnapshot/tests/chrome/test_SaveHeapSnapshot.html b/devtools/shared/heapsnapshot/tests/chrome/test_SaveHeapSnapshot.html
new file mode 100644
index 0000000000..53831bfaa2
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/chrome/test_SaveHeapSnapshot.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1024774 - Sanity test that we can take a heap snapshot in a web window.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>ChromeUtils.saveHeapSnapshot test</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ ok(ChromeUtils, "The ChromeUtils interface should be exposed in chrome windows.");
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should save a heap snapshot and shouldn't throw.");
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
new file mode 100644
index 0000000000..dc24d13e98
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that the `JS::ubi::Node`s we create from
+// `mozilla::devtools::DeserializedNode` instances look and behave as we would
+// like.
+
+#include "DevTools.h"
+#include "js/TypeDecls.h"
+#include "mozilla/devtools/DeserializedNode.h"
+
+using testing::Field;
+using testing::ReturnRef;
+
+// A mock DeserializedNode for testing.
+struct MockDeserializedNode : public DeserializedNode {
+ MockDeserializedNode(NodeId id, const char16_t* typeName, uint64_t size)
+ : DeserializedNode(id, typeName, size) {}
+
+ bool addEdge(DeserializedEdge&& edge) {
+ return edges.append(std::move(edge));
+ }
+
+ MOCK_METHOD1(getEdgeReferent, JS::ubi::Node(const DeserializedEdge&));
+};
+
+size_t fakeMallocSizeOf(const void*) {
+ EXPECT_TRUE(false);
+ MOZ_ASSERT_UNREACHABLE(
+ "fakeMallocSizeOf should never be called because "
+ "DeserializedNodes report the deserialized size.");
+ return 0;
+}
+
+DEF_TEST(DeserializedNodeUbiNodes, {
+ const char16_t* typeName = u"TestTypeName";
+ const char* className = "MyObjectClassName";
+ const char* filename = "my-cool-filename.js";
+
+ NodeId id = uint64_t(1) << 33;
+ uint64_t size = uint64_t(1) << 60;
+ MockDeserializedNode mocked(id, typeName, size);
+ mocked.coarseType = JS::ubi::CoarseType::Script;
+ mocked.jsObjectClassName = className;
+ mocked.scriptFilename = filename;
+
+ DeserializedNode& deserialized = mocked;
+ JS::ubi::Node ubi(&deserialized);
+
+ // Test the ubi::Node accessors.
+
+ EXPECT_EQ(size, ubi.size(fakeMallocSizeOf));
+ EXPECT_EQ(typeName, ubi.typeName());
+ EXPECT_EQ(JS::ubi::CoarseType::Script, ubi.coarseType());
+ EXPECT_EQ(id, ubi.identifier());
+ EXPECT_FALSE(ubi.isLive());
+ EXPECT_EQ(ubi.jsObjectClassName(), className);
+ EXPECT_EQ(ubi.scriptFilename(), filename);
+
+ // Test the ubi::Node's edges.
+
+ UniquePtr<DeserializedNode> referent1(
+ new MockDeserializedNode(1, nullptr, 10));
+ DeserializedEdge edge1(referent1->id);
+ mocked.addEdge(std::move(edge1));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent1.get())));
+
+ UniquePtr<DeserializedNode> referent2(
+ new MockDeserializedNode(2, nullptr, 20));
+ DeserializedEdge edge2(referent2->id);
+ mocked.addEdge(std::move(edge2));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent2.get())));
+
+ UniquePtr<DeserializedNode> referent3(
+ new MockDeserializedNode(3, nullptr, 30));
+ DeserializedEdge edge3(referent3->id);
+ mocked.addEdge(std::move(edge3));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent3.get())));
+
+ auto range = ubi.edges(cx);
+ ASSERT_TRUE(!!range);
+
+ for (; !range->empty(); range->popFront()) {
+ // Nothing to do here. This loop ensures that we get each edge referent
+ // that we expect above.
+ }
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp
new file mode 100644
index 0000000000..4ce2c8968f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp
@@ -0,0 +1,98 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that the `JS::ubi::StackFrame`s we create from
+// `mozilla::devtools::DeserializedStackFrame` instances look and behave as we
+// would like.
+
+#include "DevTools.h"
+#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::TaggedColumnNumberOneOrigin
+#include "js/SavedFrameAPI.h"
+#include "js/TypeDecls.h"
+#include "mozilla/devtools/DeserializedNode.h"
+
+using testing::Field;
+using testing::ReturnRef;
+
+// A mock DeserializedStackFrame for testing.
+struct MockDeserializedStackFrame : public DeserializedStackFrame {
+ MockDeserializedStackFrame() = default;
+};
+
+DEF_TEST(DeserializedStackFrameUbiStackFrames, {
+ StackFrameId id = uint64_t(1) << 42;
+ uint32_t line = 1337;
+ JS::TaggedColumnNumberOneOrigin column(
+ JS::LimitedColumnNumberOneOrigin(9)); // 3 space tabs!?
+ const char16_t* source = u"my-javascript-file.js";
+ const char16_t* functionDisplayName = u"myFunctionName";
+
+ MockDeserializedStackFrame mocked;
+ mocked.id = id;
+ mocked.line = line;
+ mocked.column = column;
+ mocked.source = source;
+ mocked.functionDisplayName = functionDisplayName;
+
+ DeserializedStackFrame& deserialized = mocked;
+ JS::ubi::StackFrame ubiFrame(&deserialized);
+
+ // Test the JS::ubi::StackFrame accessors.
+
+ EXPECT_EQ(id, ubiFrame.identifier());
+ EXPECT_EQ(JS::ubi::StackFrame(), ubiFrame.parent());
+ EXPECT_EQ(line, ubiFrame.line());
+ EXPECT_EQ(column, ubiFrame.column());
+ EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(source), ubiFrame.source());
+ EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(functionDisplayName),
+ ubiFrame.functionDisplayName());
+ EXPECT_FALSE(ubiFrame.isSelfHosted(cx));
+ EXPECT_FALSE(ubiFrame.isSystem());
+
+ JS::Rooted<JSObject*> savedFrame(cx);
+ EXPECT_TRUE(ubiFrame.constructSavedFrameStack(cx, &savedFrame));
+
+ JSPrincipals* principals = JS::GetRealmPrincipals(js::GetContextRealm(cx));
+
+ uint32_t frameLine;
+ ASSERT_EQ(JS::SavedFrameResult::Ok,
+ JS::GetSavedFrameLine(cx, principals, savedFrame, &frameLine));
+ EXPECT_EQ(line, frameLine);
+
+ JS::TaggedColumnNumberOneOrigin frameColumn;
+ ASSERT_EQ(JS::SavedFrameResult::Ok,
+ JS::GetSavedFrameColumn(cx, principals, savedFrame, &frameColumn));
+ EXPECT_EQ(column, frameColumn);
+
+ JS::Rooted<JSObject*> parent(cx);
+ ASSERT_EQ(JS::SavedFrameResult::Ok,
+ JS::GetSavedFrameParent(cx, principals, savedFrame, &parent));
+ EXPECT_EQ(nullptr, parent);
+
+ ASSERT_EQ(NS_strlen(source), 21U);
+ char16_t sourceBuf[21] = {};
+
+ // Test when the length is shorter than the string length.
+ auto written = ubiFrame.source(RangedPtr<char16_t>(sourceBuf), 3);
+ EXPECT_EQ(written, 3U);
+ for (size_t i = 0; i < 3; i++) {
+ EXPECT_EQ(sourceBuf[i], source[i]);
+ }
+
+ written = ubiFrame.source(RangedPtr<char16_t>(sourceBuf), 21);
+ EXPECT_EQ(written, 21U);
+ for (size_t i = 0; i < 21; i++) {
+ EXPECT_EQ(sourceBuf[i], source[i]);
+ }
+
+ ASSERT_EQ(NS_strlen(functionDisplayName), 14U);
+ char16_t nameBuf[14] = {};
+
+ written = ubiFrame.functionDisplayName(RangedPtr<char16_t>(nameBuf), 14);
+ EXPECT_EQ(written, 14U);
+ for (size_t i = 0; i < 14; i++) {
+ EXPECT_EQ(nameBuf[i], functionDisplayName[i]);
+ }
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp b/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp
new file mode 100644
index 0000000000..8e89d5ecd7
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.cpp
@@ -0,0 +1,7 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DevTools.h"
+const char16_t JS::ubi::Concrete<FakeNode>::concreteTypeName[] = u"FakeNode";
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
new file mode 100644
index 0000000000..a3c29e87fc
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -0,0 +1,217 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_gtest_DevTools__
+#define mozilla_devtools_gtest_DevTools__
+
+#include <utility>
+
+#include "CoreDump.pb.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "js/Principals.h"
+#include "js/UbiNode.h"
+#include "js/UniquePtr.h"
+#include "jsapi.h"
+#include "jspubtd.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "nsCRTGlue.h"
+
+using namespace mozilla;
+using namespace mozilla::devtools;
+using namespace mozilla::dom;
+using namespace testing;
+
+// GTest fixture class that all of our tests derive from.
+struct DevTools : public ::testing::Test {
+ bool _initialized;
+ JSContext* cx;
+ JS::Compartment* compartment;
+ JS::Zone* zone;
+ JS::PersistentRooted<JSObject*> global;
+
+ DevTools() : _initialized(false), cx(nullptr) {}
+
+ virtual void SetUp() {
+ MOZ_ASSERT(!_initialized);
+
+ cx = getContext();
+ if (!cx) return;
+
+ global.init(cx, createGlobal());
+ if (!global) return;
+ JS::EnterRealm(cx, global);
+
+ compartment = js::GetContextCompartment(cx);
+ zone = js::GetContextZone(cx);
+
+ _initialized = true;
+ }
+
+ JSContext* getContext() { return CycleCollectedJSContext::Get()->Context(); }
+
+ static void reportError(JSContext* cx, const char* message,
+ JSErrorReport* report) {
+ fprintf(stderr, "%s:%u:%s\n",
+ report->filename ? report->filename.c_str() : "<no filename>",
+ (unsigned int)report->lineno, message);
+ }
+
+ static const JSClass* getGlobalClass() {
+ static const JSClass globalClass = {"global", JSCLASS_GLOBAL_FLAGS,
+ &JS::DefaultGlobalClassOps};
+ return &globalClass;
+ }
+
+ JSObject* createGlobal() {
+ /* Create the global object. */
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ return JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options);
+ }
+
+ virtual void TearDown() {
+ _initialized = false;
+
+ if (global) {
+ JS::LeaveRealm(cx, nullptr);
+ global = nullptr;
+ }
+ }
+};
+
+// Helper to define a test and ensure that the fixture is initialized properly.
+#define DEF_TEST(name, body) \
+ TEST_F(DevTools, name) { \
+ ASSERT_TRUE(_initialized); \
+ body \
+ }
+
+// Fake JS::ubi::Node implementation
+class MOZ_STACK_CLASS FakeNode {
+ public:
+ JS::ubi::EdgeVector edges;
+ JS::Compartment* compartment;
+ JS::Zone* zone;
+ size_t size;
+
+ explicit FakeNode() : compartment(nullptr), zone(nullptr), size(1) {}
+};
+
+namespace JS {
+namespace ubi {
+
+template <>
+class Concrete<FakeNode> : public Base {
+ const char16_t* typeName() const override { return concreteTypeName; }
+
+ js::UniquePtr<EdgeRange> edges(JSContext*, bool) const override {
+ return js::UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
+ }
+
+ Size size(mozilla::MallocSizeOf) const override { return get().size; }
+
+ JS::Zone* zone() const override { return get().zone; }
+
+ JS::Compartment* compartment() const override { return get().compartment; }
+
+ protected:
+ explicit Concrete(FakeNode* ptr) : Base(ptr) {}
+ FakeNode& get() const { return *static_cast<FakeNode*>(ptr); }
+
+ public:
+ static const char16_t concreteTypeName[];
+ static void construct(void* storage, FakeNode* ptr) {
+ new (storage) Concrete(ptr);
+ }
+};
+
+} // namespace ubi
+} // namespace JS
+
+inline void AddEdge(FakeNode& node, FakeNode& referent,
+ const char16_t* edgeName = nullptr) {
+ char16_t* ownedEdgeName = nullptr;
+ if (edgeName) {
+ ownedEdgeName = NS_xstrdup(edgeName);
+ }
+
+ JS::ubi::Edge edge(ownedEdgeName, &referent);
+ ASSERT_TRUE(node.edges.append(std::move(edge)));
+}
+
+// Custom GMock Matchers
+
+// Use the testing namespace to avoid static analysis failures in the gmock
+// matcher classes that get generated from MATCHER_P macros.
+namespace testing {
+
+// Ensure that given node has the expected number of edges.
+MATCHER_P2(EdgesLength, cx, expectedLength, "") {
+ auto edges = arg.edges(cx);
+ if (!edges) return false;
+
+ int actualLength = 0;
+ for (; !edges->empty(); edges->popFront()) actualLength++;
+
+ return Matcher<int>(Eq(expectedLength))
+ .MatchAndExplain(actualLength, result_listener);
+}
+
+// Get the nth edge and match it with the given matcher.
+MATCHER_P3(Edge, cx, n, matcher, "") {
+ auto edges = arg.edges(cx);
+ if (!edges) return false;
+
+ int i = 0;
+ for (; !edges->empty(); edges->popFront()) {
+ if (i == n) {
+ return Matcher<const JS::ubi::Edge&>(matcher).MatchAndExplain(
+ edges->front(), result_listener);
+ }
+
+ i++;
+ }
+
+ return false;
+}
+
+// Ensures that two char16_t* strings are equal.
+MATCHER_P(UTF16StrEq, str, "") { return NS_strcmp(arg, str) == 0; }
+
+MATCHER_P(UniqueUTF16StrEq, str, "") { return NS_strcmp(arg.get(), str) == 0; }
+
+MATCHER(UniqueIsNull, "") { return arg.get() == nullptr; }
+
+// Matches an edge whose referent is the node with the given id.
+MATCHER_P(EdgeTo, id, "") {
+ return Matcher<const DeserializedEdge&>(
+ Field(&DeserializedEdge::referent, id))
+ .MatchAndExplain(arg, result_listener);
+}
+
+} // namespace testing
+
+// A mock `Writer` class to be used with testing `WriteHeapGraph`.
+class MockWriter : public CoreDumpWriter {
+ public:
+ virtual ~MockWriter() override = default;
+ MOCK_METHOD2(writeNode,
+ bool(const JS::ubi::Node&, CoreDumpWriter::EdgePolicy));
+ MOCK_METHOD1(writeMetadata, bool(uint64_t));
+};
+
+inline void ExpectWriteNode(MockWriter& writer, FakeNode& node) {
+ EXPECT_CALL(writer, writeNode(Eq(JS::ubi::Node(&node)), _))
+ .Times(1)
+ .WillOnce(Return(true));
+}
+
+#endif // mozilla_devtools_gtest_DevTools__
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
new file mode 100644
index 0000000000..d264c73738
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that heap snapshots cross compartment boundaries when expected.
+
+#include "DevTools.h"
+
+DEF_TEST(DoesCrossCompartmentBoundaries, {
+ // Create a new global to get a new compartment.
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ JS::Rooted<JSObject*> newGlobal(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ ASSERT_TRUE(newGlobal);
+ JS::Compartment* newCompartment = nullptr;
+ {
+ JSAutoRealm ar(cx, newGlobal);
+ ASSERT_TRUE(JS::InitRealmStandardClasses(cx));
+ newCompartment = js::GetContextCompartment(cx);
+ }
+ ASSERT_TRUE(newCompartment);
+ ASSERT_NE(newCompartment, compartment);
+
+ // Our set of target compartments is both the old and new compartments.
+ JS::CompartmentSet targetCompartments;
+ ASSERT_TRUE(targetCompartments.put(compartment));
+ ASSERT_TRUE(targetCompartments.put(newCompartment));
+
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+ FakeNode nodeD;
+
+ nodeA.compartment = compartment;
+ nodeB.compartment = nullptr;
+ nodeC.compartment = newCompartment;
+ nodeD.compartment = nullptr;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeA, nodeC);
+ AddEdge(nodeB, nodeD);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize nodeA, because it is in one of our target compartments.
+ ExpectWriteNode(writer, nodeA);
+
+ // Should serialize nodeB, because it doesn't belong to a compartment and is
+ // therefore assumed to be shared.
+ ExpectWriteNode(writer, nodeB);
+
+ // Should also serialize nodeC, which is in our target compartments, but a
+ // different compartment than A.
+ ExpectWriteNode(writer, nodeC);
+
+ // Should serialize nodeD because it's reachable via B and both nodes B and D
+ // don't belong to a specific compartment.
+ ExpectWriteNode(writer, nodeD);
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer,
+ /* wantNames = */ false, &targetCompartments,
+ noGC));
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
new file mode 100644
index 0000000000..6b506aabff
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that heap snapshots walk the compartment boundaries correctly.
+
+#include "DevTools.h"
+
+DEF_TEST(DoesntCrossCompartmentBoundaries, {
+ // Create a new global to get a new compartment.
+ JS::RealmOptions options;
+ // dummy
+ options.behaviors().setReduceTimerPrecisionCallerType(
+ JS::RTPCallerTypeToken{0});
+ JS::Rooted<JSObject*> newGlobal(
+ cx, JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options));
+ ASSERT_TRUE(newGlobal);
+ JS::Compartment* newCompartment = nullptr;
+ {
+ JSAutoRealm ar(cx, newGlobal);
+ ASSERT_TRUE(JS::InitRealmStandardClasses(cx));
+ newCompartment = js::GetContextCompartment(cx);
+ }
+ ASSERT_TRUE(newCompartment);
+ ASSERT_NE(newCompartment, compartment);
+
+ // Our set of target compartments is only the pre-existing compartment and
+ // does not include the new compartment.
+ JS::CompartmentSet targetCompartments;
+ ASSERT_TRUE(targetCompartments.put(compartment));
+
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+
+ nodeA.compartment = compartment;
+ nodeB.compartment = nullptr;
+ nodeC.compartment = newCompartment;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeB, nodeC);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize nodeA, because it is in our target compartments.
+ ExpectWriteNode(writer, nodeA);
+
+ // Should serialize nodeB, because it doesn't belong to a compartment and is
+ // therefore assumed to be shared.
+ ExpectWriteNode(writer, nodeB);
+
+ // But we shouldn't ever serialize nodeC.
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer,
+ /* wantNames = */ false, &targetCompartments,
+ noGC));
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
new file mode 100644
index 0000000000..ab47941e39
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that edge names get serialized correctly.
+
+#include "DevTools.h"
+
+using testing::Field;
+using testing::IsNull;
+using testing::Property;
+using testing::Return;
+
+DEF_TEST(SerializesEdgeNames, {
+ FakeNode node;
+ FakeNode referent;
+
+ const char16_t edgeName[] = u"edge name";
+ const char16_t emptyStr[] = u"";
+
+ AddEdge(node, referent, edgeName);
+ AddEdge(node, referent, emptyStr);
+ AddEdge(node, referent, nullptr);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should get the node with edges once.
+ EXPECT_CALL(
+ writer,
+ writeNode(
+ AllOf(EdgesLength(cx, 3),
+ Edge(cx, 0,
+ Field(&JS::ubi::Edge::name, UniqueUTF16StrEq(edgeName))),
+ Edge(cx, 1,
+ Field(&JS::ubi::Edge::name, UniqueUTF16StrEq(emptyStr))),
+ Edge(cx, 2, Field(&JS::ubi::Edge::name, UniqueIsNull()))),
+ _))
+ .Times(1)
+ .WillOnce(Return(true));
+
+ // Should get the referent node that doesn't have any edges once.
+ ExpectWriteNode(writer, referent);
+
+ JS::AutoCheckCannotGC noGC(cx);
+ ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&node), writer,
+ /* wantNames = */ true,
+ /* zones = */ nullptr, noGC));
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
new file mode 100644
index 0000000000..d71c86703c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that everything in the heap graph gets serialized once, and only once.
+
+#include "DevTools.h"
+
+DEF_TEST(SerializesEverythingInHeapGraphOnce, {
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+ FakeNode nodeD;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeB, nodeC);
+ AddEdge(nodeC, nodeD);
+ AddEdge(nodeD, nodeA);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize each node once.
+ ExpectWriteNode(writer, nodeA);
+ ExpectWriteNode(writer, nodeB);
+ ExpectWriteNode(writer, nodeC);
+ ExpectWriteNode(writer, nodeD);
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&nodeA), writer,
+ /* wantNames = */ false,
+ /* zones = */ nullptr, noGC));
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
new file mode 100644
index 0000000000..4c29b28832
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that a ubi::Node's typeName gets properly serialized into a core dump.
+
+#include "DevTools.h"
+
+using testing::Property;
+using testing::Return;
+
+DEF_TEST(SerializesTypeNames, {
+ FakeNode node;
+
+ ::testing::NiceMock<MockWriter> writer;
+ EXPECT_CALL(
+ writer,
+ writeNode(Property(&JS::ubi::Node::typeName, UTF16StrEq(u"FakeNode")), _))
+ .Times(1)
+ .WillOnce(Return(true));
+
+ JS::AutoCheckCannotGC noGC(cx);
+ ASSERT_TRUE(WriteHeapGraph(cx, JS::ubi::Node(&node), writer,
+ /* wantNames = */ true,
+ /* zones = */ nullptr, noGC));
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/moz.build b/devtools/shared/heapsnapshot/tests/gtest/moz.build
new file mode 100644
index 0000000000..880d7b334e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("devtoolstests")
+
+LOCAL_INCLUDES += [
+ "../..",
+]
+
+DEFINES["GOOGLE_PROTOBUF_NO_RTTI"] = True
+DEFINES["GOOGLE_PROTOBUF_NO_STATIC_INITIALIZER"] = True
+
+UNIFIED_SOURCES = [
+ "DeserializedNodeUbiNodes.cpp",
+ "DeserializedStackFrameUbiStackFrames.cpp",
+ "DevTools.cpp",
+ "DoesCrossCompartmentBoundaries.cpp",
+ "DoesntCrossCompartmentBoundaries.cpp",
+ "SerializesEdgeNames.cpp",
+ "SerializesEverythingInHeapGraphOnce.cpp",
+ "SerializesTypeNames.cpp",
+]
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG["CC_TYPE"] == "clang":
+ CXXFLAGS += ["-Wno-inconsistent-missing-override"]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/.eslintrc.js b/devtools/shared/heapsnapshot/tests/xpcshell/.eslintrc.js
new file mode 100644
index 0000000000..8611c174f5
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ extends: "../../../../.eslintrc.xpcshell.js",
+};
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/Census.sys.mjs b/devtools/shared/heapsnapshot/tests/xpcshell/Census.sys.mjs
new file mode 100644
index 0000000000..0e86f8b055
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/Census.sys.mjs
@@ -0,0 +1,176 @@
+// Functions for checking results returned by
+// Debugger.Memory.prototype.takeCensus and
+// HeapSnapshot.prototype.takeCensus. Adapted from js/src/jit-test/lib/census.js.
+
+export const Census = {};
+function dumpn(msg) {
+ dump("DBG-TEST: Census.jsm: " + msg + "\n");
+}
+
+// Census.walkCensus(subject, name, walker)
+//
+// Use |walker| to check |subject|, a census object of the sort returned by
+// Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the
+// leaves. Use |name| as the name for |subject| in diagnostic messages. Return
+// the number of leaves of |subject| we visited.
+//
+// A walker is an object with three methods:
+//
+// - enter(prop): Return the walker we should use to check the property of the
+// subject census named |prop|. This is for recursing into the subobjects of
+// the subject.
+//
+// - done(): Called after we have called 'enter' on every property of the
+// subject.
+//
+// - check(value): Check |value|, a leaf in the subject.
+//
+// Walker methods are expected to simply throw if a node we visit doesn't look
+// right.
+Census.walkCensus = (subject, name, walker) => walk(subject, name, walker, 0);
+function walk(subject, name, walker, count) {
+ if (typeof subject === "object") {
+ dumpn(name);
+ for (const prop in subject) {
+ count = walk(
+ subject[prop],
+ name + "[" + uneval(prop) + "]",
+ walker.enter(prop),
+ count
+ );
+ }
+ walker.done();
+ } else {
+ dumpn(name + " = " + uneval(subject));
+ walker.check(subject);
+ count++;
+ }
+
+ return count;
+}
+
+// A walker that doesn't check anything.
+Census.walkAnything = {
+ enter: () => Census.walkAnything,
+ done: () => undefined,
+ check: () => undefined,
+};
+
+// A walker that requires all leaves to be zeros.
+Census.assertAllZeros = {
+ enter: () => Census.assertAllZeros,
+ done: () => undefined,
+ check: elt => {
+ if (elt !== 0) {
+ throw new Error("Census mismatch: expected zero, found " + elt);
+ }
+ },
+};
+
+function expectedObject() {
+ throw new Error(
+ "Census mismatch: subject has leaf where basis has nested object"
+ );
+}
+
+function expectedLeaf() {
+ throw new Error(
+ "Census mismatch: subject has nested object where basis has leaf"
+ );
+}
+
+// Return a function that, given a 'basis' census, returns a census walker that
+// compares the subject census against the basis. The returned walker calls the
+// given |compare|, |missing|, and |extra| functions as follows:
+//
+// - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the
+// corresponding leaf of the basis.
+//
+// - missing(prop, value): Called when the subject is missing a property named
+// |prop| which is present in the basis with value |value|.
+//
+// - extra(prop): Called when the subject has a property named |prop|, but the
+// basis has no such property. This should return a walker that can check
+// the subject's value.
+function makeBasisChecker({ compare, missing, extra }) {
+ return function makeWalker(basis) {
+ if (typeof basis === "object") {
+ const unvisited = new Set(Object.getOwnPropertyNames(basis));
+ return {
+ enter: prop => {
+ unvisited.delete(prop);
+ if (prop in basis) {
+ return makeWalker(basis[prop]);
+ }
+
+ return extra(prop);
+ },
+
+ done: () => unvisited.forEach(prop => missing(prop, basis[prop])),
+ check: expectedObject,
+ };
+ }
+
+ return {
+ enter: expectedLeaf,
+ done: expectedLeaf,
+ check: elt => compare(elt, basis),
+ };
+ };
+}
+
+function missingProp(prop) {
+ throw new Error(
+ "Census mismatch: subject lacks property present in basis: " + prop
+ );
+}
+
+function extraProp(prop) {
+ throw new Error(
+ "Census mismatch: subject has property not present in basis: " + prop
+ );
+}
+
+// Return a walker that checks that the subject census has counts all equal to
+// |basis|.
+Census.assertAllEqual = makeBasisChecker({
+ compare: (a, b) => {
+ if (a !== b) {
+ throw new Error("Census mismatch: expected " + a + " got " + b);
+ }
+ },
+ missing: missingProp,
+ extra: extraProp,
+});
+
+function ok(val) {
+ if (!val) {
+ throw new Error("Census mismatch: expected truthy, got " + val);
+ }
+}
+
+// Return a walker that checks that the subject census has at least as many
+// items of each category as |basis|.
+Census.assertAllNotLessThan = makeBasisChecker({
+ compare: (subject, basis) => ok(subject >= basis),
+ missing: missingProp,
+ extra: () => Census.walkAnything,
+});
+
+// Return a walker that checks that the subject census has at most as many
+// items of each category as |basis|.
+Census.assertAllNotMoreThan = makeBasisChecker({
+ compare: (subject, basis) => ok(subject <= basis),
+ missing: missingProp,
+ extra: () => Census.walkAnything,
+});
+
+// Return a walker that checks that the subject census has within |fudge|
+// items of each category of the count in |basis|.
+Census.assertAllWithin = function (fudge, basis) {
+ return makeBasisChecker({
+ compare: (subject, base) => ok(Math.abs(subject - base) <= fudge),
+ missing: missingProp,
+ extra: () => Census.walkAnything,
+ })(basis);
+};
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/Match.sys.mjs b/devtools/shared/heapsnapshot/tests/xpcshell/Match.sys.mjs
new file mode 100644
index 0000000000..76312db86a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/Match.sys.mjs
@@ -0,0 +1,218 @@
+// A little pattern-matching library.
+//
+// Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools
+// server xpcshell tests.
+
+export const Match = (function () {
+ function Pattern(template) {
+ // act like a constructor even as a function
+ if (!(this instanceof Pattern)) {
+ return new Pattern(template);
+ }
+
+ this.template = template;
+ }
+
+ Pattern.prototype = {
+ match(act) {
+ return match(act, this.template);
+ },
+
+ matches(act) {
+ try {
+ return this.match(act);
+ } catch (e) {
+ if (e instanceof MatchError) {
+ return false;
+ }
+ }
+ return false;
+ },
+
+ assert(act, message) {
+ try {
+ return this.match(act);
+ } catch (e) {
+ if (e instanceof MatchError) {
+ throw new Error((message || "failed match") + ": " + e.message);
+ }
+ }
+ return false;
+ },
+
+ toString: () => "[object Pattern]",
+ };
+
+ Pattern.ANY = new Pattern();
+ Pattern.ANY.template = Pattern.ANY;
+
+ Pattern.NUMBER = new Pattern();
+ Pattern.NUMBER.match = function (act) {
+ if (typeof act !== "number") {
+ throw new MatchError("Expected number, got: " + quote(act));
+ }
+ };
+
+ Pattern.NATURAL = new Pattern();
+ Pattern.NATURAL.match = function (act) {
+ if (typeof act !== "number" || act !== Math.floor(act) || act < 0) {
+ throw new MatchError("Expected natural number, got: " + quote(act));
+ }
+ };
+
+ const quote = uneval;
+
+ function MatchError(msg) {
+ this.message = msg;
+ }
+
+ MatchError.prototype = {
+ toString() {
+ return "match error: " + this.message;
+ },
+ };
+
+ function isAtom(x) {
+ return (
+ typeof x === "number" ||
+ typeof x === "string" ||
+ typeof x === "boolean" ||
+ x === null ||
+ (typeof x === "object" && x instanceof RegExp)
+ );
+ }
+
+ function isObject(x) {
+ return x !== null && typeof x === "object";
+ }
+
+ function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ function isArrayLike(x) {
+ return isObject(x) && "length" in x;
+ }
+
+ function matchAtom(act, exp) {
+ if (typeof exp === "number" && isNaN(exp)) {
+ if (typeof act !== "number" || !isNaN(act)) {
+ throw new MatchError("expected NaN, got: " + quote(act));
+ }
+ return true;
+ }
+
+ if (exp === null) {
+ if (act !== null) {
+ throw new MatchError("expected null, got: " + quote(act));
+ }
+ return true;
+ }
+
+ if (exp instanceof RegExp) {
+ if (!(act instanceof RegExp) || exp.source !== act.source) {
+ throw new MatchError("expected " + quote(exp) + ", got: " + quote(act));
+ }
+ return true;
+ }
+
+ switch (typeof exp) {
+ case "string":
+ if (act !== exp) {
+ throw new MatchError(
+ "expected " + quote(exp) + ", got " + quote(act)
+ );
+ }
+ return true;
+ case "boolean":
+ case "number":
+ if (exp !== act) {
+ throw new MatchError("expected " + exp + ", got " + quote(act));
+ }
+ return true;
+ }
+
+ throw new Error("bad pattern: " + exp.toSource());
+ }
+
+ function matchObject(act, exp) {
+ if (!isObject(act)) {
+ throw new MatchError("expected object, got " + quote(act));
+ }
+
+ for (const key in exp) {
+ if (!(key in act)) {
+ throw new MatchError(
+ "expected property " + quote(key) + " not found in " + quote(act)
+ );
+ }
+ match(act[key], exp[key]);
+ }
+
+ return true;
+ }
+
+ function matchFunction(act, exp) {
+ if (!isFunction(act)) {
+ throw new MatchError("expected function, got " + quote(act));
+ }
+
+ if (act !== exp) {
+ throw new MatchError(
+ "expected function: " + exp + "\nbut got different function: " + act
+ );
+ }
+ }
+
+ function matchArray(act, exp) {
+ if (!isObject(act) || !("length" in act)) {
+ throw new MatchError("expected array-like object, got " + quote(act));
+ }
+
+ const length = exp.length;
+ if (act.length !== exp.length) {
+ throw new MatchError(
+ "expected array-like object of length " + length + ", got " + quote(act)
+ );
+ }
+
+ for (let i = 0; i < length; i++) {
+ if (i in exp) {
+ if (!(i in act)) {
+ throw new MatchError(
+ "expected array property " + i + " not found in " + quote(act)
+ );
+ }
+ match(act[i], exp[i]);
+ }
+ }
+
+ return true;
+ }
+
+ function match(act, exp) {
+ if (exp === Pattern.ANY) {
+ return true;
+ }
+
+ if (exp instanceof Pattern) {
+ return exp.match(act);
+ }
+
+ if (isAtom(exp)) {
+ return matchAtom(act, exp);
+ }
+
+ if (isArrayLike(exp)) {
+ return matchArray(act, exp);
+ }
+
+ if (isFunction(exp)) {
+ return matchFunction(act, exp);
+ }
+
+ return matchObject(act, exp);
+ }
+
+ return { Pattern, MatchError };
+})();
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/dominator-tree-worker.js b/devtools/shared/heapsnapshot/tests/xpcshell/dominator-tree-worker.js
new file mode 100644
index 0000000000..c636226101
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/dominator-tree-worker.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env worker */
+
+"use strict";
+
+console.log("Initializing worker.");
+
+self.onmessage = e => {
+ console.log("Starting test.");
+ try {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(DominatorTree.isInstance(dominatorTree));
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (excp) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+ } catch (ex) {
+ ok(
+ false,
+ "Unexpected error inside worker:\n" + ex.toString() + "\n" + ex.stack
+ );
+ } finally {
+ done();
+ }
+};
+
+// Proxy assertions to the main thread.
+function ok(val, msg) {
+ console.log("ok(" + !!val + ', "' + msg + '")');
+ self.postMessage({
+ type: "assertion",
+ passed: !!val,
+ msg,
+ stack: Error().stack,
+ });
+}
+
+// Tell the main thread we are done with the tests.
+function done() {
+ console.log("done()");
+ self.postMessage({
+ type: "done",
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/head_heapsnapshot.js b/devtools/shared/heapsnapshot/tests/xpcshell/head_heapsnapshot.js
new file mode 100644
index 0000000000..d51decb104
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/head_heapsnapshot.js
@@ -0,0 +1,554 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+/* exported Cr, CC, Match, Census, Task, DevToolsUtils, HeapAnalysesClient,
+ assertThrows, getFilePath, saveHeapSnapshotAndTakeCensus,
+ saveHeapSnapshotAndComputeDominatorTree, compareCensusViewData, assertDiff,
+ assertLabelAndShallowSize, makeTestDominatorTreeNode,
+ assertDominatorTreeNodeInsertion, assertDeduplicatedPaths,
+ assertCountToBucketBreakdown, pathEntry */
+
+var CC = Components.Constructor;
+
+const { require } = ChromeUtils.importESModule(
+ "resource://devtools/shared/loader/Loader.sys.mjs"
+);
+const { Match } = ChromeUtils.importESModule("resource://test/Match.sys.mjs");
+const { Census } = ChromeUtils.importESModule("resource://test/Census.sys.mjs");
+const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+);
+
+const DevToolsUtils = require("resource://devtools/shared/DevToolsUtils.js");
+const HeapAnalysesClient = require("resource://devtools/shared/heapsnapshot/HeapAnalysesClient.js");
+const {
+ censusReportToCensusTreeNode,
+} = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
+const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
+const {
+ deduplicatePaths,
+} = require("resource://devtools/shared/heapsnapshot/shortest-paths.js");
+const { LabelAndShallowSizeVisitor } = DominatorTreeNode;
+
+// Always log packets when running tests. runxpcshelltests.py will throw
+// the output away anyway, unless you give it the --verbose flag.
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+ Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.debugger.log");
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+ });
+}
+
+const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance(
+ Ci.nsIPrincipal
+);
+
+function dumpn(msg) {
+ dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
+}
+
+function addTestingFunctionsToGlobal(global) {
+ global.eval(
+ `
+ const testingFunctions = Components.utils.getJSTestingFunctions();
+ for (let k in testingFunctions) {
+ this[k] = testingFunctions[k];
+ }
+ `
+ );
+ if (!global.print) {
+ global.print = info;
+ }
+ if (!global.newGlobal) {
+ global.newGlobal = newGlobal;
+ }
+ if (!global.Debugger) {
+ addDebuggerToGlobal(global);
+ }
+}
+
+addTestingFunctionsToGlobal(this);
+
+/**
+ * Create a new global, with all the JS shell testing functions. Similar to the
+ * newGlobal function exposed to JS shells, and useful for porting JS shell
+ * tests to xpcshell tests.
+ */
+function newGlobal() {
+ const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
+ addTestingFunctionsToGlobal(global);
+ return global;
+}
+
+function assertThrows(f, val, msg) {
+ let fullmsg;
+ try {
+ f();
+ } catch (exc) {
+ if (exc === val && (val !== 0 || 1 / exc === 1 / val)) {
+ return;
+ } else if (exc instanceof Error && exc.message === val) {
+ return;
+ }
+ fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
+ }
+ if (fullmsg === undefined) {
+ fullmsg =
+ "Assertion failed: expected exception " + val + ", no exception thrown";
+ }
+ if (msg !== undefined) {
+ fullmsg += " - " + msg;
+ }
+ throw new Error(fullmsg);
+}
+
+/**
+ * Returns the full path of the file with the specified name in a
+ * platform-independent and URL-like form.
+ */
+function getFilePath(
+ name,
+ allowMissing = false,
+ usePlatformPathSeparator = false
+) {
+ const file = do_get_file(name, allowMissing);
+ let path = Services.io.newFileURI(file).spec;
+ let filePrePath = "file://";
+ if ("nsILocalFileWin" in Ci && file instanceof Ci.nsILocalFileWin) {
+ filePrePath += "/";
+ }
+
+ path = path.slice(filePrePath.length);
+
+ if (usePlatformPathSeparator && path.match(/^\w:/)) {
+ path = path.replace(/\//g, "\\");
+ }
+
+ return path;
+}
+
+function saveNewHeapSnapshot(opts = { runtime: true }) {
+ const filePath = ChromeUtils.saveHeapSnapshot(opts);
+ ok(filePath, "Should get a file path to save the core dump to.");
+ ok(true, "Saved a heap snapshot to " + filePath);
+ return filePath;
+}
+
+function readHeapSnapshot(filePath) {
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should have read a heap snapshot back from " + filePath);
+ ok(
+ HeapSnapshot.isInstance(snapshot),
+ "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
+ * the heap snapshot's serialized heap graph with the provided census options.
+ *
+ * @param {Object|undefined} censusOptions
+ * Options that should be passed through to the takeCensus method. See
+ * js/src/doc/Debugger/Debugger.Memory.md for details.
+ *
+ * @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.
+ *
+ * @param {String} fileName
+ * The file name to save the heap snapshot's core dump file to, within
+ * the current directory.
+ *
+ * @returns Census
+ */
+function saveHeapSnapshotAndTakeCensus(dbg = null, censusOptions = undefined) {
+ const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
+ 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.isInstance(dominatorTree),
+ "Should be an instance of DominatorTree"
+ );
+
+ return dominatorTree;
+}
+
+function isSavedFrame(obj) {
+ return Object.prototype.toString.call(obj) === "[object SavedFrame]";
+}
+
+function savedFrameReplacer(key, val) {
+ if (isSavedFrame(val)) {
+ return `<SavedFrame '${val.toString().split(/\n/g).shift()}'>`;
+ }
+ return val;
+}
+
+/**
+ * Assert that creating a CensusTreeNode from the given `report` with the
+ * specified `breakdown` creates the given `expected` CensusTreeNode.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {Object} expected
+ * The expected CensusTreeNode result.
+ *
+ * @param {Object} options
+ * The options to pass through to `censusReportToCensusTreeNode`.
+ */
+function compareCensusViewData(breakdown, report, expected, options) {
+ dumpn("Generating CensusTreeNode from report:");
+ dumpn("breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("report: " + JSON.stringify(report, null, 4));
+ dumpn("expected: " + JSON.stringify(expected, savedFrameReplacer, 4));
+
+ const actual = censusReportToCensusTreeNode(breakdown, report, options);
+ dumpn("actual: " + JSON.stringify(actual, savedFrameReplacer, 4));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+// Deep structural equivalence that can handle Map objects in addition to plain
+// objects.
+function assertStructurallyEquivalent(actual, expected, path = "root") {
+ if (actual === expected) {
+ equal(actual, expected, "actual and expected are the same");
+ return;
+ }
+
+ equal(typeof actual, typeof expected, `${path}: typeof should be the same`);
+
+ if (actual && typeof actual === "object") {
+ const actualProtoString = Object.prototype.toString.call(actual);
+ const expectedProtoString = Object.prototype.toString.call(expected);
+ equal(
+ actualProtoString,
+ expectedProtoString,
+ `${path}: Object.prototype.toString.call() should be the same`
+ );
+
+ if (actualProtoString === "[object Map]") {
+ const expectedKeys = new Set([...expected.keys()]);
+
+ for (const key of actual.keys()) {
+ ok(
+ expectedKeys.has(key),
+ `${path}: every key in actual is expected: ${String(key).slice(
+ 0,
+ 10
+ )}`
+ );
+ expectedKeys.delete(key);
+
+ assertStructurallyEquivalent(
+ actual.get(key),
+ expected.get(key),
+ path + ".get(" + String(key).slice(0, 20) + ")"
+ );
+ }
+
+ equal(
+ expectedKeys.size,
+ 0,
+ `${path}: every key in expected should also exist in actual,\
+ did not see ${[...expectedKeys]}`
+ );
+ } else if (actualProtoString === "[object Set]") {
+ const expectedItems = new Set([...expected]);
+
+ for (const item of actual) {
+ ok(
+ expectedItems.has(item),
+ `${path}: every set item in actual should exist in expected: ${item}`
+ );
+ expectedItems.delete(item);
+ }
+
+ equal(
+ expectedItems.size,
+ 0,
+ `${path}: every set item in expected should also exist in actual,\
+ did not see ${[...expectedItems]}`
+ );
+ } else {
+ const expectedKeys = new Set(Object.keys(expected));
+
+ for (const key of Object.keys(actual)) {
+ ok(
+ expectedKeys.has(key),
+ `${path}: every key in actual should exist in expected: ${key}`
+ );
+ expectedKeys.delete(key);
+
+ assertStructurallyEquivalent(
+ actual[key],
+ expected[key],
+ path + "." + key
+ );
+ }
+
+ equal(
+ expectedKeys.size,
+ 0,
+ `${path}: every key in expected should also exist in actual,\
+ did not see ${[...expectedKeys]}`
+ );
+ }
+ } else {
+ equal(actual, expected, `${path}: primitives should be equal`);
+ }
+}
+
+/**
+ * Assert that creating a diff of the `first` and `second` census reports
+ * creates the `expected` delta-report.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} first
+ * The first census report.
+ *
+ * @param {Object} second
+ * The second census report.
+ *
+ * @param {Object} expected
+ * The expected delta-report.
+ */
+function assertDiff(breakdown, first, second, expected) {
+ dumpn("Diffing census reports:");
+ dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("First census report: " + JSON.stringify(first, null, 4));
+ dumpn("Second census report: " + JSON.stringify(second, null, 4));
+ dumpn("Expected delta-report: " + JSON.stringify(expected, null, 4));
+
+ const actual = CensusUtils.diff(breakdown, first, second);
+ dumpn("Actual delta-report: " + JSON.stringify(actual, null, 4));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+/**
+ * Assert that creating a label and getting a shallow size from the given node
+ * description with the specified breakdown is as expected.
+ *
+ * @param {Object} breakdown
+ * @param {Object} givenDescription
+ * @param {Number} expectedShallowSize
+ * @param {Object} expectedLabel
+ */
+function assertLabelAndShallowSize(
+ breakdown,
+ givenDescription,
+ expectedShallowSize,
+ expectedLabel
+) {
+ dumpn("Computing label and shallow size from node description:");
+ dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("Given description: " + JSON.stringify(givenDescription, null, 4));
+
+ const visitor = new LabelAndShallowSizeVisitor();
+ CensusUtils.walk(breakdown, givenDescription, visitor);
+
+ dumpn("Expected shallow size: " + expectedShallowSize);
+ dumpn("Actual shallow size: " + visitor.shallowSize());
+ equal(
+ visitor.shallowSize(),
+ expectedShallowSize,
+ "Shallow size should be correct"
+ );
+
+ dumpn("Expected label: " + JSON.stringify(expectedLabel, null, 4));
+ dumpn("Actual label: " + JSON.stringify(visitor.label(), null, 4));
+ assertStructurallyEquivalent(visitor.label(), expectedLabel);
+}
+
+// Counter for mock DominatorTreeNode ids.
+let TEST_NODE_ID_COUNTER = 0;
+
+/**
+ * Create a mock DominatorTreeNode for testing, with sane defaults. Override any
+ * property by providing it on `opts`. Optionally pass child nodes as well.
+ *
+ * @param {Object} opts
+ * @param {Array<DominatorTreeNode>?} children
+ *
+ * @returns {DominatorTreeNode}
+ */
+function makeTestDominatorTreeNode(opts, children) {
+ const nodeId = TEST_NODE_ID_COUNTER++;
+
+ const node = Object.assign(
+ {
+ nodeId,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: (children || []).reduce(
+ (size, c) => size + c.retainedSize,
+ 1
+ ),
+ parentId: undefined,
+ children,
+ moreChildrenAvailable: true,
+ },
+ opts
+ );
+
+ if (children && children.length) {
+ children.map(c => (c.parentId = node.nodeId));
+ }
+
+ return node;
+}
+
+/**
+ * Insert `newChildren` into the given dominator `tree` as specified by the
+ * `path` from the root to the node the `newChildren` should be inserted
+ * beneath. Assert that the resulting tree matches `expected`.
+ */
+function assertDominatorTreeNodeInsertion(
+ tree,
+ path,
+ newChildren,
+ moreChildrenAvailable,
+ expected
+) {
+ dumpn("Inserting new children into a dominator tree:");
+ dumpn("Dominator tree: " + JSON.stringify(tree, null, 2));
+ dumpn("Path: " + JSON.stringify(path, null, 2));
+ dumpn("New children: " + JSON.stringify(newChildren, null, 2));
+ dumpn("Expected resulting tree: " + JSON.stringify(expected, null, 2));
+
+ const actual = DominatorTreeNode.insert(
+ tree,
+ path,
+ newChildren,
+ moreChildrenAvailable
+ );
+ dumpn("Actual resulting tree: " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+function assertDeduplicatedPaths({
+ target,
+ paths,
+ expectedNodes,
+ expectedEdges,
+}) {
+ dumpn("Deduplicating paths:");
+ dumpn("target = " + target);
+ dumpn("paths = " + JSON.stringify(paths, null, 2));
+ dumpn("expectedNodes = " + expectedNodes);
+ dumpn("expectedEdges = " + JSON.stringify(expectedEdges, null, 2));
+
+ const { nodes, edges } = deduplicatePaths(target, paths);
+
+ dumpn("Actual nodes = " + nodes);
+ dumpn("Actual edges = " + JSON.stringify(edges, null, 2));
+
+ equal(
+ nodes.length,
+ expectedNodes.length,
+ "actual number of nodes is equal to the expected number of nodes"
+ );
+
+ equal(
+ edges.length,
+ expectedEdges.length,
+ "actual number of edges is equal to the expected number of edges"
+ );
+
+ const expectedNodeSet = new Set(expectedNodes);
+ const nodeSet = new Set(nodes);
+ Assert.strictEqual(
+ nodeSet.size,
+ nodes.length,
+ "each returned node should be unique"
+ );
+
+ for (const node of nodes) {
+ ok(expectedNodeSet.has(node), `the ${node} node was expected`);
+ }
+
+ for (const expectedEdge of expectedEdges) {
+ let count = 0;
+ for (const edge of edges) {
+ if (
+ edge.from === expectedEdge.from &&
+ edge.to === expectedEdge.to &&
+ edge.name === expectedEdge.name
+ ) {
+ count++;
+ }
+ }
+ equal(
+ count,
+ 1,
+ "should have exactly one matching edge for the expected edge = " +
+ JSON.stringify(expectedEdge)
+ );
+ }
+}
+
+function assertCountToBucketBreakdown(breakdown, expected) {
+ dumpn("count => bucket breakdown");
+ dumpn("Initial breakdown = ", JSON.stringify(breakdown, null, 2));
+ dumpn("Expected results = ", JSON.stringify(expected, null, 2));
+
+ const actual = CensusUtils.countToBucketBreakdown(breakdown);
+ dumpn("Actual results = ", JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+/**
+ * Create a mock path entry for the given predecessor and edge.
+ */
+function pathEntry(predecessor, edge) {
+ return { predecessor, edge };
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/heap-snapshot-worker.js b/devtools/shared/heapsnapshot/tests/xpcshell/heap-snapshot-worker.js
new file mode 100644
index 0000000000..a79f442193
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/heap-snapshot-worker.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint-env worker */
+
+"use strict";
+
+console.log("Initializing worker.");
+
+self.onmessage = ex => {
+ console.log("Starting test.");
+ try {
+ ok(ChromeUtils, "Should have access to ChromeUtils in a worker.");
+ ok(HeapSnapshot, "Should have access to HeapSnapshot in a worker.");
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(
+ HeapSnapshot.isInstance(snapshot),
+ "Should be an instanceof HeapSnapshot"
+ );
+ } catch (e) {
+ ok(
+ false,
+ "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack
+ );
+ } finally {
+ done();
+ }
+};
+
+// Proxy assertions to the main thread.
+function ok(val, msg) {
+ console.log("ok(" + !!val + ', "' + msg + '")');
+ self.postMessage({
+ type: "assertion",
+ passed: !!val,
+ msg,
+ stack: Error().stack,
+ });
+}
+
+// Tell the main thread we are done with the tests.
+function done() {
+ console.log("done()");
+ self.postMessage({
+ type: "done",
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_01.js
new file mode 100644
index 0000000000..7d4560e6dc
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_01.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ Function: { count: 1, bytes: 32 },
+ other: { count: 0, bytes: 0 },
+ },
+ strings: {},
+ scripts: {},
+ other: {},
+ domNode: {},
+};
+
+const expected = ["objects", "Function"];
+
+const shallowSize = 32;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_02.js
new file mode 100644
index 0000000000..cd424afdd0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_02.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ other: { count: 1, bytes: 10 },
+ },
+ strings: {},
+ scripts: {},
+ other: {},
+ domNode: {},
+};
+
+const expected = ["objects", "other"];
+
+const shallowSize = 10;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_03.js
new file mode 100644
index 0000000000..098e3efc4f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_03.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ other: { count: 0, bytes: 0 },
+ },
+ strings: {
+ JSString: { count: 1, bytes: 42 },
+ },
+ scripts: {},
+ other: {},
+ domNode: {},
+};
+
+const expected = ["strings", "JSString"];
+
+const shallowSize = 42;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_04.js
new file mode 100644
index 0000000000..a087c39a2a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_LabelAndShallowSize_04.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+ },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const stack = saveStack();
+
+const description = {
+ objects: {
+ Array: new Map([[stack, { count: 1, bytes: 512 }]]),
+ other: { count: 0, bytes: 0 },
+ },
+ strings: {},
+ scripts: {},
+ other: {},
+ domNode: {},
+};
+
+const expected = ["objects", "Array", stack];
+
+const shallowSize = 512;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_attachShortestPaths_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_attachShortestPaths_01.js
new file mode 100644
index 0000000000..07894c67b1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_attachShortestPaths_01.js
@@ -0,0 +1,141 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the DominatorTreeNode.attachShortestPaths function can correctly
+// attach the deduplicated shortest retaining paths for each node it is given.
+
+const startNodeId = 9999;
+const maxNumPaths = 2;
+
+// Mock data mapping node id to shortest paths to that node id.
+const shortestPaths = new Map([
+ [
+ 1000,
+ [
+ [pathEntry(1100, "a"), pathEntry(1200, "b")],
+ [pathEntry(1100, "c"), pathEntry(1300, "d")],
+ ],
+ ],
+ [2000, [[pathEntry(2100, "e"), pathEntry(2200, "f"), pathEntry(2300, "g")]]],
+ [
+ 3000,
+ [
+ [pathEntry(3100, "h")],
+ [pathEntry(3100, "i")],
+ [pathEntry(3100, "j")],
+ [pathEntry(3200, "k")],
+ [pathEntry(3300, "l")],
+ [pathEntry(3400, "m")],
+ ],
+ ],
+]);
+
+const actual = [
+ makeTestDominatorTreeNode({ nodeId: 1000 }),
+ makeTestDominatorTreeNode({ nodeId: 2000 }),
+ makeTestDominatorTreeNode({ nodeId: 3000 }),
+];
+
+const expected = [
+ makeTestDominatorTreeNode({
+ nodeId: 1000,
+ shortestPaths: {
+ nodes: [
+ { id: 1000, label: ["SomeType-1000"] },
+ { id: 1100, label: ["SomeType-1100"] },
+ { id: 1200, label: ["SomeType-1200"] },
+ { id: 1300, label: ["SomeType-1300"] },
+ ],
+ edges: [
+ { from: 1100, to: 1200, name: "a" },
+ { from: 1100, to: 1300, name: "c" },
+ { from: 1200, to: 1000, name: "b" },
+ { from: 1300, to: 1000, name: "d" },
+ ],
+ },
+ }),
+
+ makeTestDominatorTreeNode({
+ nodeId: 2000,
+ shortestPaths: {
+ nodes: [
+ { id: 2000, label: ["SomeType-2000"] },
+ { id: 2100, label: ["SomeType-2100"] },
+ { id: 2200, label: ["SomeType-2200"] },
+ { id: 2300, label: ["SomeType-2300"] },
+ ],
+ edges: [
+ { from: 2100, to: 2200, name: "e" },
+ { from: 2200, to: 2300, name: "f" },
+ { from: 2300, to: 2000, name: "g" },
+ ],
+ },
+ }),
+
+ makeTestDominatorTreeNode({
+ nodeId: 3000,
+ shortestPaths: {
+ nodes: [
+ { id: 3000, label: ["SomeType-3000"] },
+ { id: 3100, label: ["SomeType-3100"] },
+ { id: 3200, label: ["SomeType-3200"] },
+ { id: 3300, label: ["SomeType-3300"] },
+ { id: 3400, label: ["SomeType-3400"] },
+ ],
+ edges: [
+ { from: 3100, to: 3000, name: "h" },
+ { from: 3100, to: 3000, name: "i" },
+ { from: 3100, to: 3000, name: "j" },
+ { from: 3200, to: 3000, name: "k" },
+ { from: 3300, to: 3000, name: "l" },
+ { from: 3400, to: 3000, name: "m" },
+ ],
+ },
+ }),
+];
+
+const breakdown = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+};
+
+const mockSnapshot = {
+ computeShortestPaths: (start, nodeIds, max) => {
+ equal(start, startNodeId);
+ equal(max, maxNumPaths);
+
+ return new Map(
+ nodeIds.map(nodeId => {
+ const paths = shortestPaths.get(nodeId);
+ ok(paths, "Expected computeShortestPaths call for node id = " + nodeId);
+ return [nodeId, paths];
+ })
+ );
+ },
+
+ describeNode: (bd, nodeId) => {
+ equal(bd, breakdown);
+ return {
+ ["SomeType-" + nodeId]: {
+ count: 1,
+ bytes: 10,
+ },
+ };
+ },
+};
+
+function run_test() {
+ DominatorTreeNode.attachShortestPaths(
+ mockSnapshot,
+ breakdown,
+ startNodeId,
+ actual,
+ maxNumPaths
+ );
+
+ dumpn("Expected = " + JSON.stringify(expected, null, 2));
+ dumpn("Actual = " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(expected, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_getNodeByIdAlongPath_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_getNodeByIdAlongPath_01.js
new file mode 100644
index 0000000000..be1f210c3e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_getNodeByIdAlongPath_01.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can find the node with the given id along the specified path.
+
+const node3000 = makeTestDominatorTreeNode({ nodeId: 3000 });
+
+const node2000 = makeTestDominatorTreeNode({ nodeId: 2000 }, [
+ makeTestDominatorTreeNode({}),
+ node3000,
+ makeTestDominatorTreeNode({}),
+]);
+
+const node1000 = makeTestDominatorTreeNode({ nodeId: 1000 }, [
+ makeTestDominatorTreeNode({}),
+ node2000,
+ makeTestDominatorTreeNode({}),
+]);
+
+const tree = node1000;
+
+const path = [1000, 2000, 3000];
+
+const tests = [
+ { id: 1000, expected: node1000 },
+ { id: 2000, expected: node2000 },
+ { id: 3000, expected: node3000 },
+];
+
+function run_test() {
+ for (const { id, expected } of tests) {
+ const actual = DominatorTreeNode.getNodeByIdAlongPath(id, tree, path);
+ equal(actual, expected, `We should have got the node with id = ${id}`);
+ }
+
+ equal(
+ null,
+ DominatorTreeNode.getNodeByIdAlongPath(999999999999, tree, path),
+ "null is returned for nodes that are not even in the tree"
+ );
+
+ const lastNodeId = tree.children[tree.children.length - 1].nodeId;
+ equal(
+ null,
+ DominatorTreeNode.getNodeByIdAlongPath(lastNodeId, tree, path),
+ "null is returned for nodes that are not along the path"
+ );
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_01.js
new file mode 100644
index 0000000000..7567c473e0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_01.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can insert new children into an existing DominatorTreeNode tree.
+
+const tree = makeTestDominatorTreeNode({ nodeId: 1000 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({ nodeId: 2000 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({ nodeId: 3000 }),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [1000, 2000, 3000];
+
+const newChildren = [
+ makeTestDominatorTreeNode({ parentId: 3000 }),
+ makeTestDominatorTreeNode({ parentId: 3000 }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = {
+ nodeId: 1000,
+ parentId: undefined,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 7,
+ children: [
+ {
+ nodeId: 0,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 1000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 2000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 4,
+ parentId: 1000,
+ children: [
+ {
+ nodeId: 1,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ children: [
+ {
+ nodeId: 7,
+ parentId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 8,
+ parentId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: false,
+ },
+ {
+ nodeId: 3,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: true,
+ },
+ {
+ nodeId: 5,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 1000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: true,
+};
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(
+ tree,
+ path,
+ newChildren,
+ moreChildrenAvailable,
+ expected
+ );
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_02.js
new file mode 100644
index 0000000000..b0b80c3c95
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_02.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test attempting to insert new children into an existing DominatorTreeNode
+// tree with a bad path.
+
+const tree = makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [111111, 222222, 333333];
+
+const newChildren = [
+ makeTestDominatorTreeNode({ parentId: 333333 }),
+ makeTestDominatorTreeNode({ parentId: 333333 }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = tree;
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(
+ tree,
+ path,
+ newChildren,
+ moreChildrenAvailable,
+ expected
+ );
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_03.js
new file mode 100644
index 0000000000..552ed72735
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_insert_03.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test inserting new children into an existing DominatorTreeNode at the root.
+
+const tree = makeTestDominatorTreeNode({ nodeId: 666 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [666];
+
+const newChildren = [
+ makeTestDominatorTreeNode({
+ nodeId: 777,
+ parentId: 666,
+ }),
+ makeTestDominatorTreeNode({
+ nodeId: 888,
+ parentId: 666,
+ }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = {
+ nodeId: 666,
+ label: undefined,
+ parentId: undefined,
+ shallowSize: 1,
+ retainedSize: 7,
+ children: [
+ {
+ nodeId: 0,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 4,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 4,
+ parentId: 666,
+ children: [
+ {
+ nodeId: 1,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 2,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 3,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: true,
+ },
+ {
+ nodeId: 5,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 777,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 888,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: false,
+};
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(
+ tree,
+ path,
+ newChildren,
+ moreChildrenAvailable,
+ expected
+ );
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_partialTraversal_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_partialTraversal_01.js
new file mode 100644
index 0000000000..6da0587f57
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTreeNode_partialTraversal_01.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we correctly set `moreChildrenAvailable` when doing a partial
+// traversal of a dominator tree to create the initial incrementally loaded
+// `DominatorTreeNode` tree.
+
+// `tree` maps parent to children:
+//
+// 100
+// |- 200
+// | |- 500
+// | |- 600
+// | `- 700
+// |- 300
+// | |- 800
+// | |- 900
+// `- 400
+// |- 1000
+// |- 1100
+// `- 1200
+const tree = new Map([
+ [100, [200, 300, 400]],
+ [200, [500, 600, 700]],
+ [300, [800, 900]],
+ [400, [1000, 1100, 1200]],
+]);
+
+const mockDominatorTree = {
+ root: 100,
+ getRetainedSize: _ => 10,
+ getImmediatelyDominated: id => (tree.get(id) || []).slice(),
+};
+
+const mockSnapshot = {
+ describeNode: _ => ({
+ objects: { count: 0, bytes: 0 },
+ strings: { count: 0, bytes: 0 },
+ scripts: { count: 0, bytes: 0 },
+ other: { SomeType: { count: 1, bytes: 10 } },
+ domNode: { count: 0, bytes: 0 },
+ }),
+};
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: { by: "count", count: true, bytes: true },
+};
+
+const expected = {
+ nodeId: 100,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 200,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 100,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 500,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 200,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined,
+ },
+ {
+ nodeId: 600,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 200,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: true,
+ },
+ {
+ nodeId: 300,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 100,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 800,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 300,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined,
+ },
+ {
+ nodeId: 900,
+ label: ["other", "SomeType"],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 300,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: false,
+ },
+ ],
+ moreChildrenAvailable: true,
+ parentId: undefined,
+};
+
+function run_test() {
+ // Traverse the whole depth of the test tree, but one short of the number of
+ // siblings. This will exercise the moreChildrenAvailable handling for
+ // siblings.
+ const actual = DominatorTreeNode.partialTraversal(
+ mockDominatorTree,
+ mockSnapshot,
+ breakdown,
+ // maxDepth
+ 4,
+ // siblings
+ 2
+ );
+
+ dumpn("Expected = " + JSON.stringify(expected, null, 2));
+ dumpn("Actual = " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(expected, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_01.js
new file mode 100644
index 0000000000..f98094fd32
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_01.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Sanity test that we can compute dominator trees.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(DominatorTree.isInstance(dominatorTree));
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_02.js
new file mode 100644
index 0000000000..525e031ccf
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_02.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can compute dominator trees from a snapshot in a worker.
+
+add_task(async function () {
+ const worker = new ChromeWorker("resource://test/dominator-tree-worker.js");
+ worker.postMessage({});
+
+ let assertionCount = 0;
+ worker.onmessage = e => {
+ if (e.data.type !== "assertion") {
+ return;
+ }
+
+ ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
+ assertionCount++;
+ };
+
+ await waitForDone(worker);
+
+ Assert.greater(assertionCount, 0);
+ worker.terminate();
+});
+
+function waitForDone(w) {
+ return new Promise((resolve, reject) => {
+ w.onerror = e => {
+ reject();
+ ok(false, "Error in worker: " + e);
+ };
+
+ w.addEventListener("message", function listener(e) {
+ if (e.data.type === "done") {
+ w.removeEventListener("message", listener);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_03.js
new file mode 100644
index 0000000000..d304845328
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_03.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// 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"
+ );
+ Assert.greaterOrEqual(dominatorTree.root, 0, "root should be positive");
+ Assert.lessOrEqual(
+ 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/xpcshell/test_DominatorTree_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_04.js
new file mode 100644
index 0000000000..2bfe46977f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_04.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can get the retained sizes of dominator trees.
+
+function run_test() {
+ const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+ equal(
+ typeof dominatorTree.getRetainedSize,
+ "function",
+ "getRetainedSize should be a function"
+ );
+
+ const size = dominatorTree.getRetainedSize(dominatorTree.root);
+ ok(size, "should get a size for the root");
+ equal(typeof size, "number", "retained sizes should be a number");
+ equal(Math.floor(size), size, "size should be an integer");
+ Assert.greater(size, 0, "size should be positive");
+ Assert.lessOrEqual(
+ size,
+ Math.pow(2, 64),
+ "size should be less than or equal to 2^64"
+ );
+
+ const bad = dominatorTree.getRetainedSize(1);
+ equal(bad, null, "null is returned for unknown node ids");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_05.js
new file mode 100644
index 0000000000..23abf1bd74
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_05.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can get the set of immediately dominated nodes for any given
+// node and that this forms a tree.
+
+function run_test() {
+ const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+ equal(
+ typeof dominatorTree.getImmediatelyDominated,
+ "function",
+ "getImmediatelyDominated should be a function"
+ );
+
+ // Do a traversal of the dominator tree.
+ //
+ // Note that we don't assert directly, only if we get an unexpected
+ // value. There are just way too many nodes in the heap graph to assert for
+ // every one. This test would constantly time out and assertion messages would
+ // overflow the log size.
+
+ const root = dominatorTree.root;
+ equal(
+ dominatorTree.getImmediateDominator(root),
+ null,
+ "The root should not have a parent"
+ );
+
+ const seen = new Set();
+ const stack = [root];
+ while (stack.length) {
+ const top = stack.pop();
+
+ if (seen.has(top)) {
+ ok(
+ false,
+ "This is a tree, not a graph: we shouldn't have " +
+ "multiple edges to the same node"
+ );
+ }
+ seen.add(top);
+ if (seen.size % 1000 === 0) {
+ dumpn("Progress update: seen size = " + seen.size);
+ }
+
+ const newNodes = dominatorTree.getImmediatelyDominated(top);
+ if (Object.prototype.toString.call(newNodes) !== "[object Array]") {
+ ok(
+ false,
+ "getImmediatelyDominated should return an array for known node ids"
+ );
+ }
+
+ const topSize = dominatorTree.getRetainedSize(top);
+
+ let lastSize = Infinity;
+ for (let i = 0; i < newNodes.length; i++) {
+ if (typeof newNodes[i] !== "number") {
+ ok(false, "Every dominated id should be a number");
+ }
+
+ if (dominatorTree.getImmediateDominator(newNodes[i]) !== top) {
+ ok(false, "child's parent should be the expected parent");
+ }
+
+ const thisSize = dominatorTree.getRetainedSize(newNodes[i]);
+
+ if (thisSize >= topSize) {
+ ok(
+ false,
+ "the size of children in the dominator tree should" +
+ " always be less than that of their parent"
+ );
+ }
+
+ if (thisSize > lastSize) {
+ ok(
+ false,
+ "children should be sorted by greatest to least retained size, " +
+ "lastSize = " +
+ lastSize +
+ ", thisSize = " +
+ thisSize
+ );
+ }
+
+ lastSize = thisSize;
+ stack.push(newNodes[i]);
+ }
+ }
+
+ ok(true, "Successfully walked the tree");
+ dumpn("Walked " + seen.size + " nodes");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_06.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_06.js
new file mode 100644
index 0000000000..fdd4191c5f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_DominatorTree_06.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the retained size of a node is the sum of its children retained
+// sizes plus its shallow size.
+
+// Note that we don't assert directly, only if we get an unexpected
+// value. There are just way too many nodes in the heap graph to assert for
+// every one. This test would constantly time out and assertion messages would
+// overflow the log size.
+function fastAssert(cond, msg) {
+ if (!cond) {
+ ok(false, msg);
+ }
+}
+
+const COUNT = { by: "count", count: false, bytes: true };
+
+function run_test() {
+ const path = saveNewHeapSnapshot();
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+ const dominatorTree = snapshot.computeDominatorTree();
+
+ // Do a traversal of the dominator tree and assert the relationship between
+ // retained size, shallow size, and children's retained sizes.
+
+ const root = dominatorTree.root;
+ const stack = [root];
+ while (stack.length) {
+ const top = stack.pop();
+
+ const children = dominatorTree.getImmediatelyDominated(top);
+
+ const topRetainedSize = dominatorTree.getRetainedSize(top);
+ const topShallowSize = snapshot.describeNode(COUNT, top).bytes;
+ fastAssert(
+ topShallowSize <= topRetainedSize,
+ "The shallow size should be less than or equal to the " + "retained size"
+ );
+
+ let sumOfChildrensRetainedSizes = 0;
+ for (let i = 0; i < children.length; i++) {
+ sumOfChildrensRetainedSizes += dominatorTree.getRetainedSize(children[i]);
+ stack.push(children[i]);
+ }
+
+ fastAssert(
+ sumOfChildrensRetainedSizes <= topRetainedSize,
+ "The sum of the children's retained sizes should be less than " +
+ "or equal to the retained size"
+ );
+ fastAssert(
+ sumOfChildrensRetainedSizes + topShallowSize === topRetainedSize,
+ "The sum of the children's retained sizes plus the shallow " +
+ "size should be equal to the retained size"
+ );
+ }
+
+ ok(true, "Successfully walked the tree");
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_01.js
new file mode 100644
index 0000000000..ba622f55df
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_01.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+ equal(
+ typeof dominatorTreeId,
+ "number",
+ "should get a dominator tree id, and it should be a number"
+ );
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_02.js
new file mode 100644
index 0000000000..c3ee76be13
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_computeDominatorTree_02.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request with bad
+// file paths.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ await client.computeDominatorTree("/etc/passwd");
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "should throw when given a bad path");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_01.js
new file mode 100644
index 0000000000..b85e4b19fc
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_01.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can delete heap snapshots.
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+ ok(true, "Should have computed the dominator tree");
+
+ await client.deleteHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have deleted the snapshot");
+
+ let threw = false;
+ try {
+ await client.getDominatorTree({
+ dominatorTreeId,
+ breakdown,
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getDominatorTree on deleted tree should throw an error");
+
+ threw = false;
+ try {
+ await client.computeDominatorTree(snapshotFilePath);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "computeDominatorTree on deleted snapshot should throw an error");
+
+ threw = false;
+ try {
+ await client.takeCensus(snapshotFilePath);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "takeCensus on deleted tree should throw an error");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_02.js
new file mode 100644
index 0000000000..6960081afd
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_02.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test deleteHeapSnapshot is a noop if the provided path matches no snapshot
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ await client.deleteHeapSnapshot("path-does-not-exist");
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "deleteHeapSnapshot on non-existant path should throw an error");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_03.js
new file mode 100644
index 0000000000..c84622f633
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_deleteHeapSnapshot_03.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test other dominatorTrees can still be retrieved after deleting a snapshot
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ domNode: { by: "count", count: true, bytes: true },
+};
+
+async function createSnapshotAndDominatorTree(client) {
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+ return { dominatorTreeId, snapshotFilePath };
+}
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const savedSnapshots = [
+ await createSnapshotAndDominatorTree(client),
+ await createSnapshotAndDominatorTree(client),
+ await createSnapshotAndDominatorTree(client),
+ ];
+ ok(true, "Create 3 snapshots and dominator trees");
+
+ await client.deleteHeapSnapshot(savedSnapshots[1].snapshotFilePath);
+ ok(true, "Snapshot deleted");
+
+ let tree = await client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[0].dominatorTreeId,
+ breakdown,
+ });
+ ok(tree, "Should get a valid tree for first snapshot");
+
+ let threw = false;
+ try {
+ await client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[1].dominatorTreeId,
+ breakdown,
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getDominatorTree on a deleted snapshot should throw an error");
+
+ tree = await client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[2].dominatorTreeId,
+ breakdown,
+ });
+ ok(tree, "Should get a valid tree for third snapshot");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCensusIndividuals_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCensusIndividuals_01.js
new file mode 100644
index 0000000000..942547835b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCensusIndividuals_01.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can get census individuals.
+
+const COUNT = { by: "count", count: true, bytes: true };
+
+const CENSUS_BREAKDOWN = {
+ by: "coarseType",
+ objects: COUNT,
+ strings: COUNT,
+ scripts: COUNT,
+ other: COUNT,
+ domNode: COUNT,
+};
+
+const LABEL_BREAKDOWN = {
+ by: "internalType",
+ then: COUNT,
+};
+
+const MAX_INDIVIDUALS = 10;
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+ ok(true, "Should have computed dominator tree");
+
+ const { report } = await client.takeCensus(
+ snapshotFilePath,
+ { breakdown: CENSUS_BREAKDOWN },
+ { asTreeNode: true }
+ );
+ ok(report, "Should get a report");
+
+ let nodesWithLeafIndicesFound = 0;
+
+ await (async function assertCanGetIndividuals(censusNode) {
+ if (censusNode.reportLeafIndex !== undefined) {
+ nodesWithLeafIndicesFound++;
+
+ const response = await client.getCensusIndividuals({
+ dominatorTreeId,
+ indices: DevToolsUtils.isSet(censusNode.reportLeafIndex)
+ ? censusNode.reportLeafIndex
+ : new Set([censusNode.reportLeafIndex]),
+ censusBreakdown: CENSUS_BREAKDOWN,
+ labelBreakdown: LABEL_BREAKDOWN,
+ maxRetainingPaths: 1,
+ maxIndividuals: MAX_INDIVIDUALS,
+ });
+
+ dumpn(`response = ${JSON.stringify(response, null, 4)}`);
+
+ equal(
+ response.nodes.length,
+ Math.min(MAX_INDIVIDUALS, censusNode.count),
+ "response.nodes.length === Math.min(MAX_INDIVIDUALS, censusNode.count)"
+ );
+
+ let lastRetainedSize = Infinity;
+ for (const individual of response.nodes) {
+ equal(
+ typeof individual.nodeId,
+ "number",
+ "individual.nodeId should be a number"
+ );
+ Assert.lessOrEqual(
+ individual.retainedSize,
+ lastRetainedSize,
+ "individual.retainedSize <= lastRetainedSize"
+ );
+ lastRetainedSize = individual.retainedSize;
+ ok(
+ individual.shallowSize,
+ "individual.shallowSize should exist and be non-zero"
+ );
+ ok(individual.shortestPaths, "individual.shortestPaths should exist");
+ ok(
+ individual.shortestPaths.nodes,
+ "individual.shortestPaths.nodes should exist"
+ );
+ ok(
+ individual.shortestPaths.edges,
+ "individual.shortestPaths.edges should exist"
+ );
+ ok(individual.label, "individual.label should exist");
+ }
+ }
+
+ if (censusNode.children) {
+ for (const child of censusNode.children) {
+ await assertCanGetIndividuals(child);
+ }
+ }
+ })(report);
+
+ equal(
+ nodesWithLeafIndicesFound,
+ 4,
+ "Should have found a leaf for each coarse type"
+ );
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCreationTime_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCreationTime_01.js
new file mode 100644
index 0000000000..b52280d970
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getCreationTime_01.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can get a HeapSnapshot's
+// creation time.
+
+function waitForThirtyMilliseconds() {
+ const start = Date.now();
+ while (Date.now() - start < 30) {
+ // do nothing
+ }
+}
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+ const start = Date.now() * 1000;
+
+ // Because Date.now() is less precise than the snapshot's time stamp, give it
+ // a little bit of head room. Additionally, WinXP's timers have a granularity
+ // of only +/-15 ms.
+ waitForThirtyMilliseconds();
+ const snapshotFilePath = saveNewHeapSnapshot();
+ waitForThirtyMilliseconds();
+ const end = Date.now() * 1000;
+
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ let threw = false;
+ try {
+ await client.getCreationTime("/not/a/real/path", {
+ breakdown: BREAKDOWN,
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getCreationTime should throw when snapshot does not exist");
+
+ const time = await client.getCreationTime(snapshotFilePath, {
+ breakdown: BREAKDOWN,
+ });
+
+ dumpn("Start = " + start);
+ dumpn("End = " + end);
+ dumpn("Time = " + time);
+
+ Assert.greaterOrEqual(time, start, "creation time occurred after start");
+ Assert.lessOrEqual(time, end, "creation time occurred before end");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_01.js
new file mode 100644
index 0000000000..9035624ca2
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_01.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request.
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ domNode: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+ equal(
+ typeof dominatorTreeId,
+ "number",
+ "should get a dominator tree id, and it should be a number"
+ );
+
+ const partialTree = await client.getDominatorTree({
+ dominatorTreeId,
+ breakdown,
+ });
+ ok(partialTree, "Should get a partial tree");
+ equal(typeof partialTree, "object", "partialTree should be an object");
+
+ function checkTree(node) {
+ equal(typeof node.nodeId, "number", "each node should have an id");
+
+ if (node === partialTree) {
+ equal(node.parentId, undefined, "the root has no parent");
+ } else {
+ equal(
+ typeof node.parentId,
+ "number",
+ "each node should have a parent id"
+ );
+ }
+
+ equal(
+ typeof node.retainedSize,
+ "number",
+ "each node should have a retained size"
+ );
+
+ ok(
+ node.children === undefined || Array.isArray(node.children),
+ "each node either has a list of children, " +
+ "or undefined meaning no children loaded"
+ );
+ equal(
+ typeof node.moreChildrenAvailable,
+ "boolean",
+ "each node should indicate if there are more children available or not"
+ );
+
+ equal(typeof node.shortestPaths, "object", "Should have shortest paths");
+ equal(
+ typeof node.shortestPaths.nodes,
+ "object",
+ "Should have shortest paths' nodes"
+ );
+ equal(
+ typeof node.shortestPaths.edges,
+ "object",
+ "Should have shortest paths' edges"
+ );
+
+ if (node.children) {
+ node.children.forEach(checkTree);
+ }
+ }
+
+ checkTree(partialTree);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_02.js
new file mode 100644
index 0000000000..83a7cbbd3d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getDominatorTree_02.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request with bad
+// dominator tree ids.
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ await client.getDominatorTree({ dominatorTreeId: 42, breakdown });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "should throw when given a bad id");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getImmediatelyDominated_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getImmediatelyDominated_01.js
new file mode 100644
index 0000000000..47860870cb
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_getImmediatelyDominated_01.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the HeapAnalyses{Client,Worker} "getImmediatelyDominated" request.
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ domNode: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ const dominatorTreeId = await client.computeDominatorTree(snapshotFilePath);
+
+ const partialTree = await client.getDominatorTree({
+ dominatorTreeId,
+ breakdown,
+ });
+ ok(
+ !!partialTree.children.length,
+ "root should immediately dominate some nodes"
+ );
+
+ // First, test getting a subset of children available.
+ const response = await client.getImmediatelyDominated({
+ dominatorTreeId,
+ breakdown,
+ nodeId: partialTree.nodeId,
+ startIndex: 0,
+ maxCount: partialTree.children.length - 1,
+ });
+
+ ok(Array.isArray(response.nodes));
+ ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
+ ok(response.moreChildrenAvailable);
+ equal(response.path.length, 1);
+ equal(response.path[0], partialTree.nodeId);
+
+ for (const node of response.nodes) {
+ equal(typeof node.shortestPaths, "object", "Should have shortest paths");
+ equal(
+ typeof node.shortestPaths.nodes,
+ "object",
+ "Should have shortest paths' nodes"
+ );
+ equal(
+ typeof node.shortestPaths.edges,
+ "object",
+ "Should have shortest paths' edges"
+ );
+ }
+
+ // Next, test getting a subset of children available.
+ const secondResponse = await client.getImmediatelyDominated({
+ dominatorTreeId,
+ breakdown,
+ nodeId: partialTree.nodeId,
+ startIndex: 0,
+ maxCount: Infinity,
+ });
+
+ ok(Array.isArray(secondResponse.nodes));
+ ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
+ ok(!secondResponse.moreChildrenAvailable);
+ equal(secondResponse.path.length, 1);
+ equal(secondResponse.path[0], partialTree.nodeId);
+
+ for (const node of secondResponse.nodes) {
+ equal(typeof node.shortestPaths, "object", "Should have shortest paths");
+ equal(
+ typeof node.shortestPaths.nodes,
+ "object",
+ "Should have shortest paths' nodes"
+ );
+ equal(
+ typeof node.shortestPaths.edges,
+ "object",
+ "Should have shortest paths' edges"
+ );
+ }
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_readHeapSnapshot_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_readHeapSnapshot_01.js
new file mode 100644
index 0000000000..dea8269a92
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_readHeapSnapshot_01.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can read heap snapshots.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_01.js
new file mode 100644
index 0000000000..9703229438
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_01.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses.
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: false },
+ other: { by: "count", count: true, bytes: false },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const markers = [allocationMarker()];
+
+ const firstSnapshotFilePath = saveNewHeapSnapshot();
+
+ // Allocate and hold an additional AllocationMarker object so we can see it in
+ // the next heap snapshot.
+ markers.push(allocationMarker());
+
+ const secondSnapshotFilePath = saveNewHeapSnapshot();
+
+ await client.readHeapSnapshot(firstSnapshotFilePath);
+ await client.readHeapSnapshot(secondSnapshotFilePath);
+ ok(true, "Should have read both heap snapshot files");
+
+ const { delta } = await client.takeCensusDiff(
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN }
+ );
+
+ equal(
+ delta.AllocationMarker.count,
+ 1,
+ "There exists one new AllocationMarker in the second heap snapshot"
+ );
+
+ const { delta: deltaTreeNode } = await client.takeCensusDiff(
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN },
+ { asTreeNode: true }
+ );
+
+ // Have to manually set these because symbol properties aren't structured
+ // cloned.
+ delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
+ delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
+
+ compareCensusViewData(
+ BREAKDOWN,
+ delta,
+ deltaTreeNode,
+ "Returning delta-census as a tree node represents same data as the report"
+ );
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_02.js
new file mode 100644
index 0000000000..d5d988f78f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensusDiff_02.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses as
+// inverted trees.
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+add_task(async function () {
+ const firstSnapshotFilePath = saveNewHeapSnapshot();
+ const secondSnapshotFilePath = saveNewHeapSnapshot();
+
+ const client = new HeapAnalysesClient();
+ await client.readHeapSnapshot(firstSnapshotFilePath);
+ await client.readHeapSnapshot(secondSnapshotFilePath);
+
+ ok(true, "Should have read both heap snapshot files");
+
+ const { delta } = await client.takeCensusDiff(
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN }
+ );
+
+ const { delta: deltaTreeNode } = await client.takeCensusDiff(
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN },
+ { asInvertedTreeNode: true }
+ );
+
+ // Have to manually set these because symbol properties aren't structured
+ // cloned.
+ delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
+ delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
+
+ compareCensusViewData(BREAKDOWN, delta, deltaTreeNode, { invert: true });
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_01.js
new file mode 100644
index 0000000000..55da2bf4b8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_01.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = await client.takeCensus(snapshotFilePath);
+ ok(report, "Should get a report");
+ equal(typeof report, "object", "report should be an object");
+
+ ok(report.objects);
+ ok(report.scripts);
+ ok(report.strings);
+ ok(report.other);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_02.js
new file mode 100644
index 0000000000..5b1dcefe03
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_02.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses with breakdown
+// options.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = await client.takeCensus(snapshotFilePath, {
+ breakdown: { by: "count", count: true, bytes: true },
+ });
+
+ ok(report, "Should get a report");
+ equal(typeof report, "object", "report should be an object");
+
+ ok(report.count);
+ ok(report.bytes);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_03.js
new file mode 100644
index 0000000000..0dfda73f1f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_03.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} bubbles errors properly when things
+// go wrong.
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ // Snapshot file path to a file that doesn't exist.
+ let failed = false;
+ try {
+ await client.readHeapSnapshot(
+ getFilePath("foo-bar-baz" + Math.random(), true)
+ );
+ } catch (e) {
+ failed = true;
+ }
+ ok(failed, "should not read heap snapshots that do not exist");
+
+ // Snapshot file path to a file that is not a heap snapshot.
+ failed = false;
+ try {
+ await client.readHeapSnapshot(
+ getFilePath("test_HeapAnalyses_takeCensus_03.js")
+ );
+ } catch (e) {
+ failed = true;
+ }
+ ok(
+ failed,
+ "should not be able to read a file " +
+ "that is not a heap snapshot as a heap snapshot"
+ );
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ // Bad census breakdown options.
+ failed = false;
+ try {
+ await client.takeCensus(snapshotFilePath, {
+ breakdown: { by: "some classification that we do not have" },
+ });
+ } catch (e) {
+ failed = true;
+ }
+ ok(failed, "should not be able to breakdown by an unknown classification");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_04.js
new file mode 100644
index 0000000000..26f78ad4f7
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_04.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can send SavedFrame stacks from
+// by-allocation-stack reports from the worker.
+
+add_task(async function test() {
+ const client = new HeapAnalysesClient();
+
+ // Track some allocation stacks.
+
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+ g.eval(` // 1
+ this.log = []; // 2
+ function f() { this.log.push(allocationMarker()); } // 3
+ function g() { this.log.push(allocationMarker()); } // 4
+ function h() { this.log.push(allocationMarker()); } // 5
+ `);
+
+ // Create one allocationMarker with tracking turned off,
+ // so it will have no associated stack.
+ g.f();
+
+ dbg.memory.allocationSamplingProbability = 1;
+
+ for (const [func, n] of [
+ [g.f, 20],
+ [g.g, 10],
+ [g.h, 5],
+ ]) {
+ for (let i = 0; i < n; i++) {
+ dbg.memory.trackingAllocationSites = true;
+ // All allocations of allocationMarker occur with this line as the oldest
+ // stack frame.
+ func();
+ dbg.memory.trackingAllocationSites = false;
+ }
+ }
+
+ // Take a heap snapshot.
+
+ const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ // Run a census broken down by class name -> allocation stack so we can grab
+ // only the AllocationMarker objects we have complete control over.
+
+ const { report } = await client.takeCensus(snapshotFilePath, {
+ breakdown: {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: {
+ by: "count",
+ bytes: true,
+ count: true,
+ },
+ noStack: {
+ by: "count",
+ bytes: true,
+ count: true,
+ },
+ },
+ },
+ });
+
+ // Test the generated report.
+
+ ok(report, "Should get a report");
+
+ const map = report.AllocationMarker;
+ ok(map, "Should get AllocationMarkers in the report.");
+ // From a module with a different global, and therefore a different Map
+ // constructor, so we can't use instanceof.
+ equal(Object.getPrototypeOf(map).constructor.name, "Map");
+
+ equal(
+ map.size,
+ 4,
+ "Should have 4 allocation stacks (including the lack of a stack)"
+ );
+
+ // Gather the stacks we are expecting to appear as keys, and
+ // check that there are no unexpected keys.
+ const stacks = {};
+
+ map.forEach((v, k) => {
+ if (k === "noStack") {
+ // No need to save this key.
+ } else if (
+ k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "test"
+ ) {
+ stacks.f = k;
+ } else if (
+ k.functionDisplayName === "g" &&
+ k.parent.functionDisplayName === "test"
+ ) {
+ stacks.g = k;
+ } else if (
+ k.functionDisplayName === "h" &&
+ k.parent.functionDisplayName === "test"
+ ) {
+ stacks.h = k;
+ } else {
+ dumpn("Unexpected allocation stack:");
+ k.toString()
+ .split(/\n/g)
+ .forEach(s => dumpn(s));
+ ok(false);
+ }
+ });
+
+ ok(map.get("noStack"));
+ equal(map.get("noStack").count, 1);
+
+ ok(stacks.f);
+ ok(map.get(stacks.f));
+ equal(map.get(stacks.f).count, 20);
+
+ ok(stacks.g);
+ ok(map.get(stacks.g));
+ equal(map.get(stacks.g).count, 10);
+
+ ok(stacks.h);
+ ok(map.get(stacks.h));
+ equal(map.get(stacks.h).count, 5);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_05.js
new file mode 100644
index 0000000000..951a3b3133
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_05.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses and return
+// a CensusTreeNode.
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = await client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN,
+ });
+
+ const { report: treeNode } = await client.takeCensus(
+ snapshotFilePath,
+ {
+ breakdown: BREAKDOWN,
+ },
+ {
+ asTreeNode: true,
+ }
+ );
+
+ ok(!!treeNode.children.length, "treeNode has children");
+ ok(
+ treeNode.children.every(type => {
+ return "name" in type && "bytes" in type && "count" in type;
+ }),
+ "all of tree node's children have name, bytes, count"
+ );
+
+ compareCensusViewData(
+ BREAKDOWN,
+ report,
+ treeNode,
+ "Returning census as a tree node represents same data as the report"
+ );
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_06.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_06.js
new file mode 100644
index 0000000000..d9fdaa3708
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_06.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses by
+// "allocationStack" and return a CensusTreeNode.
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+ },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(async function () {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ // 5 allocation markers with no stack.
+ g.eval(`
+ this.markers = [];
+ for (var i = 0; i < 5; i++) {
+ markers.push(allocationMarker());
+ }
+ `);
+
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+
+ // 5 allocation markers at 5 stacks.
+ g.eval(`
+ (function shouldHaveCountOfOne() {
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ }());
+ `);
+
+ // 5 allocation markers at 1 stack.
+ g.eval(`
+ (function shouldHaveCountOfFive() {
+ for (var i = 0; i < 5; i++) {
+ markers.push(allocationMarker());
+ }
+ }());
+ `);
+
+ const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
+
+ const client = new HeapAnalysesClient();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = await client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN,
+ });
+
+ const { report: treeNode } = await client.takeCensus(
+ snapshotFilePath,
+ {
+ breakdown: BREAKDOWN,
+ },
+ {
+ asTreeNode: true,
+ }
+ );
+
+ const markers = treeNode.children.find(c => c.name === "AllocationMarker");
+ ok(markers);
+
+ const noStack = markers.children.find(c => c.name === "noStack");
+ equal(noStack.count, 5);
+
+ let numShouldHaveFiveFound = 0;
+ let numShouldHaveOneFound = 0;
+
+ function walk(node) {
+ if (node.children) {
+ node.children.forEach(walk);
+ }
+
+ if (!isSavedFrame(node.name)) {
+ return;
+ }
+
+ if (node.name.functionDisplayName === "shouldHaveCountOfFive") {
+ equal(node.count, 5, "shouldHaveCountOfFive should have count of five");
+ numShouldHaveFiveFound++;
+ }
+
+ if (node.name.functionDisplayName === "shouldHaveCountOfOne") {
+ equal(node.count, 1, "shouldHaveCountOfOne should have count of one");
+ numShouldHaveOneFound++;
+ }
+ }
+ markers.children.forEach(walk);
+
+ equal(numShouldHaveFiveFound, 1);
+ equal(numShouldHaveOneFound, 5);
+
+ compareCensusViewData(
+ BREAKDOWN,
+ report,
+ treeNode,
+ "Returning census as a tree node represents same data as the report"
+ );
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_07.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_07.js
new file mode 100644
index 0000000000..3f603122d3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapAnalyses_takeCensus_07.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses and return
+// an inverted CensusTreeNode.
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+add_task(async function () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ await client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = await client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN,
+ });
+
+ const { report: treeNode } = await client.takeCensus(
+ snapshotFilePath,
+ {
+ breakdown: BREAKDOWN,
+ },
+ {
+ asInvertedTreeNode: true,
+ }
+ );
+
+ compareCensusViewData(BREAKDOWN, report, treeNode, { invert: true });
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_01.js
new file mode 100644
index 0000000000..354aa129a0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_01.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Sanity test that we can compute shortest paths.
+//
+// Because the actual heap graph is too unpredictable and likely to drastically
+// change as various implementation bits change, we don't test exact paths
+// here. See js/src/jsapi-tests/testUbiNode.cpp for such tests, where we can
+// control the specific graph shape and structure and so testing exact paths is
+// reliable.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ const dominatedByRoot = dominatorTree
+ .getImmediatelyDominated(dominatorTree.root)
+ .slice(0, 10);
+ ok(dominatedByRoot);
+ ok(dominatedByRoot.length);
+
+ const targetSet = new Set(dominatedByRoot);
+
+ const shortestPaths = snapshot.computeShortestPaths(
+ dominatorTree.root,
+ dominatedByRoot,
+ 2
+ );
+ ok(shortestPaths);
+ ok(shortestPaths instanceof Map);
+ Assert.strictEqual(shortestPaths.size, targetSet.size);
+
+ for (const [target, paths] of shortestPaths) {
+ ok(targetSet.has(target), "We should only get paths for our targets");
+ targetSet.delete(target);
+
+ ok(
+ !!paths.length,
+ "We must have at least one path, since the target is dominated by the root"
+ );
+ Assert.lessOrEqual(
+ paths.length,
+ 2,
+ "Should not have recorded more paths than the max requested"
+ );
+
+ dumpn("---------------------");
+ dumpn("Shortest paths for 0x" + target.toString(16) + ":");
+ for (const pth of paths) {
+ dumpn(" path =");
+ for (const part of pth) {
+ dumpn(
+ " predecessor: 0x" +
+ part.predecessor.toString(16) +
+ "; edge: " +
+ part.edge
+ );
+ }
+ }
+ dumpn("---------------------");
+
+ for (const path2 of paths) {
+ ok(!!path2.length, "Cannot have zero length paths");
+ Assert.strictEqual(
+ path2[0].predecessor,
+ dominatorTree.root,
+ "The first predecessor is always our start node"
+ );
+
+ for (const part of path2) {
+ ok(part.predecessor, "Each part of a path has a predecessor");
+ ok(
+ !!snapshot.describeNode(
+ { by: "count", count: true, bytes: true },
+ part.predecessor
+ ),
+ "The predecessor is in the heap snapshot"
+ );
+ ok("edge" in part, "Each part has an (potentially null) edge property");
+ }
+ }
+ }
+
+ Assert.strictEqual(
+ targetSet.size,
+ 0,
+ "We found paths for all of our targets"
+ );
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_02.js
new file mode 100644
index 0000000000..714986c601
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_computeShortestPaths_02.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test computing shortest paths with invalid arguments.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ const target = dominatorTree
+ .getImmediatelyDominated(dominatorTree.root)
+ .pop();
+ ok(target);
+
+ let threw = false;
+ try {
+ snapshot.computeShortestPaths(0, [target], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "invalid start node should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [0], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "invalid target nodes should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "empty target nodes should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [target], 0);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "0 max paths should throw");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_creationTime_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_creationTime_01.js
new file mode 100644
index 0000000000..e18c79752f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_creationTime_01.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.creationTime returns the expected time.
+
+function waitForThirtyMilliseconds() {
+ const start = Date.now();
+ while (Date.now() - start < 30) {
+ // do nothing
+ }
+}
+
+function run_test() {
+ const start = Date.now() * 1000;
+ info("start = " + start);
+
+ // Because Date.now() is less precise than the snapshot's time stamp, give it
+ // a little bit of head room. Additionally, WinXP's timer only has granularity
+ // of +/- 15ms.
+ waitForThirtyMilliseconds();
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ waitForThirtyMilliseconds();
+
+ const end = Date.now() * 1000;
+ info("end = " + end);
+
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+ info("snapshot.creationTime = " + snapshot.creationTime);
+
+ Assert.greaterOrEqual(snapshot.creationTime, start);
+ Assert.lessOrEqual(snapshot.creationTime, end);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_deepStack_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_deepStack_01.js
new file mode 100644
index 0000000000..7f88d0a142
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_deepStack_01.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can save a core dump with very deep allocation stacks and read
+// it back into a HeapSnapshot.
+
+function stackDepth(stack) {
+ return stack ? 1 + stackDepth(stack.parent) : 0;
+}
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "security.allow_parent_unrestricted_js_loads",
+ true
+ );
+ Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+ Services.prefs.setBoolPref("security.allow_eval_in_parent_process", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+ Services.prefs.clearUserPref("security.allow_eval_in_parent_process");
+ });
+
+ // Create a Debugger observing a debuggee's allocations.
+ const debuggee = new Cu.Sandbox(null);
+ const dbg = new Debugger(debuggee);
+ dbg.memory.trackingAllocationSites = true;
+
+ // Allocate some objects in the debuggee that will have their allocation
+ // stacks recorded by the Debugger.
+
+ debuggee.eval("this.objects = []");
+ debuggee.eval(
+ function recursiveAllocate(n) {
+ if (n <= 0) {
+ return;
+ }
+
+ // Make sure to recurse before pushing the object so that when TCO is
+ // implemented sometime in the future, it doesn't invalidate this test.
+ recursiveAllocate(n - 1);
+ this.objects.push({});
+ }.toString()
+ );
+ debuggee.eval("recursiveAllocate = recursiveAllocate.bind(this);");
+ debuggee.eval("recursiveAllocate(200);");
+
+ // Now save a snapshot that will include the allocation stacks and read it
+ // back again.
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(HeapSnapshot.isInstance(snapshot), "Should be an instanceof HeapSnapshot");
+
+ const report = snapshot.takeCensus({
+ breakdown: {
+ by: "allocationStack",
+ then: { by: "count", bytes: true, count: true },
+ noStack: { by: "count", bytes: true, count: true },
+ },
+ });
+
+ // Keep this synchronized with `HeapSnapshot::MAX_STACK_DEPTH`!
+ const MAX_STACK_DEPTH = 60;
+
+ let foundStacks = false;
+ report.forEach((v, k) => {
+ if (k === "noStack") {
+ return;
+ }
+
+ foundStacks = true;
+ const depth = stackDepth(k);
+ dumpn("Stack depth is " + depth);
+ Assert.lessOrEqual(
+ depth,
+ MAX_STACK_DEPTH,
+ "Every stack should have depth less than or equal to the maximum stack depth"
+ );
+ });
+ ok(foundStacks);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_describeNode_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_describeNode_01.js
new file mode 100644
index 0000000000..8597fb4edf
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_describeNode_01.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can describe nodes with a breakdown.
+
+function run_test() {
+ const path = saveNewHeapSnapshot();
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+ ok(snapshot.describeNode);
+ equal(typeof snapshot.describeNode, "function");
+
+ const dt = snapshot.computeDominatorTree();
+
+ let threw = false;
+ try {
+ snapshot.describeNode(undefined, dt.root);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "Should require a breakdown");
+
+ const breakdown = {
+ by: "coarseType",
+ objects: { by: "objectClass" },
+ scripts: { by: "internalType" },
+ strings: { by: "internalType" },
+ other: { by: "internalType" },
+ };
+
+ threw = false;
+ try {
+ snapshot.describeNode(breakdown, 0);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "Should throw when given an invalid node id");
+
+ const description = snapshot.describeNode(breakdown, dt.root);
+ ok(description);
+ ok(description.other);
+ ok(description.other["JS::ubi::RootList"]);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_getObjectNodeId_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_getObjectNodeId_01.js
new file mode 100644
index 0000000000..26997dd904
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_getObjectNodeId_01.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test ChromeUtils.getObjectNodeId()
+
+function run_test() {
+ // Create a test object, which we want to analyse
+ const testObject = {
+ foo: {
+ bar: {},
+ },
+ };
+
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ // Get the NodeId for our test object
+ const objectNodeIdRoot = ChromeUtils.getObjectNodeId(testObject);
+ const objectNodeIdFoo = ChromeUtils.getObjectNodeId(testObject.foo);
+ const objectNodeIdBar = ChromeUtils.getObjectNodeId(testObject.foo.bar);
+
+ // Also try to ensure that this is the right object via its retained path
+ const shortestPaths = snapshot.computeShortestPaths(
+ objectNodeIdRoot,
+ [objectNodeIdBar],
+ 50
+ );
+ ok(shortestPaths);
+ ok(shortestPaths instanceof Map);
+ Assert.equal(
+ shortestPaths.size,
+ 1,
+ "We get only one path between the root object and bar object"
+ );
+
+ const paths = shortestPaths.get(objectNodeIdBar);
+ Assert.equal(paths.length, 1, "There is only one path between root and bar");
+ Assert.equal(
+ paths[0].length,
+ 2,
+ "The shortest path is made of two edges: foo and bar"
+ );
+
+ const [path1, path2] = paths[0];
+ Assert.equal(
+ path1.predecessor,
+ objectNodeIdRoot,
+ "The first edge goes from the root object"
+ );
+ Assert.equal(path1.edge, "foo", "The first edge is the foo attribute");
+
+ Assert.equal(
+ path2.predecessor,
+ objectNodeIdFoo,
+ "The second edge goes from the foo object"
+ );
+ Assert.equal(path2.edge, "bar", "The first edge is the bar attribute");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_01.js
new file mode 100644
index 0000000000..f4a7836be3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_01.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus returns a value of an appropriate
+// shape. Ported from js/src/jit-tests/debug/Memory-takeCensus-01.js
+
+function run_test() {
+ const dbg = new Debugger();
+
+ function checkProperties(census) {
+ equal(typeof census, "object");
+ for (const prop of Object.getOwnPropertyNames(census)) {
+ const desc = Object.getOwnPropertyDescriptor(census, prop);
+ equal(desc.enumerable, true);
+ equal(desc.configurable, true);
+ equal(desc.writable, true);
+ if (typeof desc.value === "object") {
+ checkProperties(desc.value);
+ } else {
+ equal(typeof desc.value, "number");
+ }
+ }
+ }
+
+ checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
+
+ const g = newGlobal();
+ dbg.addDebuggee(g);
+ checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_02.js
new file mode 100644
index 0000000000..a26e9a96d8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_02.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus behaves plausibly as we allocate objects.
+//
+// Exact object counts vary in ways we can't predict. For example,
+// BaselineScripts can hold onto "template objects", which exist only to hold
+// the shape and type for newly created objects. When BaselineScripts are
+// discarded, these template objects go with them.
+//
+// So instead of expecting precise counts, we expect counts that are at least as
+// many as we would expect given the object graph we've built.
+//
+// Ported from js/src/jit-tests/debug/Memory-takeCensus-02.js
+
+function run_test() {
+ // A Debugger with no debuggees had better not find anything.
+ const dbg = new Debugger();
+ const census0 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+ function newGlobalWithDefs() {
+ const g = newGlobal();
+ g.eval(`
+ function times(n, fn) {
+ var a=[];
+ for (var i = 0; i<n; i++)
+ a.push(fn());
+ return a;
+ }
+ `);
+ return g;
+ }
+
+ // Allocate a large number of various types of objects, and check that census
+ // finds them.
+ const g = newGlobalWithDefs();
+ dbg.addDebuggee(g);
+
+ g.eval("var objs = times(100, () => ({}));");
+ g.eval("var rxs = times(200, () => /foo/);");
+ g.eval("var ars = times(400, () => []);");
+ g.eval("var fns = times(800, () => () => {});");
+
+ const census1 = dbg.memory.takeCensus(dbg);
+ Census.walkCensus(
+ census1,
+ "census1",
+ Census.assertAllNotLessThan({
+ objects: {
+ Object: { count: 100 },
+ RegExp: { count: 200 },
+ Array: { count: 400 },
+ Function: { count: 800 },
+ },
+ })
+ );
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_03.js
new file mode 100644
index 0000000000..06c574d4e0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_03.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus behaves plausibly as we add and remove
+// debuggees.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-03.js
+
+function run_test() {
+ const dbg = new Debugger();
+
+ const census0 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+ const g1 = newGlobal();
+ dbg.addDebuggee(g1);
+ const census1 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
+
+ const g2 = newGlobal();
+ dbg.addDebuggee(g2);
+ const census2 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1));
+
+ dbg.removeDebuggee(g2);
+ const census3 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census3, "census3", Census.assertAllEqual(census1));
+
+ dbg.removeDebuggee(g1);
+ const census4 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census4, "census4", Census.assertAllEqual(census0));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_04.js
new file mode 100644
index 0000000000..ac484c8815
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_04.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that HeapSnapshot.prototype.takeCensus finds GC roots that are on the
+// stack.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-04.js
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ g.eval(`
+function withAllocationMarkerOnStack(f) {
+ (function () {
+ var onStack = allocationMarker();
+ f();
+ }());
+}
+`);
+
+ equal(
+ "AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects,
+ false,
+ "There shouldn't exist any allocation markers in the census."
+ );
+
+ let allocationMarkerCount;
+ g.withAllocationMarkerOnStack(() => {
+ const census = saveHeapSnapshotAndTakeCensus(dbg);
+ allocationMarkerCount = census.objects.AllocationMarker.count;
+ });
+
+ equal(
+ allocationMarkerCount,
+ 1,
+ "Should have one allocation marker in the census, because there " +
+ "was one on the stack."
+ );
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_05.js
new file mode 100644
index 0000000000..22c5324c68
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_05.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that HeapSnapshot.prototype.takeCensus finds cross compartment
+// wrapper GC roots.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-05.js
+
+/* eslint-disable strict */
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ equal(
+ "AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects,
+ false,
+ "No allocation markers should exist in the census."
+ );
+
+ this.ccw = g.allocationMarker();
+
+ const census = saveHeapSnapshotAndTakeCensus(dbg);
+ equal(
+ census.objects.AllocationMarker.count,
+ 1,
+ "Should have one allocation marker in the census, because there " +
+ "is one cross-compartment wrapper referring to it."
+ );
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_06.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_06.js
new file mode 100644
index 0000000000..2b639411f9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_06.js
@@ -0,0 +1,127 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Check HeapSnapshot.prototype.takeCensus handling of 'breakdown' argument.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-06.js
+
+function run_test() {
+ const Pattern = Match.Pattern;
+
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ Pattern({ count: Pattern.NATURAL, bytes: Pattern.NATURAL }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count" } })
+ );
+
+ let census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", count: false, bytes: false },
+ });
+ equal("count" in census, false);
+ equal("bytes" in census, false);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", count: true, bytes: false },
+ });
+ equal("count" in census, true);
+ equal("bytes" in census, false);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", count: false, bytes: true },
+ });
+ equal("count" in census, false);
+ equal("bytes" in census, true);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", count: true, bytes: true },
+ });
+ equal("count" in census, true);
+ equal("bytes" in census, true);
+
+ // Pattern doesn't mind objects with extra properties, so we'll restrict this
+ // list to the object classes we're pretty sure are going to stick around for
+ // the forseeable future.
+ Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ DebuggerPrototype: { count: Pattern.NATURAL },
+ Sandbox: { count: Pattern.NATURAL },
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass" } })
+ );
+
+ Pattern({
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL },
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "coarseType" } })
+ );
+
+ // As for { by: 'objectClass' }, restrict our pattern to the types
+ // we predict will stick around for a long time.
+ Pattern({
+ JSString: { count: Pattern.NATURAL },
+ "js::Shape": { count: Pattern.NATURAL },
+ JSObject: { count: Pattern.NATURAL },
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "internalType" } })
+ );
+
+ // Nested breakdowns.
+
+ const coarseTypePattern = {
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL },
+ };
+
+ Pattern({
+ JSString: coarseTypePattern,
+ "js::Shape": coarseTypePattern,
+ JSObject: coarseTypePattern,
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "internalType", then: { by: "coarseType" } },
+ })
+ );
+
+ Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ DebuggerPrototype: { count: Pattern.NATURAL },
+ Sandbox: { count: Pattern.NATURAL },
+ other: coarseTypePattern,
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "objectClass",
+ then: { by: "count" },
+ other: { by: "coarseType" },
+ },
+ })
+ );
+
+ Pattern({
+ objects: { count: Pattern.NATURAL, label: "object" },
+ scripts: { count: Pattern.NATURAL, label: "scripts" },
+ strings: { count: Pattern.NATURAL, label: "strings" },
+ other: { count: Pattern.NATURAL, label: "other" },
+ }).assert(
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ objects: { by: "count", label: "object" },
+ scripts: { by: "count", label: "scripts" },
+ strings: { by: "count", label: "strings" },
+ other: { by: "count", label: "other" },
+ },
+ })
+ );
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_07.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_07.js
new file mode 100644
index 0000000000..56bec51074
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_07.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus breakdown: check error handling on property
+// gets.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-07.js
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ get by() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "count",
+ get count() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "count",
+ get bytes() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "objectClass",
+ get then() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "objectClass",
+ get other() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ get objects() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ get scripts() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ get strings() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ get other() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ assertThrows(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "internalType",
+ get then() {
+ throw Error("ಠ_ಠ");
+ },
+ },
+ });
+ }, "ಠ_ಠ");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_08.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_08.js
new file mode 100644
index 0000000000..f40d367cd6
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_08.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus: test by: 'count' breakdown
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-08.js
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ g.eval(`
+ var stuff = [];
+ function add(n, c) {
+ for (let i = 0; i < n; i++)
+ stuff.push(c());
+ }
+
+ let count = 0;
+
+ function obj() { return { count: count++ }; }
+ obj.factor = 1;
+
+ // This creates a closure (a function JSObject) that has captured
+ // a Call object. So each call creates two items.
+ function fun() { let v = count; return () => { return v; } }
+ fun.factor = 2;
+
+ function str() { return 'perambulator' + count++; }
+ str.factor = 1;
+
+ // Eval a fresh text each time, allocating:
+ // - a fresh ScriptSourceObject
+ // - a new JSScripts, not an eval cache hits
+ // - a fresh prototype object
+ // - a fresh Call object, since the eval makes 'ev' heavyweight
+ // - the new function itself
+ function ev() {
+ return eval(\`(function () { return \${ count++ } })\`);
+ }
+ ev.factor = 5;
+
+ // A new object (1) with a new shape (2) with a new atom (3)
+ function shape() { return { [ 'theobroma' + count++ ]: count }; }
+ shape.factor = 3;
+ `);
+
+ let baseline = 0;
+ function countIncreasedByAtLeast(n) {
+ const oldBaseline = baseline;
+
+ // Since a census counts only reachable objects, one might assume that calling
+ // GC here would have no effect on the census results. But GC also throws away
+ // JIT code and any objects it might be holding (template objects, say);
+ // takeCensus reaches those. Shake everything loose that we can, to make the
+ // census approximate reachability a bit more closely, and make our results a
+ // bit more predictable.
+ gc(g, "shrinking");
+
+ baseline = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count" },
+ }).count;
+ return baseline >= oldBaseline + n;
+ }
+
+ countIncreasedByAtLeast(0);
+
+ g.add(100, g.obj);
+ ok(countIncreasedByAtLeast(g.obj.factor * 100));
+
+ g.add(100, g.fun);
+ ok(countIncreasedByAtLeast(g.fun.factor * 100));
+
+ g.add(100, g.str);
+ ok(countIncreasedByAtLeast(g.str.factor * 100));
+
+ g.add(100, g.ev);
+ ok(countIncreasedByAtLeast(g.ev.factor * 100));
+
+ g.add(100, g.shape);
+ ok(countIncreasedByAtLeast(g.shape.factor * 100));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_09.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_09.js
new file mode 100644
index 0000000000..99830d75cb
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_09.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// HeapSnapshot.prototype.takeCensus: by: allocationStack breakdown
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-09.js
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ g.eval(` // 1
+ var log = []; // 2
+ function f() { log.push(allocationMarker()); } // 3
+ function g() { f(); } // 4
+ function h() { f(); } // 5
+ `);
+
+ // Create one allocationMarker with tracking turned off,
+ // so it will have no associated stack.
+ g.f();
+
+ dbg.memory.allocationSamplingProbability = 1;
+
+ for (const [func, n] of [
+ [g.f, 20],
+ [g.g, 10],
+ [g.h, 5],
+ ]) {
+ for (let i = 0; i < n; i++) {
+ dbg.memory.trackingAllocationSites = true;
+ // All allocations of allocationMarker occur with this line as the oldest
+ // stack frame.
+ func();
+ dbg.memory.trackingAllocationSites = false;
+ }
+ }
+
+ const census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: { by: "count", label: "haz stack" },
+ noStack: {
+ by: "count",
+ label: "no haz stack",
+ },
+ },
+ },
+ });
+
+ const map = census.AllocationMarker;
+ ok(map instanceof Map, "Should be a Map instance");
+ equal(
+ map.size,
+ 4,
+ "Should have 4 allocation stacks (including the lack of a stack)"
+ );
+
+ // Gather the stacks we are expecting to appear as keys, and
+ // check that there are no unexpected keys.
+ const stacks = {};
+
+ map.forEach((v, k) => {
+ if (k === "noStack") {
+ // No need to save this key.
+ } else if (
+ k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "run_test"
+ ) {
+ stacks.f = k;
+ } else if (
+ k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "g" &&
+ k.parent.parent.functionDisplayName === "run_test"
+ ) {
+ stacks.fg = k;
+ } else if (
+ k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "h" &&
+ k.parent.parent.functionDisplayName === "run_test"
+ ) {
+ stacks.fh = k;
+ } else {
+ dumpn("Unexpected allocation stack:");
+ k.toString()
+ .split(/\n/g)
+ .forEach(s => dumpn(s));
+ ok(false);
+ }
+ });
+
+ equal(map.get("noStack").label, "no haz stack");
+ equal(map.get("noStack").count, 1);
+
+ ok(stacks.f);
+ equal(map.get(stacks.f).label, "haz stack");
+ equal(map.get(stacks.f).count, 20);
+
+ ok(stacks.fg);
+ equal(map.get(stacks.fg).label, "haz stack");
+ equal(map.get(stacks.fg).count, 10);
+
+ ok(stacks.fh);
+ equal(map.get(stacks.fh).label, "haz stack");
+ equal(map.get(stacks.fh).count, 5);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_10.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_10.js
new file mode 100644
index 0000000000..9b0e0f8c74
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_10.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Check byte counts produced by takeCensus.
+//
+// Note that tracking allocation sites adds unique IDs to objects which
+// increases their size, making it hard to test reported sizes exactly.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-take Census-10.js
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ const sizeOfAM = byteSize(allocationMarker());
+
+ // Allocate a single allocation marker, and check that we can find it.
+ g.eval("var hold = allocationMarker();");
+ let census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "objectClass" },
+ });
+ equal(census.AllocationMarker.count, 1);
+ equal(census.AllocationMarker.bytes >= sizeOfAM, true);
+ g.hold = null;
+
+ g.eval(` // 1
+ var objs = []; // 2
+ function fnerd() { // 3
+ objs.push(allocationMarker()); // 4
+ for (let i = 0; i < 10; i++) // 5
+ objs.push(allocationMarker()); // 6
+ } // 7
+ `);
+
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+ g.fnerd();
+ dbg.memory.trackingAllocationSites = false;
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "objectClass", then: { by: "allocationStack" } },
+ });
+
+ let seen = 0;
+ census.AllocationMarker.forEach((v, k) => {
+ equal(k.functionDisplayName, "fnerd");
+ switch (k.line) {
+ case 4:
+ equal(v.count, 1);
+ equal(v.bytes >= sizeOfAM, true);
+ seen++;
+ break;
+
+ case 6:
+ equal(v.count, 10);
+ equal(v.bytes >= 10 * sizeOfAM, true);
+ seen++;
+ break;
+
+ default:
+ dumpn("Unexpected stack:");
+ k.toString()
+ .split(/\n/g)
+ .forEach(s => dumpn(s));
+ ok(false);
+ break;
+ }
+ });
+
+ equal(seen, 2);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_11.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_11.js
new file mode 100644
index 0000000000..151437bb41
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_11.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that Debugger.Memory.prototype.takeCensus and
+// HeapSnapshot.prototype.takeCensus return the same data for the same heap
+// graph.
+
+function doLiveAndOfflineCensus(g, dbg, opts) {
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+ g.eval(` // 1
+ (function unsafeAtAnySpeed() { // 2
+ for (var i = 0; i < 100; i++) { // 3
+ this.markers.push(allocationMarker()); // 4
+ } // 5
+ }()); // 6
+ `);
+ dbg.memory.trackingAllocationSites = false;
+
+ return {
+ live: dbg.memory.takeCensus(opts),
+ offline: saveHeapSnapshotAndTakeCensus(dbg, opts),
+ };
+}
+
+function getMarkerSize(g, dbg) {
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+ g.eval("var hold = allocationMarker();");
+ dbg.memory.trackingAllocationSites = false;
+ const live = dbg.memory.takeCensus({
+ breakdown: { by: "objectClass", then: { by: "count" } },
+ });
+ g.hold = null;
+ equal(live.AllocationMarker.count, 1);
+ return live.AllocationMarker.bytes;
+}
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ g.eval("this.markers = []");
+ const markerSize = getMarkerSize(g, dbg);
+
+ // First, test that we get the same counts and sizes as we allocate and retain
+ // more things.
+
+ let prevCount = 0;
+ let prevBytes = 0;
+
+ for (let i = 0; i < 10; i++) {
+ const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
+ breakdown: { by: "objectClass", then: { by: "count" } },
+ });
+
+ equal(live.AllocationMarker.count, offline.AllocationMarker.count);
+ equal(live.AllocationMarker.bytes, offline.AllocationMarker.bytes);
+ equal(live.AllocationMarker.count, prevCount + 100);
+ equal(live.AllocationMarker.bytes, prevBytes + 100 * markerSize);
+
+ prevCount = live.AllocationMarker.count;
+ prevBytes = live.AllocationMarker.bytes;
+ }
+
+ // Second, test that the reported allocation stacks and counts and sizes at
+ // those allocation stacks match up.
+
+ const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
+ breakdown: { by: "objectClass", then: { by: "allocationStack" } },
+ });
+
+ equal(live.AllocationMarker.size, offline.AllocationMarker.size);
+ // One stack with the loop further above, and another stack featuring the call
+ // right above.
+ equal(live.AllocationMarker.size, 2);
+
+ // Note that because SavedFrame stacks reconstructed from an offline heap
+ // snapshot don't have the same principals as SavedFrame stacks captured from
+ // a live stack, the live and offline allocation stacks won't be identity
+ // equal, but should be structurally the same.
+
+ const liveEntries = [];
+ live.AllocationMarker.forEach((v, k) => {
+ dumpn("Allocation stack:");
+ k.toString()
+ .split(/\n/g)
+ .forEach(s => dumpn(s));
+
+ equal(k.functionDisplayName, "unsafeAtAnySpeed");
+ equal(k.line, 4);
+
+ liveEntries.push([k.toString(), v]);
+ });
+
+ const offlineEntries = [];
+ offline.AllocationMarker.forEach((v, k) => {
+ dumpn("Allocation stack:");
+ k.toString()
+ .split(/\n/g)
+ .forEach(s => dumpn(s));
+
+ equal(k.functionDisplayName, "unsafeAtAnySpeed");
+ equal(k.line, 4);
+
+ offlineEntries.push([k.toString(), v]);
+ });
+
+ const sortEntries = (a, b) => {
+ if (a[0] < b[0]) {
+ return -1;
+ } else if (a[0] > b[0]) {
+ return 1;
+ }
+ return 0;
+ };
+ liveEntries.sort(sortEntries);
+ offlineEntries.sort(sortEntries);
+
+ equal(liveEntries.length, live.AllocationMarker.size);
+ equal(liveEntries.length, offlineEntries.length);
+
+ for (let i = 0; i < liveEntries.length; i++) {
+ equal(liveEntries[i][0], offlineEntries[i][0]);
+ equal(liveEntries[i][1].count, offlineEntries[i][1].count);
+ equal(liveEntries[i][1].bytes, offlineEntries[i][1].bytes);
+ }
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_12.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_12.js
new file mode 100644
index 0000000000..f3a72102b0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_HeapSnapshot_takeCensus_12.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that when we take a census and get a bucket list of ids that matched the
+// given category, that the returned ids are all in the snapshot and their
+// reported category.
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ const path = saveNewHeapSnapshot({ debugger: dbg });
+ const snapshot = readHeapSnapshot(path);
+
+ const bucket = { by: "bucket" };
+ const count = { by: "count", count: true, bytes: false };
+ const objectClassCount = { by: "objectClass", then: count, other: count };
+
+ const byClassName = snapshot.takeCensus({
+ breakdown: {
+ by: "objectClass",
+ then: bucket,
+ other: bucket,
+ },
+ });
+
+ const byClassNameCount = snapshot.takeCensus({
+ breakdown: objectClassCount,
+ });
+
+ const keys = new Set(Object.keys(byClassName));
+ equal(
+ keys.size,
+ Object.keys(byClassNameCount).length,
+ "Should have the same number of keys."
+ );
+ for (const k of Object.keys(byClassNameCount)) {
+ ok(keys.has(k), "Should not have any unexpected class names");
+ }
+
+ for (const key of Object.keys(byClassName)) {
+ equal(
+ byClassNameCount[key].count,
+ byClassName[key].length,
+ "Length of the bucket and count should be equal"
+ );
+
+ for (const id of byClassName[key]) {
+ const desc = snapshot.describeNode(objectClassCount, id);
+ equal(
+ desc[key].count,
+ 1,
+ "Describing the bucketed node confirms that it belongs to the category"
+ );
+ }
+ }
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot.js
new file mode 100644
index 0000000000..b7a3c9c2f8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can read core dumps into HeapSnapshot instances.
+/* eslint-disable strict */
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+ );
+ addDebuggerToGlobal(globalThis);
+}
+
+function run_test() {
+ const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(HeapSnapshot.isInstance(snapshot), "Should be an instanceof HeapSnapshot");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_allocations.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_allocations.js
new file mode 100644
index 0000000000..273736abcd
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_allocations.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can save a core dump with allocation stacks and read it back
+// into a HeapSnapshot.
+
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+ );
+ addDebuggerToGlobal(globalThis);
+}
+
+function run_test() {
+ Services.prefs.setBoolPref(
+ "security.allow_parent_unrestricted_js_loads",
+ true
+ );
+ Services.prefs.setBoolPref("security.allow_eval_with_system_principal", true);
+ Services.prefs.setBoolPref("security.allow_eval_in_parent_process", true);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("security.allow_parent_unrestricted_js_loads");
+ Services.prefs.clearUserPref("security.allow_eval_with_system_principal");
+ Services.prefs.clearUserPref("security.allow_eval_in_parent_process");
+ });
+
+ // Create a Debugger observing a debuggee's allocations.
+ const debuggee = new Cu.Sandbox(null);
+ const dbg = new Debugger(debuggee);
+ dbg.memory.trackingAllocationSites = true;
+
+ // Allocate some objects in the debuggee that will have their allocation
+ // stacks recorded by the Debugger.
+ debuggee.eval("this.objects = []");
+ for (let i = 0; i < 100; i++) {
+ debuggee.eval("this.objects.push({})");
+ }
+
+ // Now save a snapshot that will include the allocation stacks and read it
+ // back again.
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(HeapSnapshot.isInstance(snapshot), "Should be an instanceof HeapSnapshot");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_utf8_paths.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_utf8_paths.js
new file mode 100644
index 0000000000..aecbd9cdda
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_with_utf8_paths.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can read core dumps with a UTF8 path into HeapSnapshot instances.
+/* eslint-disable strict */
+add_task(async function () {
+ const fileNameWithRussianCharacters =
+ "Снимок памяти Click.ru 08.06.2020 (Firefox dump).fxsnapshot";
+ const filePathWithRussianCharacters = PathUtils.join(
+ PathUtils.tempDir,
+ fileNameWithRussianCharacters
+ );
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot.");
+
+ await IOUtils.copy(filePath, filePathWithRussianCharacters);
+
+ ok(
+ await IOUtils.exists(filePathWithRussianCharacters),
+ `We could copy the file to the expected path ${filePathWithRussianCharacters}`
+ );
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePathWithRussianCharacters);
+ ok(snapshot, "Should be able to read a heap snapshot from an utf8 path");
+ ok(HeapSnapshot.isInstance(snapshot), "Should be an instanceof HeapSnapshot");
+});
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_worker.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_worker.js
new file mode 100644
index 0000000000..d7427a3367
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_ReadHeapSnapshot_worker.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that we can read core dumps into HeapSnapshot instances in a worker.
+add_task(async function () {
+ const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js");
+ worker.postMessage({});
+
+ let assertionCount = 0;
+ worker.onmessage = e => {
+ if (e.data.type !== "assertion") {
+ return;
+ }
+
+ ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
+ assertionCount++;
+ };
+
+ await waitForDone(worker);
+
+ Assert.greater(assertionCount, 0);
+ worker.terminate();
+});
+
+function waitForDone(w) {
+ return new Promise((resolve, reject) => {
+ w.onerror = e => {
+ reject();
+ ok(false, "Error in worker: " + e);
+ };
+
+ w.addEventListener("message", function listener(e) {
+ if (e.data.type === "done") {
+ w.removeEventListener("message", listener);
+ resolve();
+ }
+ });
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_SaveHeapSnapshot.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_SaveHeapSnapshot.js
new file mode 100644
index 0000000000..33367af7f7
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_SaveHeapSnapshot.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the ChromeUtils interface.
+// eslint-disable-next-line
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = ChromeUtils.importESModule(
+ "resource://gre/modules/jsdebugger.sys.mjs"
+ );
+ addDebuggerToGlobal(globalThis);
+}
+
+function run_test() {
+ ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
+
+ testBadParameters();
+ testGoodParameters();
+
+ do_test_finished();
+}
+
+function testBadParameters() {
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot(),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if arguments aren't passed in."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot(null),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if boundaries isn't an object."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({}),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if the boundaries object doesn't have any properties."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ runtime: true, globals: [this] }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if the boundaries object has more than one property."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ debugger: {} }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if the debuggees object is not a Debugger object"
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ globals: [{}] }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if the globals array contains non-global objects."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ runtime: false }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if runtime is supplied and is not true."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ globals: null }),
+ /TypeError:.*can't be converted to a sequence/,
+ "Should throw if globals is not an object."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ globals: {} }),
+ /TypeError:.*can't be converted to a sequence/,
+ "Should throw if globals is not an array."
+ );
+
+ Assert.throws(
+ () => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if debugger is the Debugger.prototype object."
+ );
+
+ Assert.throws(
+ () =>
+ ChromeUtils.saveHeapSnapshot({
+ get globals() {
+ return [this];
+ },
+ }),
+ /NS_ERROR_ILLEGAL_VALUE/,
+ "Should throw if boundaries property is a getter."
+ );
+}
+
+const makeNewSandbox = () =>
+ Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
+
+function testGoodParameters() {
+ const sandbox = makeNewSandbox();
+ let dbg = new Debugger(sandbox);
+
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot for a debuggee global.");
+
+ dbg = new Debugger();
+ const sandboxes = Array(10).fill(null).map(makeNewSandbox);
+ sandboxes.forEach(sb => dbg.addDebuggee(sb));
+
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot for many debuggee globals.");
+
+ dbg = new Debugger();
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot with no debuggee globals.");
+
+ ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot for a specific global.");
+
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot of the full runtime.");
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-01.js
new file mode 100644
index 0000000000..625d359675
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-01.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests CensusTreeNode with `internalType` breakdown.
+ */
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+};
+
+const REPORT = {
+ JSObject: {
+ bytes: 100,
+ count: 10,
+ },
+ "js::Shape": {
+ bytes: 500,
+ count: 50,
+ },
+ JSString: {
+ bytes: 10,
+ count: 1,
+ },
+};
+
+const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 610,
+ count: 0,
+ totalCount: 61,
+ children: [
+ {
+ name: "js::Shape",
+ bytes: 500,
+ totalBytes: 500,
+ count: 50,
+ totalCount: 50,
+ children: undefined,
+ id: 3,
+ parent: 1,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "JSObject",
+ bytes: 100,
+ totalBytes: 100,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 2,
+ parent: 1,
+ reportLeafIndex: 1,
+ },
+ {
+ name: "JSString",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 4,
+ parent: 1,
+ reportLeafIndex: 3,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-02.js
new file mode 100644
index 0000000000..75ed87c25b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-02.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests CensusTreeNode with `coarseType` breakdown.
+ */
+
+const countBreakdown = { by: "count", count: true, bytes: true };
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: countBreakdown },
+ strings: countBreakdown,
+ scripts: countBreakdown,
+ other: { by: "internalType", then: countBreakdown },
+ domNode: countBreakdown,
+};
+
+const REPORT = {
+ objects: {
+ Function: { bytes: 10, count: 1 },
+ Array: { bytes: 20, count: 2 },
+ },
+ strings: { bytes: 10, count: 1 },
+ scripts: { bytes: 1, count: 1 },
+ other: {
+ "js::Shape": { bytes: 30, count: 3 },
+ "js::Shape2": { bytes: 40, count: 4 },
+ },
+ domNode: { bytes: 0, count: 0 },
+};
+
+const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 111,
+ count: 0,
+ totalCount: 12,
+ children: [
+ {
+ name: "other",
+ count: 0,
+ totalCount: 7,
+ bytes: 0,
+ totalBytes: 70,
+ children: [
+ {
+ name: "js::Shape2",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 9,
+ parent: 7,
+ reportLeafIndex: 8,
+ },
+ {
+ name: "js::Shape",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 8,
+ parent: 7,
+ reportLeafIndex: 7,
+ },
+ ],
+ id: 7,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "objects",
+ count: 0,
+ totalCount: 3,
+ bytes: 0,
+ totalBytes: 30,
+ children: [
+ {
+ name: "Array",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 4,
+ parent: 2,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Function",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 3,
+ parent: 2,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "strings",
+ count: 1,
+ totalCount: 1,
+ bytes: 10,
+ totalBytes: 10,
+ children: undefined,
+ id: 6,
+ parent: 1,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "scripts",
+ count: 1,
+ totalCount: 1,
+ bytes: 1,
+ totalBytes: 1,
+ children: undefined,
+ id: 5,
+ parent: 1,
+ reportLeafIndex: 4,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-03.js
new file mode 100644
index 0000000000..e0ca7dc15d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-03.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests CensusTreeNode with `objectClass` breakdown.
+ */
+
+const countBreakdown = { by: "count", count: true, bytes: true };
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: countBreakdown,
+ other: { by: "internalType", then: countBreakdown },
+};
+
+const REPORT = {
+ Function: { bytes: 10, count: 10 },
+ Array: { bytes: 100, count: 1 },
+ other: {
+ "JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
+ "JIT::CODE::LATER!!!": { bytes: 40, count: 4 },
+ },
+};
+
+const EXPECTED = {
+ name: null,
+ count: 0,
+ totalCount: 17,
+ bytes: 0,
+ totalBytes: 170,
+ children: [
+ {
+ name: "Array",
+ bytes: 100,
+ totalBytes: 100,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 3,
+ parent: 1,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "other",
+ count: 0,
+ totalCount: 6,
+ bytes: 0,
+ totalBytes: 60,
+ children: [
+ {
+ name: "JIT::CODE::LATER!!!",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 6,
+ parent: 4,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "JIT::CODE::NOW!!!",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 4,
+ },
+ ],
+ id: 4,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "Function",
+ bytes: 10,
+ totalBytes: 10,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 2,
+ parent: 1,
+ reportLeafIndex: 1,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-04.js
new file mode 100644
index 0000000000..e1cc32d697
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-04.js
@@ -0,0 +1,160 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests CensusTreeNode with `allocationStack` breakdown.
+ */
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ let stack1, stack2, stack3, stack4;
+
+ (function a() {
+ (function b() {
+ (function c() {
+ stack1 = saveStack(3);
+ })();
+ (function d() {
+ stack2 = saveStack(3);
+ stack3 = saveStack(3);
+ })();
+ stack4 = saveStack(2);
+ })();
+ })();
+
+ const stack5 = saveStack(1);
+
+ const REPORT = new Map([
+ [stack1, { bytes: 10, count: 1 }],
+ [stack2, { bytes: 20, count: 2 }],
+ [stack3, { bytes: 30, count: 3 }],
+ [stack4, { bytes: 40, count: 4 }],
+ [stack5, { bytes: 50, count: 5 }],
+ ["noStack", { bytes: 60, count: 6 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 210,
+ count: 0,
+ totalCount: 21,
+ children: [
+ {
+ name: stack4.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack3.parent,
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: stack3,
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 7,
+ parent: 5,
+ reportLeafIndex: 3,
+ },
+ {
+ name: stack2,
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 6,
+ parent: 5,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 5,
+ parent: 2,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: stack4,
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 8,
+ parent: 2,
+ reportLeafIndex: 4,
+ },
+ {
+ name: stack1.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: stack1,
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 4,
+ parent: 3,
+ reportLeafIndex: 1,
+ },
+ ],
+ id: 3,
+ parent: 2,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "noStack",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ children: undefined,
+ id: 10,
+ parent: 1,
+ reportLeafIndex: 6,
+ },
+ {
+ name: stack5,
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 9,
+ parent: 1,
+ reportLeafIndex: 5,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-05.js
new file mode 100644
index 0000000000..fbd9944507
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-05.js
@@ -0,0 +1,150 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Tests CensusTreeNode with `allocationStack` => `objectClass` breakdown.
+ */
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: {
+ by: "objectClass",
+ then: countBreakdown,
+ other: countBreakdown,
+ },
+ noStack: countBreakdown,
+ };
+
+ let stack;
+
+ (function a() {
+ (function b() {
+ (function c() {
+ stack = saveStack(3);
+ })();
+ })();
+ })();
+
+ const REPORT = new Map([
+ [
+ stack,
+ {
+ Foo: { bytes: 10, count: 1 },
+ Bar: { bytes: 20, count: 2 },
+ Baz: { bytes: 30, count: 3 },
+ other: { bytes: 40, count: 4 },
+ },
+ ],
+ ["noStack", { bytes: 50, count: 5 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 150,
+ count: 0,
+ totalCount: 15,
+ children: [
+ {
+ name: stack.parent.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "other",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 8,
+ parent: 4,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "Baz",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 7,
+ parent: 4,
+ reportLeafIndex: 4,
+ },
+ {
+ name: "Bar",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 6,
+ parent: 4,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Foo",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 4,
+ parent: 3,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 3,
+ parent: 2,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "noStack",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 9,
+ parent: 1,
+ reportLeafIndex: 6,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-06.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-06.js
new file mode 100644
index 0000000000..70cc4a2696
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-06.js
@@ -0,0 +1,199 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test inverting CensusTreeNode with a by alloaction stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+ };
+
+ function a(n) {
+ return b(n);
+ }
+ function b(n) {
+ return c(n);
+ }
+ function c(n) {
+ return saveStack(n);
+ }
+ function d(n) {
+ return b(n);
+ }
+ function e(n) {
+ return c(n);
+ }
+
+ const abc_Stack = a(3);
+ const bc_Stack = b(2);
+ const c_Stack = c(1);
+ const dbc_Stack = d(3);
+ const ec_Stack = e(2);
+
+ const REPORT = new Map([
+ [abc_Stack, { bytes: 10, count: 1 }],
+ [bc_Stack, { bytes: 10, count: 1 }],
+ [c_Stack, { bytes: 10, count: 1 }],
+ [dbc_Stack, { bytes: 10, count: 1 }],
+ [ec_Stack, { bytes: 10, count: 1 }],
+ ["noStack", { bytes: 50, count: 5 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "noStack",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 16,
+ parent: 15,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 15,
+ parent: 14,
+ reportLeafIndex: 6,
+ },
+ {
+ name: abc_Stack,
+ bytes: 50,
+ totalBytes: 10,
+ count: 5,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 18,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: abc_Stack.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 22,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: abc_Stack.parent.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 21,
+ parent: 20,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 20,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: dbc_Stack.parent.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 24,
+ parent: 23,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 23,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 19,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: ec_Stack.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 26,
+ parent: 25,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 25,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 17,
+ parent: 14,
+ reportLeafIndex: new Set([1, 2, 3, 4, 5]),
+ },
+ ],
+ id: 14,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-07.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-07.js
new file mode 100644
index 0000000000..009848b9e6
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-07.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test inverting CensusTreeNode with a non-allocation stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ },
+ domNode: {},
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: [
+ {
+ name: "js::Shape",
+ bytes: 80,
+ totalBytes: 80,
+ count: 8,
+ totalCount: 8,
+ children: [
+ {
+ name: "other",
+ bytes: 0,
+ totalBytes: 80,
+ count: 0,
+ totalCount: 8,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 15,
+ parent: 14,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 14,
+ parent: 13,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 13,
+ parent: 12,
+ reportLeafIndex: 9,
+ },
+ {
+ name: "JSAtom",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ children: [
+ {
+ name: "strings",
+ bytes: 0,
+ totalBytes: 60,
+ count: 0,
+ totalCount: 6,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 18,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 17,
+ parent: 16,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 16,
+ parent: 12,
+ reportLeafIndex: 7,
+ },
+ {
+ name: "Array",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 21,
+ parent: 20,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 20,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 19,
+ parent: 12,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "js::jit::JitScript",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: [
+ {
+ name: "scripts",
+ bytes: 0,
+ totalBytes: 30,
+ count: 0,
+ totalCount: 3,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 24,
+ parent: 23,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 23,
+ parent: 22,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 22,
+ parent: 12,
+ reportLeafIndex: 5,
+ },
+ ],
+ id: 12,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-08.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-08.js
new file mode 100644
index 0000000000..0e8e2ebcc1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-08.js
@@ -0,0 +1,143 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test inverting CensusTreeNode with a non-allocation stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ "http://example.com/app.js": {
+ JSScript: { count: 10, bytes: 100 },
+ },
+ "http://example.com/ads.js": {
+ "js::LazyScript": { count: 20, bytes: 200 },
+ },
+ "http://example.com/trackers.js": {
+ JSScript: { count: 30, bytes: 300 },
+ },
+ noFilename: {
+ "js::jit::JitCode": { count: 40, bytes: 400 },
+ },
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 1000,
+ count: 0,
+ totalCount: 100,
+ children: [
+ {
+ name: "noFilename",
+ bytes: 0,
+ totalBytes: 400,
+ count: 0,
+ totalCount: 40,
+ children: [
+ {
+ name: "js::jit::JitCode",
+ bytes: 400,
+ totalBytes: 400,
+ count: 40,
+ totalCount: 40,
+ children: undefined,
+ id: 9,
+ parent: 8,
+ reportLeafIndex: 8,
+ },
+ ],
+ id: 8,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/trackers.js",
+ bytes: 0,
+ totalBytes: 300,
+ count: 0,
+ totalCount: 30,
+ children: [
+ {
+ name: "JSScript",
+ bytes: 300,
+ totalBytes: 300,
+ count: 30,
+ totalCount: 30,
+ children: undefined,
+ id: 7,
+ parent: 6,
+ reportLeafIndex: 6,
+ },
+ ],
+ id: 6,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/ads.js",
+ bytes: 0,
+ totalBytes: 200,
+ count: 0,
+ totalCount: 20,
+ children: [
+ {
+ name: "js::LazyScript",
+ bytes: 200,
+ totalBytes: 200,
+ count: 20,
+ totalCount: 20,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 4,
+ },
+ ],
+ id: 4,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/app.js",
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "JSScript",
+ bytes: 100,
+ totalBytes: 100,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 3,
+ parent: 2,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-09.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-09.js
new file mode 100644
index 0000000000..5f91e5e67b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-09.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that repeatedly converting the same census report to a CensusTreeNode
+ * tree results in the same CensusTreeNode tree.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ "http://example.com/app.js": {
+ JSScript: { count: 10, bytes: 100 },
+ },
+ "http://example.com/ads.js": {
+ "js::LazyScript": { count: 20, bytes: 200 },
+ },
+ "http://example.com/trackers.js": {
+ JSScript: { count: 30, bytes: 300 },
+ },
+ noFilename: {
+ "js::jit::JitCode": { count: 40, bytes: 400 },
+ },
+ };
+
+ const first = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ const second = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ const third = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+
+ assertStructurallyEquivalent(first, second);
+ assertStructurallyEquivalent(second, third);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-10.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-10.js
new file mode 100644
index 0000000000..ba783f8e47
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census-tree-node-10.js
@@ -0,0 +1,51 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test when multiple leaves in the census report map to the same node in an
+ * inverted CensusReportTree.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ domNode: { by: "count", count: true, bytes: true },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { count: 1, bytes: 10 },
+ },
+ other: {
+ Array: { count: 1, bytes: 10 },
+ },
+ strings: { count: 0, bytes: 0 },
+ scripts: { count: 0, bytes: 0 },
+ domNode: { count: 0, bytes: 0 },
+ };
+
+ const node = censusReportToCensusTreeNode(BREAKDOWN, REPORT, {
+ invert: true,
+ });
+
+ equal(node.children[0].name, "Array");
+ equal(node.children[0].reportLeafIndex.size, 2);
+ dumpn(
+ `node.children[0].reportLeafIndex = ${[
+ ...node.children[0].reportLeafIndex,
+ ]}`
+ );
+ ok(node.children[0].reportLeafIndex.has(2));
+ ok(node.children[0].reportLeafIndex.has(6));
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_01.js
new file mode 100644
index 0000000000..c90b34af70
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_01.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of breakdown by "internalType".
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+};
+
+const REPORT1 = {
+ JSObject: {
+ count: 10,
+ bytes: 100,
+ },
+ "js::Shape": {
+ count: 50,
+ bytes: 500,
+ },
+ JSString: {
+ count: 0,
+ bytes: 0,
+ },
+ "js::LazyScript": {
+ count: 1,
+ bytes: 10,
+ },
+};
+
+const REPORT2 = {
+ JSObject: {
+ count: 11,
+ bytes: 110,
+ },
+ "js::Shape": {
+ count: 51,
+ bytes: 510,
+ },
+ JSString: {
+ count: 1,
+ bytes: 1,
+ },
+ "js::BaseShape": {
+ count: 1,
+ bytes: 42,
+ },
+};
+
+const EXPECTED = {
+ JSObject: {
+ count: 1,
+ bytes: 10,
+ },
+ "js::Shape": {
+ count: 1,
+ bytes: 10,
+ },
+ JSString: {
+ count: 1,
+ bytes: 1,
+ },
+ "js::LazyScript": {
+ count: -1,
+ bytes: -10,
+ },
+ "js::BaseShape": {
+ count: 1,
+ bytes: 42,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_02.js
new file mode 100644
index 0000000000..cefe76abde
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_02.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of breakdown by "count".
+
+const BREAKDOWN = { by: "count", count: true, bytes: true };
+
+const REPORT1 = {
+ count: 10,
+ bytes: 100,
+};
+
+const REPORT2 = {
+ count: 11,
+ bytes: 110,
+};
+
+const EXPECTED = {
+ count: 1,
+ bytes: 10,
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_03.js
new file mode 100644
index 0000000000..e625f4e0be
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_03.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of breakdown by "coarseType".
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ domNode: { by: "count", count: true, bytes: true },
+};
+
+const REPORT1 = {
+ objects: {
+ count: 1,
+ bytes: 10,
+ },
+ scripts: {
+ count: 1,
+ bytes: 10,
+ },
+ strings: {
+ count: 1,
+ bytes: 10,
+ },
+ other: {
+ count: 3,
+ bytes: 30,
+ },
+ domNode: {
+ count: 0,
+ bytes: 0,
+ },
+};
+
+const REPORT2 = {
+ objects: {
+ count: 1,
+ bytes: 10,
+ },
+ scripts: {
+ count: 0,
+ bytes: 0,
+ },
+ strings: {
+ count: 2,
+ bytes: 20,
+ },
+ other: {
+ count: 4,
+ bytes: 40,
+ },
+ domNode: {
+ count: 0,
+ bytes: 0,
+ },
+};
+
+const EXPECTED = {
+ objects: {
+ count: 0,
+ bytes: 0,
+ },
+ scripts: {
+ count: -1,
+ bytes: -10,
+ },
+ strings: {
+ count: 1,
+ bytes: 10,
+ },
+ other: {
+ count: 1,
+ bytes: 10,
+ },
+ domNode: {
+ count: 0,
+ bytes: 0,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_04.js
new file mode 100644
index 0000000000..036a33804f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_04.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of breakdown by "objectClass".
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+const REPORT1 = {
+ Array: {
+ count: 1,
+ bytes: 100,
+ },
+ Function: {
+ count: 10,
+ bytes: 10,
+ },
+ other: {
+ count: 10,
+ bytes: 100,
+ },
+};
+
+const REPORT2 = {
+ Object: {
+ count: 1,
+ bytes: 100,
+ },
+ Function: {
+ count: 20,
+ bytes: 20,
+ },
+ other: {
+ count: 10,
+ bytes: 100,
+ },
+};
+
+const EXPECTED = {
+ Array: {
+ count: -1,
+ bytes: -100,
+ },
+ Function: {
+ count: 10,
+ bytes: 10,
+ },
+ other: {
+ count: 0,
+ bytes: 0,
+ },
+ Object: {
+ count: 1,
+ bytes: 100,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_05.js
new file mode 100644
index 0000000000..1ce0cfeb5b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_05.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of breakdown by "allocationStack".
+
+const BREAKDOWN = {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+};
+
+const stack1 = saveStack();
+const stack2 = saveStack();
+const stack3 = saveStack();
+
+const REPORT1 = new Map([
+ [stack1, { count: 10, bytes: 100 }],
+ [stack2, { count: 1, bytes: 10 }],
+]);
+
+const REPORT2 = new Map([
+ [stack2, { count: 10, bytes: 100 }],
+ [stack3, { count: 1, bytes: 10 }],
+]);
+
+const EXPECTED = new Map([
+ [stack1, { count: -10, bytes: -100 }],
+ [stack2, { count: 9, bytes: 90 }],
+ [stack3, { count: 1, bytes: 10 }],
+]);
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_06.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_06.js
new file mode 100644
index 0000000000..b805de48dd
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_diff_06.js
@@ -0,0 +1,139 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test diffing census reports of a "complex" and "realistic" breakdown.
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "allocationStack",
+ then: {
+ by: "objectClass",
+ then: { by: "count", count: false, bytes: true },
+ other: { by: "count", count: false, bytes: true },
+ },
+ noStack: {
+ by: "objectClass",
+ then: { by: "count", count: false, bytes: true },
+ other: { by: "count", count: false, bytes: true },
+ },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true },
+ },
+ domNode: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true },
+ },
+};
+
+const stack1 = saveStack();
+const stack2 = saveStack();
+const stack3 = saveStack();
+
+const REPORT1 = {
+ objects: new Map([
+ [
+ stack1,
+ { Function: { bytes: 1 }, Object: { bytes: 2 }, other: { bytes: 0 } },
+ ],
+ [stack2, { Array: { bytes: 3 }, Date: { bytes: 4 }, other: { bytes: 0 } }],
+ ["noStack", { Object: { bytes: 3 } }],
+ ]),
+ strings: {
+ JSAtom: { bytes: 10 },
+ JSLinearString: { bytes: 5 },
+ },
+ scripts: {
+ JSScript: { bytes: 1 },
+ "js::jit::JitCode": { bytes: 2 },
+ },
+ other: {
+ "mozilla::dom::Thing": { bytes: 1 },
+ },
+ domNode: {},
+};
+
+const REPORT2 = {
+ objects: new Map([
+ [stack2, { Array: { bytes: 1 }, Date: { bytes: 2 }, other: { bytes: 3 } }],
+ [
+ stack3,
+ { Function: { bytes: 1 }, Object: { bytes: 2 }, other: { bytes: 0 } },
+ ],
+ ["noStack", { Object: { bytes: 3 } }],
+ ]),
+ strings: {
+ JSAtom: { bytes: 5 },
+ JSLinearString: { bytes: 10 },
+ },
+ scripts: {
+ JSScript: { bytes: 2 },
+ "js::LazyScript": { bytes: 42 },
+ "js::jit::JitCode": { bytes: 1 },
+ },
+ other: {
+ "mozilla::dom::OtherThing": { bytes: 1 },
+ },
+ domNode: {},
+};
+
+const EXPECTED = {
+ objects: new Map([
+ [
+ stack1,
+ { Function: { bytes: -1 }, Object: { bytes: -2 }, other: { bytes: 0 } },
+ ],
+ [
+ stack2,
+ { Array: { bytes: -2 }, Date: { bytes: -2 }, other: { bytes: 3 } },
+ ],
+ [
+ stack3,
+ { Function: { bytes: 1 }, Object: { bytes: 2 }, other: { bytes: 0 } },
+ ],
+ ["noStack", { Object: { bytes: 0 } }],
+ ]),
+ scripts: {
+ JSScript: {
+ bytes: 1,
+ },
+ "js::jit::JitCode": {
+ bytes: -1,
+ },
+ "js::LazyScript": {
+ bytes: 42,
+ },
+ },
+ strings: {
+ JSAtom: {
+ bytes: -5,
+ },
+ JSLinearString: {
+ bytes: 5,
+ },
+ },
+ other: {
+ "mozilla::dom::Thing": {
+ bytes: -1,
+ },
+ "mozilla::dom::OtherThing": {
+ bytes: 1,
+ },
+ },
+ domNode: {},
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_01.js
new file mode 100644
index 0000000000..961023b2c2
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_01.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test filtering basic CensusTreeNode trees.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ UInt8Array: { bytes: 80, count: 8 },
+ Int32Array: { bytes: 320, count: 32 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ },
+ domNode: {},
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 620,
+ count: 0,
+ totalCount: 62,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 450,
+ count: 0,
+ totalCount: 45,
+ children: [
+ {
+ name: "Int32Array",
+ bytes: 320,
+ totalBytes: 320,
+ count: 32,
+ totalCount: 32,
+ children: undefined,
+ id: 16,
+ parent: 15,
+ reportLeafIndex: 4,
+ },
+ {
+ name: "UInt8Array",
+ bytes: 80,
+ totalBytes: 80,
+ count: 8,
+ totalCount: 8,
+ children: undefined,
+ id: 17,
+ parent: 15,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Array",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 18,
+ parent: 15,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 15,
+ parent: 14,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 14,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "Array" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_02.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_02.js
new file mode 100644
index 0000000000..9915acb678
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_02.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test filtering CensusTreeNode trees with an `allocationStack` breakdown.
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ let stack1, stack2, stack3, stack4;
+
+ (function foo() {
+ (function bar() {
+ (function baz() {
+ stack1 = saveStack(3);
+ })();
+ (function quux() {
+ stack2 = saveStack(3);
+ stack3 = saveStack(3);
+ })();
+ })();
+ stack4 = saveStack(2);
+ })();
+
+ const stack5 = saveStack(1);
+
+ const REPORT = new Map([
+ [stack1, { bytes: 10, count: 1 }],
+ [stack2, { bytes: 20, count: 2 }],
+ [stack3, { bytes: 30, count: 3 }],
+ [stack4, { bytes: 40, count: 4 }],
+ [stack5, { bytes: 50, count: 5 }],
+ ["noStack", { bytes: 60, count: 6 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 210,
+ count: 0,
+ totalCount: 21,
+ children: [
+ {
+ name: stack1.parent.parent,
+ bytes: 0,
+ totalBytes: 60,
+ count: 0,
+ totalCount: 6,
+ children: [
+ {
+ name: stack2.parent,
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: stack3,
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 15,
+ parent: 14,
+ reportLeafIndex: 3,
+ },
+ {
+ name: stack2,
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 16,
+ parent: 14,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 14,
+ parent: 13,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: stack1.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: stack1,
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 18,
+ parent: 17,
+ reportLeafIndex: 1,
+ },
+ ],
+ id: 17,
+ parent: 13,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 13,
+ parent: 12,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 12,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "bar" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_03.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_03.js
new file mode 100644
index 0000000000..dc144bac2e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_03.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test filtering with no matches.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ UInt8Array: { bytes: 80, count: 8 },
+ Int32Array: { bytes: 320, count: 32 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ },
+ domNode: {},
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 620,
+ count: 0,
+ totalCount: 62,
+ children: undefined,
+ id: 14,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, {
+ filter: "zzzzzzzzzzzzzzzzzzzz",
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_04.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_04.js
new file mode 100644
index 0000000000..7e37b2c5da
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_04.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the filtered nodes' counts and bytes are the same as they were when
+// unfiltered.
+
+function run_test() {
+ const COUNT = { by: "count", count: true, bytes: true };
+ const INTERNAL_TYPE = { by: "internalType", then: COUNT };
+
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: COUNT, other: COUNT },
+ strings: COUNT,
+ scripts: {
+ by: "filename",
+ then: INTERNAL_TYPE,
+ noFilename: INTERNAL_TYPE,
+ },
+ other: INTERNAL_TYPE,
+ domNode: { by: "descriptiveType", then: COUNT, other: COUNT },
+ };
+
+ const REPORT = {
+ objects: {
+ Function: {
+ count: 7,
+ bytes: 70,
+ },
+ Array: {
+ count: 6,
+ bytes: 60,
+ },
+ },
+ scripts: {
+ "http://mozilla.github.io/pdf.js/build/pdf.js": {
+ "js::LazyScript": {
+ count: 4,
+ bytes: 40,
+ },
+ },
+ },
+ strings: {
+ count: 2,
+ bytes: 20,
+ },
+ other: {
+ "js::Shape": {
+ count: 1,
+ bytes: 10,
+ },
+ },
+ domNode: {},
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 200,
+ count: 0,
+ totalCount: 20,
+ parent: undefined,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 130,
+ count: 0,
+ totalCount: 13,
+ children: [
+ {
+ name: "Function",
+ bytes: 70,
+ totalBytes: 70,
+ count: 7,
+ totalCount: 7,
+ id: 14,
+ parent: 13,
+ children: undefined,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "Array",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ id: 15,
+ parent: 13,
+ children: undefined,
+ reportLeafIndex: 3,
+ },
+ ],
+ id: 13,
+ parent: 12,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 12,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "objects" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_05.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_05.js
new file mode 100644
index 0000000000..667717688c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_census_filtering_05.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that filtered and inverted allocation stack census trees are sorted
+// properly.
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ const stacks = [];
+
+ function foo(depth = 1) {
+ stacks.push(saveStack(depth));
+ bar(depth + 1);
+ baz(depth + 1);
+ stacks.push(saveStack(depth));
+ }
+
+ function bar(depth = 1) {
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ }
+
+ function baz(depth = 1) {
+ stacks.push(saveStack(depth));
+ bang(depth + 1);
+ stacks.push(saveStack(depth));
+ }
+
+ function bang(depth = 1) {
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ }
+
+ foo();
+ bar();
+ baz();
+ bang();
+
+ const REPORT = new Map(
+ stacks.map((s, i) => {
+ return [
+ s,
+ {
+ count: i + 1,
+ bytes: (i + 1) * 10,
+ },
+ ];
+ })
+ );
+
+ const tree = censusReportToCensusTreeNode(BREAKDOWN, REPORT, {
+ filter: "baz",
+ invert: true,
+ });
+
+ dumpn("tree = " + JSON.stringify(tree, savedFrameReplacer, 4));
+
+ (function assertSortedBySelf(node) {
+ if (node.children) {
+ let lastSelfBytes = Infinity;
+ for (const child of node.children) {
+ Assert.lessOrEqual(
+ child.bytes,
+ lastSelfBytes,
+ `${child.bytes} <= ${lastSelfBytes}`
+ );
+ lastSelfBytes = child.bytes;
+ assertSortedBySelf(child);
+ }
+ }
+ })(tree);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_countToBucketBreakdown_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_countToBucketBreakdown_01.js
new file mode 100644
index 0000000000..e89048c333
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_countToBucketBreakdown_01.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can turn a breakdown with { by: "count" } leaves into a
+// breakdown with { by: "bucket" } leaves.
+
+const COUNT = { by: "count", count: true, bytes: true };
+const BUCKET = { by: "bucket" };
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: COUNT, other: COUNT },
+ strings: COUNT,
+ scripts: {
+ by: "filename",
+ then: { by: "internalType", then: COUNT },
+ noFilename: { by: "internalType", then: COUNT },
+ },
+ other: { by: "internalType", then: COUNT },
+};
+
+const EXPECTED = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: BUCKET, other: BUCKET },
+ strings: BUCKET,
+ scripts: {
+ by: "filename",
+ then: { by: "internalType", then: BUCKET },
+ noFilename: { by: "internalType", then: BUCKET },
+ },
+ other: { by: "internalType", then: BUCKET },
+};
+
+function run_test() {
+ assertCountToBucketBreakdown(BREAKDOWN, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_deduplicatePaths_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_deduplicatePaths_01.js
new file mode 100644
index 0000000000..fc2864f5f4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_deduplicatePaths_01.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the behavior of the deduplicatePaths utility function.
+
+function edge(from, to, name) {
+ return { from, to, name };
+}
+
+function run_test() {
+ const a = 1;
+ const b = 2;
+ const c = 3;
+ const d = 4;
+ const e = 5;
+ const f = 6;
+ const g = 7;
+
+ dumpn("Single long path");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [
+ pathEntry(a, "e1"),
+ pathEntry(b, "e2"),
+ pathEntry(c, "e3"),
+ pathEntry(d, "e4"),
+ pathEntry(e, "e5"),
+ pathEntry(f, "e6"),
+ ],
+ ],
+ expectedNodes: [a, b, c, d, e, f, g],
+ expectedEdges: [
+ edge(a, b, "e1"),
+ edge(b, c, "e2"),
+ edge(c, d, "e3"),
+ edge(d, e, "e4"),
+ edge(e, f, "e5"),
+ edge(f, g, "e6"),
+ ],
+ });
+
+ dumpn("Multiple edges from and to the same nodes");
+ assertDeduplicatedPaths({
+ target: a,
+ paths: [[pathEntry(b, "x")], [pathEntry(b, "y")], [pathEntry(b, "z")]],
+ expectedNodes: [a, b],
+ expectedEdges: [edge(b, a, "x"), edge(b, a, "y"), edge(b, a, "z")],
+ });
+
+ dumpn("Multiple paths sharing some nodes and edges");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [pathEntry(a, "a->b"), pathEntry(b, "b->c"), pathEntry(c, "foo")],
+ [pathEntry(a, "a->b"), pathEntry(b, "b->d"), pathEntry(d, "bar")],
+ [pathEntry(a, "a->b"), pathEntry(b, "b->e"), pathEntry(e, "baz")],
+ ],
+ expectedNodes: [a, b, c, d, e, g],
+ expectedEdges: [
+ edge(a, b, "a->b"),
+ edge(b, c, "b->c"),
+ edge(b, d, "b->d"),
+ edge(b, e, "b->e"),
+ edge(c, g, "foo"),
+ edge(d, g, "bar"),
+ edge(e, g, "baz"),
+ ],
+ });
+
+ dumpn("Second shortest path contains target itself");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [pathEntry(a, "a->b"), pathEntry(b, "b->g")],
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->g"),
+ pathEntry(g, "g->f"),
+ pathEntry(f, "f->g"),
+ ],
+ ],
+ expectedNodes: [a, b, g],
+ expectedEdges: [edge(a, b, "a->b"), edge(b, g, "b->g")],
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_getCensusIndividuals_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_getCensusIndividuals_01.js
new file mode 100644
index 0000000000..6963f3f33a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_getCensusIndividuals_01.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test basic functionality of `CensusUtils.getCensusIndividuals`.
+
+function run_test() {
+ const stack1 = saveStack(1);
+ const stack2 = saveStack(1);
+ const stack3 = saveStack(1);
+
+ const COUNT = { by: "count", count: true, bytes: true };
+ const INTERNAL_TYPE = { by: "internalType", then: COUNT };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: INTERNAL_TYPE,
+ noStack: INTERNAL_TYPE,
+ };
+
+ const MOCK_SNAPSHOT = {
+ takeCensus: ({ breakdown }) => {
+ assertStructurallyEquivalent(
+ breakdown,
+ CensusUtils.countToBucketBreakdown(BREAKDOWN)
+ );
+
+ // DFS Index
+ // prettier-ignore
+ return new Map([ // 0
+ [stack1, { // 1
+ JSObject: [101, 102, 103], // 2
+ JSString: [111, 112, 113], // 3
+ }],
+ [stack2, { // 4
+ JSObject: [201, 202, 203], // 5
+ JSString: [211, 212, 213], // 6
+ }],
+ [stack3, { // 7
+ JSObject: [301, 302, 303], // 8
+ JSString: [311, 312, 313], // 9
+ }],
+ ["noStack", { // 10
+ JSObject: [401, 402, 403], // 11
+ JSString: [411, 412, 413], // 12
+ }],
+ ]);
+ },
+ };
+
+ const INDICES = new Set([3, 5, 9]);
+
+ const EXPECTED = new Set([111, 112, 113, 201, 202, 203, 311, 312, 313]);
+
+ const actual = new Set(
+ CensusUtils.getCensusIndividuals(INDICES, BREAKDOWN, MOCK_SNAPSHOT)
+ );
+
+ assertStructurallyEquivalent(EXPECTED, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_getReportLeaves_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_getReportLeaves_01.js
new file mode 100644
index 0000000000..04aae90e99
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_getReportLeaves_01.js
@@ -0,0 +1,135 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test basic functionality of `CensusUtils.getReportLeaves`.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ domNode: {
+ by: "descriptiveType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { count: 6, bytes: 60 },
+ Function: { count: 1, bytes: 10 },
+ Object: { count: 1, bytes: 10 },
+ RegExp: { count: 1, bytes: 10 },
+ other: { count: 0, bytes: 0 },
+ },
+ strings: { count: 1, bytes: 10 },
+ scripts: {
+ "foo.js": {
+ JSScript: { count: 1, bytes: 10 },
+ "js::jit::IonScript": { count: 1, bytes: 10 },
+ },
+ noFilename: {
+ JSScript: { count: 1, bytes: 10 },
+ "js::jit::IonScript": { count: 1, bytes: 10 },
+ },
+ },
+ other: {
+ "js::Shape": { count: 7, bytes: 70 },
+ "js::BaseShape": { count: 1, bytes: 10 },
+ },
+ domNode: {},
+ };
+
+ const root = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ dumpn("CensusTreeNode tree = " + JSON.stringify(root, null, 4));
+
+ (function assertEveryNodeCanFindItsLeaf(node) {
+ if (node.reportLeafIndex) {
+ const [leaf] = CensusUtils.getReportLeaves(
+ new Set([node.reportLeafIndex]),
+ BREAKDOWN,
+ REPORT
+ );
+ ok(
+ leaf,
+ "Should be able to find leaf " +
+ "for a node with a reportLeafIndex = " +
+ node.reportLeafIndex
+ );
+ }
+
+ if (node.children) {
+ for (const child of node.children) {
+ assertEveryNodeCanFindItsLeaf(child);
+ }
+ }
+ })(root);
+
+ // Test finding multiple leaves at a time.
+
+ function find(name, node) {
+ if (node.name === name) {
+ return node;
+ }
+
+ if (node.children) {
+ for (const child of node.children) {
+ const found = find(name, child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+
+ return undefined;
+ }
+
+ const arrayNode = find("Array", root);
+ ok(arrayNode);
+ equal(typeof arrayNode.reportLeafIndex, "number");
+
+ const shapeNode = find("js::Shape", root);
+ ok(shapeNode);
+ equal(typeof shapeNode.reportLeafIndex, "number");
+
+ const indices = new Set([
+ arrayNode.reportLeafIndex,
+ shapeNode.reportLeafIndex,
+ ]);
+ const leaves = CensusUtils.getReportLeaves(indices, BREAKDOWN, REPORT);
+ equal(leaves.length, 2);
+
+ // `getReportLeaves` does not guarantee order of the results, so handle both
+ // cases.
+ ok(leaves.some(l => l === REPORT.objects.Array));
+ ok(leaves.some(l => l === REPORT.other["js::Shape"]));
+
+ // Test that bad indices do not yield results.
+
+ const none = CensusUtils.getReportLeaves(
+ new Set([999999999999]),
+ BREAKDOWN,
+ REPORT
+ );
+ equal(none.length, 0);
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/test_saveHeapSnapshot_e10s_01.js b/devtools/shared/heapsnapshot/tests/xpcshell/test_saveHeapSnapshot_e10s_01.js
new file mode 100644
index 0000000000..0a46618003
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/test_saveHeapSnapshot_e10s_01.js
@@ -0,0 +1,9 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test saving a heap snapshot in the sandboxed e10s child process.
+
+function run_test() {
+ run_test_in_child("test_SaveHeapSnapshot.js");
+}
diff --git a/devtools/shared/heapsnapshot/tests/xpcshell/xpcshell.toml b/devtools/shared/heapsnapshot/tests/xpcshell/xpcshell.toml
new file mode 100644
index 0000000000..155aeef009
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/xpcshell/xpcshell.toml
@@ -0,0 +1,184 @@
+[DEFAULT]
+tags = "devtools heapsnapshot devtools-memory"
+head = "head_heapsnapshot.js"
+firefox-appdir = "browser"
+skip-if = ["os == 'android'"]
+support-files = [
+ "Census.sys.mjs",
+ "dominator-tree-worker.js",
+ "heap-snapshot-worker.js",
+ "Match.sys.mjs",
+]
+
+["test_DominatorTreeNode_LabelAndShallowSize_01.js"]
+
+["test_DominatorTreeNode_LabelAndShallowSize_02.js"]
+
+["test_DominatorTreeNode_LabelAndShallowSize_03.js"]
+
+["test_DominatorTreeNode_LabelAndShallowSize_04.js"]
+
+["test_DominatorTreeNode_attachShortestPaths_01.js"]
+
+["test_DominatorTreeNode_getNodeByIdAlongPath_01.js"]
+
+["test_DominatorTreeNode_insert_01.js"]
+
+["test_DominatorTreeNode_insert_02.js"]
+
+["test_DominatorTreeNode_insert_03.js"]
+
+["test_DominatorTreeNode_partialTraversal_01.js"]
+
+["test_DominatorTree_01.js"]
+
+["test_DominatorTree_02.js"]
+
+["test_DominatorTree_03.js"]
+
+["test_DominatorTree_04.js"]
+
+["test_DominatorTree_05.js"]
+
+["test_DominatorTree_06.js"]
+
+["test_HeapAnalyses_computeDominatorTree_01.js"]
+
+["test_HeapAnalyses_computeDominatorTree_02.js"]
+
+["test_HeapAnalyses_deleteHeapSnapshot_01.js"]
+
+["test_HeapAnalyses_deleteHeapSnapshot_02.js"]
+
+["test_HeapAnalyses_deleteHeapSnapshot_03.js"]
+
+["test_HeapAnalyses_getCensusIndividuals_01.js"]
+
+["test_HeapAnalyses_getCreationTime_01.js"]
+
+["test_HeapAnalyses_getDominatorTree_01.js"]
+
+["test_HeapAnalyses_getDominatorTree_02.js"]
+
+["test_HeapAnalyses_getImmediatelyDominated_01.js"]
+skip-if = ["tsan"] # Unreasonably slow, bug 1612707
+
+["test_HeapAnalyses_readHeapSnapshot_01.js"]
+
+["test_HeapAnalyses_takeCensusDiff_01.js"]
+
+["test_HeapAnalyses_takeCensusDiff_02.js"]
+
+["test_HeapAnalyses_takeCensus_01.js"]
+
+["test_HeapAnalyses_takeCensus_02.js"]
+
+["test_HeapAnalyses_takeCensus_03.js"]
+
+["test_HeapAnalyses_takeCensus_04.js"]
+
+["test_HeapAnalyses_takeCensus_05.js"]
+
+["test_HeapAnalyses_takeCensus_06.js"]
+
+["test_HeapAnalyses_takeCensus_07.js"]
+
+["test_HeapSnapshot_computeShortestPaths_01.js"]
+
+["test_HeapSnapshot_computeShortestPaths_02.js"]
+
+["test_HeapSnapshot_creationTime_01.js"]
+
+["test_HeapSnapshot_deepStack_01.js"]
+
+["test_HeapSnapshot_describeNode_01.js"]
+
+["test_HeapSnapshot_getObjectNodeId_01.js"]
+
+["test_HeapSnapshot_takeCensus_01.js"]
+
+["test_HeapSnapshot_takeCensus_02.js"]
+
+["test_HeapSnapshot_takeCensus_03.js"]
+
+["test_HeapSnapshot_takeCensus_04.js"]
+
+["test_HeapSnapshot_takeCensus_05.js"]
+
+["test_HeapSnapshot_takeCensus_06.js"]
+
+["test_HeapSnapshot_takeCensus_07.js"]
+
+["test_HeapSnapshot_takeCensus_08.js"]
+
+["test_HeapSnapshot_takeCensus_09.js"]
+
+["test_HeapSnapshot_takeCensus_10.js"]
+
+["test_HeapSnapshot_takeCensus_11.js"]
+
+["test_HeapSnapshot_takeCensus_12.js"]
+
+["test_ReadHeapSnapshot.js"]
+
+["test_ReadHeapSnapshot_with_allocations.js"]
+skip-if = ["os == 'linux'"] # Bug 1176173
+
+["test_ReadHeapSnapshot_with_utf8_paths.js"]
+
+["test_ReadHeapSnapshot_worker.js"]
+skip-if = ["os == 'linux'"] # Bug 1176173
+
+["test_SaveHeapSnapshot.js"]
+
+["test_census-tree-node-01.js"]
+
+["test_census-tree-node-02.js"]
+
+["test_census-tree-node-03.js"]
+
+["test_census-tree-node-04.js"]
+
+["test_census-tree-node-05.js"]
+
+["test_census-tree-node-06.js"]
+
+["test_census-tree-node-07.js"]
+
+["test_census-tree-node-08.js"]
+
+["test_census-tree-node-09.js"]
+
+["test_census-tree-node-10.js"]
+
+["test_census_diff_01.js"]
+
+["test_census_diff_02.js"]
+
+["test_census_diff_03.js"]
+
+["test_census_diff_04.js"]
+
+["test_census_diff_05.js"]
+
+["test_census_diff_06.js"]
+
+["test_census_filtering_01.js"]
+
+["test_census_filtering_02.js"]
+
+["test_census_filtering_03.js"]
+
+["test_census_filtering_04.js"]
+
+["test_census_filtering_05.js"]
+
+["test_countToBucketBreakdown_01.js"]
+
+["test_deduplicatePaths_01.js"]
+
+["test_getCensusIndividuals_01.js"]
+
+["test_getReportLeaves_01.js"]
+
+["test_saveHeapSnapshot_e10s_01.js"]