@memlab/lens 1.0.0 → 1.0.2
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 +15 -2
- 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
|
@@ -1,57 +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 {createVisualizerElement} from '../visual-utils';
|
|
11
|
-
|
|
12
|
-
export function createToggleButton(
|
|
13
|
-
overlayDiv: HTMLDivElement,
|
|
14
|
-
hideAllRef: {value: boolean},
|
|
15
|
-
): HTMLDivElement {
|
|
16
|
-
const toggleWrapper = createVisualizerElement('div') as HTMLDivElement;
|
|
17
|
-
toggleWrapper.style.width = '40px';
|
|
18
|
-
toggleWrapper.style.height = '24px';
|
|
19
|
-
toggleWrapper.style.borderRadius = '9999px';
|
|
20
|
-
toggleWrapper.style.backgroundColor = '#34C759'; // ON by default
|
|
21
|
-
toggleWrapper.style.cursor = 'pointer';
|
|
22
|
-
toggleWrapper.style.position = 'relative';
|
|
23
|
-
toggleWrapper.style.transition = 'background-color 0.3s ease';
|
|
24
|
-
|
|
25
|
-
const knob = createVisualizerElement('div');
|
|
26
|
-
knob.style.width = '18px';
|
|
27
|
-
knob.style.height = '18px';
|
|
28
|
-
knob.style.backgroundColor = 'white';
|
|
29
|
-
knob.style.borderRadius = '50%';
|
|
30
|
-
knob.style.position = 'absolute';
|
|
31
|
-
knob.style.top = '3px';
|
|
32
|
-
knob.style.left = '3px'; // ON position
|
|
33
|
-
knob.style.transition = 'left 0.25s ease';
|
|
34
|
-
toggleWrapper.appendChild(knob);
|
|
35
|
-
|
|
36
|
-
toggleWrapper.addEventListener('click', () => {
|
|
37
|
-
hideAllRef.value = !hideAllRef.value;
|
|
38
|
-
|
|
39
|
-
if (hideAllRef.value) {
|
|
40
|
-
// OFF state
|
|
41
|
-
overlayDiv.style.display = 'none';
|
|
42
|
-
overlayDiv.style.pointerEvents = 'none';
|
|
43
|
-
overlayDiv.style.userSelect = 'none';
|
|
44
|
-
knob.style.left = '19px';
|
|
45
|
-
toggleWrapper.style.backgroundColor = '#FF3B30'; // Apple red
|
|
46
|
-
} else {
|
|
47
|
-
// ON state
|
|
48
|
-
overlayDiv.style.display = 'block';
|
|
49
|
-
overlayDiv.style.pointerEvents = 'auto';
|
|
50
|
-
overlayDiv.style.userSelect = 'auto';
|
|
51
|
-
knob.style.left = '3px';
|
|
52
|
-
toggleWrapper.style.backgroundColor = '#34C759'; // Apple green
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
return toggleWrapper;
|
|
57
|
-
}
|
|
@@ -1,19 +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 {createVisualizerElement} from '../visual-utils';
|
|
11
|
-
|
|
12
|
-
export function createOverlayDiv(): HTMLDivElement {
|
|
13
|
-
const overlayDiv = createVisualizerElement('div') as HTMLDivElement;
|
|
14
|
-
overlayDiv.style.position = 'absolute';
|
|
15
|
-
overlayDiv.style.top = '0px';
|
|
16
|
-
overlayDiv.style.left = '0px';
|
|
17
|
-
overlayDiv.id = 'memory-visualization-overlay';
|
|
18
|
-
return overlayDiv;
|
|
19
|
-
}
|
|
@@ -1,358 +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 {DOMElementInfo, AnyValue, Optional, Nullable} from '../core/types';
|
|
11
|
-
import DOMElementVisualizer from './dom-element-visualizer';
|
|
12
|
-
import {createOverlayDiv} from './components/visual-overlay';
|
|
13
|
-
import {createControlWidget} from './components/control-widget';
|
|
14
|
-
import {createOverlayRectangle} from './components/overlay-rectangle';
|
|
15
|
-
import {getDOMElementCount} from '../utils/utils';
|
|
16
|
-
import {tryToAttachOverlay} from './visual-utils';
|
|
17
|
-
|
|
18
|
-
type ElementVisualizer = {
|
|
19
|
-
elementInfo: DOMElementInfo;
|
|
20
|
-
visualizerElementRef: WeakRef<Element>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export type VisualizerData = {
|
|
24
|
-
detachedDOMElementsCount: number;
|
|
25
|
-
totalDOMElementsCount: number;
|
|
26
|
-
selectedElementId: Nullable<number>;
|
|
27
|
-
pinnedElementId: Nullable<number>;
|
|
28
|
-
selectedReactComponentStack: Array<string>;
|
|
29
|
-
setPinnedElementId: (elementId: Nullable<number>) => void;
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
export type DateUpdateCallback = (data: VisualizerData) => void;
|
|
33
|
-
export type RegisterDataUpdateCallback = (cb: DateUpdateCallback) => void;
|
|
34
|
-
|
|
35
|
-
export default class DOMElementVisualizerInteractive extends DOMElementVisualizer {
|
|
36
|
-
#elementIdToRectangle: Map<number, ElementVisualizer>;
|
|
37
|
-
#visualizationOverlayDiv: HTMLDivElement;
|
|
38
|
-
#controlWidget: HTMLDivElement;
|
|
39
|
-
#blockedElementIds: Set<number>;
|
|
40
|
-
#currentVisualData: VisualizerData;
|
|
41
|
-
#hideAllRef: {value: boolean};
|
|
42
|
-
#updateDataCallbacks: Array<DateUpdateCallback> = [];
|
|
43
|
-
|
|
44
|
-
constructor() {
|
|
45
|
-
super();
|
|
46
|
-
this.#hideAllRef = {value: false};
|
|
47
|
-
this.#visualizationOverlayDiv = createOverlayDiv();
|
|
48
|
-
tryToAttachOverlay(this.#visualizationOverlayDiv);
|
|
49
|
-
this.#controlWidget = createControlWidget(
|
|
50
|
-
this.#visualizationOverlayDiv,
|
|
51
|
-
this.#hideAllRef,
|
|
52
|
-
cb => {
|
|
53
|
-
this.#updateDataCallbacks.push(cb);
|
|
54
|
-
},
|
|
55
|
-
);
|
|
56
|
-
tryToAttachOverlay(this.#controlWidget);
|
|
57
|
-
this.#elementIdToRectangle = new Map();
|
|
58
|
-
this.#currentVisualData = this.#initVisualizerData();
|
|
59
|
-
this.#blockedElementIds = new Set();
|
|
60
|
-
this.#listenToKeyboardEvent();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
#listenToKeyboardEvent() {
|
|
64
|
-
document.addEventListener('keydown', event => {
|
|
65
|
-
if (event.key === 'd' || event.key === 'D') {
|
|
66
|
-
if (this.#currentVisualData.selectedElementId != null) {
|
|
67
|
-
this.#blockedElementIds.add(
|
|
68
|
-
this.#currentVisualData.selectedElementId,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
#initVisualizerData(): VisualizerData {
|
|
76
|
-
const data: VisualizerData = {
|
|
77
|
-
detachedDOMElementsCount: 0,
|
|
78
|
-
totalDOMElementsCount: getDOMElementCount(),
|
|
79
|
-
selectedElementId: null,
|
|
80
|
-
selectedReactComponentStack: [],
|
|
81
|
-
pinnedElementId: null,
|
|
82
|
-
setPinnedElementId: (pinnedElementId: Nullable<number>) => {
|
|
83
|
-
if (data.pinnedElementId == pinnedElementId) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
// unpin the original pinned element
|
|
87
|
-
const oldPin = this.#getOutlineElementByElementId(data.pinnedElementId);
|
|
88
|
-
(oldPin as AnyValue)?.__unpinned?.();
|
|
89
|
-
// pin the newly pinned element
|
|
90
|
-
const newPin = this.#getOutlineElementByElementId(pinnedElementId);
|
|
91
|
-
(newPin as AnyValue)?.__pinned?.();
|
|
92
|
-
data.pinnedElementId = pinnedElementId;
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
return data;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
#getOutlineElementByElementId(
|
|
99
|
-
elementId: Nullable<number>,
|
|
100
|
-
): Optional<Element> {
|
|
101
|
-
if (elementId == null) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
const info = this.#elementIdToRectangle.get(elementId);
|
|
105
|
-
if (info == null) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
return info.visualizerElementRef.deref();
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
#getElementIdSet(domElementInfoList: Array<DOMElementInfo>): Set<number> {
|
|
112
|
-
const elementIdSet = new Set<number>();
|
|
113
|
-
for (const info of domElementInfoList) {
|
|
114
|
-
const element = info.element.deref() as AnyValue;
|
|
115
|
-
if (element == null) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
const elementId = element.detachedElementId;
|
|
119
|
-
if (elementId == null) {
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
elementIdSet.add(elementId);
|
|
123
|
-
}
|
|
124
|
-
return elementIdSet;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
#getComponentStackForElement(
|
|
128
|
-
elementId: Optional<number>,
|
|
129
|
-
): ElementVisualizer[] {
|
|
130
|
-
const ret: ElementVisualizer[] = [];
|
|
131
|
-
if (elementId == null) {
|
|
132
|
-
return ret;
|
|
133
|
-
}
|
|
134
|
-
const visualizer = this.#elementIdToRectangle.get(elementId);
|
|
135
|
-
if (visualizer == null) {
|
|
136
|
-
return ret;
|
|
137
|
-
}
|
|
138
|
-
const element = visualizer.elementInfo.element.deref() as AnyValue;
|
|
139
|
-
if (element == null) {
|
|
140
|
-
return ret;
|
|
141
|
-
}
|
|
142
|
-
// traverse parent elements
|
|
143
|
-
let currentElement: Optional<Element> = element;
|
|
144
|
-
while (currentElement) {
|
|
145
|
-
if (currentElement.isConnected) {
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
const elementId = (currentElement as AnyValue).detachedElementId;
|
|
149
|
-
const info = this.#elementIdToRectangle.get(elementId);
|
|
150
|
-
if (info == null) {
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
ret.push(info);
|
|
154
|
-
currentElement = currentElement.parentElement;
|
|
155
|
-
}
|
|
156
|
-
return ret;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
#traverseUpOutlineElements(
|
|
160
|
-
elementId: number,
|
|
161
|
-
callback: (element: HTMLElement) => void,
|
|
162
|
-
): void {
|
|
163
|
-
const visualizer = this.#elementIdToRectangle.get(elementId);
|
|
164
|
-
if (visualizer == null) {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const visitedElementIds = new Set<number>();
|
|
168
|
-
let currentElement: Optional<Element> =
|
|
169
|
-
visualizer.elementInfo.element.deref();
|
|
170
|
-
while (currentElement) {
|
|
171
|
-
if (currentElement.isConnected) {
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
174
|
-
const elementIdStr = (currentElement as AnyValue).detachedElementId;
|
|
175
|
-
const elementId = parseInt(elementIdStr, 10);
|
|
176
|
-
if (visitedElementIds.has(elementId)) {
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
visitedElementIds.add(elementId);
|
|
180
|
-
const visualizerInfo = this.#elementIdToRectangle.get(elementId);
|
|
181
|
-
if (visualizerInfo == null) {
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
const visualizerElement = visualizerInfo.visualizerElementRef.deref();
|
|
185
|
-
if (visualizerElement == null) {
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
188
|
-
callback(visualizerElement as HTMLElement);
|
|
189
|
-
currentElement = currentElement.parentElement;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
#removeVisualizerElement(elementId: number) {
|
|
194
|
-
const visualizer = this.#elementIdToRectangle.get(elementId);
|
|
195
|
-
this.#elementIdToRectangle.delete(elementId);
|
|
196
|
-
if (visualizer == null) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const visualizerElement = visualizer.visualizerElementRef.deref();
|
|
200
|
-
if (visualizerElement == null) {
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
// invoke the overlay specific code to clean
|
|
204
|
-
(visualizerElement as AnyValue)?.__cleanup?.();
|
|
205
|
-
visualizerElement.remove();
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
#cleanup(domElementInfoList: Array<DOMElementInfo>) {
|
|
209
|
-
// first pass remove all those painted visualizer for elements
|
|
210
|
-
// that either 1) is GCed or 2) is connected to the DOM tree
|
|
211
|
-
for (const [
|
|
212
|
-
elementId,
|
|
213
|
-
elementVistualizer,
|
|
214
|
-
] of this.#elementIdToRectangle.entries()) {
|
|
215
|
-
if (elementId == null) {
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
const element = elementVistualizer.elementInfo.element.deref();
|
|
219
|
-
if (element == null) {
|
|
220
|
-
this.#removeVisualizerElement(elementId);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// second pass remove all those outlines that won't be painted later
|
|
225
|
-
const willPaintElementIdSet = this.#getElementIdSet(domElementInfoList);
|
|
226
|
-
for (const [elementId] of this.#elementIdToRectangle.entries()) {
|
|
227
|
-
if (elementId == null) {
|
|
228
|
-
continue;
|
|
229
|
-
}
|
|
230
|
-
if (
|
|
231
|
-
willPaintElementIdSet.has(elementId) &&
|
|
232
|
-
!this.#blockedElementIds.has(elementId)
|
|
233
|
-
) {
|
|
234
|
-
continue;
|
|
235
|
-
}
|
|
236
|
-
this.#removeVisualizerElement(elementId);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
#getZIndex(info: DOMElementInfo): number {
|
|
241
|
-
const zIndexBase = 9999;
|
|
242
|
-
const element = info.element.deref() as AnyValue;
|
|
243
|
-
if (element == null) {
|
|
244
|
-
return 0;
|
|
245
|
-
}
|
|
246
|
-
const elementId = element.detachedElementId;
|
|
247
|
-
if (elementId == null) {
|
|
248
|
-
return 0;
|
|
249
|
-
}
|
|
250
|
-
const rectangle = info.boundingRect ?? {width: 50, height: 50};
|
|
251
|
-
let ret = zIndexBase;
|
|
252
|
-
// element with a higher element id (created later) has a higher z-index
|
|
253
|
-
ret += parseInt(elementId, 10);
|
|
254
|
-
// element with bigger area will have a lower z-index
|
|
255
|
-
ret += Math.floor(10000000 / (rectangle.width * rectangle.height));
|
|
256
|
-
return ret;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
#paint(domElementInfoList: Array<DOMElementInfo>) {
|
|
260
|
-
for (const info of domElementInfoList) {
|
|
261
|
-
const element = info.element.deref() as AnyValue;
|
|
262
|
-
if (element == null) {
|
|
263
|
-
continue;
|
|
264
|
-
}
|
|
265
|
-
const elementId = element.detachedElementId;
|
|
266
|
-
if (elementId == null) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
if (this.#blockedElementIds.has(elementId)) {
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (this.#elementIdToRectangle.has(elementId)) {
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if (element == null) {
|
|
276
|
-
continue;
|
|
277
|
-
}
|
|
278
|
-
if (element.isConnected) {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const zIndex = this.#getZIndex(info);
|
|
282
|
-
const visualizerElementRef = createOverlayRectangle(
|
|
283
|
-
elementId,
|
|
284
|
-
info,
|
|
285
|
-
this.#visualizationOverlayDiv,
|
|
286
|
-
(selectedId: number | null) => {
|
|
287
|
-
this.#currentVisualData.selectedElementId = selectedId;
|
|
288
|
-
this.#updateVisualizerData();
|
|
289
|
-
if (selectedId == null) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
this.#traverseUpOutlineElements(selectedId, element => {
|
|
293
|
-
(element as AnyValue)?.__selected?.();
|
|
294
|
-
});
|
|
295
|
-
},
|
|
296
|
-
(unselectedId: number | null) => {
|
|
297
|
-
if (this.#currentVisualData.selectedElementId === unselectedId) {
|
|
298
|
-
this.#currentVisualData.selectedElementId = null;
|
|
299
|
-
this.#updateVisualizerData();
|
|
300
|
-
}
|
|
301
|
-
if (unselectedId == null) {
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
this.#traverseUpOutlineElements(unselectedId, element => {
|
|
305
|
-
(element as AnyValue)?.__unselected?.();
|
|
306
|
-
});
|
|
307
|
-
},
|
|
308
|
-
(clickedId: Nullable<number>) => {
|
|
309
|
-
if (this.#currentVisualData.pinnedElementId === clickedId) {
|
|
310
|
-
this.#currentVisualData.setPinnedElementId(null);
|
|
311
|
-
} else {
|
|
312
|
-
this.#currentVisualData.setPinnedElementId(clickedId);
|
|
313
|
-
}
|
|
314
|
-
this.#updateVisualizerData();
|
|
315
|
-
},
|
|
316
|
-
zIndex,
|
|
317
|
-
);
|
|
318
|
-
if (
|
|
319
|
-
visualizerElementRef == null ||
|
|
320
|
-
visualizerElementRef.deref() == null
|
|
321
|
-
) {
|
|
322
|
-
return null;
|
|
323
|
-
}
|
|
324
|
-
const visualizer = {
|
|
325
|
-
elementInfo: info,
|
|
326
|
-
visualizerElementRef,
|
|
327
|
-
};
|
|
328
|
-
this.#elementIdToRectangle.set(elementId, visualizer);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
#updateVisualizerData() {
|
|
333
|
-
const data = this.#currentVisualData;
|
|
334
|
-
if (data.pinnedElementId != null) {
|
|
335
|
-
data.selectedElementId = data.pinnedElementId;
|
|
336
|
-
}
|
|
337
|
-
data.detachedDOMElementsCount = this.#elementIdToRectangle.size;
|
|
338
|
-
data.totalDOMElementsCount = getDOMElementCount();
|
|
339
|
-
const selectedElementInfo = this.#elementIdToRectangle.get(
|
|
340
|
-
data.selectedElementId ?? -1,
|
|
341
|
-
)?.elementInfo;
|
|
342
|
-
data.selectedReactComponentStack =
|
|
343
|
-
selectedElementInfo?.componentStack ?? [];
|
|
344
|
-
for (const cb of this.#updateDataCallbacks) {
|
|
345
|
-
cb({...this.#currentVisualData});
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
repaint(domElementInfoList: Array<DOMElementInfo>) {
|
|
350
|
-
// this.#controlWidget.remove();
|
|
351
|
-
this.#visualizationOverlayDiv.remove();
|
|
352
|
-
this.#cleanup(domElementInfoList);
|
|
353
|
-
this.#paint(domElementInfoList);
|
|
354
|
-
this.#updateVisualizerData();
|
|
355
|
-
tryToAttachOverlay(this.#visualizationOverlayDiv);
|
|
356
|
-
// tryToAttachOverlay(this.#controlWidget);
|
|
357
|
-
}
|
|
358
|
-
}
|
|
@@ -1,130 +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 {DOMElementInfo, Nullable, AnyValue} from '../core/types';
|
|
11
|
-
import {createVisualizerElement} from './visual-utils';
|
|
12
|
-
|
|
13
|
-
export default class DOMElementVisualizer {
|
|
14
|
-
#canvas: Nullable<HTMLCanvasElement>;
|
|
15
|
-
constructor() {
|
|
16
|
-
this.#canvas = null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
#paint(domElementInfoList: Array<DOMElementInfo>) {
|
|
20
|
-
if (!this.#canvas) {
|
|
21
|
-
const canvas = this.#createAndAppendCanvas();
|
|
22
|
-
this.#canvas = canvas;
|
|
23
|
-
}
|
|
24
|
-
this.#paintRectangles(domElementInfoList);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
#cleanupExistingCanvas() {
|
|
28
|
-
// Clean up any existing canvas
|
|
29
|
-
if (this.#canvas) {
|
|
30
|
-
const ctx = this.#canvas.getContext('2d');
|
|
31
|
-
ctx?.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
|
|
32
|
-
this.#canvas.remove();
|
|
33
|
-
this.#canvas = null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#tryToAttachCanvas(canvas: Element) {
|
|
38
|
-
if (document.body) {
|
|
39
|
-
document.body.appendChild(canvas);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
#createAndAppendCanvas() {
|
|
44
|
-
// Create and insert the canvas element
|
|
45
|
-
const canvas = createVisualizerElement('canvas') as HTMLCanvasElement;
|
|
46
|
-
canvas.id = 'overlayCanvas';
|
|
47
|
-
this.#tryToAttachCanvas(canvas);
|
|
48
|
-
|
|
49
|
-
// Style the canvas to cover the entire page
|
|
50
|
-
canvas.style.position = 'absolute';
|
|
51
|
-
canvas.style.top = '0';
|
|
52
|
-
canvas.style.left = '0';
|
|
53
|
-
canvas.style.width = '100%';
|
|
54
|
-
canvas.style.height = '100%';
|
|
55
|
-
canvas.style.pointerEvents = 'none';
|
|
56
|
-
canvas.style.zIndex = '99999';
|
|
57
|
-
|
|
58
|
-
// Set canvas dimensions to match the window dimensions
|
|
59
|
-
canvas.width = window.innerWidth;
|
|
60
|
-
canvas.height = window.innerHeight;
|
|
61
|
-
return canvas;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
#paintKey(info: DOMElementInfo): string {
|
|
65
|
-
const {boundingRect} = info;
|
|
66
|
-
return JSON.stringify({boundingRect});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#paintRectangles(domElementInfoList: Array<DOMElementInfo>) {
|
|
70
|
-
const canvas = this.#canvas;
|
|
71
|
-
if (!canvas) {
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Get the 2D drawing context
|
|
75
|
-
const ctx = canvas.getContext('2d');
|
|
76
|
-
if (ctx == null) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Set rectangle styles
|
|
81
|
-
ctx.strokeStyle = 'rgba(75, 192, 192, 0.8)';
|
|
82
|
-
ctx.lineWidth = 2;
|
|
83
|
-
ctx.fillStyle = 'rgba(75, 192, 192, 0.05)';
|
|
84
|
-
ctx.font = '11px Inter, system-ui, -apple-system, sans-serif';
|
|
85
|
-
|
|
86
|
-
const paintedInfo = new Set<string>();
|
|
87
|
-
|
|
88
|
-
// Draw the rectangles
|
|
89
|
-
domElementInfoList.forEach((info: DOMElementInfo) => {
|
|
90
|
-
const rect = info.boundingRect;
|
|
91
|
-
if (rect == null) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
const key = this.#paintKey(info);
|
|
95
|
-
if (paintedInfo.has(key)) {
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
paintedInfo.add(key);
|
|
99
|
-
ctx.fillRect(rect.left, rect.top, rect.width, rect.height);
|
|
100
|
-
ctx.strokeRect(rect.left, rect.top, rect.width, rect.height);
|
|
101
|
-
|
|
102
|
-
const component = info.componentStack?.[0];
|
|
103
|
-
if (component) {
|
|
104
|
-
// attach detached element id key so that it is easy to search in heap snapshot
|
|
105
|
-
const element = info.element.deref() as AnyValue;
|
|
106
|
-
const elementId = element?.detachedElementId;
|
|
107
|
-
const elementIdText = elementId ? ` (${elementId})` : '';
|
|
108
|
-
const text = `${component}${elementIdText}`;
|
|
109
|
-
ctx.fillStyle = 'rgba(74, 131, 224, 1)';
|
|
110
|
-
ctx.fillText(text, rect.left + 5, rect.top + 15); // Draw the name
|
|
111
|
-
ctx.fillStyle = 'rgba(75, 192, 192, 0.05)';
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
#cleanup() {
|
|
117
|
-
const canvas = this.#canvas;
|
|
118
|
-
if (canvas) {
|
|
119
|
-
const ctx = canvas.getContext('2d');
|
|
120
|
-
ctx?.clearRect(0, 0, canvas.width, canvas.height);
|
|
121
|
-
canvas.parentNode?.removeChild(canvas);
|
|
122
|
-
}
|
|
123
|
-
this.#canvas = null;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
repaint(domElementInfoList: Array<DOMElementInfo>) {
|
|
127
|
-
this.#cleanup();
|
|
128
|
-
this.#paint(domElementInfoList);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
@@ -1,89 +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 {AnyValue} from '../core/types';
|
|
11
|
-
|
|
12
|
-
const VISUALIZER_DATA_ATTR = 'data-visualizer';
|
|
13
|
-
|
|
14
|
-
function setVisualizerElement(element: Element) {
|
|
15
|
-
element.setAttribute(VISUALIZER_DATA_ATTR, 'true');
|
|
16
|
-
element.setAttribute('data-visualcompletion', 'ignore');
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function isVisualizerElement(element: Element): boolean {
|
|
20
|
-
return element.getAttribute(VISUALIZER_DATA_ATTR) === 'true';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function createVisualizerElement(tag: string): HTMLElement {
|
|
24
|
-
const element = document.createElement(tag);
|
|
25
|
-
setVisualizerElement(element);
|
|
26
|
-
return element;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function tryToAttachOverlay(overlayDiv: HTMLDivElement) {
|
|
30
|
-
if (document.body) {
|
|
31
|
-
document.body.appendChild(overlayDiv);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
type EventListenerEntry = {
|
|
36
|
-
type: string;
|
|
37
|
-
cb: EventListenerOrEventListenerObject;
|
|
38
|
-
options?: boolean | AddEventListenerOptions;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const listenerMap = new WeakMap<EventTarget, EventListenerEntry[]>();
|
|
42
|
-
|
|
43
|
-
export function addTrackedListener(
|
|
44
|
-
elRef: WeakRef<EventTarget>,
|
|
45
|
-
type: string,
|
|
46
|
-
cb: EventListenerOrEventListenerObject,
|
|
47
|
-
options?: boolean | AddEventListenerOptions,
|
|
48
|
-
): void {
|
|
49
|
-
const el = elRef.deref();
|
|
50
|
-
if (!el) return;
|
|
51
|
-
|
|
52
|
-
el.addEventListener(type, cb, options);
|
|
53
|
-
|
|
54
|
-
if (!listenerMap.has(el)) {
|
|
55
|
-
listenerMap.set(el, []);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
listenerMap.get(el)?.push({type, cb, options});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function removeAllListeners(elRef: WeakRef<EventTarget>): void {
|
|
62
|
-
const el = elRef.deref();
|
|
63
|
-
if (!el) return;
|
|
64
|
-
|
|
65
|
-
const listeners = listenerMap.get(el);
|
|
66
|
-
if (!listeners) return;
|
|
67
|
-
|
|
68
|
-
for (const {type, cb, options} of listeners) {
|
|
69
|
-
el.removeEventListener(type, cb, options);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
listenerMap.delete(el);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function debounce<T extends (...args: AnyValue[]) => AnyValue>(
|
|
76
|
-
callback: T,
|
|
77
|
-
delay: number,
|
|
78
|
-
): (...args: Parameters<T>) => void {
|
|
79
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
80
|
-
|
|
81
|
-
return (...args: Parameters<T>) => {
|
|
82
|
-
if (timer) {
|
|
83
|
-
clearTimeout(timer);
|
|
84
|
-
}
|
|
85
|
-
timer = setTimeout(() => {
|
|
86
|
-
callback(...args);
|
|
87
|
-
}, delay);
|
|
88
|
-
};
|
|
89
|
-
}
|
package/tsconfig.json
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES6", // Choose ES5 or ES6 for broad browser support
|
|
4
|
-
"module": "NodeNext", // Use ESNext for modern module systems; CommonJS for Node.js
|
|
5
|
-
"moduleResolution": "nodenext",
|
|
6
|
-
"lib": ["DOM", "ES2021"], // Include DOM for browser types
|
|
7
|
-
"outDir": "./dist", // Output directory for compiled files
|
|
8
|
-
"rootDir": "./src", // Source directory for TypeScript files
|
|
9
|
-
"strict": true, // Enable all strict type-checking options
|
|
10
|
-
"esModuleInterop": true, // Enable interoperability between CommonJS and ES Modules
|
|
11
|
-
"skipLibCheck": true, // Skip type-checking of declaration files for performance
|
|
12
|
-
"forceConsistentCasingInFileNames": true, // Ensure consistent casing in imports
|
|
13
|
-
"declaration": true, // generate .d.ts files
|
|
14
|
-
"composite": true
|
|
15
|
-
},
|
|
16
|
-
"include": ["src/**/*"], // Include all files in the src directory
|
|
17
|
-
"exclude": ["node_modules", "dist", "src/lib-index.js.flow"] // Exclude unnecessary directories
|
|
18
|
-
}
|