@memlab/core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/__tests__/parser/HeapParser.test.d.ts +11 -0
- package/dist/__tests__/parser/HeapParser.test.d.ts.map +1 -0
- package/dist/__tests__/parser/HeapParser.test.js +54 -0
- package/dist/__tests__/parser/NodeHeap.test.d.ts +11 -0
- package/dist/__tests__/parser/NodeHeap.test.d.ts.map +1 -0
- package/dist/__tests__/parser/NodeHeap.test.js +96 -0
- package/dist/__tests__/parser/StringNode.test.d.ts +11 -0
- package/dist/__tests__/parser/StringNode.test.d.ts.map +1 -0
- package/dist/__tests__/parser/StringNode.test.js +61 -0
- package/dist/__tests__/parser/traverse/HeapNodeTraverse.test.d.ts +16 -0
- package/dist/__tests__/parser/traverse/HeapNodeTraverse.test.d.ts.map +1 -0
- package/dist/__tests__/parser/traverse/HeapNodeTraverse.test.js +140 -0
- package/dist/__tests__/utils/utils.test.d.ts +11 -0
- package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
- package/dist/__tests__/utils/utils.test.js +81 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +62 -0
- package/dist/lib/BaseOption.d.ts +31 -0
- package/dist/lib/BaseOption.d.ts.map +1 -0
- package/dist/lib/BaseOption.js +109 -0
- package/dist/lib/BrowserInfo.d.ts +33 -0
- package/dist/lib/BrowserInfo.d.ts.map +1 -0
- package/dist/lib/BrowserInfo.js +117 -0
- package/dist/lib/Config.d.ts +203 -0
- package/dist/lib/Config.d.ts.map +1 -0
- package/dist/lib/Config.js +427 -0
- package/dist/lib/Console.d.ts +67 -0
- package/dist/lib/Console.d.ts.map +1 -0
- package/dist/lib/Console.js +344 -0
- package/dist/lib/Constant.d.ts +38 -0
- package/dist/lib/Constant.d.ts.map +1 -0
- package/dist/lib/Constant.js +58 -0
- package/dist/lib/FileManager.d.ts +69 -0
- package/dist/lib/FileManager.d.ts.map +1 -0
- package/dist/lib/FileManager.js +309 -0
- package/dist/lib/HeapAnalyzer.d.ts +51 -0
- package/dist/lib/HeapAnalyzer.d.ts.map +1 -0
- package/dist/lib/HeapAnalyzer.js +719 -0
- package/dist/lib/HeapParser.d.ts +19 -0
- package/dist/lib/HeapParser.d.ts.map +1 -0
- package/dist/lib/HeapParser.js +128 -0
- package/dist/lib/InternalValueSetter.d.ts +14 -0
- package/dist/lib/InternalValueSetter.d.ts.map +1 -0
- package/dist/lib/InternalValueSetter.js +43 -0
- package/dist/lib/NodeHeap.d.ts +16 -0
- package/dist/lib/NodeHeap.d.ts.map +1 -0
- package/dist/lib/NodeHeap.js +62 -0
- package/dist/lib/ProcessManager.d.ts +25 -0
- package/dist/lib/ProcessManager.d.ts.map +1 -0
- package/dist/lib/ProcessManager.js +67 -0
- package/dist/lib/Serializer.d.ts +49 -0
- package/dist/lib/Serializer.d.ts.map +1 -0
- package/dist/lib/Serializer.js +702 -0
- package/dist/lib/StringLoader.d.ts +26 -0
- package/dist/lib/StringLoader.d.ts.map +1 -0
- package/dist/lib/StringLoader.js +290 -0
- package/dist/lib/Types.d.ts +432 -0
- package/dist/lib/Types.d.ts.map +1 -0
- package/dist/lib/Types.js +11 -0
- package/dist/lib/Utils.d.ts +223 -0
- package/dist/lib/Utils.d.ts.map +1 -0
- package/dist/lib/Utils.js +1736 -0
- package/dist/lib/heap-data/HeapEdge.d.ts +27 -0
- package/dist/lib/heap-data/HeapEdge.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapEdge.js +75 -0
- package/dist/lib/heap-data/HeapLocation.d.ts +22 -0
- package/dist/lib/heap-data/HeapLocation.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapLocation.js +40 -0
- package/dist/lib/heap-data/HeapNode.d.ts +55 -0
- package/dist/lib/heap-data/HeapNode.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapNode.js +344 -0
- package/dist/lib/heap-data/HeapSnapshot.d.ts +85 -0
- package/dist/lib/heap-data/HeapSnapshot.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapSnapshot.js +462 -0
- package/dist/lib/heap-data/HeapStringNode.d.ts +18 -0
- package/dist/lib/heap-data/HeapStringNode.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapStringNode.js +43 -0
- package/dist/lib/heap-data/HeapUtils.d.ts +17 -0
- package/dist/lib/heap-data/HeapUtils.d.ts.map +1 -0
- package/dist/lib/heap-data/HeapUtils.js +25 -0
- package/dist/logger/LeakClusterLogger.d.ts +40 -0
- package/dist/logger/LeakClusterLogger.d.ts.map +1 -0
- package/dist/logger/LeakClusterLogger.js +228 -0
- package/dist/logger/LeakTraceDetailsLogger.d.ts +19 -0
- package/dist/logger/LeakTraceDetailsLogger.d.ts.map +1 -0
- package/dist/logger/LeakTraceDetailsLogger.js +50 -0
- package/dist/modes/BaseMode.d.ts +30 -0
- package/dist/modes/BaseMode.d.ts.map +1 -0
- package/dist/modes/BaseMode.js +95 -0
- package/dist/modes/InteractionTestMode.d.ts +23 -0
- package/dist/modes/InteractionTestMode.d.ts.map +1 -0
- package/dist/modes/InteractionTestMode.js +46 -0
- package/dist/modes/MeasureMode.d.ts +23 -0
- package/dist/modes/MeasureMode.d.ts.map +1 -0
- package/dist/modes/MeasureMode.js +58 -0
- package/dist/modes/RunningModes.d.ts +15 -0
- package/dist/modes/RunningModes.d.ts.map +1 -0
- package/dist/modes/RunningModes.js +40 -0
- package/dist/paths/TraceFinder.d.ts +31 -0
- package/dist/paths/TraceFinder.d.ts.map +1 -0
- package/dist/paths/TraceFinder.js +537 -0
- package/dist/trace-cluster/ClusterUtils.d.ts +14 -0
- package/dist/trace-cluster/ClusterUtils.d.ts.map +1 -0
- package/dist/trace-cluster/ClusterUtils.js +17 -0
- package/dist/trace-cluster/ClusterUtilsHelper.d.ts +38 -0
- package/dist/trace-cluster/ClusterUtilsHelper.d.ts.map +1 -0
- package/dist/trace-cluster/ClusterUtilsHelper.js +373 -0
- package/dist/trace-cluster/ClusteringHeuristics.d.ts +22 -0
- package/dist/trace-cluster/ClusteringHeuristics.d.ts.map +1 -0
- package/dist/trace-cluster/ClusteringHeuristics.js +23 -0
- package/dist/trace-cluster/EvalutationMetric.d.ts +22 -0
- package/dist/trace-cluster/EvalutationMetric.d.ts.map +1 -0
- package/dist/trace-cluster/EvalutationMetric.js +158 -0
- package/dist/trace-cluster/TraceBucket.d.ts +36 -0
- package/dist/trace-cluster/TraceBucket.d.ts.map +1 -0
- package/dist/trace-cluster/TraceBucket.js +238 -0
- package/dist/trace-cluster/TraceElement.d.ts +71 -0
- package/dist/trace-cluster/TraceElement.d.ts.map +1 -0
- package/dist/trace-cluster/TraceElement.js +182 -0
- package/dist/trace-cluster/strategies/TraceAsClusterStrategy.d.ts +15 -0
- package/dist/trace-cluster/strategies/TraceAsClusterStrategy.d.ts.map +1 -0
- package/dist/trace-cluster/strategies/TraceAsClusterStrategy.js +37 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.d.ts +15 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.d.ts.map +1 -0
- package/dist/trace-cluster/strategies/TraceSimilarityStrategy.js +60 -0
- package/package.json +60 -0
- package/static/run-meta.json +10 -0
- package/static/visit-order.json +27 -0
|
@@ -0,0 +1,1736 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*
|
|
8
|
+
* @emails oncall+ws_labs
|
|
9
|
+
* @format
|
|
10
|
+
*/
|
|
11
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
14
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
15
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
16
|
+
}
|
|
17
|
+
Object.defineProperty(o, k2, desc);
|
|
18
|
+
}) : (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
o[k2] = m[k];
|
|
21
|
+
}));
|
|
22
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
23
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
24
|
+
}) : function(o, v) {
|
|
25
|
+
o["default"] = v;
|
|
26
|
+
});
|
|
27
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
35
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
36
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
37
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
38
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
39
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
40
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.resolveSnapshotFilePath = void 0;
|
|
48
|
+
const fs_1 = __importDefault(require("fs"));
|
|
49
|
+
const path_1 = __importDefault(require("path"));
|
|
50
|
+
const process_1 = __importDefault(require("process"));
|
|
51
|
+
const Config_1 = __importStar(require("./Config"));
|
|
52
|
+
const Console_1 = __importDefault(require("./Console"));
|
|
53
|
+
const Constant_1 = __importDefault(require("./Constant"));
|
|
54
|
+
const HeapParser_1 = __importDefault(require("./HeapParser"));
|
|
55
|
+
const BrowserInfo_1 = __importDefault(require("./BrowserInfo"));
|
|
56
|
+
const memCache = Object.create(null);
|
|
57
|
+
const FileManager_1 = __importDefault(require("./FileManager"));
|
|
58
|
+
const __1 = require("..");
|
|
59
|
+
// For more details see ReactWorkTags.js of React
|
|
60
|
+
const reactWorkTag = {
|
|
61
|
+
FunctionComponent: 0,
|
|
62
|
+
ClassComponent: 1,
|
|
63
|
+
IndeterminateComponent: 2,
|
|
64
|
+
HostRoot: 3,
|
|
65
|
+
HostPortal: 4,
|
|
66
|
+
HostComponent: 5,
|
|
67
|
+
HostText: 6,
|
|
68
|
+
Fragment: 7,
|
|
69
|
+
Mode: 8,
|
|
70
|
+
ContextConsumer: 9,
|
|
71
|
+
ContextProvider: 10,
|
|
72
|
+
ForwardRef: 11,
|
|
73
|
+
Profiler: 12,
|
|
74
|
+
SuspenseComponent: 13,
|
|
75
|
+
MemoComponent: 14,
|
|
76
|
+
SimpleMemoComponent: 15,
|
|
77
|
+
LazyComponent: 16,
|
|
78
|
+
IncompleteClassComponent: 17,
|
|
79
|
+
DehydratedFragment: 18,
|
|
80
|
+
SuspenseListComponent: 19,
|
|
81
|
+
ScopeComponent: 21,
|
|
82
|
+
OffscreenComponent: 22,
|
|
83
|
+
LegacyHiddenComponent: 23,
|
|
84
|
+
CacheComponent: 24,
|
|
85
|
+
};
|
|
86
|
+
const reactTagIdToName = [];
|
|
87
|
+
Object.entries(reactWorkTag).forEach(workTag => (reactTagIdToName[workTag[1]] = workTag[0]));
|
|
88
|
+
function _getReactWorkTagName(tagId) {
|
|
89
|
+
if (typeof tagId === 'string') {
|
|
90
|
+
tagId = parseInt(tagId, 10);
|
|
91
|
+
}
|
|
92
|
+
if (typeof tagId !== 'number' || tagId !== tagId) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
return reactTagIdToName[tagId];
|
|
96
|
+
}
|
|
97
|
+
function isHermesInternalObject(node) {
|
|
98
|
+
return (node.type === 'number' ||
|
|
99
|
+
node.name === 'HiddenClass' ||
|
|
100
|
+
node.name === 'Environment' ||
|
|
101
|
+
node.name === 'ArrayStorage' ||
|
|
102
|
+
node.name === 'SegmentedArray' ||
|
|
103
|
+
node.name === 'WeakValueMap' ||
|
|
104
|
+
node.name === 'HashMapEntry');
|
|
105
|
+
}
|
|
106
|
+
function isStackTraceFrame(node) {
|
|
107
|
+
if (!node || node.type !== 'hidden') {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return node.name === 'system / StackTraceFrame';
|
|
111
|
+
}
|
|
112
|
+
// returns true if it is detached DOM element or detached FiberNode
|
|
113
|
+
// NOTE: Doesn't work for FiberNode without detachedness field
|
|
114
|
+
function isDetached(node) {
|
|
115
|
+
if (Config_1.default.snapshotHasDetachedness) {
|
|
116
|
+
return node.is_detached;
|
|
117
|
+
}
|
|
118
|
+
return node.name.startsWith('Detached ');
|
|
119
|
+
}
|
|
120
|
+
function isFiberNode(node) {
|
|
121
|
+
if (!node || node.type !== 'object') {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const name = node.name;
|
|
125
|
+
return name === 'FiberNode' || name === 'Detached FiberNode';
|
|
126
|
+
}
|
|
127
|
+
// quickly check the detachedness field
|
|
128
|
+
// need to call hasHostRoot(node) before this function
|
|
129
|
+
// does not traverse and check the existance of HostRoot
|
|
130
|
+
// NOTE: Doesn't work for FiberNode without detachedness field
|
|
131
|
+
function isDetachedFiberNode(node) {
|
|
132
|
+
return isFiberNode(node) && isDetached(node);
|
|
133
|
+
}
|
|
134
|
+
// this function returns a more general sense of DOM nodes. Specifically,
|
|
135
|
+
// any detached DOM nodes (e.g., HTMLXXElement, IntersectionObserver etc.)
|
|
136
|
+
// that are not internal nodes.
|
|
137
|
+
function isDetachedDOMNode(node, args = {}) {
|
|
138
|
+
if (!node || typeof node.name !== 'string') {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
const name = node.name;
|
|
142
|
+
if (isFiberNode(node)) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
if (name === 'Detached InternalNode' && args.ignoreInternalNode) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return isDetached(node);
|
|
149
|
+
}
|
|
150
|
+
function isWeakMapEdge(edge) {
|
|
151
|
+
if (!edge || typeof edge.name_or_index !== 'string') {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
if (edge.name_or_index.indexOf('WeakMap') < 0) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
function isWeakMapEdgeToKey(edge) {
|
|
160
|
+
if (!isWeakMapEdge(edge)) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const weakMapKeyObjectId = getWeakMapEdgeKeyId(edge);
|
|
164
|
+
const toNodeObjectId = edge.toNode.id;
|
|
165
|
+
// in WeakMap, keys are weakly referenced
|
|
166
|
+
if (weakMapKeyObjectId === toNodeObjectId) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
function isWeakMapEdgeToValue(edge) {
|
|
172
|
+
if (!isWeakMapEdge(edge)) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const weakMapKeyObjectId = getWeakMapEdgeKeyId(edge);
|
|
176
|
+
const toNodeObjectId = edge.toNode.id;
|
|
177
|
+
// in WeakMap, keys are weakly referenced
|
|
178
|
+
if (weakMapKeyObjectId !== toNodeObjectId) {
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
function isEssentialEdge(nodeIndex, edgeType, rootNodeIndex) {
|
|
184
|
+
// According to Chrome Devtools, most shortcut edges are non-essential
|
|
185
|
+
// except at the root node, which have special meaning of marking user
|
|
186
|
+
// global objects
|
|
187
|
+
// NOTE: However, bound function may have a shortcut edge to the bound
|
|
188
|
+
// host object
|
|
189
|
+
return (edgeType !== 'weak' &&
|
|
190
|
+
(edgeType !== 'shortcut' || nodeIndex === rootNodeIndex));
|
|
191
|
+
}
|
|
192
|
+
function isFiberNodeDeletionsEdge(edge) {
|
|
193
|
+
if (!edge || !edge.fromNode || !edge.toNode) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
if (!isFiberNode(edge.fromNode)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
return edge.name_or_index === 'deletions';
|
|
200
|
+
}
|
|
201
|
+
function isBlinkRootNode(node) {
|
|
202
|
+
if (!node || !node.name) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
return (node.type === 'synthetic' &&
|
|
206
|
+
(node.name === 'Blink cross-thread roots' || node.name === 'Blink roots'));
|
|
207
|
+
}
|
|
208
|
+
function isPendingActivityNode(node) {
|
|
209
|
+
if (!node || !node.name) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
return node.type === 'synthetic' && node.name === 'Pending activities';
|
|
213
|
+
}
|
|
214
|
+
function isRootNode(node, opt = {}) {
|
|
215
|
+
if (!node || !node.name) {
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
// consider Hermes snapshot GC roots
|
|
219
|
+
if (Config_1.default.jsEngine === 'hermes') {
|
|
220
|
+
return node.name === '(GC roots)' || node.name === '(GC Roots)';
|
|
221
|
+
}
|
|
222
|
+
// the window object
|
|
223
|
+
if (node.type === 'native' && node.name.indexOf('Window') === 0) {
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
if (node.type === 'synthetic' && node.name === '(GC roots)') {
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (!opt.excludeBlinkRoot && isBlinkRootNode(node)) {
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
if (!opt.excludePendingActivity && isPendingActivityNode(node)) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
// in Hermes engine, directProp edge is a shortcut reference
|
|
238
|
+
// and is less useful for debugging leak trace
|
|
239
|
+
const directPropRegex = /^directProp\d+$/;
|
|
240
|
+
function isDirectPropEdge(edge) {
|
|
241
|
+
return directPropRegex.test(`${edge.name_or_index}`);
|
|
242
|
+
}
|
|
243
|
+
function isReturnEdge(edge) {
|
|
244
|
+
if (!edge) {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
if (typeof edge.name_or_index !== 'string') {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
return edge.name_or_index.startsWith('return');
|
|
251
|
+
}
|
|
252
|
+
function isReactPropsEdge(edge) {
|
|
253
|
+
if (!edge) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
if (typeof edge.name_or_index !== 'string') {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
return edge.name_or_index.startsWith('__reactProps$');
|
|
260
|
+
}
|
|
261
|
+
function isReactFiberEdge(edge) {
|
|
262
|
+
if (!edge) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (typeof edge.name_or_index !== 'string') {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
return edge.name_or_index.startsWith('__reactFiber$');
|
|
269
|
+
}
|
|
270
|
+
function hasReactEdges(node) {
|
|
271
|
+
if (!node) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
let ret = false;
|
|
275
|
+
node.forEachReference((edge) => {
|
|
276
|
+
if (isReactFiberEdge(edge) || isReactPropsEdge(edge)) {
|
|
277
|
+
ret = true;
|
|
278
|
+
}
|
|
279
|
+
return { stop: true };
|
|
280
|
+
});
|
|
281
|
+
return ret;
|
|
282
|
+
}
|
|
283
|
+
// HostRoot's stateNode should be a FiberRootNode
|
|
284
|
+
function isHostRoot(node) {
|
|
285
|
+
if (!isFiberNode(node)) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
const stateNode = getToNodeByEdge(node, 'stateNode', 'property');
|
|
289
|
+
return !!stateNode && stateNode.name === 'FiberRootNode';
|
|
290
|
+
}
|
|
291
|
+
function getReactFiberNode(node, propName) {
|
|
292
|
+
if (!node || !isFiberNode(node)) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const targetNode = getToNodeByEdge(node, propName, 'property');
|
|
296
|
+
return isFiberNode(targetNode) ? targetNode : undefined;
|
|
297
|
+
}
|
|
298
|
+
// check if the current node's parent has the node as a child
|
|
299
|
+
function checkIsChildOfParent(node) {
|
|
300
|
+
const parent = getToNodeByEdge(node, 'return', 'property');
|
|
301
|
+
let matched = false;
|
|
302
|
+
iterateChildFiberNodes(parent, child => {
|
|
303
|
+
if (child.id === node.id) {
|
|
304
|
+
matched = true;
|
|
305
|
+
return { stop: true };
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
return matched;
|
|
309
|
+
}
|
|
310
|
+
// iterate through immediate children
|
|
311
|
+
function iterateChildFiberNodes(node, cb) {
|
|
312
|
+
if (!isFiberNode(node)) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
const visited = new Set();
|
|
316
|
+
let cur = getReactFiberNode(node, 'child');
|
|
317
|
+
while (cur && isFiberNode(cur) && !visited.has(cur.id)) {
|
|
318
|
+
const ret = cb(cur);
|
|
319
|
+
visited.add(cur.id);
|
|
320
|
+
if (ret && ret.stop) {
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
cur = getReactFiberNode(cur, 'sibling');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function iterateDescendantFiberNodes(node, iteratorCB) {
|
|
327
|
+
if (!isFiberNode(node)) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
const visited = new Set();
|
|
331
|
+
const stack = [node];
|
|
332
|
+
while (stack.length > 0) {
|
|
333
|
+
const cur = stack.pop();
|
|
334
|
+
if (!cur) {
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const ret = iteratorCB(cur);
|
|
338
|
+
visited.add(cur.id);
|
|
339
|
+
if (ret && ret.stop) {
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
iterateChildFiberNodes(cur, child => {
|
|
343
|
+
if (visited.has(child.id)) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
stack.push(child);
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function getNodesIdSet(snapshot) {
|
|
351
|
+
const set = new Set();
|
|
352
|
+
snapshot.nodes.forEach(node => {
|
|
353
|
+
set.add(node.id);
|
|
354
|
+
});
|
|
355
|
+
return set;
|
|
356
|
+
}
|
|
357
|
+
// given a set of nodes S, return a subset S' where
|
|
358
|
+
// no nodes are dominaeted by nodes in S
|
|
359
|
+
function getConditionalDominatorIds(ids, snapshot, condCb) {
|
|
360
|
+
const dominatorIds = new Set();
|
|
361
|
+
// set all node ids
|
|
362
|
+
applyToNodes(ids, snapshot, node => {
|
|
363
|
+
if (condCb(node)) {
|
|
364
|
+
dominatorIds.add(node.id);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
// traverse the dominators and remove the node
|
|
368
|
+
// if one of it's dominators is already in the set
|
|
369
|
+
applyToNodes(ids, snapshot, node => {
|
|
370
|
+
const visited = new Set([node.id]);
|
|
371
|
+
let cur = node.dominatorNode;
|
|
372
|
+
while (cur) {
|
|
373
|
+
if (visited.has(cur.id)) {
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
if (dominatorIds.has(cur.id)) {
|
|
377
|
+
dominatorIds.delete(node.id);
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
380
|
+
visited.add(cur.id);
|
|
381
|
+
cur = cur.dominatorNode;
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
return dominatorIds;
|
|
385
|
+
}
|
|
386
|
+
const ALTERNATE_NODE_FLAG = 0b1;
|
|
387
|
+
const REGULAR_NODE_FLAG = 0b10;
|
|
388
|
+
function setFiberNodeAttribute(node, flag) {
|
|
389
|
+
if (!node || !isFiberNode(node)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
// eslint-disable-next-line no-bitwise
|
|
393
|
+
node.attributes |= flag;
|
|
394
|
+
}
|
|
395
|
+
function hasFiberNodeAttribute(node, flag) {
|
|
396
|
+
if (!isFiberNode(node)) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
return !!(node.attributes & flag);
|
|
400
|
+
}
|
|
401
|
+
function setIsAlternateNode(node) {
|
|
402
|
+
setFiberNodeAttribute(node, ALTERNATE_NODE_FLAG);
|
|
403
|
+
}
|
|
404
|
+
function isAlternateNode(node) {
|
|
405
|
+
return hasFiberNodeAttribute(node, ALTERNATE_NODE_FLAG);
|
|
406
|
+
}
|
|
407
|
+
function setIsRegularFiberNode(node) {
|
|
408
|
+
setFiberNodeAttribute(node, REGULAR_NODE_FLAG);
|
|
409
|
+
}
|
|
410
|
+
function isRegularFiberNode(node) {
|
|
411
|
+
return hasFiberNodeAttribute(node, REGULAR_NODE_FLAG);
|
|
412
|
+
}
|
|
413
|
+
// The Fiber tree starts with a special type of Fiber node (HostRoot).
|
|
414
|
+
function hasHostRoot(node) {
|
|
415
|
+
if (node && node.is_detached) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
let cur = node;
|
|
419
|
+
const visitedIds = new Set();
|
|
420
|
+
const visitedNodes = new Set();
|
|
421
|
+
while (cur && isFiberNode(cur)) {
|
|
422
|
+
if (cur.id == null || visitedIds.has(cur.id)) {
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
visitedNodes.add(cur);
|
|
426
|
+
visitedIds.add(cur.id);
|
|
427
|
+
if (isHostRoot(cur)) {
|
|
428
|
+
return true;
|
|
429
|
+
}
|
|
430
|
+
cur = getReactFiberNode(cur, 'return');
|
|
431
|
+
}
|
|
432
|
+
for (const visitedNode of visitedNodes) {
|
|
433
|
+
visitedNode.markAsDetached();
|
|
434
|
+
}
|
|
435
|
+
return false;
|
|
436
|
+
}
|
|
437
|
+
function filterNodesInPlace(idSet, snapshot, cb) {
|
|
438
|
+
const ids = Array.from(idSet.keys());
|
|
439
|
+
for (const id of ids) {
|
|
440
|
+
const node = snapshot.getNodeById(id);
|
|
441
|
+
if (node && !cb(node, snapshot)) {
|
|
442
|
+
idSet.delete(id);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
function applyToNodes(idSet, snapshot, cb, options = {}) {
|
|
447
|
+
let ids = Array.from(idSet.keys());
|
|
448
|
+
if (options.shuffle) {
|
|
449
|
+
// eslint-disable-next-line fb-www/unsafe-math-random
|
|
450
|
+
ids.sort(() => Math.random() - 0.5);
|
|
451
|
+
}
|
|
452
|
+
else if (options.reverse) {
|
|
453
|
+
ids = ids.reverse();
|
|
454
|
+
}
|
|
455
|
+
for (const id of ids) {
|
|
456
|
+
const node = snapshot.getNodeById(id);
|
|
457
|
+
if (!node) {
|
|
458
|
+
Console_1.default.warning(`node @${id} is not found`);
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
cb(node, snapshot);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function checkScenarioInstance(s) {
|
|
465
|
+
if (typeof s !== 'object' ||
|
|
466
|
+
typeof s.url !== 'function' ||
|
|
467
|
+
(s.action && typeof s.action !== 'function') ||
|
|
468
|
+
(s.back && typeof s.back !== 'function') ||
|
|
469
|
+
(s.repeat && typeof s.repeat !== 'function') ||
|
|
470
|
+
(s.isPageLoaded && typeof s.isPageLoaded !== 'function') ||
|
|
471
|
+
(s.leakFilter && typeof s.leakFilter !== 'function') ||
|
|
472
|
+
(s.beforeLeakFilter && typeof s.beforeLeakFilter !== 'function')) {
|
|
473
|
+
throw new Error('Invalid senario');
|
|
474
|
+
}
|
|
475
|
+
return s;
|
|
476
|
+
}
|
|
477
|
+
function loadLeakFilter(filename) {
|
|
478
|
+
const filepath = resolveFilePath(filename);
|
|
479
|
+
if (!filepath || !fs_1.default.existsSync(filepath)) {
|
|
480
|
+
// add a throw to silent the type error
|
|
481
|
+
throw haltOrThrow(`Leak filter definition file doesn't exist: ${filepath}`);
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
485
|
+
let filter = require(filepath);
|
|
486
|
+
if (typeof filter === 'function') {
|
|
487
|
+
return { leakFilter: filter };
|
|
488
|
+
}
|
|
489
|
+
filter = (filter === null || filter === void 0 ? void 0 : filter.default) || filter;
|
|
490
|
+
if (typeof filter === 'function') {
|
|
491
|
+
return { leakFilter: filter };
|
|
492
|
+
}
|
|
493
|
+
if (typeof (filter === null || filter === void 0 ? void 0 : filter.leakFilter) === 'function') {
|
|
494
|
+
return filter;
|
|
495
|
+
}
|
|
496
|
+
throw haltOrThrow(`Invalid leak filter in ${filepath}`);
|
|
497
|
+
}
|
|
498
|
+
catch (ex) {
|
|
499
|
+
throw haltOrThrow('Invalid leak filter definition file: ' + filename);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function loadScenario(filename) {
|
|
503
|
+
const filepath = resolveFilePath(filename);
|
|
504
|
+
if (!filepath || !fs_1.default.existsSync(filepath)) {
|
|
505
|
+
// add a throw to silent the type error
|
|
506
|
+
throw haltOrThrow(`Scenario file doesn't exist: ${filepath}`);
|
|
507
|
+
}
|
|
508
|
+
let scenario;
|
|
509
|
+
try {
|
|
510
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
511
|
+
scenario = require(filepath);
|
|
512
|
+
scenario = checkScenarioInstance(scenario);
|
|
513
|
+
if (scenario.name == null) {
|
|
514
|
+
scenario.name = () => path_1.default.basename(filename);
|
|
515
|
+
}
|
|
516
|
+
return scenario;
|
|
517
|
+
}
|
|
518
|
+
catch (ex) {
|
|
519
|
+
throw haltOrThrow('Invalid scenario file: ' + filename);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
function getScenarioName(scenario) {
|
|
523
|
+
if (!scenario.name) {
|
|
524
|
+
return Constant_1.default.namePrefixForScenarioFromFile;
|
|
525
|
+
}
|
|
526
|
+
return Constant_1.default.namePrefixForScenarioFromFile + '-' + scenario.name();
|
|
527
|
+
}
|
|
528
|
+
function handleSnapshotError(e) {
|
|
529
|
+
haltOrThrow(e, {
|
|
530
|
+
primaryMessageToPrint: 'Error parsing heap snapshot',
|
|
531
|
+
secondaryMessageToPrint: 'Please pass in a valid heap snapshot file',
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
function getSnapshotFromFile(filename, options) {
|
|
535
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
536
|
+
Console_1.default.overwrite('parsing ' + filename + ' ...');
|
|
537
|
+
let ret = null;
|
|
538
|
+
try {
|
|
539
|
+
ret = yield HeapParser_1.default.parse(filename, options);
|
|
540
|
+
}
|
|
541
|
+
catch (e) {
|
|
542
|
+
handleSnapshotError(getError(e));
|
|
543
|
+
}
|
|
544
|
+
return ret;
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
function getSnapshotNodeIdsFromFile(filename, options) {
|
|
548
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
549
|
+
Console_1.default.overwrite('lightweight parsing ' + filename + ' ...');
|
|
550
|
+
let ret = new Set();
|
|
551
|
+
try {
|
|
552
|
+
ret = yield HeapParser_1.default.getNodeIdsFromFile(filename, options);
|
|
553
|
+
}
|
|
554
|
+
catch (e) {
|
|
555
|
+
handleSnapshotError(getError(e));
|
|
556
|
+
}
|
|
557
|
+
return ret;
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
const weakMapKeyRegExp = /@(\d+)\) ->/;
|
|
561
|
+
function getWeakMapEdgeKeyId(edge) {
|
|
562
|
+
const name = edge.name_or_index;
|
|
563
|
+
if (typeof name !== 'string') {
|
|
564
|
+
return -1;
|
|
565
|
+
}
|
|
566
|
+
const ret = name.match(weakMapKeyRegExp);
|
|
567
|
+
if (!ret) {
|
|
568
|
+
return -1;
|
|
569
|
+
}
|
|
570
|
+
return Number(ret[1]);
|
|
571
|
+
}
|
|
572
|
+
function isDocumentDOMTreesRoot(node) {
|
|
573
|
+
if (!node) {
|
|
574
|
+
return false;
|
|
575
|
+
}
|
|
576
|
+
return node.type === 'synthetic' && node.name === '(Document DOM trees)';
|
|
577
|
+
}
|
|
578
|
+
function getEdgeByNameAndType(node, edgeName, type) {
|
|
579
|
+
if (!node) {
|
|
580
|
+
return null;
|
|
581
|
+
}
|
|
582
|
+
return node.findReference((edge) => edge.name_or_index === edgeName &&
|
|
583
|
+
(type === undefined || edge.type === type));
|
|
584
|
+
}
|
|
585
|
+
function getEdgeStartsWithName(node, prefix) {
|
|
586
|
+
if (!node) {
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
return node.findReference(edge => typeof edge.name_or_index === 'string' &&
|
|
590
|
+
edge.name_or_index.startsWith(prefix));
|
|
591
|
+
}
|
|
592
|
+
function isStringNode(node) {
|
|
593
|
+
const type = node.type;
|
|
594
|
+
return (type === 'string' ||
|
|
595
|
+
type === 'sliced string' ||
|
|
596
|
+
type === 'concatenated string');
|
|
597
|
+
}
|
|
598
|
+
function isSlicedStringNode(node) {
|
|
599
|
+
return node.type === 'sliced string';
|
|
600
|
+
}
|
|
601
|
+
function getStringNodeValue(node) {
|
|
602
|
+
var _a, _b, _c;
|
|
603
|
+
if (!node) {
|
|
604
|
+
return '';
|
|
605
|
+
}
|
|
606
|
+
if (node.type === 'concatenated string') {
|
|
607
|
+
const firstNode = (_a = getEdgeByNameAndType(node, 'first')) === null || _a === void 0 ? void 0 : _a.toNode;
|
|
608
|
+
const secondNode = (_b = getEdgeByNameAndType(node, 'second')) === null || _b === void 0 ? void 0 : _b.toNode;
|
|
609
|
+
return getStringNodeValue(firstNode) + getStringNodeValue(secondNode);
|
|
610
|
+
}
|
|
611
|
+
if (isSlicedStringNode(node)) {
|
|
612
|
+
const parentNode = (_c = getEdgeByNameAndType(node, 'parent')) === null || _c === void 0 ? void 0 : _c.toNode;
|
|
613
|
+
return getStringNodeValue(parentNode);
|
|
614
|
+
}
|
|
615
|
+
return node.name;
|
|
616
|
+
}
|
|
617
|
+
function extractClosureNodeInfo(node) {
|
|
618
|
+
let name = _extractClosureNodeInfo(node);
|
|
619
|
+
// replace all [, ], (, and )
|
|
620
|
+
name = name.replace(/[[\]()]/g, '');
|
|
621
|
+
return name;
|
|
622
|
+
}
|
|
623
|
+
function _extractClosureNodeInfo(node) {
|
|
624
|
+
if (!node) {
|
|
625
|
+
return '';
|
|
626
|
+
}
|
|
627
|
+
const name = node.name === '' ? '<anonymous>' : node.name;
|
|
628
|
+
if (node.type !== 'closure') {
|
|
629
|
+
return name;
|
|
630
|
+
}
|
|
631
|
+
// node.shared
|
|
632
|
+
const sharedEdge = getEdgeByNameAndType(node, 'shared');
|
|
633
|
+
if (!sharedEdge) {
|
|
634
|
+
return name;
|
|
635
|
+
}
|
|
636
|
+
// node.shared.function_data
|
|
637
|
+
const sharedNode = sharedEdge.toNode;
|
|
638
|
+
const functionDataEdge = getEdgeByNameAndType(sharedNode, 'function_data');
|
|
639
|
+
if (!functionDataEdge) {
|
|
640
|
+
return name;
|
|
641
|
+
}
|
|
642
|
+
// node.shared.function_data[0]
|
|
643
|
+
const functionDataNode = functionDataEdge.toNode;
|
|
644
|
+
const displaynameEdge = getEdgeByNameAndType(functionDataNode, 0, 'hidden');
|
|
645
|
+
if (!displaynameEdge) {
|
|
646
|
+
return name;
|
|
647
|
+
}
|
|
648
|
+
// extract display name
|
|
649
|
+
const displayNameNode = displaynameEdge.toNode;
|
|
650
|
+
if (displayNameNode.type === 'concatenated string' ||
|
|
651
|
+
displayNameNode.type === 'string' ||
|
|
652
|
+
displayNameNode.type === 'sliced string') {
|
|
653
|
+
const str = getStringNodeValue(displayNameNode);
|
|
654
|
+
if (str !== '') {
|
|
655
|
+
return `${name} ${str}`;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return name;
|
|
659
|
+
}
|
|
660
|
+
function extractFiberNodeInfo(node) {
|
|
661
|
+
let name = _extractFiberNodeInfo(node);
|
|
662
|
+
const tagName = _extractFiberNodeTagInfo(node);
|
|
663
|
+
if (tagName) {
|
|
664
|
+
name += ` ${tagName}`;
|
|
665
|
+
}
|
|
666
|
+
// simplify redundant pattern:
|
|
667
|
+
// "(Detached )FiberNode X from X.react" -> "(Detached )FiberNode X"
|
|
668
|
+
const detachedPrefix = 'Detached ';
|
|
669
|
+
let prefix = '';
|
|
670
|
+
if (name.startsWith(detachedPrefix)) {
|
|
671
|
+
prefix = detachedPrefix;
|
|
672
|
+
name = name.substring(detachedPrefix.length);
|
|
673
|
+
}
|
|
674
|
+
const matches = name.match(/^FiberNode (\w+) \[from (\w+)\.react\]$/);
|
|
675
|
+
if (matches && matches[1] === matches[2]) {
|
|
676
|
+
name = `FiberNode ${matches[1]}`;
|
|
677
|
+
}
|
|
678
|
+
// replace all [, ], (, and )
|
|
679
|
+
name = name.replace(/[[\]()]/g, '');
|
|
680
|
+
return prefix + name;
|
|
681
|
+
}
|
|
682
|
+
function getNumberNodeValue(node) {
|
|
683
|
+
if (!node) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
if (Config_1.default.jsEngine === 'hermes') {
|
|
687
|
+
return +node.name;
|
|
688
|
+
}
|
|
689
|
+
const valueNode = getToNodeByEdge(node, 'value', 'internal');
|
|
690
|
+
if (!valueNode) {
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
return +valueNode.name;
|
|
694
|
+
}
|
|
695
|
+
function getBooleanNodeValue(node) {
|
|
696
|
+
if (node === null || node === undefined) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
if (Config_1.default.jsEngine === 'hermes') {
|
|
700
|
+
return node.name === 'true';
|
|
701
|
+
}
|
|
702
|
+
const valueNode = getToNodeByEdge(node, 'value', 'internal');
|
|
703
|
+
if (valueNode === null || valueNode === undefined) {
|
|
704
|
+
return null;
|
|
705
|
+
}
|
|
706
|
+
return valueNode.name === 'true';
|
|
707
|
+
}
|
|
708
|
+
function _extractFiberNodeTagInfo(node) {
|
|
709
|
+
if (!node) {
|
|
710
|
+
return null;
|
|
711
|
+
}
|
|
712
|
+
if (!isFiberNode(node)) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const tagNode = getToNodeByEdge(node, 'tag', 'property');
|
|
716
|
+
if (!tagNode) {
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
if (tagNode.type !== 'number') {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
const tagId = getNumberNodeValue(tagNode);
|
|
723
|
+
return _getReactWorkTagName(tagId);
|
|
724
|
+
}
|
|
725
|
+
function getToNodeByEdge(node, propName, propType) {
|
|
726
|
+
const edge = getEdgeByNameAndType(node, propName, propType);
|
|
727
|
+
if (!edge) {
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
return edge.toNode;
|
|
731
|
+
}
|
|
732
|
+
function getSymbolNodeValue(node) {
|
|
733
|
+
if (!node || node.name !== 'symbol') {
|
|
734
|
+
return null;
|
|
735
|
+
}
|
|
736
|
+
const nameNode = getToNodeByEdge(node, 'name');
|
|
737
|
+
if (!nameNode) {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
return nameNode.name;
|
|
741
|
+
}
|
|
742
|
+
function _extractFiberNodeInfo(node) {
|
|
743
|
+
if (!node) {
|
|
744
|
+
return '';
|
|
745
|
+
}
|
|
746
|
+
const name = node.name;
|
|
747
|
+
if (!isFiberNode(node)) {
|
|
748
|
+
return name;
|
|
749
|
+
}
|
|
750
|
+
// extract FiberNode.type
|
|
751
|
+
const typeNode = getToNodeByEdge(node, 'type', 'property');
|
|
752
|
+
if (!typeNode) {
|
|
753
|
+
return name;
|
|
754
|
+
}
|
|
755
|
+
if (typeNode.type === 'string') {
|
|
756
|
+
return `${name} ${typeNode.name}`;
|
|
757
|
+
}
|
|
758
|
+
// extract FiberNode.type.render
|
|
759
|
+
const renderNode = getToNodeByEdge(typeNode, 'render');
|
|
760
|
+
if (renderNode && renderNode.name) {
|
|
761
|
+
return `${name} ${renderNode.name}`;
|
|
762
|
+
}
|
|
763
|
+
// if FiberNode.type or FiberNode.elementType is a symbol
|
|
764
|
+
let value = getSymbolNodeValue(typeNode);
|
|
765
|
+
if (value) {
|
|
766
|
+
return `${name} ${value}`;
|
|
767
|
+
}
|
|
768
|
+
const elementTypeNode = getToNodeByEdge(node, 'elementType', 'property');
|
|
769
|
+
value = getSymbolNodeValue(elementTypeNode);
|
|
770
|
+
if (value) {
|
|
771
|
+
return `${name} ${value}`;
|
|
772
|
+
}
|
|
773
|
+
// extract FiberNode.elementType.$$typeof
|
|
774
|
+
const typeofNode = getToNodeByEdge(elementTypeNode, '$$typeof', 'property');
|
|
775
|
+
value = getSymbolNodeValue(typeofNode);
|
|
776
|
+
if (value) {
|
|
777
|
+
return `${name} ${value}`;
|
|
778
|
+
}
|
|
779
|
+
// extract FiberNode.type.displayName
|
|
780
|
+
const displayNameNode = getToNodeByEdge(typeNode, 'displayName');
|
|
781
|
+
if (!displayNameNode) {
|
|
782
|
+
return name;
|
|
783
|
+
}
|
|
784
|
+
if (displayNameNode.type === 'string') {
|
|
785
|
+
return `${name} ${displayNameNode.name}`;
|
|
786
|
+
}
|
|
787
|
+
if (displayNameNode.type === 'concatenated string') {
|
|
788
|
+
return `${name} ${getStringNodeValue(displayNameNode)}`;
|
|
789
|
+
}
|
|
790
|
+
return name;
|
|
791
|
+
}
|
|
792
|
+
function extractHTMLElementNodeInfo(node) {
|
|
793
|
+
if (!node) {
|
|
794
|
+
return '';
|
|
795
|
+
}
|
|
796
|
+
const reactFiberEdge = getEdgeStartsWithName(node, '__reactFiber$');
|
|
797
|
+
if (!reactFiberEdge) {
|
|
798
|
+
return node.name;
|
|
799
|
+
}
|
|
800
|
+
return `${node.name} ${extractFiberNodeInfo(reactFiberEdge.toNode)}`;
|
|
801
|
+
}
|
|
802
|
+
function hasOnlyWeakReferrers(node) {
|
|
803
|
+
const referrer = node.findAnyReferrer((edge) => edge.type !== 'weak' && edge.type !== 'shortcut');
|
|
804
|
+
return !!referrer;
|
|
805
|
+
}
|
|
806
|
+
function getRunMetaFilePath() {
|
|
807
|
+
return Config_1.default.useExternalSnapshot
|
|
808
|
+
? Config_1.default.externalRunMetaFile
|
|
809
|
+
: Config_1.default.runMetaFile;
|
|
810
|
+
}
|
|
811
|
+
function loadRunMetaInfo() {
|
|
812
|
+
const file = getRunMetaFilePath();
|
|
813
|
+
try {
|
|
814
|
+
const content = fs_1.default.readFileSync(file, 'UTF-8');
|
|
815
|
+
return JSON.parse(content);
|
|
816
|
+
// eslint-disable-next-line fb-www/no-unused-catch-bindings
|
|
817
|
+
}
|
|
818
|
+
catch (_) {
|
|
819
|
+
throw haltOrThrow('Run info missing. Please make sure `memlab run` is complete.');
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
function loadTargetInfoFromRunMeta() {
|
|
823
|
+
const meta = loadRunMetaInfo();
|
|
824
|
+
Config_1.default.targetApp = meta.app;
|
|
825
|
+
Config_1.default.targetTab = meta.interaction;
|
|
826
|
+
BrowserInfo_1.default.load(meta.browserInfo);
|
|
827
|
+
}
|
|
828
|
+
function getSnapshotSequenceFilePath() {
|
|
829
|
+
if (!Config_1.default.useExternalSnapshot) {
|
|
830
|
+
// load the snapshot sequence meta file from the default location
|
|
831
|
+
return Config_1.default.snapshotSequenceFile;
|
|
832
|
+
}
|
|
833
|
+
if (Config_1.default.externalSnapshotDir) {
|
|
834
|
+
// try to load the snap-seq.json file from the specified external dir
|
|
835
|
+
const metaFile = path_1.default.join(Config_1.default.externalSnapshotDir, 'snap-seq.json');
|
|
836
|
+
if (fs_1.default.existsSync(metaFile)) {
|
|
837
|
+
return metaFile;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
// otherwise return the default meta file for external snapshots
|
|
841
|
+
return Config_1.default.externalSnapshotVisitOrderFile;
|
|
842
|
+
}
|
|
843
|
+
// this should be called only after exploration
|
|
844
|
+
function loadTabsOrder() {
|
|
845
|
+
try {
|
|
846
|
+
const content = fs_1.default.readFileSync(getSnapshotSequenceFilePath(), 'UTF-8');
|
|
847
|
+
return JSON.parse(content);
|
|
848
|
+
}
|
|
849
|
+
catch (_a) {
|
|
850
|
+
throw haltOrThrow('snapshot meta data invalid or missing');
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
// if true the leak trace is will be reported
|
|
854
|
+
function isInterestingPath(p) {
|
|
855
|
+
// do not filter paths when analyzing Hermes snapshots
|
|
856
|
+
if (Config_1.default.jsEngine === 'hermes') {
|
|
857
|
+
return true;
|
|
858
|
+
}
|
|
859
|
+
// if the path has pattern: Window -> [InternalNode]+ -> DetachedElement
|
|
860
|
+
if (Config_1.default.hideBrowserLeak && internalNodeRetainsDetachedElement(p)) {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
// if the path has pattern: ShadowRoot -> DetachedElement
|
|
864
|
+
if (Config_1.default.hideBrowserLeak && shadowRootRetainsDetachedElement(p)) {
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
return true;
|
|
868
|
+
}
|
|
869
|
+
// return true if the heap node represents JS object or closure
|
|
870
|
+
function isObjectNode(node) {
|
|
871
|
+
if (isPlainJSObjectNode(node)) {
|
|
872
|
+
return true;
|
|
873
|
+
}
|
|
874
|
+
return node.type === 'closure';
|
|
875
|
+
}
|
|
876
|
+
// return true if the heap node represents JS object
|
|
877
|
+
function isPlainJSObjectNode(node) {
|
|
878
|
+
if (!node) {
|
|
879
|
+
return false;
|
|
880
|
+
}
|
|
881
|
+
if (Config_1.default.jsEngine === 'hermes') {
|
|
882
|
+
return node.name === 'Object' || node.name.startsWith('Object(');
|
|
883
|
+
}
|
|
884
|
+
return node.name === 'Object';
|
|
885
|
+
}
|
|
886
|
+
// check if the path has pattern:
|
|
887
|
+
// Window -> [InternalNode | Text]+ -> DetachedElement
|
|
888
|
+
function internalNodeRetainsDetachedElement(path) {
|
|
889
|
+
var _a, _b;
|
|
890
|
+
if (!path) {
|
|
891
|
+
return false;
|
|
892
|
+
}
|
|
893
|
+
let p = path;
|
|
894
|
+
// GC root is not Window
|
|
895
|
+
if (!p.node || !p.node.name.startsWith('Window')) {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
p = p.next;
|
|
899
|
+
// Window is not poining to InternalNode
|
|
900
|
+
if (!p || !p.node || p.node.name !== 'InternalNode') {
|
|
901
|
+
return false;
|
|
902
|
+
}
|
|
903
|
+
// skip the rest InternalNode
|
|
904
|
+
while (((_a = p.node) === null || _a === void 0 ? void 0 : _a.name) === 'InternalNode' || ((_b = p.node) === null || _b === void 0 ? void 0 : _b.name) === 'Text') {
|
|
905
|
+
p = p.next;
|
|
906
|
+
if (!p) {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
// check if the node is a detached element
|
|
911
|
+
return p && isDetachedDOMNode(p.node);
|
|
912
|
+
}
|
|
913
|
+
// check if the path has pattern: ShadowRoot -> DetachedElement
|
|
914
|
+
function shadowRootRetainsDetachedElement(path) {
|
|
915
|
+
let p = path;
|
|
916
|
+
// find the ShadowRoot
|
|
917
|
+
while (p && p.node && p.node.name !== 'ShadowRoot') {
|
|
918
|
+
p = p.next;
|
|
919
|
+
if (!p) {
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
p = p.next;
|
|
924
|
+
// check if the node is a detached element
|
|
925
|
+
return !!p && isDetachedDOMNode(p.node);
|
|
926
|
+
}
|
|
927
|
+
function pathHasDetachedHTMLNode(path) {
|
|
928
|
+
if (!path) {
|
|
929
|
+
return false;
|
|
930
|
+
}
|
|
931
|
+
let p = path;
|
|
932
|
+
while (p) {
|
|
933
|
+
if (p.node && isDetachedDOMNode(p.node)) {
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
p = p.next;
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
940
|
+
function pathHasEdgeWithIndex(path, idx) {
|
|
941
|
+
if (!path || typeof idx !== 'number') {
|
|
942
|
+
return false;
|
|
943
|
+
}
|
|
944
|
+
let p = path;
|
|
945
|
+
while (p) {
|
|
946
|
+
if (p.edge && p.edge.edgeIndex === idx) {
|
|
947
|
+
return true;
|
|
948
|
+
}
|
|
949
|
+
p = p.next;
|
|
950
|
+
}
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
function getLastNodeId(path) {
|
|
954
|
+
if (!path) {
|
|
955
|
+
return -1;
|
|
956
|
+
}
|
|
957
|
+
let p = path;
|
|
958
|
+
while (p) {
|
|
959
|
+
if (!p.next && p.node) {
|
|
960
|
+
return p.node.id;
|
|
961
|
+
}
|
|
962
|
+
p = p.next;
|
|
963
|
+
}
|
|
964
|
+
return -1;
|
|
965
|
+
}
|
|
966
|
+
function getReadablePercent(num) {
|
|
967
|
+
if (Number.isNaN(num)) {
|
|
968
|
+
return `${num}%`;
|
|
969
|
+
}
|
|
970
|
+
const v = num * 100;
|
|
971
|
+
let str = v.toFixed(2);
|
|
972
|
+
if (str.endsWith('.00')) {
|
|
973
|
+
str = str.slice(0, -3);
|
|
974
|
+
}
|
|
975
|
+
else if (str.endsWith('0')) {
|
|
976
|
+
str = str.slice(0, -1);
|
|
977
|
+
}
|
|
978
|
+
return str + '%';
|
|
979
|
+
}
|
|
980
|
+
function getReadableBytes(bytes) {
|
|
981
|
+
let n, suffix;
|
|
982
|
+
if (bytes === undefined || bytes === null) {
|
|
983
|
+
return '';
|
|
984
|
+
}
|
|
985
|
+
if (bytes >= 1e12) {
|
|
986
|
+
n = ((bytes / 1e11) | 0) / 10;
|
|
987
|
+
suffix = 'TB';
|
|
988
|
+
}
|
|
989
|
+
else if (bytes >= 1e9) {
|
|
990
|
+
n = ((bytes / 1e8) | 0) / 10;
|
|
991
|
+
suffix = 'GB';
|
|
992
|
+
}
|
|
993
|
+
else if (bytes >= 1e6) {
|
|
994
|
+
n = ((bytes / 1e5) | 0) / 10;
|
|
995
|
+
suffix = 'MB';
|
|
996
|
+
}
|
|
997
|
+
else if (bytes >= 1e3) {
|
|
998
|
+
n = ((bytes / 1e2) | 0) / 10;
|
|
999
|
+
suffix = 'KB';
|
|
1000
|
+
}
|
|
1001
|
+
else if (bytes > 1) {
|
|
1002
|
+
n = bytes;
|
|
1003
|
+
suffix = ' bytes';
|
|
1004
|
+
}
|
|
1005
|
+
else if (bytes >= 0) {
|
|
1006
|
+
n = bytes;
|
|
1007
|
+
suffix = ' byte';
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
return '';
|
|
1011
|
+
}
|
|
1012
|
+
return n + suffix;
|
|
1013
|
+
}
|
|
1014
|
+
function p1(n, divide) {
|
|
1015
|
+
return (((n * 10) / divide) | 0) / 10;
|
|
1016
|
+
}
|
|
1017
|
+
function getReadableTime(ms) {
|
|
1018
|
+
let time = ms;
|
|
1019
|
+
if (time < 1000) {
|
|
1020
|
+
return `${time}ms`;
|
|
1021
|
+
}
|
|
1022
|
+
time /= 1000;
|
|
1023
|
+
if (time < 60) {
|
|
1024
|
+
return `${p1(time, 1)}s`;
|
|
1025
|
+
}
|
|
1026
|
+
time /= 60;
|
|
1027
|
+
if (time < 60) {
|
|
1028
|
+
return `${p1(time, 1)}min`;
|
|
1029
|
+
}
|
|
1030
|
+
time /= 60;
|
|
1031
|
+
if (time < 24) {
|
|
1032
|
+
return `${p1(time, 1)}hr`;
|
|
1033
|
+
}
|
|
1034
|
+
time /= 24;
|
|
1035
|
+
return `${p1(time, 1)} days`;
|
|
1036
|
+
}
|
|
1037
|
+
function shouldShowMoreInfo(node) {
|
|
1038
|
+
if (!node || !node.name) {
|
|
1039
|
+
return false;
|
|
1040
|
+
}
|
|
1041
|
+
if (!Config_1.default.nodeToShowMoreInfo) {
|
|
1042
|
+
return false;
|
|
1043
|
+
}
|
|
1044
|
+
return Config_1.default.nodeToShowMoreInfo.has(node.name);
|
|
1045
|
+
}
|
|
1046
|
+
function isDebuggableNode(node) {
|
|
1047
|
+
if (!node) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
if (node.type === 'native' && !isDetachedDOMNode(node)) {
|
|
1051
|
+
return false;
|
|
1052
|
+
}
|
|
1053
|
+
if (node.type === 'hidden' ||
|
|
1054
|
+
node.type === 'array' ||
|
|
1055
|
+
node.type === 'string' ||
|
|
1056
|
+
node.type === 'number' ||
|
|
1057
|
+
node.type === 'concatenated string' ||
|
|
1058
|
+
node.type === 'sliced string' ||
|
|
1059
|
+
node.type === 'code' ||
|
|
1060
|
+
node.name === 'system / Context') {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
function throwError(error) {
|
|
1066
|
+
if (error) {
|
|
1067
|
+
error.stack;
|
|
1068
|
+
}
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
function callAsync(f) {
|
|
1072
|
+
const promise = f();
|
|
1073
|
+
if (promise && promise.catch) {
|
|
1074
|
+
promise.catch((e) => {
|
|
1075
|
+
var _a;
|
|
1076
|
+
const parsedError = getError(e);
|
|
1077
|
+
Console_1.default.error(parsedError.message);
|
|
1078
|
+
Console_1.default.lowLevel((_a = parsedError.stack) !== null && _a !== void 0 ? _a : '');
|
|
1079
|
+
});
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
function checkUninstalledLibrary(ex) {
|
|
1083
|
+
var _a;
|
|
1084
|
+
const stackStr = (_a = ex.stack) === null || _a === void 0 ? void 0 : _a.toString();
|
|
1085
|
+
if (stackStr === null || stackStr === void 0 ? void 0 : stackStr.includes('cannot open shared object file')) {
|
|
1086
|
+
haltOrThrow(ex, {
|
|
1087
|
+
primaryMessageToPrint: 'Could not launch Chrome. To run MemLab on a CentOS 8 devserver, please run the following command:\n',
|
|
1088
|
+
secondaryMessageToPrint: 'sudo dnf install nss libwayland-client libwayland-egl egl-wayland libpng15 mesa-libGL atk java-atk-wrapper at-spi2-atk gtk3 libXt',
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
function closePuppeteer(browser, pages, options = {}) {
|
|
1093
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1094
|
+
if (Config_1.default.isLocalPuppeteer && !options.warmup) {
|
|
1095
|
+
yield Promise.all(pages.map(page => page.close()));
|
|
1096
|
+
yield browser.disconnect();
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
yield browser.close();
|
|
1100
|
+
}
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
function camelCaseToReadableString(str) {
|
|
1104
|
+
let ret = '';
|
|
1105
|
+
const isUpperCase = (c) => /^[A-Z]$/.test(c);
|
|
1106
|
+
for (const c of str) {
|
|
1107
|
+
if (isUpperCase(c)) {
|
|
1108
|
+
ret += ret.length > 0 ? ' ' : '';
|
|
1109
|
+
ret += c.toLowerCase();
|
|
1110
|
+
}
|
|
1111
|
+
else {
|
|
1112
|
+
ret += c;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
return ret;
|
|
1116
|
+
}
|
|
1117
|
+
// Given a file path (relative or absolute),
|
|
1118
|
+
// this function tries to resolve to a absolute path that exists
|
|
1119
|
+
// in MemLab's directories.
|
|
1120
|
+
// if nothing is found, it returns null.
|
|
1121
|
+
function resolveFilePath(file) {
|
|
1122
|
+
if (!file) {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
const dirs = [
|
|
1126
|
+
Config_1.default.curDataDir,
|
|
1127
|
+
Config_1.default.persistentDataDir,
|
|
1128
|
+
Config_1.default.monoRepoDir,
|
|
1129
|
+
];
|
|
1130
|
+
const paths = [file].concat(dirs.map(d => path_1.default.join(d, file)));
|
|
1131
|
+
for (const p of paths) {
|
|
1132
|
+
const filepath = path_1.default.resolve(p);
|
|
1133
|
+
if (fs_1.default.existsSync(filepath)) {
|
|
1134
|
+
return filepath;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
const snapshotNamePattern = /^s(\d+)\.heapsnapshot$/;
|
|
1140
|
+
function compareSnapshotName(f1, f2) {
|
|
1141
|
+
// if file name follows the 's{\d+}.heapsnapshot' pattern
|
|
1142
|
+
// then order based on the ascending order of the number
|
|
1143
|
+
const m1 = f1.match(snapshotNamePattern);
|
|
1144
|
+
const m2 = f2.match(snapshotNamePattern);
|
|
1145
|
+
if (m1 && m2) {
|
|
1146
|
+
return parseInt(m1[1], 10) - parseInt(m2[1], 10);
|
|
1147
|
+
}
|
|
1148
|
+
// otherwise sort in alpha numeric order
|
|
1149
|
+
return f1 < f2 ? -1 : f1 === f2 ? 0 : 1;
|
|
1150
|
+
}
|
|
1151
|
+
function getSnapshotFilesInDir(dir) {
|
|
1152
|
+
try {
|
|
1153
|
+
return fs_1.default
|
|
1154
|
+
.readdirSync(dir)
|
|
1155
|
+
.filter(file => file.endsWith('.heapsnapshot'))
|
|
1156
|
+
.sort(compareSnapshotName)
|
|
1157
|
+
.map(file => path_1.default.join(dir, file));
|
|
1158
|
+
}
|
|
1159
|
+
catch (ex) {
|
|
1160
|
+
throw __1.utils.haltOrThrow(__1.utils.getError(ex));
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function getSnapshotFilesFromTabsOrder(options = {}) {
|
|
1164
|
+
const tabsOrder = loadTabsOrder();
|
|
1165
|
+
const ret = [];
|
|
1166
|
+
const typesSeen = new Set();
|
|
1167
|
+
for (let i = 0; i < tabsOrder.length; i++) {
|
|
1168
|
+
const tab = tabsOrder[i];
|
|
1169
|
+
if (!tab.snapshot) {
|
|
1170
|
+
continue;
|
|
1171
|
+
}
|
|
1172
|
+
if (tab.type) {
|
|
1173
|
+
typesSeen.add(tab.type);
|
|
1174
|
+
}
|
|
1175
|
+
if (options.skipBeforeTabType &&
|
|
1176
|
+
!typesSeen.has(options.skipBeforeTabType)) {
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
ret.push(getSnapshotFilePath(tab));
|
|
1180
|
+
}
|
|
1181
|
+
return ret;
|
|
1182
|
+
}
|
|
1183
|
+
// checks if the snapshots along with their meta data are complete
|
|
1184
|
+
function checkSnapshots(options = {}) {
|
|
1185
|
+
if (Config_1.default.skipSnapshot) {
|
|
1186
|
+
haltOrThrow('This command is run with `--no-snapshot`, skip snapshot check.');
|
|
1187
|
+
}
|
|
1188
|
+
let snapshotDir;
|
|
1189
|
+
if (options.snapshotDir) {
|
|
1190
|
+
snapshotDir = options.snapshotDir;
|
|
1191
|
+
}
|
|
1192
|
+
else if (Config_1.default.useExternalSnapshot) {
|
|
1193
|
+
snapshotDir = Config_1.default.externalSnapshotDir || '<missing>';
|
|
1194
|
+
}
|
|
1195
|
+
else {
|
|
1196
|
+
snapshotDir = FileManager_1.default.getCurDataDir({});
|
|
1197
|
+
}
|
|
1198
|
+
if (options.snapshotDir) {
|
|
1199
|
+
const snapshots = getSnapshotFilesInDir(snapshotDir);
|
|
1200
|
+
const min = options.minSnapshots || 1;
|
|
1201
|
+
if (snapshots.length < min) {
|
|
1202
|
+
__1.utils.haltOrThrow(`Directory has < ${min} snapshot files: ${options.snapshotDir}`);
|
|
1203
|
+
}
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
// check if any snapshot file is missing
|
|
1207
|
+
const tabsOrder = loadTabsOrder();
|
|
1208
|
+
const missingTabs = Object.create(null);
|
|
1209
|
+
let miss = 0;
|
|
1210
|
+
for (const tab of tabsOrder) {
|
|
1211
|
+
if (!tab.snapshot) {
|
|
1212
|
+
continue;
|
|
1213
|
+
}
|
|
1214
|
+
const file = getSnapshotFilePath(tab);
|
|
1215
|
+
if (!fs_1.default.existsSync(file)) {
|
|
1216
|
+
++miss;
|
|
1217
|
+
missingTabs[tab.idx] = {
|
|
1218
|
+
name: tab.name,
|
|
1219
|
+
url: tab.url,
|
|
1220
|
+
type: tab.type,
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
if (miss > 0) {
|
|
1225
|
+
const msg = 'snapshot for the following tabs are missing:';
|
|
1226
|
+
const printCallback = () => {
|
|
1227
|
+
Console_1.default.warning(msg);
|
|
1228
|
+
Console_1.default.table(missingTabs);
|
|
1229
|
+
};
|
|
1230
|
+
haltOrThrow(msg + JSON.stringify(missingTabs, null, 2), {
|
|
1231
|
+
printCallback,
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
function resolveSnapshotFilePath(snapshotFile) {
|
|
1236
|
+
const file = resolveFilePath(snapshotFile);
|
|
1237
|
+
if (!file) {
|
|
1238
|
+
throw haltOrThrow(new Error(`Error: snapshot file doesn't exist ${snapshotFile}`));
|
|
1239
|
+
}
|
|
1240
|
+
return file;
|
|
1241
|
+
}
|
|
1242
|
+
exports.resolveSnapshotFilePath = resolveSnapshotFilePath;
|
|
1243
|
+
function getSnapshotDirForAnalysis() {
|
|
1244
|
+
const dir = Config_1.default.externalSnapshotDir;
|
|
1245
|
+
if (!dir) {
|
|
1246
|
+
throw __1.utils.haltOrThrow(new Error('external snapshot file not set'));
|
|
1247
|
+
}
|
|
1248
|
+
return dir;
|
|
1249
|
+
}
|
|
1250
|
+
function getSingleSnapshotFileForAnalysis() {
|
|
1251
|
+
const path = Config_1.default.useExternalSnapshot && Config_1.default.externalSnapshotFilePaths[0]
|
|
1252
|
+
? Config_1.default.externalSnapshotFilePaths[0]
|
|
1253
|
+
: getSnapshotFilePathWithTabType(/(final)|(target)|(baseline)/);
|
|
1254
|
+
return resolveSnapshotFilePath(path);
|
|
1255
|
+
}
|
|
1256
|
+
function getSnapshotFilePath(tab) {
|
|
1257
|
+
if (!Config_1.default.useExternalSnapshot) {
|
|
1258
|
+
return path_1.default.join(Config_1.default.curDataDir, `s${tab.idx}.heapsnapshot`);
|
|
1259
|
+
}
|
|
1260
|
+
// if we are loading snapshot from external snapshot dir
|
|
1261
|
+
if (Config_1.default.externalSnapshotDir) {
|
|
1262
|
+
return path_1.default.join(Config_1.default.externalSnapshotDir, `s${tab.idx}.heapsnapshot`);
|
|
1263
|
+
}
|
|
1264
|
+
return Config_1.default.externalSnapshotFilePaths[tab.idx - 1];
|
|
1265
|
+
}
|
|
1266
|
+
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any
|
|
1267
|
+
function equalOrMatch(v1, v2) {
|
|
1268
|
+
const t1 = typeof v1;
|
|
1269
|
+
const t2 = typeof v2;
|
|
1270
|
+
if (t1 === t2) {
|
|
1271
|
+
return v1 === v2;
|
|
1272
|
+
}
|
|
1273
|
+
if (t1 === 'string' && v2 instanceof RegExp) {
|
|
1274
|
+
return v2.test(v1);
|
|
1275
|
+
}
|
|
1276
|
+
if (t2 === 'string' && v1 instanceof RegExp) {
|
|
1277
|
+
return v1.test(v2);
|
|
1278
|
+
}
|
|
1279
|
+
return false;
|
|
1280
|
+
}
|
|
1281
|
+
function getSnapshotFilePathWithTabType(type) {
|
|
1282
|
+
checkSnapshots();
|
|
1283
|
+
const tabsOrder = loadTabsOrder();
|
|
1284
|
+
for (let i = tabsOrder.length - 1; i >= 0; --i) {
|
|
1285
|
+
const tab = tabsOrder[i];
|
|
1286
|
+
if (!tab.snapshot) {
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
if (equalOrMatch(tab.type, type)) {
|
|
1290
|
+
return getSnapshotFilePath(tab);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
function isMeaningfulNode(node) {
|
|
1296
|
+
if (!node) {
|
|
1297
|
+
return false;
|
|
1298
|
+
}
|
|
1299
|
+
if (Config_1.default.nodeNameBlockList.has(node.name)) {
|
|
1300
|
+
return false;
|
|
1301
|
+
}
|
|
1302
|
+
if (isFiberNode(node)) {
|
|
1303
|
+
const displayName = extractFiberNodeInfo(node);
|
|
1304
|
+
if (Config_1.default.nodeNameBlockList.has(displayName)) {
|
|
1305
|
+
return false;
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
// More details in https://github.com/ChromeDevTools/devtools-frontend
|
|
1309
|
+
// under front_end/heap_snapshot_worker/HeapSnapshot.ts
|
|
1310
|
+
if (node.name === 'system / NativeContext') {
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
if (node.name === 'system / SourcePositionTableWithFrameCache') {
|
|
1314
|
+
return false;
|
|
1315
|
+
}
|
|
1316
|
+
if (node.name === '(map descriptors)') {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
if (node.type === 'code') {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
return true;
|
|
1323
|
+
}
|
|
1324
|
+
function isMeaningfulEdge(edge, options = {}) {
|
|
1325
|
+
const node = options.isForward ? edge.toNode : edge.fromNode;
|
|
1326
|
+
const source = options.isForward ? edge.fromNode : edge.toNode;
|
|
1327
|
+
// exclude self references
|
|
1328
|
+
if (source.id === node.id) {
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
if (typeof edge.name_or_index === 'string' &&
|
|
1332
|
+
Config_1.default.edgeNameBlockList.has(edge.name_or_index)) {
|
|
1333
|
+
return false;
|
|
1334
|
+
}
|
|
1335
|
+
// shortcut edge may be meaningful edges
|
|
1336
|
+
// --forceUpdate (variable)---> [native_bind]
|
|
1337
|
+
// --bound_argument_0 (shortcut)---> [FiberNode]
|
|
1338
|
+
if (edge.type === 'weak' /* || edge.type === 'shortcut' */) {
|
|
1339
|
+
return false;
|
|
1340
|
+
}
|
|
1341
|
+
if (options.excludeWeakMapEdge && isWeakMapEdgeToKey(edge)) {
|
|
1342
|
+
return false;
|
|
1343
|
+
}
|
|
1344
|
+
if (options.visited && options.visited[node.nodeIndex]) {
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
if (options.queued && options.queued[node.nodeIndex]) {
|
|
1348
|
+
return false;
|
|
1349
|
+
}
|
|
1350
|
+
if (!options.includeString && node.type === 'string') {
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
if (edge.type === 'internal' && edge.name_or_index === 'code') {
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
// More details about the following three special cases are available
|
|
1357
|
+
// in https://github.com/ChromeDevTools/devtools-frontend
|
|
1358
|
+
// under front_end/heap_snapshot_worker/HeapSnapshot.ts
|
|
1359
|
+
if (edge.type === 'hidden' && edge.name_or_index === 'sloppy_function_map') {
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
if (edge.type === 'hidden' && node.name === 'system / NativeContext') {
|
|
1363
|
+
return false;
|
|
1364
|
+
}
|
|
1365
|
+
// In v8, (map descriptors) are fixed-length descriptors arrays used
|
|
1366
|
+
// to hold JS descriptors.
|
|
1367
|
+
if (node.type === 'array' && node.name === '(map descriptors)') {
|
|
1368
|
+
const index = edge.name_or_index;
|
|
1369
|
+
// only elements at particular indexes of (map descriptors) are holding
|
|
1370
|
+
// representative references to objects.
|
|
1371
|
+
if (index >= 2 || (typeof index === 'number' && index % 3 === 1)) {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
if (!isMeaningfulNode(node)) {
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
if (Config_1.default.jsEngine === 'hermes' && isDirectPropEdge(edge)) {
|
|
1379
|
+
return false;
|
|
1380
|
+
}
|
|
1381
|
+
if (Config_1.default.ignoreInternalNode && node.name.includes('InternalNode')) {
|
|
1382
|
+
return false;
|
|
1383
|
+
}
|
|
1384
|
+
if (Config_1.default.ignoreDevToolsConsoleLeak) {
|
|
1385
|
+
const name = edge.name_or_index;
|
|
1386
|
+
if (typeof name === 'string' && name.includes('DevTools console')) {
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
// check if two URLs are equivalent
|
|
1393
|
+
// for example, the following pairs are equal
|
|
1394
|
+
// 'https://test.com/?a=1&b=2&a=3', 'https://test.com?b=2&a=3&a=1'
|
|
1395
|
+
// 'https://test.com/p1/p2?a=1,b=2', 'https://test.com/p1/p2/?a=1,b=2'
|
|
1396
|
+
function isURLEqual(url1, url2) {
|
|
1397
|
+
let u1, u2;
|
|
1398
|
+
try {
|
|
1399
|
+
u1 = new URL(url1);
|
|
1400
|
+
u2 = new URL(url2);
|
|
1401
|
+
// eslint-disable-next-line fb-www/no-unused-catch-bindings
|
|
1402
|
+
}
|
|
1403
|
+
catch (_e) {
|
|
1404
|
+
return false;
|
|
1405
|
+
}
|
|
1406
|
+
// compare URL fields
|
|
1407
|
+
if (u1.protocol !== u2.protocol ||
|
|
1408
|
+
u1.host !== u2.host ||
|
|
1409
|
+
u1.hostname !== u2.hostname ||
|
|
1410
|
+
u1.port !== u2.port ||
|
|
1411
|
+
u1.hash !== u2.hash) {
|
|
1412
|
+
return false;
|
|
1413
|
+
}
|
|
1414
|
+
// compare path
|
|
1415
|
+
let p1 = u1.pathname;
|
|
1416
|
+
p1 = p1.endsWith('/') ? p1.slice(0, -1) : p1;
|
|
1417
|
+
let p2 = u2.pathname;
|
|
1418
|
+
p2 = p2.endsWith('/') ? p2.slice(0, -1) : p2;
|
|
1419
|
+
if (p1 !== p2) {
|
|
1420
|
+
return false;
|
|
1421
|
+
}
|
|
1422
|
+
// compare URL params
|
|
1423
|
+
const paramKeys = new Set([
|
|
1424
|
+
...Array.from(u1.searchParams.keys()),
|
|
1425
|
+
...Array.from(u2.searchParams.keys()),
|
|
1426
|
+
]);
|
|
1427
|
+
for (const key of paramKeys) {
|
|
1428
|
+
const v1 = u1.searchParams.getAll(key).sort().join(' ');
|
|
1429
|
+
const v2 = u2.searchParams.getAll(key).sort().join(' ');
|
|
1430
|
+
if (v1 !== v2) {
|
|
1431
|
+
return false;
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
return true;
|
|
1435
|
+
}
|
|
1436
|
+
function shuffleArray(array) {
|
|
1437
|
+
for (let i = array.length - 1; i > 0; i--) {
|
|
1438
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
1439
|
+
[array[i], array[j]] = [array[j], array[i]];
|
|
1440
|
+
}
|
|
1441
|
+
return array;
|
|
1442
|
+
}
|
|
1443
|
+
function getLeakedNode(path) {
|
|
1444
|
+
let p = path;
|
|
1445
|
+
const set = new Set([p]);
|
|
1446
|
+
while (p.next && !set.has(p.next)) {
|
|
1447
|
+
set.add(p.next);
|
|
1448
|
+
p = p.next;
|
|
1449
|
+
}
|
|
1450
|
+
if (!p || !p.node) {
|
|
1451
|
+
return null;
|
|
1452
|
+
}
|
|
1453
|
+
return p.node;
|
|
1454
|
+
}
|
|
1455
|
+
// print snapshot to file for local testing
|
|
1456
|
+
function dumpSnapshot(file, snapshot) {
|
|
1457
|
+
fs_1.default.writeFileSync(file, JSON.stringify(snapshot.snapshot.meta, null, 0), 'UTF-8');
|
|
1458
|
+
const dumpSection = (name, arr) => {
|
|
1459
|
+
let buf = '';
|
|
1460
|
+
fs_1.default.appendFileSync(file, `\n\n ${name}:\n\n`, 'UTF-8');
|
|
1461
|
+
for (let i = 0; i < arr.length; ++i) {
|
|
1462
|
+
buf += arr[i] + ',';
|
|
1463
|
+
if (buf.length > 1024 * 1024) {
|
|
1464
|
+
fs_1.default.appendFileSync(file, '\n\n' + buf, 'UTF-8');
|
|
1465
|
+
buf = '';
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
fs_1.default.appendFileSync(file, '\n\n' + buf, 'UTF-8');
|
|
1469
|
+
buf = '';
|
|
1470
|
+
};
|
|
1471
|
+
dumpSection('nodes', snapshot.nodes);
|
|
1472
|
+
dumpSection('edges', snapshot.edges);
|
|
1473
|
+
dumpSection('locations', snapshot.locations);
|
|
1474
|
+
}
|
|
1475
|
+
function markAllDetachedFiberNode(snapshot) {
|
|
1476
|
+
Console_1.default.overwrite('marking all detached Fiber nodes...');
|
|
1477
|
+
snapshot.nodes.forEach(node => {
|
|
1478
|
+
// hasHostRoot checks and marks detached Fiber Nodes
|
|
1479
|
+
isFiberNode(node) && !hasHostRoot(node);
|
|
1480
|
+
});
|
|
1481
|
+
}
|
|
1482
|
+
function markAlternateFiberNode(snapshot) {
|
|
1483
|
+
Console_1.default.overwrite('marking alternate Fiber nodes...');
|
|
1484
|
+
snapshot.nodes.forEach(node => {
|
|
1485
|
+
// mark the fiber root node
|
|
1486
|
+
if (!isHostRoot(node)) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
iterateDescendantFiberNodes(node, (descendant) => {
|
|
1490
|
+
// check if the node is doubly linked to its parent
|
|
1491
|
+
if (checkIsChildOfParent(descendant)) {
|
|
1492
|
+
setIsRegularFiberNode(descendant);
|
|
1493
|
+
}
|
|
1494
|
+
// mark explicit alternate fiber node
|
|
1495
|
+
setIsAlternateNode(getToNodeByEdge(descendant, 'alternate', 'property'));
|
|
1496
|
+
});
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
function getAllDominators(node) {
|
|
1500
|
+
const visited = new Set();
|
|
1501
|
+
const dominators = [];
|
|
1502
|
+
let cur = node;
|
|
1503
|
+
while (cur && !visited.has(cur.id)) {
|
|
1504
|
+
visited.add(cur.id);
|
|
1505
|
+
dominators.push(cur);
|
|
1506
|
+
cur = cur.dominatorNode;
|
|
1507
|
+
}
|
|
1508
|
+
return dominators;
|
|
1509
|
+
}
|
|
1510
|
+
function upperCaseFirstCharacter(text) {
|
|
1511
|
+
if (text.length === 0) {
|
|
1512
|
+
return text;
|
|
1513
|
+
}
|
|
1514
|
+
return text[0].toUpperCase() + text.substring(1);
|
|
1515
|
+
}
|
|
1516
|
+
function repeat(str, n) {
|
|
1517
|
+
let ret = '';
|
|
1518
|
+
for (let i = 0; i < n; ++i) {
|
|
1519
|
+
ret += str;
|
|
1520
|
+
}
|
|
1521
|
+
return ret;
|
|
1522
|
+
}
|
|
1523
|
+
function normalizeBaseUrl(url) {
|
|
1524
|
+
let ret = url;
|
|
1525
|
+
if (url.length > 0 &&
|
|
1526
|
+
!url.endsWith('.html') &&
|
|
1527
|
+
!url.endsWith('.htm') &&
|
|
1528
|
+
!url.endsWith('/')) {
|
|
1529
|
+
ret += '/';
|
|
1530
|
+
}
|
|
1531
|
+
return ret;
|
|
1532
|
+
}
|
|
1533
|
+
function haltOrThrow(errorInfo, options = {}) {
|
|
1534
|
+
var _a;
|
|
1535
|
+
const err = getError(errorInfo);
|
|
1536
|
+
const halt = () => __awaiter(this, void 0, void 0, function* () {
|
|
1537
|
+
var _b;
|
|
1538
|
+
if (options.printErrorBeforeHalting !== false) {
|
|
1539
|
+
// only print the error.message when there is no
|
|
1540
|
+
// primary message to print or there is no print callback
|
|
1541
|
+
if (!options.primaryMessageToPrint && !options.printCallback) {
|
|
1542
|
+
Console_1.default.error(err.message);
|
|
1543
|
+
}
|
|
1544
|
+
// only print stack trace in verbose mode
|
|
1545
|
+
if (Config_1.default.verbose) {
|
|
1546
|
+
Console_1.default.lowLevel((_b = err.stack) !== null && _b !== void 0 ? _b : '');
|
|
1547
|
+
}
|
|
1548
|
+
else {
|
|
1549
|
+
Console_1.default.topLevel('Use `memlab help` or `memlab <COMMAND> -h` to get helper text');
|
|
1550
|
+
}
|
|
1551
|
+
if (options.primaryMessageToPrint) {
|
|
1552
|
+
Console_1.default.error(options.primaryMessageToPrint);
|
|
1553
|
+
}
|
|
1554
|
+
if (options.secondaryMessageToPrint) {
|
|
1555
|
+
Console_1.default.lowLevel(options.secondaryMessageToPrint);
|
|
1556
|
+
}
|
|
1557
|
+
if (options.printCallback) {
|
|
1558
|
+
options.printCallback();
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
throw process_1.default.exit(1);
|
|
1562
|
+
});
|
|
1563
|
+
const throwErr = () => {
|
|
1564
|
+
let message = '';
|
|
1565
|
+
// show primary message
|
|
1566
|
+
if (options.primaryMessageToPrint) {
|
|
1567
|
+
message = options.primaryMessageToPrint;
|
|
1568
|
+
}
|
|
1569
|
+
else {
|
|
1570
|
+
message = err.message;
|
|
1571
|
+
}
|
|
1572
|
+
// append secondary message
|
|
1573
|
+
if (options.secondaryMessageToPrint) {
|
|
1574
|
+
message += `(${options.secondaryMessageToPrint})`;
|
|
1575
|
+
}
|
|
1576
|
+
// if already specified a primary message,
|
|
1577
|
+
// append the error.message at the end
|
|
1578
|
+
if (options.primaryMessageToPrint) {
|
|
1579
|
+
if (message.length > 0 && !message.endsWith('.')) {
|
|
1580
|
+
message += '. ';
|
|
1581
|
+
}
|
|
1582
|
+
message += err.message;
|
|
1583
|
+
}
|
|
1584
|
+
err.message = message;
|
|
1585
|
+
throw err;
|
|
1586
|
+
};
|
|
1587
|
+
const handling = (_a = options === null || options === void 0 ? void 0 : options.errorHandling) !== null && _a !== void 0 ? _a : Config_1.default.errorHandling;
|
|
1588
|
+
switch (handling) {
|
|
1589
|
+
case Config_1.ErrorHandling.Halt:
|
|
1590
|
+
halt();
|
|
1591
|
+
break;
|
|
1592
|
+
case Config_1.ErrorHandling.Throw:
|
|
1593
|
+
throwErr();
|
|
1594
|
+
break;
|
|
1595
|
+
}
|
|
1596
|
+
throw 'unreachable';
|
|
1597
|
+
}
|
|
1598
|
+
function getError(maybeError) {
|
|
1599
|
+
if (maybeError instanceof Error) {
|
|
1600
|
+
return maybeError;
|
|
1601
|
+
}
|
|
1602
|
+
return convertToError(maybeError);
|
|
1603
|
+
}
|
|
1604
|
+
function convertToError(maybeError) {
|
|
1605
|
+
if (isErrorWithMessage(maybeError)) {
|
|
1606
|
+
return new Error(maybeError.message);
|
|
1607
|
+
}
|
|
1608
|
+
try {
|
|
1609
|
+
const msg = typeof maybeError === 'string' ? maybeError : JSON.stringify(maybeError);
|
|
1610
|
+
return new Error(msg);
|
|
1611
|
+
}
|
|
1612
|
+
catch (_a) {
|
|
1613
|
+
// fallback in case there's an error stringifying the maybeError
|
|
1614
|
+
// like with circular references for example.
|
|
1615
|
+
return new Error(String(maybeError));
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
function isErrorWithMessage(error) {
|
|
1619
|
+
return (typeof error === 'object' &&
|
|
1620
|
+
error !== null &&
|
|
1621
|
+
'message' in error &&
|
|
1622
|
+
typeof error.message === 'string');
|
|
1623
|
+
}
|
|
1624
|
+
// check if a node is dominated by an array referenced as 'deletions'
|
|
1625
|
+
// React stores unmounted fiber nodes that will be deleted soon in
|
|
1626
|
+
// a 'deletions' array.
|
|
1627
|
+
function isNodeDominatedByDeletionsArray(node) {
|
|
1628
|
+
const dominators = getAllDominators(node);
|
|
1629
|
+
return dominators.some(dominator => {
|
|
1630
|
+
const edges = dominator.referrers;
|
|
1631
|
+
return edges.some(e => e.name_or_index === 'deletions');
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
let uindex = 1;
|
|
1635
|
+
function getUniqueID() {
|
|
1636
|
+
return `${process_1.default.pid}-${Date.now()}-${uindex++}`;
|
|
1637
|
+
}
|
|
1638
|
+
exports.default = {
|
|
1639
|
+
applyToNodes,
|
|
1640
|
+
callAsync,
|
|
1641
|
+
camelCaseToReadableString,
|
|
1642
|
+
checkSnapshots,
|
|
1643
|
+
checkUninstalledLibrary,
|
|
1644
|
+
checkIsChildOfParent,
|
|
1645
|
+
closePuppeteer,
|
|
1646
|
+
dumpSnapshot,
|
|
1647
|
+
equalOrMatch,
|
|
1648
|
+
extractClosureNodeInfo,
|
|
1649
|
+
extractFiberNodeInfo,
|
|
1650
|
+
extractHTMLElementNodeInfo,
|
|
1651
|
+
filterNodesInPlace,
|
|
1652
|
+
getAllDominators,
|
|
1653
|
+
getConditionalDominatorIds,
|
|
1654
|
+
getError,
|
|
1655
|
+
getEdgeByNameAndType,
|
|
1656
|
+
getLastNodeId,
|
|
1657
|
+
getLeakedNode,
|
|
1658
|
+
getNodesIdSet,
|
|
1659
|
+
getNumberNodeValue,
|
|
1660
|
+
getReadableBytes,
|
|
1661
|
+
getReadablePercent,
|
|
1662
|
+
getReadableTime,
|
|
1663
|
+
getRunMetaFilePath,
|
|
1664
|
+
getScenarioName,
|
|
1665
|
+
getSingleSnapshotFileForAnalysis,
|
|
1666
|
+
getSnapshotDirForAnalysis,
|
|
1667
|
+
getSnapshotFilesInDir,
|
|
1668
|
+
getSnapshotFilesFromTabsOrder,
|
|
1669
|
+
getSnapshotFromFile,
|
|
1670
|
+
getSnapshotNodeIdsFromFile,
|
|
1671
|
+
getSnapshotSequenceFilePath,
|
|
1672
|
+
getSnapshotFilePath,
|
|
1673
|
+
getSnapshotFilePathWithTabType,
|
|
1674
|
+
getStringNodeValue,
|
|
1675
|
+
getToNodeByEdge,
|
|
1676
|
+
getUniqueID,
|
|
1677
|
+
getWeakMapEdgeKeyId,
|
|
1678
|
+
haltOrThrow,
|
|
1679
|
+
hasHostRoot,
|
|
1680
|
+
hasOnlyWeakReferrers,
|
|
1681
|
+
hasReactEdges,
|
|
1682
|
+
isAlternateNode,
|
|
1683
|
+
isBlinkRootNode,
|
|
1684
|
+
isDebuggableNode,
|
|
1685
|
+
isDetachedFiberNode,
|
|
1686
|
+
isDetachedDOMNode,
|
|
1687
|
+
isDirectPropEdge,
|
|
1688
|
+
isDocumentDOMTreesRoot,
|
|
1689
|
+
isEssentialEdge,
|
|
1690
|
+
isFiberNode,
|
|
1691
|
+
isFiberNodeDeletionsEdge,
|
|
1692
|
+
isHermesInternalObject,
|
|
1693
|
+
isHostRoot,
|
|
1694
|
+
isInterestingPath,
|
|
1695
|
+
isMeaningfulEdge,
|
|
1696
|
+
isMeaningfulNode,
|
|
1697
|
+
isNodeDominatedByDeletionsArray,
|
|
1698
|
+
isObjectNode,
|
|
1699
|
+
isPendingActivityNode,
|
|
1700
|
+
isPlainJSObjectNode,
|
|
1701
|
+
isReactFiberEdge,
|
|
1702
|
+
isReactPropsEdge,
|
|
1703
|
+
isRegularFiberNode,
|
|
1704
|
+
isReturnEdge,
|
|
1705
|
+
isRootNode,
|
|
1706
|
+
isSlicedStringNode,
|
|
1707
|
+
isStackTraceFrame,
|
|
1708
|
+
isStringNode,
|
|
1709
|
+
isURLEqual,
|
|
1710
|
+
isWeakMapEdge,
|
|
1711
|
+
isWeakMapEdgeToKey,
|
|
1712
|
+
isWeakMapEdgeToValue,
|
|
1713
|
+
iterateChildFiberNodes,
|
|
1714
|
+
iterateDescendantFiberNodes,
|
|
1715
|
+
loadRunMetaInfo,
|
|
1716
|
+
loadLeakFilter,
|
|
1717
|
+
loadScenario,
|
|
1718
|
+
loadTabsOrder,
|
|
1719
|
+
loadTargetInfoFromRunMeta,
|
|
1720
|
+
markAllDetachedFiberNode,
|
|
1721
|
+
markAlternateFiberNode,
|
|
1722
|
+
memCache,
|
|
1723
|
+
normalizeBaseUrl,
|
|
1724
|
+
pathHasDetachedHTMLNode,
|
|
1725
|
+
pathHasEdgeWithIndex,
|
|
1726
|
+
repeat,
|
|
1727
|
+
resolveFilePath,
|
|
1728
|
+
resolveSnapshotFilePath,
|
|
1729
|
+
setIsAlternateNode,
|
|
1730
|
+
setIsRegularFiberNode,
|
|
1731
|
+
shouldShowMoreInfo,
|
|
1732
|
+
shuffleArray,
|
|
1733
|
+
throwError,
|
|
1734
|
+
upperCaseFirstCharacter,
|
|
1735
|
+
getBooleanNodeValue,
|
|
1736
|
+
};
|