@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
|
@@ -1,308 +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
|
-
|
|
11
|
-
import {
|
|
12
|
-
AnyValue,
|
|
13
|
-
Entry,
|
|
14
|
-
FallbackMode,
|
|
15
|
-
Nullable,
|
|
16
|
-
WeakMapPlusOptions,
|
|
17
|
-
} from '../core/types';
|
|
18
|
-
|
|
19
|
-
const globalScope: typeof globalThis =
|
|
20
|
-
typeof window !== 'undefined' ? window : self;
|
|
21
|
-
|
|
22
|
-
const _weakMap: typeof WeakMap<AnyValue, AnyValue> | null =
|
|
23
|
-
globalScope.WeakMap ?? null;
|
|
24
|
-
const _weakMapIsNative: boolean = isWeakMapNative();
|
|
25
|
-
const _weakSet: typeof WeakSet<AnyValue> | null = globalScope.WeakSet ?? null;
|
|
26
|
-
const _weakSetIsNative: boolean = isWeakSetNative();
|
|
27
|
-
const _weakRef: typeof WeakRef<AnyValue> | null = globalScope.WeakRef ?? null;
|
|
28
|
-
const _weakRefIsNative: boolean = isWeakRefNative();
|
|
29
|
-
const _weakAPIsAreNative: boolean =
|
|
30
|
-
_weakMapIsNative && _weakSetIsNative && _weakRefIsNative;
|
|
31
|
-
|
|
32
|
-
export class WeakRefNoOp<T extends object | ReadonlyArray<unknown>> {
|
|
33
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
34
|
-
constructor(_targetObject: T) {
|
|
35
|
-
// to be overridden
|
|
36
|
-
}
|
|
37
|
-
deref(): T | undefined {
|
|
38
|
-
return undefined;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export class WeakSetNoOp<T extends object | ReadonlyArray<unknown>> {
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
44
|
-
constructor(_iterable?: Iterable<T> | null) {
|
|
45
|
-
// to be overridden
|
|
46
|
-
}
|
|
47
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
48
|
-
add(_value: T): this {
|
|
49
|
-
return this;
|
|
50
|
-
}
|
|
51
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
52
|
-
delete(_value: T): boolean {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
56
|
-
has(_value: T): boolean {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export class WeakMapNoOp<K extends object | ReadonlyArray<unknown>, V> {
|
|
62
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
63
|
-
constructor(_iterable?: Iterable<[K, V]> | null) {
|
|
64
|
-
// to be overridden
|
|
65
|
-
}
|
|
66
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
67
|
-
delete(_key: K): boolean {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
-
get(_key: K): V | undefined {
|
|
72
|
-
return undefined;
|
|
73
|
-
}
|
|
74
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
75
|
-
has(_key: K): boolean {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
79
|
-
set(_key: K, _value: V): this {
|
|
80
|
-
return this;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type WeakMapOrNoOp<TK extends object, TV> =
|
|
85
|
-
| WeakMap<TK, TV>
|
|
86
|
-
| WeakMapNoOp<TK, TV>;
|
|
87
|
-
export type WeakSetOrNoOp<T extends object> = WeakSet<T> | WeakSetNoOp<T>;
|
|
88
|
-
export type WeakRefOrNoOp<T extends object> = WeakRef<T> | WeakRefNoOp<T>;
|
|
89
|
-
|
|
90
|
-
export type WeakMapOrFallback = typeof WeakMap | typeof WeakMapNoOp;
|
|
91
|
-
export type WeakSetOrFallback = typeof WeakSet | typeof WeakSetNoOp;
|
|
92
|
-
export type WeakRefOrFallback = typeof WeakRef | typeof WeakRefNoOp;
|
|
93
|
-
|
|
94
|
-
export function getNativeWeakMap(): typeof WeakMap | null {
|
|
95
|
-
return _weakMapIsNative ? _weakMap : null;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export function getNativeWeakMapOrFallback(): WeakMapOrFallback {
|
|
99
|
-
return _weakMapIsNative && _weakMap ? _weakMap : WeakMapNoOp;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function getNativeWeakSet(): typeof WeakSet | null {
|
|
103
|
-
return _weakSetIsNative ? _weakSet : null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function getNativeWeakSetOrFallback(): WeakSetOrFallback {
|
|
107
|
-
return _weakSetIsNative && _weakSet ? _weakSet : WeakSetNoOp;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function getNativeWeakRef(): typeof WeakRef | null {
|
|
111
|
-
return _weakRefIsNative ? _weakRef : null;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function getNativeWeakRefOrFallback(): WeakRefOrFallback {
|
|
115
|
-
return _weakRefIsNative && _weakRef ? _weakRef : WeakRefNoOp;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function normalize(input: string): string | null {
|
|
119
|
-
return typeof input.replace === 'function'
|
|
120
|
-
? input.replace(/\n/g, ' ').replace(/\s+/g, ' ')
|
|
121
|
-
: null;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function isWeakMapNative(): boolean {
|
|
125
|
-
return (
|
|
126
|
-
_weakMap !== null &&
|
|
127
|
-
typeof _weakMap.toString === 'function' &&
|
|
128
|
-
normalize(_weakMap.toString()) === 'function WeakMap() { [native code] }'
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function isWeakSetNative(): boolean {
|
|
133
|
-
return (
|
|
134
|
-
_weakSet !== null &&
|
|
135
|
-
typeof _weakSet.toString === 'function' &&
|
|
136
|
-
normalize(_weakSet.toString()) === 'function WeakSet() { [native code] }'
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function isWeakRefNative(): boolean {
|
|
141
|
-
return (
|
|
142
|
-
_weakRef !== null &&
|
|
143
|
-
typeof _weakRef.toString === 'function' &&
|
|
144
|
-
normalize(_weakRef.toString()) === 'function WeakRef() { [native code] }'
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function isWeakAPINative(): boolean {
|
|
149
|
-
return _weakAPIsAreNative;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
export class WeakMapPlus<K extends object, V> {
|
|
153
|
-
private isWeak: boolean;
|
|
154
|
-
private fallbackMode: FallbackMode;
|
|
155
|
-
private strongMap: Nullable<Map<K, V>> = null;
|
|
156
|
-
private noopMap = false;
|
|
157
|
-
|
|
158
|
-
private entriesMap: Map<symbol, Entry<K, V>> = new Map();
|
|
159
|
-
private keyToId: WeakMap<K, symbol> = new WeakMap();
|
|
160
|
-
private cleanupInterval: number;
|
|
161
|
-
|
|
162
|
-
constructor(options: WeakMapPlusOptions = {}) {
|
|
163
|
-
const {fallback = 'strong', cleanupMs = 1000} = options;
|
|
164
|
-
this.isWeak = _weakAPIsAreNative;
|
|
165
|
-
this.fallbackMode = fallback;
|
|
166
|
-
|
|
167
|
-
if (!this.isWeak) {
|
|
168
|
-
if (fallback === 'strong') {
|
|
169
|
-
this.strongMap = new Map<K, V>();
|
|
170
|
-
} else if (fallback === 'noop') {
|
|
171
|
-
this.noopMap = true;
|
|
172
|
-
}
|
|
173
|
-
this.cleanupInterval = -1;
|
|
174
|
-
} else {
|
|
175
|
-
this.cleanupInterval = setInterval(
|
|
176
|
-
() => this.cleanup(),
|
|
177
|
-
cleanupMs,
|
|
178
|
-
) as unknown as number;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
private getOrCreateId(key: K): symbol {
|
|
183
|
-
let id = this.keyToId.get(key);
|
|
184
|
-
if (!id) {
|
|
185
|
-
id = Symbol();
|
|
186
|
-
this.keyToId.set(key, id);
|
|
187
|
-
}
|
|
188
|
-
return id;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
set(key: K, value: V): this {
|
|
192
|
-
if (!this.isWeak) {
|
|
193
|
-
if (this.noopMap) return this;
|
|
194
|
-
this.strongMap?.set(key, value);
|
|
195
|
-
return this;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const id = this.getOrCreateId(key);
|
|
199
|
-
this.entriesMap.set(id, {ref: new WeakRef(key), value});
|
|
200
|
-
return this;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
get(key: K): V | undefined {
|
|
204
|
-
if (!this.isWeak) {
|
|
205
|
-
if (this.noopMap) return undefined;
|
|
206
|
-
return this.strongMap?.get(key);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const id = this.keyToId.get(key);
|
|
210
|
-
if (!id) return undefined;
|
|
211
|
-
|
|
212
|
-
const entry = this.entriesMap.get(id);
|
|
213
|
-
const derefKey = entry?.ref?.deref();
|
|
214
|
-
return derefKey ? entry?.value : undefined;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
has(key: K): boolean {
|
|
218
|
-
if (!this.isWeak) {
|
|
219
|
-
if (this.noopMap) return false;
|
|
220
|
-
return this.strongMap?.has(key) ?? false;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const id = this.keyToId.get(key);
|
|
224
|
-
if (!id) return false;
|
|
225
|
-
|
|
226
|
-
const entry = this.entriesMap.get(id);
|
|
227
|
-
return !!(entry && entry.ref.deref());
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
delete(key: K): boolean {
|
|
231
|
-
if (!this.isWeak) {
|
|
232
|
-
if (this.noopMap) return false;
|
|
233
|
-
return this.strongMap?.delete(key) ?? false;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const id = this.keyToId.get(key);
|
|
237
|
-
if (!id) return false;
|
|
238
|
-
|
|
239
|
-
this.keyToId.delete(key);
|
|
240
|
-
return this.entriesMap.delete(id);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private *liveEntries(): IterableIterator<[K, V]> {
|
|
244
|
-
for (const [, entry] of this.entriesMap) {
|
|
245
|
-
const key = entry.ref.deref();
|
|
246
|
-
if (key) {
|
|
247
|
-
yield [key, entry.value];
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
entries(): IterableIterator<[K, V]> {
|
|
253
|
-
if (!this.isWeak) {
|
|
254
|
-
if (this.noopMap) return [][Symbol.iterator]();
|
|
255
|
-
return this.strongMap?.entries() ?? [][Symbol.iterator]();
|
|
256
|
-
}
|
|
257
|
-
return this.liveEntries();
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
keys(): IterableIterator<K> {
|
|
261
|
-
return (function* (self: WeakMapPlus<K, V>) {
|
|
262
|
-
for (const [key] of self.entries()) yield key;
|
|
263
|
-
})(this);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
values(): IterableIterator<V> {
|
|
267
|
-
return (function* (self: WeakMapPlus<K, V>) {
|
|
268
|
-
for (const [, value] of self.entries()) yield value;
|
|
269
|
-
})(this);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
[Symbol.iterator](): IterableIterator<[K, V]> {
|
|
273
|
-
return this.entries();
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
get size(): number {
|
|
277
|
-
if (!this.isWeak) {
|
|
278
|
-
if (this.noopMap) return 0;
|
|
279
|
-
return this.strongMap?.size ?? 0;
|
|
280
|
-
}
|
|
281
|
-
let count = 0;
|
|
282
|
-
// eslint-disable-next-line
|
|
283
|
-
for (const _ of this.liveEntries()) {
|
|
284
|
-
count++;
|
|
285
|
-
}
|
|
286
|
-
return count;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
getFallbackMode(): FallbackMode {
|
|
290
|
-
return this.fallbackMode;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
cleanup(): void {
|
|
294
|
-
if (!this.isWeak) return;
|
|
295
|
-
|
|
296
|
-
for (const [id, entry] of this.entriesMap) {
|
|
297
|
-
if (!entry.ref.deref()) {
|
|
298
|
-
this.entriesMap.delete(id);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
destroy(): void {
|
|
304
|
-
if (this.isWeak) {
|
|
305
|
-
clearInterval(this.cleanupInterval);
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
@@ -1,85 +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 {
|
|
11
|
-
RegisterDataUpdateCallback,
|
|
12
|
-
VisualizerData,
|
|
13
|
-
} from '../dom-element-visualizer-interactive';
|
|
14
|
-
import {createVisualizerElement, debounce} from '../visual-utils';
|
|
15
|
-
|
|
16
|
-
export function createComponentStackPanel(
|
|
17
|
-
registerDataUpdateCallback: RegisterDataUpdateCallback,
|
|
18
|
-
): HTMLDivElement {
|
|
19
|
-
const panel = createVisualizerElement('div') as HTMLDivElement;
|
|
20
|
-
panel.style.width = '100%';
|
|
21
|
-
// Ensure max height is at most 80% of viewport height
|
|
22
|
-
panel.style.maxHeight = '80vh';
|
|
23
|
-
panel.style.background = 'rgba(0, 0, 0, 0.5)';
|
|
24
|
-
panel.style.borderTop = '1px solid rgba(255, 255, 255, 0.1)';
|
|
25
|
-
panel.style.display = 'none';
|
|
26
|
-
panel.style.flexDirection = 'column';
|
|
27
|
-
panel.style.padding = '8px';
|
|
28
|
-
panel.style.boxSizing = 'border-box';
|
|
29
|
-
panel.style.borderRadius = '8px';
|
|
30
|
-
panel.style.overflowY = 'scroll';
|
|
31
|
-
panel.style.overflowX = 'hidden';
|
|
32
|
-
panel.style.textShadow = 'none';
|
|
33
|
-
panel.style.font = '9px Inter, system-ui, -apple-system, sans-serif';
|
|
34
|
-
panel.style.color = 'white';
|
|
35
|
-
panel.id = 'memory-visualization-component-stack-panel';
|
|
36
|
-
|
|
37
|
-
let pinned = false;
|
|
38
|
-
|
|
39
|
-
panel.addEventListener('mouseenter', () => {
|
|
40
|
-
pinned = true;
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
panel.addEventListener('mouseleave', () => {
|
|
44
|
-
pinned = false;
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
// Register data update callback to update component stack panel
|
|
48
|
-
registerDataUpdateCallback(
|
|
49
|
-
debounce((data: VisualizerData) => {
|
|
50
|
-
if (pinned) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
panel.style.display = data.selectedElementId != null ? 'flex' : 'none';
|
|
54
|
-
panel.innerHTML = '';
|
|
55
|
-
|
|
56
|
-
if (
|
|
57
|
-
data.selectedElementId == null ||
|
|
58
|
-
!data.selectedReactComponentStack?.length
|
|
59
|
-
) {
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const title = createVisualizerElement('div');
|
|
64
|
-
title.textContent = 'Component Stack:';
|
|
65
|
-
title.style.fontWeight = 'bold';
|
|
66
|
-
title.style.marginBottom = '8px';
|
|
67
|
-
panel.appendChild(title);
|
|
68
|
-
|
|
69
|
-
let actualComponentStackLength = 0;
|
|
70
|
-
data.selectedReactComponentStack.forEach((component: string) => {
|
|
71
|
-
const componentDiv = createVisualizerElement('div');
|
|
72
|
-
componentDiv.style.marginBottom = '4px';
|
|
73
|
-
componentDiv.textContent = component;
|
|
74
|
-
panel.appendChild(componentDiv);
|
|
75
|
-
++actualComponentStackLength;
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
if (actualComponentStackLength === 0) {
|
|
79
|
-
title.remove();
|
|
80
|
-
}
|
|
81
|
-
}, 1),
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
return panel;
|
|
85
|
-
}
|
|
@@ -1,96 +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 {RegisterDataUpdateCallback} from '../dom-element-visualizer-interactive';
|
|
11
|
-
import {createVisualizerElement} from '../visual-utils';
|
|
12
|
-
import {createStatusText} from './status-text';
|
|
13
|
-
import {createToggleButton} from './toggle-button';
|
|
14
|
-
import {createComponentStackPanel} from './component-stack-panel';
|
|
15
|
-
|
|
16
|
-
export function createControlWidget(
|
|
17
|
-
overlayDiv: HTMLDivElement,
|
|
18
|
-
hideAllRef: {value: boolean},
|
|
19
|
-
registerDataUpdateCallback: RegisterDataUpdateCallback,
|
|
20
|
-
): HTMLDivElement {
|
|
21
|
-
const controlWidget = createVisualizerElement('div') as HTMLDivElement;
|
|
22
|
-
controlWidget.style.position = 'fixed';
|
|
23
|
-
controlWidget.style.top = '50px';
|
|
24
|
-
controlWidget.style.right = '50px';
|
|
25
|
-
controlWidget.style.width = '400px';
|
|
26
|
-
controlWidget.style.background = 'rgba(0, 0, 0, 0.7)';
|
|
27
|
-
controlWidget.style.border = 'none';
|
|
28
|
-
controlWidget.style.borderRadius = '8px';
|
|
29
|
-
controlWidget.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.3)';
|
|
30
|
-
controlWidget.style.zIndex = '999999999';
|
|
31
|
-
controlWidget.style.display = 'flex';
|
|
32
|
-
controlWidget.style.flexDirection = 'column';
|
|
33
|
-
controlWidget.style.textShadow = 'none';
|
|
34
|
-
controlWidget.style.boxSizing = 'border-box';
|
|
35
|
-
controlWidget.style.font = '9px Inter, system-ui, -apple-system, sans-serif';
|
|
36
|
-
controlWidget.id = 'memory-visualization-control-widget';
|
|
37
|
-
|
|
38
|
-
// Create header section
|
|
39
|
-
const header = createVisualizerElement('div') as HTMLDivElement;
|
|
40
|
-
header.style.display = 'flex';
|
|
41
|
-
header.style.alignItems = 'center';
|
|
42
|
-
header.style.justifyContent = 'flex-start';
|
|
43
|
-
header.style.padding = '0 8px';
|
|
44
|
-
header.style.height = '36px';
|
|
45
|
-
controlWidget.appendChild(header);
|
|
46
|
-
|
|
47
|
-
// Create component stack panel
|
|
48
|
-
const componentStackPanel = createComponentStackPanel(
|
|
49
|
-
registerDataUpdateCallback,
|
|
50
|
-
);
|
|
51
|
-
controlWidget.appendChild(componentStackPanel);
|
|
52
|
-
|
|
53
|
-
supportDragging(controlWidget);
|
|
54
|
-
|
|
55
|
-
const toggleButton = createToggleButton(overlayDiv, hideAllRef);
|
|
56
|
-
header.appendChild(toggleButton);
|
|
57
|
-
|
|
58
|
-
const statusText = createStatusText(registerDataUpdateCallback);
|
|
59
|
-
header.appendChild(statusText);
|
|
60
|
-
|
|
61
|
-
return controlWidget;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function supportDragging(controlWidget: HTMLDivElement) {
|
|
65
|
-
let isDragging = false;
|
|
66
|
-
let offsetX = 0;
|
|
67
|
-
let offsetY = 0;
|
|
68
|
-
|
|
69
|
-
controlWidget.addEventListener('mousedown', e => {
|
|
70
|
-
// Only allow dragging from the header
|
|
71
|
-
if (
|
|
72
|
-
!(e.target as HTMLElement).closest(
|
|
73
|
-
'#memory-visualization-control-widget > div:first-child',
|
|
74
|
-
)
|
|
75
|
-
) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
isDragging = true;
|
|
79
|
-
offsetX = e.clientX - controlWidget.offsetLeft;
|
|
80
|
-
offsetY = e.clientY - controlWidget.offsetTop;
|
|
81
|
-
controlWidget.style.cursor = 'move';
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
document.addEventListener('mousemove', e => {
|
|
85
|
-
if (isDragging) {
|
|
86
|
-
controlWidget.style.left = `${e.clientX - offsetX}px`;
|
|
87
|
-
controlWidget.style.top = `${e.clientY - offsetY}px`;
|
|
88
|
-
controlWidget.style.right = '';
|
|
89
|
-
}
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
document.addEventListener('mouseup', () => {
|
|
93
|
-
isDragging = false;
|
|
94
|
-
controlWidget.style.cursor = 'default';
|
|
95
|
-
});
|
|
96
|
-
}
|
|
@@ -1,167 +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, DOMElementInfo} from '../../core/types';
|
|
11
|
-
import {IntersectionObserverManager} from '../../utils/intersection-observer';
|
|
12
|
-
import {
|
|
13
|
-
createVisualizerElement,
|
|
14
|
-
addTrackedListener,
|
|
15
|
-
removeAllListeners,
|
|
16
|
-
} from '../visual-utils';
|
|
17
|
-
|
|
18
|
-
type OutlineState = {pinned: boolean; selected: boolean};
|
|
19
|
-
|
|
20
|
-
const MAX_Z_INDEX = `${Math.pow(2, 30) - 1}`;
|
|
21
|
-
|
|
22
|
-
// Set up intersection observer
|
|
23
|
-
const observerManager = IntersectionObserverManager.getInstance();
|
|
24
|
-
|
|
25
|
-
function createLabelDiv(): HTMLElement {
|
|
26
|
-
const labelDiv = createVisualizerElement('div');
|
|
27
|
-
|
|
28
|
-
labelDiv.style.color = 'white';
|
|
29
|
-
labelDiv.style.background = 'rgba(75, 192, 192, 0.8)';
|
|
30
|
-
labelDiv.style.textShadow = 'none';
|
|
31
|
-
labelDiv.style.font = '9px Inter, system-ui, -apple-system, sans-serif';
|
|
32
|
-
labelDiv.style.padding = '2px 6px';
|
|
33
|
-
labelDiv.style.borderRadius = '2px';
|
|
34
|
-
labelDiv.style.whiteSpace = 'nowrap'; // Force single-line text
|
|
35
|
-
labelDiv.style.position = 'absolute'; // Allows positioning above parent
|
|
36
|
-
labelDiv.style.bottom = '100%'; // Places it just above the parent
|
|
37
|
-
labelDiv.style.left = '0'; // Align left with parent
|
|
38
|
-
labelDiv.style.marginBottom = '2px'; // Small space between label and parent
|
|
39
|
-
labelDiv.style.display = 'none';
|
|
40
|
-
labelDiv.style.zIndex = MAX_Z_INDEX;
|
|
41
|
-
|
|
42
|
-
return labelDiv;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const labelDiv = createLabelDiv();
|
|
46
|
-
|
|
47
|
-
export function createOverlayRectangle(
|
|
48
|
-
elementId: number,
|
|
49
|
-
info: DOMElementInfo,
|
|
50
|
-
container: HTMLDivElement,
|
|
51
|
-
setSelectedId: (id: number | null) => void,
|
|
52
|
-
setUnSelectedId: (id: number | null) => void,
|
|
53
|
-
setClickedId: (id: number | null) => void,
|
|
54
|
-
zIndex: number,
|
|
55
|
-
): WeakRef<Element> | null {
|
|
56
|
-
const rect = info.boundingRect;
|
|
57
|
-
if (!rect) return null;
|
|
58
|
-
|
|
59
|
-
const div = createVisualizerElement('div') as HTMLDivElement;
|
|
60
|
-
div.style.position = 'absolute';
|
|
61
|
-
div.style.width = `${rect.width}px`;
|
|
62
|
-
div.style.height = `${rect.height}px`;
|
|
63
|
-
div.style.top = `${rect.top + rect.scrollTop}px`;
|
|
64
|
-
div.style.left = `${rect.left + rect.scrollLeft}px`;
|
|
65
|
-
div.style.border = '1px dotted rgba(75, 192, 192, 0.8)';
|
|
66
|
-
div.style.borderRadius = '1px';
|
|
67
|
-
div.style.zIndex = zIndex.toString();
|
|
68
|
-
|
|
69
|
-
const componentStack = info.componentStack ?? [];
|
|
70
|
-
const componentName = componentStack[0] ?? '';
|
|
71
|
-
|
|
72
|
-
let pinned = false;
|
|
73
|
-
let selected = false;
|
|
74
|
-
|
|
75
|
-
const divRef = new WeakRef(div);
|
|
76
|
-
|
|
77
|
-
addTrackedListener(divRef, 'mouseover', () => {
|
|
78
|
-
// note that elementIdStr should not be genearated in the
|
|
79
|
-
// inside the function scope of createOverlayRectangle
|
|
80
|
-
// to avoid unnecessary retainer path in the heap snapshot
|
|
81
|
-
const elementIdStr = `memory-id-${elementId}@`;
|
|
82
|
-
labelDiv.remove();
|
|
83
|
-
divRef.deref()?.appendChild(labelDiv);
|
|
84
|
-
labelDiv.textContent = `${componentName} (${elementIdStr})`;
|
|
85
|
-
labelDiv.style.display = 'inline-block';
|
|
86
|
-
setSelectedId(elementId);
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
addTrackedListener(divRef, 'mouseout', () => {
|
|
90
|
-
labelDiv.style.display = 'none';
|
|
91
|
-
labelDiv.remove();
|
|
92
|
-
setUnSelectedId(elementId);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
addTrackedListener(divRef, 'click', () => {
|
|
96
|
-
setClickedId(elementId);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
(div as AnyValue).__selected = () => {
|
|
100
|
-
selected = true;
|
|
101
|
-
styleOnInteraction(divRef, {selected, pinned});
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
(div as AnyValue).__unselected = () => {
|
|
105
|
-
selected = false;
|
|
106
|
-
styleOnInteraction(divRef, {selected, pinned});
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
(div as AnyValue).__pinned = () => {
|
|
110
|
-
pinned = true;
|
|
111
|
-
styleOnInteraction(divRef, {selected, pinned});
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
(div as AnyValue).__unpinned = () => {
|
|
115
|
-
pinned = false;
|
|
116
|
-
styleOnInteraction(divRef, {selected, pinned});
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
observerManager.observe(divRef, (entry: IntersectionObserverEntry) => {
|
|
120
|
-
if (!entry.isIntersecting) {
|
|
121
|
-
div.style.visibility = 'hidden';
|
|
122
|
-
} else {
|
|
123
|
-
div.style.visibility = 'visible';
|
|
124
|
-
}
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
(div as AnyValue).__cleanup = () => {
|
|
128
|
-
const div = divRef.deref();
|
|
129
|
-
if (div == null) {
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
removeAllListeners(divRef);
|
|
133
|
-
observerManager.unobserve(divRef);
|
|
134
|
-
(div as AnyValue).__cleanup = null;
|
|
135
|
-
(div as AnyValue).__selected = null;
|
|
136
|
-
(div as AnyValue).__unselected = null;
|
|
137
|
-
(div as AnyValue).__pinned = null;
|
|
138
|
-
(div as AnyValue).__unpinned = null;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
container.appendChild(div);
|
|
142
|
-
return divRef;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
function styleOnInteraction(
|
|
146
|
-
divRef: WeakRef<HTMLDivElement>,
|
|
147
|
-
state: OutlineState,
|
|
148
|
-
): void {
|
|
149
|
-
const div = divRef.deref();
|
|
150
|
-
if (div == null) {
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
const {pinned, selected} = state;
|
|
154
|
-
if (!pinned) {
|
|
155
|
-
if (selected) {
|
|
156
|
-
div.style.border = '1px solid rgba(75, 192, 192, 0.8)';
|
|
157
|
-
div.style.background = 'rgba(75, 192, 192, 0.02)';
|
|
158
|
-
} else {
|
|
159
|
-
div.style.border = '1px dotted rgba(75, 192, 192, 0.8)';
|
|
160
|
-
div.style.background = '';
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
// pinned
|
|
164
|
-
div.style.border = '1px solid rgba(255, 215, 0, 0.9)';
|
|
165
|
-
div.style.background = 'rgba(255, 215, 0, 0.08)';
|
|
166
|
-
}
|
|
167
|
-
}
|
|
@@ -1,53 +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 {AnyValue} from '../../core/types';
|
|
11
|
-
import {
|
|
12
|
-
RegisterDataUpdateCallback,
|
|
13
|
-
VisualizerData,
|
|
14
|
-
} from '../dom-element-visualizer-interactive';
|
|
15
|
-
import {createVisualizerElement} from '../visual-utils';
|
|
16
|
-
|
|
17
|
-
function formatBytes(bytes: number): string {
|
|
18
|
-
if (bytes === 0) return '0 B';
|
|
19
|
-
const k = 1024;
|
|
20
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
21
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
22
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function createStatusText(
|
|
26
|
-
registerDataUpdateCallback: RegisterDataUpdateCallback,
|
|
27
|
-
): HTMLDivElement {
|
|
28
|
-
const statusWidget = createVisualizerElement('div') as HTMLDivElement;
|
|
29
|
-
statusWidget.style.marginLeft = '10px';
|
|
30
|
-
statusWidget.style.color = 'white';
|
|
31
|
-
statusWidget.style.fontSize = '10px';
|
|
32
|
-
statusWidget.style.fontFamily = 'Inter, system-ui, sans-serif';
|
|
33
|
-
statusWidget.style.overflow = 'hidden';
|
|
34
|
-
statusWidget.style.whiteSpace = 'nowrap';
|
|
35
|
-
statusWidget.style.textOverflow = 'ellipsis';
|
|
36
|
-
statusWidget.textContent = '';
|
|
37
|
-
|
|
38
|
-
registerDataUpdateCallback((data: VisualizerData) => {
|
|
39
|
-
const performance = (window as AnyValue).performance;
|
|
40
|
-
const memory = performance?.memory;
|
|
41
|
-
|
|
42
|
-
const usedHeap = memory?.usedJSHeapSize ?? 0;
|
|
43
|
-
const totalHeap = memory?.totalJSHeapSize ?? 0;
|
|
44
|
-
const totalElements = data.totalDOMElementsCount ?? 0;
|
|
45
|
-
const detachedElements = data.detachedDOMElementsCount ?? 0;
|
|
46
|
-
|
|
47
|
-
statusWidget.textContent =
|
|
48
|
-
`DOM: ${totalElements} total, ${detachedElements} detached | ` +
|
|
49
|
-
`Heap: ${formatBytes(usedHeap)} / ${formatBytes(totalHeap)}`;
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
return statusWidget;
|
|
53
|
-
}
|