@memlab/lens 1.0.0 → 1.0.1
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/LICENSE +21 -0
- package/dist/config/config.d.ts +22 -0
- package/dist/core/dom-observer.d.ts +10 -0
- package/dist/core/event-listener-tracker.d.ts +33 -0
- package/dist/core/react-fiber-analysis.d.ts +4 -0
- package/dist/core/react-memory-scan.d.ts +36 -0
- package/{src/core/types.ts → dist/core/types.d.ts} +56 -80
- package/{src/index.ts → dist/core/valid-component-name.d.ts} +2 -7
- package/dist/extensions/basic-extension.d.ts +30 -0
- package/dist/extensions/dom-visualization-extension.d.ts +17 -0
- package/dist/index.d.ts +1 -0
- package/dist/memlens.lib.bundle.js +1695 -0
- package/dist/memlens.lib.bundle.min.js +1 -0
- package/dist/memlens.lib.d.ts +12 -0
- package/dist/memlens.run.bundle.js +2673 -0
- package/dist/memlens.run.bundle.min.js +1 -0
- package/dist/memlens.run.d.ts +1 -0
- package/dist/tests/bundle/lib.bundle.test.d.ts +1 -0
- package/dist/tests/bundle/run.bundle.start.head.test.d.ts +1 -0
- package/dist/tests/bundle/run.bundle.start.test.d.ts +1 -0
- package/dist/tests/bundle/run.bundle.test.d.ts +1 -0
- package/dist/tests/fiber/dev.fiber.complex.dev.test.d.ts +1 -0
- package/dist/tests/fiber/dev.fiber.complex.list.dev.test.d.ts +1 -0
- package/dist/tests/fiber/dev.fiber.complex.prod.test.d.ts +1 -0
- package/dist/tests/fiber/dev.fiber.name.dev.test.d.ts +1 -0
- package/dist/tests/fiber/dev.fiber.name.prod.test.d.ts +1 -0
- package/dist/tests/utils/test-utils.d.ts +11 -0
- package/dist/utils/intersection-observer.d.ts +18 -0
- package/dist/utils/react-fiber-utils.d.ts +56 -0
- package/dist/utils/utils.d.ts +26 -0
- package/dist/utils/weak-ref-utils.d.ts +67 -0
- package/dist/visual/components/component-stack-panel.d.ts +11 -0
- package/dist/visual/components/control-widget.d.ts +13 -0
- package/dist/visual/components/overlay-rectangle.d.ts +11 -0
- package/dist/visual/components/status-text.d.ts +2 -0
- package/dist/visual/components/toggle-button.d.ts +3 -0
- package/dist/visual/components/visual-overlay.d.ts +1 -0
- package/dist/visual/dom-element-visualizer-interactive.d.ts +26 -0
- package/{src/core/valid-component-name.ts → dist/visual/dom-element-visualizer.d.ts} +5 -7
- package/dist/visual/visual-utils.d.ts +16 -0
- package/package.json +5 -1
- package/explainer.md +0 -54
- package/playwright.config.ts +0 -21
- package/src/config/config.ts +0 -32
- package/src/core/dom-observer.ts +0 -189
- package/src/core/event-listener-tracker.ts +0 -171
- package/src/core/react-fiber-analysis.ts +0 -123
- package/src/core/react-memory-scan.ts +0 -366
- package/src/extensions/basic-extension.ts +0 -41
- package/src/extensions/dom-visualization-extension.ts +0 -42
- package/src/memlens.lib.js.flow +0 -75
- package/src/memlens.lib.ts +0 -22
- package/src/memlens.run.ts +0 -21
- package/src/tests/bundle/lib.bundle.test.ts +0 -31
- package/src/tests/bundle/run.bundle.start.head.test.ts +0 -48
- package/src/tests/bundle/run.bundle.start.test.ts +0 -48
- package/src/tests/bundle/run.bundle.test.ts +0 -51
- package/src/tests/fiber/dev.fiber.complex.dev.test.ts +0 -92
- package/src/tests/fiber/dev.fiber.complex.list.dev.test.ts +0 -118
- package/src/tests/fiber/dev.fiber.complex.prod.test.ts +0 -92
- package/src/tests/fiber/dev.fiber.name.dev.test.ts +0 -77
- package/src/tests/fiber/dev.fiber.name.prod.test.ts +0 -79
- package/src/tests/lib/babel.prod.js +0 -4
- package/src/tests/lib/react-dom-v18.dev.js +0 -29926
- package/src/tests/lib/react-dom-v18.prod.js +0 -269
- package/src/tests/lib/react-v18.dev.js +0 -3345
- package/src/tests/lib/react-v18.prod.js +0 -33
- package/src/tests/manual/playwright-open-manual.js +0 -40
- package/src/tests/manual/todo-list/todo-with-run.bundle.html +0 -80
- package/src/tests/utils/test-utils.ts +0 -28
- package/src/utils/intersection-observer.ts +0 -65
- package/src/utils/react-fiber-utils.ts +0 -212
- package/src/utils/utils.ts +0 -201
- package/src/utils/weak-ref-utils.ts +0 -308
- package/src/visual/components/component-stack-panel.ts +0 -85
- package/src/visual/components/control-widget.ts +0 -96
- package/src/visual/components/overlay-rectangle.ts +0 -167
- package/src/visual/components/status-text.ts +0 -53
- package/src/visual/components/toggle-button.ts +0 -57
- package/src/visual/components/visual-overlay.ts +0 -19
- package/src/visual/dom-element-visualizer-interactive.ts +0 -358
- package/src/visual/dom-element-visualizer.ts +0 -130
- package/src/visual/visual-utils.ts +0 -89
- package/tsconfig.json +0 -18
- package/webpack.config.js +0 -105
package/src/core/dom-observer.ts
DELETED
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
* @format
|
|
8
|
-
* @oncall memory_lab
|
|
9
|
-
*/
|
|
10
|
-
import {isWeakAPINative} from '../utils/weak-ref-utils';
|
|
11
|
-
import {isVisualizerElement} from '../visual/visual-utils';
|
|
12
|
-
import {DOMElementStats, DOMObserveCallback, Optional} from './types';
|
|
13
|
-
|
|
14
|
-
// if false, we only capture the top-most element of a removed subtree
|
|
15
|
-
const CAPTURE_ALL_REMOVED_ELEMENTS = true;
|
|
16
|
-
const IS_WEAK_REF_NATIVE = isWeakAPINative();
|
|
17
|
-
const IS_MUTATION_OBSERVER_SUPPORTED = window.MutationObserver !== undefined;
|
|
18
|
-
|
|
19
|
-
export class DOMObserver {
|
|
20
|
-
#elementCount: number;
|
|
21
|
-
#detachedElementCount: number;
|
|
22
|
-
#mutationObserver: Optional<MutationObserver>;
|
|
23
|
-
#trackedElements: Array<WeakRef<Element>>;
|
|
24
|
-
#trackedElementSet: WeakSet<Element>;
|
|
25
|
-
#consolidateInterval: Optional<number>;
|
|
26
|
-
#observeCallback: Array<DOMObserveCallback>;
|
|
27
|
-
|
|
28
|
-
constructor() {
|
|
29
|
-
this.#elementCount = 0;
|
|
30
|
-
this.#detachedElementCount = 0;
|
|
31
|
-
this.#trackedElements = [];
|
|
32
|
-
this.#trackedElementSet = new WeakSet();
|
|
33
|
-
this.#observeCallback = [];
|
|
34
|
-
this.startMonitoring();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
register(callback: DOMObserveCallback): void {
|
|
38
|
-
this.#observeCallback.push(callback);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
startMonitoring(): void {
|
|
42
|
-
if (!IS_WEAK_REF_NATIVE || !IS_MUTATION_OBSERVER_SUPPORTED) {
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
if (this.#mutationObserver != null) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
this.#mutationObserver = new MutationObserver(mutationsList => {
|
|
49
|
-
let newlyAdded: Array<WeakRef<Element>> = [];
|
|
50
|
-
const updateCallback = (node: Node) => {
|
|
51
|
-
if (node == null) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (node.nodeType != Node.ELEMENT_NODE) {
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const element = node as Element;
|
|
58
|
-
if (isVisualizerElement(element)) {
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
if (CAPTURE_ALL_REMOVED_ELEMENTS) {
|
|
62
|
-
const diff = this.#gatherAllElementsInRemovedSubtree(element);
|
|
63
|
-
newlyAdded = [...newlyAdded, ...diff];
|
|
64
|
-
} else if (!this.#trackedElementSet.has(element)) {
|
|
65
|
-
const ref = new WeakRef(element);
|
|
66
|
-
this.#trackedElements.push(ref);
|
|
67
|
-
this.#trackedElementSet.add(element);
|
|
68
|
-
newlyAdded.push(ref);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
mutationsList.forEach(mutation => {
|
|
73
|
-
mutation.addedNodes.forEach(updateCallback);
|
|
74
|
-
mutation.removedNodes.forEach(updateCallback);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
this.#observeCallback.forEach(cb => cb(newlyAdded));
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
const waitForBodyAndObserve = () => {
|
|
81
|
-
if (document.body) {
|
|
82
|
-
// observe changes in DOM tree
|
|
83
|
-
this.#mutationObserver?.observe(document.body, {
|
|
84
|
-
childList: true, // Detect direct additions/removals
|
|
85
|
-
subtree: true, // Observe all descendants
|
|
86
|
-
});
|
|
87
|
-
} else {
|
|
88
|
-
setTimeout(waitForBodyAndObserve, 0);
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
waitForBodyAndObserve();
|
|
93
|
-
|
|
94
|
-
// starts consolidating removedElements weak references;
|
|
95
|
-
this.#consolidateInterval = window.setInterval(() => {
|
|
96
|
-
this.#consolidateElementRefs();
|
|
97
|
-
}, 3000);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
stopMonitoring(): void {
|
|
101
|
-
if (this.#mutationObserver != null) {
|
|
102
|
-
this.#mutationObserver.disconnect();
|
|
103
|
-
this.#mutationObserver = null;
|
|
104
|
-
}
|
|
105
|
-
if (this.#consolidateInterval != null) {
|
|
106
|
-
window.clearInterval(this.#consolidateInterval);
|
|
107
|
-
this.#consolidateInterval = null;
|
|
108
|
-
}
|
|
109
|
-
// TODO: clean up memory
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
#consolidateElementRefs(): void {
|
|
113
|
-
const consolidatedList = [];
|
|
114
|
-
const trackedElements = new Set<Element>();
|
|
115
|
-
for (const ref of this.#trackedElements) {
|
|
116
|
-
const element = ref.deref();
|
|
117
|
-
if (element != null && !trackedElements.has(element)) {
|
|
118
|
-
consolidatedList.push(ref);
|
|
119
|
-
trackedElements.add(element);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
this.#trackedElements = consolidatedList;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
#getTotalDOMElementCount(): number {
|
|
126
|
-
return (this.#elementCount =
|
|
127
|
-
document?.getElementsByTagName('*')?.length ?? 0);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
#getDetachedElementCount(): number {
|
|
131
|
-
let count = 0;
|
|
132
|
-
for (const ref of this.#trackedElements) {
|
|
133
|
-
const element = ref.deref();
|
|
134
|
-
if (element && element.isConnected === false) {
|
|
135
|
-
++count;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
return (this.#detachedElementCount = count);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
#gatherAllElementsInRemovedSubtree(node: Node): Array<WeakRef<Element>> {
|
|
142
|
-
const queue = [node];
|
|
143
|
-
const visited = new Set<Node>();
|
|
144
|
-
const newlyAdded: Array<WeakRef<Element>> = [];
|
|
145
|
-
while (queue.length > 0) {
|
|
146
|
-
const current = queue.pop();
|
|
147
|
-
if (current == null || visited.has(current)) {
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
if (current?.nodeType !== Node.ELEMENT_NODE) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
const element = current as Element;
|
|
154
|
-
if (isVisualizerElement(element)) {
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
visited.add(element);
|
|
158
|
-
if (!this.#trackedElementSet.has(element)) {
|
|
159
|
-
const ref = new WeakRef(element);
|
|
160
|
-
this.#trackedElements.push(ref);
|
|
161
|
-
this.#trackedElementSet.add(element);
|
|
162
|
-
newlyAdded.push(ref);
|
|
163
|
-
}
|
|
164
|
-
const list = element.childNodes;
|
|
165
|
-
for (let i = 0; i < list.length; ++i) {
|
|
166
|
-
queue.push(list[i]);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return newlyAdded;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
getDOMElements(): Array<WeakRef<Element>> {
|
|
173
|
-
return [...this.#trackedElements];
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
getStats(): DOMElementStats {
|
|
177
|
-
try {
|
|
178
|
-
this.#elementCount = this.#getTotalDOMElementCount();
|
|
179
|
-
this.#detachedElementCount = this.#getDetachedElementCount();
|
|
180
|
-
} catch (ex) {
|
|
181
|
-
// do nothing
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
elements: this.#elementCount,
|
|
186
|
-
detachedElements: this.#detachedElementCount,
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
}
|
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
* @format
|
|
8
|
-
* @oncall memory_lab
|
|
9
|
-
*/
|
|
10
|
-
import type {Fiber} from 'react-reconciler';
|
|
11
|
-
import {getFiberNodeFromElement} from '../utils/react-fiber-utils';
|
|
12
|
-
import {WeakMapPlus} from '../utils/weak-ref-utils';
|
|
13
|
-
|
|
14
|
-
type EventListenerEntry = {
|
|
15
|
-
type: string;
|
|
16
|
-
cb: WeakRef<EventListenerOrEventListenerObject>;
|
|
17
|
-
options?: boolean | AddEventListenerOptions;
|
|
18
|
-
fiber?: WeakRef<Fiber>;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
type DetachedListenerGroup = {
|
|
22
|
-
type: string;
|
|
23
|
-
count: number;
|
|
24
|
-
entries: WeakRef<EventListenerEntry>[];
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export class EventListenerTracker {
|
|
28
|
-
private static instance: EventListenerTracker | null = null;
|
|
29
|
-
#listenerMap: WeakMapPlus<EventTarget, EventListenerEntry[]>;
|
|
30
|
-
#detachedListeners: Map<string, DetachedListenerGroup[]>;
|
|
31
|
-
#originalAddEventListener: typeof EventTarget.prototype.addEventListener;
|
|
32
|
-
#originalRemoveEventListener: typeof EventTarget.prototype.removeEventListener;
|
|
33
|
-
|
|
34
|
-
private constructor() {
|
|
35
|
-
this.#listenerMap = new WeakMapPlus({fallback: 'noop', cleanupMs: 100});
|
|
36
|
-
this.#detachedListeners = new Map();
|
|
37
|
-
this.#originalAddEventListener = EventTarget.prototype.addEventListener;
|
|
38
|
-
this.#originalRemoveEventListener =
|
|
39
|
-
EventTarget.prototype.removeEventListener;
|
|
40
|
-
this.#patchEventListeners();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
static getInstance(): EventListenerTracker {
|
|
44
|
-
if (!EventListenerTracker.instance) {
|
|
45
|
-
EventListenerTracker.instance = new EventListenerTracker();
|
|
46
|
-
}
|
|
47
|
-
return EventListenerTracker.instance;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
#patchEventListeners(): void {
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
52
|
-
const self = this;
|
|
53
|
-
EventTarget.prototype.addEventListener = function (
|
|
54
|
-
type: string,
|
|
55
|
-
listener: EventListenerOrEventListenerObject,
|
|
56
|
-
options?: boolean | AddEventListenerOptions,
|
|
57
|
-
) {
|
|
58
|
-
self.#originalAddEventListener.call(this, type, listener, options);
|
|
59
|
-
if (this instanceof Element) {
|
|
60
|
-
const fiber = getFiberNodeFromElement(this);
|
|
61
|
-
const entry: EventListenerEntry = {
|
|
62
|
-
type,
|
|
63
|
-
cb: new WeakRef(listener),
|
|
64
|
-
options,
|
|
65
|
-
fiber: fiber ? new WeakRef(fiber) : undefined,
|
|
66
|
-
};
|
|
67
|
-
const listeners = self.#listenerMap.get(this) ?? [];
|
|
68
|
-
listeners.push(entry);
|
|
69
|
-
self.#listenerMap.set(this, listeners);
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
EventTarget.prototype.removeEventListener = function (
|
|
74
|
-
type: string,
|
|
75
|
-
listener: EventListenerOrEventListenerObject,
|
|
76
|
-
options?: boolean | EventListenerOptions,
|
|
77
|
-
) {
|
|
78
|
-
self.#originalRemoveEventListener.call(this, type, listener, options);
|
|
79
|
-
if (this instanceof Element) {
|
|
80
|
-
const listeners = self.#listenerMap.get(this);
|
|
81
|
-
if (listeners) {
|
|
82
|
-
const index = listeners.findIndex(
|
|
83
|
-
entry =>
|
|
84
|
-
entry.type === type &&
|
|
85
|
-
entry.cb.deref() === listener &&
|
|
86
|
-
entry.options === options,
|
|
87
|
-
);
|
|
88
|
-
if (index !== -1) {
|
|
89
|
-
listeners.splice(index, 1);
|
|
90
|
-
}
|
|
91
|
-
if (listeners.length === 0) {
|
|
92
|
-
self.#listenerMap.delete(this);
|
|
93
|
-
} else {
|
|
94
|
-
self.#listenerMap.set(this, listeners);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
#unpatchEventListeners(): void {
|
|
102
|
-
EventTarget.prototype.addEventListener = this.#originalAddEventListener;
|
|
103
|
-
EventTarget.prototype.removeEventListener =
|
|
104
|
-
this.#originalRemoveEventListener;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
addListener(
|
|
108
|
-
el: EventTarget,
|
|
109
|
-
type: string,
|
|
110
|
-
cb: EventListenerOrEventListenerObject,
|
|
111
|
-
options?: boolean | AddEventListenerOptions,
|
|
112
|
-
): void {
|
|
113
|
-
el.addEventListener(type, cb, options);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
removeListener(
|
|
117
|
-
el: EventTarget,
|
|
118
|
-
type: string,
|
|
119
|
-
cb: EventListenerOrEventListenerObject,
|
|
120
|
-
options?: boolean | AddEventListenerOptions,
|
|
121
|
-
): void {
|
|
122
|
-
el.removeEventListener(type, cb, options);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
scan(
|
|
126
|
-
getComponentName: (elRef: WeakRef<Element>) => string,
|
|
127
|
-
): Map<string, DetachedListenerGroup[]> {
|
|
128
|
-
const detachedListeners = new Map<string, DetachedListenerGroup[]>();
|
|
129
|
-
|
|
130
|
-
for (const [el, listeners] of this.#listenerMap.entries()) {
|
|
131
|
-
if (el instanceof Element && !el.isConnected) {
|
|
132
|
-
for (const listener of listeners) {
|
|
133
|
-
// Skip if the callback has been garbage collected
|
|
134
|
-
if (!listener.cb.deref()) continue;
|
|
135
|
-
|
|
136
|
-
const componentName = getComponentName(new WeakRef(el));
|
|
137
|
-
if (!detachedListeners.has(componentName)) {
|
|
138
|
-
detachedListeners.set(componentName, []);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const groups = detachedListeners.get(componentName);
|
|
142
|
-
let group = groups?.find(g => g.type === listener.type);
|
|
143
|
-
if (!group) {
|
|
144
|
-
group = {
|
|
145
|
-
type: listener.type,
|
|
146
|
-
count: 0,
|
|
147
|
-
entries: [],
|
|
148
|
-
};
|
|
149
|
-
groups?.push(group);
|
|
150
|
-
}
|
|
151
|
-
group.count++;
|
|
152
|
-
group.entries.push(new WeakRef(listener));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
this.#detachedListeners = detachedListeners;
|
|
158
|
-
return detachedListeners;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
getDetachedListeners(): Map<string, DetachedListenerGroup[]> {
|
|
162
|
-
return this.#detachedListeners;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
destroy(): void {
|
|
166
|
-
this.#unpatchEventListeners();
|
|
167
|
-
this.#listenerMap.destroy();
|
|
168
|
-
this.#detachedListeners.clear();
|
|
169
|
-
EventListenerTracker.instance = null;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
-
*
|
|
4
|
-
* This source code is licensed under the MIT license found in the
|
|
5
|
-
* LICENSE file in the root directory of this source tree.
|
|
6
|
-
*
|
|
7
|
-
* @format
|
|
8
|
-
* @oncall memory_lab
|
|
9
|
-
*/
|
|
10
|
-
import * as utils from '../utils/utils';
|
|
11
|
-
import type {Fiber} from 'react-reconciler';
|
|
12
|
-
import {
|
|
13
|
-
traverseFiber,
|
|
14
|
-
getDisplayNameOfFiberNode,
|
|
15
|
-
getFiberNodeFromElement,
|
|
16
|
-
getTopMostFiberWithChild,
|
|
17
|
-
} from '../utils/react-fiber-utils';
|
|
18
|
-
import {ScanResult, AnyValue} from './types';
|
|
19
|
-
import {isValidComponentName} from './valid-component-name';
|
|
20
|
-
|
|
21
|
-
export default class ReactFiberAnalyzer {
|
|
22
|
-
scan(
|
|
23
|
-
elementWeakRefList: Array<WeakRef<Element>>,
|
|
24
|
-
elementToComponentStack: WeakMap<Element, string[]>,
|
|
25
|
-
): ScanResult {
|
|
26
|
-
const visitedRootFibers = new Set<Fiber>();
|
|
27
|
-
const components = new Set<string>();
|
|
28
|
-
const componentToFiberNodeCount = new Map();
|
|
29
|
-
const detachedComponentToFiberNodeCount = new Map();
|
|
30
|
-
const topDownVisited = new Set();
|
|
31
|
-
const analyzedFibers = new Set();
|
|
32
|
-
const fiberNodes: Array<WeakRef<Fiber>> = [];
|
|
33
|
-
let totalElements = 0;
|
|
34
|
-
let totalDetachedElements = 0;
|
|
35
|
-
|
|
36
|
-
function analyzeFiber(fiberNode: Fiber): void {
|
|
37
|
-
traverseFiber(
|
|
38
|
-
fiberNode,
|
|
39
|
-
(fiberNode: Fiber) => {
|
|
40
|
-
// skip if the fiber node has already been analyzed
|
|
41
|
-
if (analyzedFibers.has(fiberNode)) {
|
|
42
|
-
return true;
|
|
43
|
-
}
|
|
44
|
-
analyzedFibers.add(fiberNode);
|
|
45
|
-
|
|
46
|
-
// traverse the fiber tree up to find the component name
|
|
47
|
-
const displayName = getDisplayNameOfFiberNode(fiberNode);
|
|
48
|
-
if (displayName != null && isValidComponentName(displayName)) {
|
|
49
|
-
components.add(displayName);
|
|
50
|
-
utils.addCountbyKey(componentToFiberNodeCount, displayName, 1);
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
true,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
for (const weakRef of elementWeakRefList) {
|
|
59
|
-
const elem = weakRef.deref();
|
|
60
|
-
if (elem == null) {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
// elements stats
|
|
64
|
-
++totalElements;
|
|
65
|
-
// TODO: simplify this logic
|
|
66
|
-
if (!elem.isConnected) {
|
|
67
|
-
if (elementToComponentStack.has(elem)) {
|
|
68
|
-
const componentStack = elementToComponentStack.get(elem) ?? [];
|
|
69
|
-
// set component name
|
|
70
|
-
const component = componentStack[0];
|
|
71
|
-
(elem as AnyValue).__component_name = component;
|
|
72
|
-
utils.addCountbyKey(detachedComponentToFiberNodeCount, component, 1);
|
|
73
|
-
}
|
|
74
|
-
++totalDetachedElements;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// analyze fiber nodes
|
|
78
|
-
const fiberNode = getFiberNodeFromElement(elem);
|
|
79
|
-
if (fiberNode == null) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
analyzeFiber(fiberNode);
|
|
83
|
-
|
|
84
|
-
// try to traverse each fiber node in the entire fiber tree
|
|
85
|
-
const rootFiber = getTopMostFiberWithChild(fiberNode);
|
|
86
|
-
if (rootFiber == null) {
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (visitedRootFibers.has(rootFiber)) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
visitedRootFibers.add(rootFiber);
|
|
93
|
-
|
|
94
|
-
// start traversing fiber tree from the know root host
|
|
95
|
-
traverseFiber(
|
|
96
|
-
rootFiber,
|
|
97
|
-
(node: Fiber) => {
|
|
98
|
-
if (topDownVisited.has(node)) {
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
topDownVisited.add(node);
|
|
102
|
-
fiberNodes.push(new WeakRef(node));
|
|
103
|
-
analyzeFiber(node);
|
|
104
|
-
},
|
|
105
|
-
false,
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
topDownVisited.clear();
|
|
110
|
-
analyzedFibers.clear();
|
|
111
|
-
visitedRootFibers.clear();
|
|
112
|
-
|
|
113
|
-
return {
|
|
114
|
-
components,
|
|
115
|
-
componentToFiberNodeCount,
|
|
116
|
-
totalElements,
|
|
117
|
-
totalDetachedElements,
|
|
118
|
-
detachedComponentToFiberNodeCount,
|
|
119
|
-
fiberNodes,
|
|
120
|
-
leakedFibers: [],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
}
|