@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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/config.d.ts +22 -0
  3. package/dist/core/dom-observer.d.ts +10 -0
  4. package/dist/core/event-listener-tracker.d.ts +33 -0
  5. package/dist/core/react-fiber-analysis.d.ts +4 -0
  6. package/dist/core/react-memory-scan.d.ts +36 -0
  7. package/{src/core/types.ts → dist/core/types.d.ts} +56 -80
  8. package/{src/index.ts → dist/core/valid-component-name.d.ts} +2 -7
  9. package/dist/extensions/basic-extension.d.ts +30 -0
  10. package/dist/extensions/dom-visualization-extension.d.ts +17 -0
  11. package/dist/index.d.ts +1 -0
  12. package/dist/memlens.lib.bundle.js +1695 -0
  13. package/dist/memlens.lib.bundle.min.js +1 -0
  14. package/dist/memlens.lib.d.ts +12 -0
  15. package/dist/memlens.run.bundle.js +2673 -0
  16. package/dist/memlens.run.bundle.min.js +1 -0
  17. package/dist/memlens.run.d.ts +1 -0
  18. package/dist/tests/bundle/lib.bundle.test.d.ts +1 -0
  19. package/dist/tests/bundle/run.bundle.start.head.test.d.ts +1 -0
  20. package/dist/tests/bundle/run.bundle.start.test.d.ts +1 -0
  21. package/dist/tests/bundle/run.bundle.test.d.ts +1 -0
  22. package/dist/tests/fiber/dev.fiber.complex.dev.test.d.ts +1 -0
  23. package/dist/tests/fiber/dev.fiber.complex.list.dev.test.d.ts +1 -0
  24. package/dist/tests/fiber/dev.fiber.complex.prod.test.d.ts +1 -0
  25. package/dist/tests/fiber/dev.fiber.name.dev.test.d.ts +1 -0
  26. package/dist/tests/fiber/dev.fiber.name.prod.test.d.ts +1 -0
  27. package/dist/tests/utils/test-utils.d.ts +11 -0
  28. package/dist/utils/intersection-observer.d.ts +18 -0
  29. package/dist/utils/react-fiber-utils.d.ts +56 -0
  30. package/dist/utils/utils.d.ts +26 -0
  31. package/dist/utils/weak-ref-utils.d.ts +67 -0
  32. package/dist/visual/components/component-stack-panel.d.ts +11 -0
  33. package/dist/visual/components/control-widget.d.ts +13 -0
  34. package/dist/visual/components/overlay-rectangle.d.ts +11 -0
  35. package/dist/visual/components/status-text.d.ts +2 -0
  36. package/dist/visual/components/toggle-button.d.ts +3 -0
  37. package/dist/visual/components/visual-overlay.d.ts +1 -0
  38. package/dist/visual/dom-element-visualizer-interactive.d.ts +26 -0
  39. package/{src/core/valid-component-name.ts → dist/visual/dom-element-visualizer.d.ts} +5 -7
  40. package/dist/visual/visual-utils.d.ts +16 -0
  41. package/package.json +5 -1
  42. package/explainer.md +0 -54
  43. package/playwright.config.ts +0 -21
  44. package/src/config/config.ts +0 -32
  45. package/src/core/dom-observer.ts +0 -189
  46. package/src/core/event-listener-tracker.ts +0 -171
  47. package/src/core/react-fiber-analysis.ts +0 -123
  48. package/src/core/react-memory-scan.ts +0 -366
  49. package/src/extensions/basic-extension.ts +0 -41
  50. package/src/extensions/dom-visualization-extension.ts +0 -42
  51. package/src/memlens.lib.js.flow +0 -75
  52. package/src/memlens.lib.ts +0 -22
  53. package/src/memlens.run.ts +0 -21
  54. package/src/tests/bundle/lib.bundle.test.ts +0 -31
  55. package/src/tests/bundle/run.bundle.start.head.test.ts +0 -48
  56. package/src/tests/bundle/run.bundle.start.test.ts +0 -48
  57. package/src/tests/bundle/run.bundle.test.ts +0 -51
  58. package/src/tests/fiber/dev.fiber.complex.dev.test.ts +0 -92
  59. package/src/tests/fiber/dev.fiber.complex.list.dev.test.ts +0 -118
  60. package/src/tests/fiber/dev.fiber.complex.prod.test.ts +0 -92
  61. package/src/tests/fiber/dev.fiber.name.dev.test.ts +0 -77
  62. package/src/tests/fiber/dev.fiber.name.prod.test.ts +0 -79
  63. package/src/tests/lib/babel.prod.js +0 -4
  64. package/src/tests/lib/react-dom-v18.dev.js +0 -29926
  65. package/src/tests/lib/react-dom-v18.prod.js +0 -269
  66. package/src/tests/lib/react-v18.dev.js +0 -3345
  67. package/src/tests/lib/react-v18.prod.js +0 -33
  68. package/src/tests/manual/playwright-open-manual.js +0 -40
  69. package/src/tests/manual/todo-list/todo-with-run.bundle.html +0 -80
  70. package/src/tests/utils/test-utils.ts +0 -28
  71. package/src/utils/intersection-observer.ts +0 -65
  72. package/src/utils/react-fiber-utils.ts +0 -212
  73. package/src/utils/utils.ts +0 -201
  74. package/src/utils/weak-ref-utils.ts +0 -308
  75. package/src/visual/components/component-stack-panel.ts +0 -85
  76. package/src/visual/components/control-widget.ts +0 -96
  77. package/src/visual/components/overlay-rectangle.ts +0 -167
  78. package/src/visual/components/status-text.ts +0 -53
  79. package/src/visual/components/toggle-button.ts +0 -57
  80. package/src/visual/components/visual-overlay.ts +0 -19
  81. package/src/visual/dom-element-visualizer-interactive.ts +0 -358
  82. package/src/visual/dom-element-visualizer.ts +0 -130
  83. package/src/visual/visual-utils.ts +0 -89
  84. package/tsconfig.json +0 -18
  85. 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
- }