@buoy-gg/highlight-updates 7.0.36 → 7.0.38

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 (104) hide show
  1. package/README.md +28 -6
  2. package/lib/commonjs/highlight-updates/HighlightUpdatesOverlay.js +1 -281
  3. package/lib/commonjs/highlight-updates/components/HighlightFilterView.js +1 -1474
  4. package/lib/commonjs/highlight-updates/components/HighlightUpdatesModal.js +1 -641
  5. package/lib/commonjs/highlight-updates/components/IdentifierBadge.js +1 -273
  6. package/lib/commonjs/highlight-updates/components/IsolatedRenderList.js +1 -188
  7. package/lib/commonjs/highlight-updates/components/ModalHeaderContent.js +1 -293
  8. package/lib/commonjs/highlight-updates/components/RenderCauseBadge.js +1 -508
  9. package/lib/commonjs/highlight-updates/components/RenderDetailView.js +1 -846
  10. package/lib/commonjs/highlight-updates/components/RenderHistoryViewer.js +1 -915
  11. package/lib/commonjs/highlight-updates/components/RenderListItem.js +1 -235
  12. package/lib/commonjs/highlight-updates/components/RendersCopySettingsView.js +1 -466
  13. package/lib/commonjs/highlight-updates/components/StatsDisplay.js +1 -73
  14. package/lib/commonjs/highlight-updates/types/copySettings.js +1 -107
  15. package/lib/commonjs/highlight-updates/utils/CommitProfiler.js +1 -446
  16. package/lib/commonjs/highlight-updates/utils/HighlightUpdatesController.js +1 -1920
  17. package/lib/commonjs/highlight-updates/utils/PerformanceLogger.js +1 -359
  18. package/lib/commonjs/highlight-updates/utils/ProfilerInterceptor.js +1 -371
  19. package/lib/commonjs/highlight-updates/utils/RenderCauseDetector.js +1 -1840
  20. package/lib/commonjs/highlight-updates/utils/RenderTracker.js +1 -952
  21. package/lib/commonjs/highlight-updates/utils/ViewTypeMapper.js +1 -264
  22. package/lib/commonjs/highlight-updates/utils/copySettingsStorage.js +1 -32
  23. package/lib/commonjs/highlight-updates/utils/devtoolsComponentNames.js +1 -188
  24. package/lib/commonjs/highlight-updates/utils/namedComposite.js +1 -41
  25. package/lib/commonjs/highlight-updates/utils/ownStateChanged.js +1 -103
  26. package/lib/commonjs/highlight-updates/utils/rawEventEmitter.js +1 -50
  27. package/lib/commonjs/highlight-updates/utils/renderExportFormatter.js +1 -58
  28. package/lib/commonjs/highlight-updates/utils/rendersExportFormatter.js +1 -485
  29. package/lib/commonjs/highlight-updates/utils/screenElements.js +1 -1248
  30. package/lib/commonjs/highlight-updates/utils/tapMatch.js +1 -83
  31. package/lib/commonjs/highlight-updates/utils/testIDs.js +1 -68
  32. package/lib/commonjs/highlight-updates/utils/touchCapture.js +1 -277
  33. package/lib/commonjs/index.js +1 -429
  34. package/lib/commonjs/preset.js +1 -278
  35. package/lib/commonjs/sync/highlightUpdatesSyncAdapter.js +1 -235
  36. package/lib/module/highlight-updates/HighlightUpdatesOverlay.js +1 -274
  37. package/lib/module/highlight-updates/components/HighlightFilterView.js +1 -1468
  38. package/lib/module/highlight-updates/components/HighlightUpdatesModal.js +1 -634
  39. package/lib/module/highlight-updates/components/IdentifierBadge.js +1 -265
  40. package/lib/module/highlight-updates/components/IsolatedRenderList.js +1 -183
  41. package/lib/module/highlight-updates/components/ModalHeaderContent.js +1 -288
  42. package/lib/module/highlight-updates/components/RenderCauseBadge.js +1 -499
  43. package/lib/module/highlight-updates/components/RenderDetailView.js +1 -840
  44. package/lib/module/highlight-updates/components/RenderHistoryViewer.js +1 -908
  45. package/lib/module/highlight-updates/components/RenderListItem.js +1 -230
  46. package/lib/module/highlight-updates/components/RendersCopySettingsView.js +1 -462
  47. package/lib/module/highlight-updates/components/StatsDisplay.js +1 -70
  48. package/lib/module/highlight-updates/types/copySettings.js +1 -102
  49. package/lib/module/highlight-updates/utils/CommitProfiler.js +1 -442
  50. package/lib/module/highlight-updates/utils/HighlightUpdatesController.js +1 -1916
  51. package/lib/module/highlight-updates/utils/PerformanceLogger.js +1 -353
  52. package/lib/module/highlight-updates/utils/ProfilerInterceptor.js +1 -358
  53. package/lib/module/highlight-updates/utils/RenderCauseDetector.js +1 -1830
  54. package/lib/module/highlight-updates/utils/RenderTracker.js +1 -949
  55. package/lib/module/highlight-updates/utils/ViewTypeMapper.js +1 -255
  56. package/lib/module/highlight-updates/utils/copySettingsStorage.js +1 -26
  57. package/lib/module/highlight-updates/utils/devtoolsComponentNames.js +1 -178
  58. package/lib/module/highlight-updates/utils/namedComposite.js +1 -36
  59. package/lib/module/highlight-updates/utils/ownStateChanged.js +1 -98
  60. package/lib/module/highlight-updates/utils/rawEventEmitter.js +1 -46
  61. package/lib/module/highlight-updates/utils/renderExportFormatter.js +1 -54
  62. package/lib/module/highlight-updates/utils/rendersExportFormatter.js +1 -478
  63. package/lib/module/highlight-updates/utils/screenElements.js +1 -1242
  64. package/lib/module/highlight-updates/utils/tapMatch.js +1 -76
  65. package/lib/module/highlight-updates/utils/testIDs.js +1 -60
  66. package/lib/module/highlight-updates/utils/touchCapture.js +1 -270
  67. package/lib/module/index.js +1 -130
  68. package/lib/module/preset.js +1 -272
  69. package/lib/module/sync/highlightUpdatesSyncAdapter.js +1 -230
  70. package/package.json +8 -8
  71. package/lib/typescript/highlight-updates/HighlightUpdatesOverlay.d.ts.map +0 -1
  72. package/lib/typescript/highlight-updates/components/HighlightFilterView.d.ts.map +0 -1
  73. package/lib/typescript/highlight-updates/components/HighlightUpdatesModal.d.ts.map +0 -1
  74. package/lib/typescript/highlight-updates/components/IdentifierBadge.d.ts.map +0 -1
  75. package/lib/typescript/highlight-updates/components/IsolatedRenderList.d.ts.map +0 -1
  76. package/lib/typescript/highlight-updates/components/ModalHeaderContent.d.ts.map +0 -1
  77. package/lib/typescript/highlight-updates/components/RenderCauseBadge.d.ts.map +0 -1
  78. package/lib/typescript/highlight-updates/components/RenderDetailView.d.ts.map +0 -1
  79. package/lib/typescript/highlight-updates/components/RenderHistoryViewer.d.ts.map +0 -1
  80. package/lib/typescript/highlight-updates/components/RenderListItem.d.ts.map +0 -1
  81. package/lib/typescript/highlight-updates/components/RendersCopySettingsView.d.ts.map +0 -1
  82. package/lib/typescript/highlight-updates/components/StatsDisplay.d.ts.map +0 -1
  83. package/lib/typescript/highlight-updates/types/copySettings.d.ts.map +0 -1
  84. package/lib/typescript/highlight-updates/utils/CommitProfiler.d.ts.map +0 -1
  85. package/lib/typescript/highlight-updates/utils/HighlightUpdatesController.d.ts.map +0 -1
  86. package/lib/typescript/highlight-updates/utils/PerformanceLogger.d.ts.map +0 -1
  87. package/lib/typescript/highlight-updates/utils/ProfilerInterceptor.d.ts.map +0 -1
  88. package/lib/typescript/highlight-updates/utils/RenderCauseDetector.d.ts.map +0 -1
  89. package/lib/typescript/highlight-updates/utils/RenderTracker.d.ts.map +0 -1
  90. package/lib/typescript/highlight-updates/utils/ViewTypeMapper.d.ts.map +0 -1
  91. package/lib/typescript/highlight-updates/utils/copySettingsStorage.d.ts.map +0 -1
  92. package/lib/typescript/highlight-updates/utils/devtoolsComponentNames.d.ts.map +0 -1
  93. package/lib/typescript/highlight-updates/utils/namedComposite.d.ts.map +0 -1
  94. package/lib/typescript/highlight-updates/utils/ownStateChanged.d.ts.map +0 -1
  95. package/lib/typescript/highlight-updates/utils/rawEventEmitter.d.ts.map +0 -1
  96. package/lib/typescript/highlight-updates/utils/renderExportFormatter.d.ts.map +0 -1
  97. package/lib/typescript/highlight-updates/utils/rendersExportFormatter.d.ts.map +0 -1
  98. package/lib/typescript/highlight-updates/utils/screenElements.d.ts.map +0 -1
  99. package/lib/typescript/highlight-updates/utils/tapMatch.d.ts.map +0 -1
  100. package/lib/typescript/highlight-updates/utils/testIDs.d.ts.map +0 -1
  101. package/lib/typescript/highlight-updates/utils/touchCapture.d.ts.map +0 -1
  102. package/lib/typescript/index.d.ts.map +0 -1
  103. package/lib/typescript/preset.d.ts.map +0 -1
  104. package/lib/typescript/sync/highlightUpdatesSyncAdapter.d.ts.map +0 -1
@@ -1,446 +1 @@
1
- "use strict";
2
-
3
- Object.defineProperty(exports, "__esModule", {
4
- value: true
5
- });
6
- exports.CommitProfiler = void 0;
7
- var _devtoolsComponentNames = require("./devtoolsComponentNames");
8
- var _ownStateChanged = require("./ownStateChanged");
9
- /**
10
- * CommitProfiler — per-component render counts + durations across a capture
11
- * window, React-DevTools-profiler style, without React DevTools attached.
12
- *
13
- * Wraps `__REACT_DEVTOOLS_GLOBAL_HOOK__.onCommitFiberRoot` (the callback React
14
- * invokes after every commit) and, while a capture is active, walks the
15
- * committed fiber tree collecting composite fibers that performed work this
16
- * commit. Self duration follows React's convention: `actualDuration` minus the
17
- * sum of direct children's `actualDuration` (profiling timers are enabled in
18
- * dev builds, where Buoy runs).
19
- *
20
- * This is intentionally separate from the traceUpdates flow the highlight
21
- * overlay uses — traceUpdates emits host views with no timing data.
22
- *
23
- * Consumed by perf-monitor benchmarks via the shared-ui render-capture
24
- * registry (see the registration in this package's index.tsx).
25
- */
26
-
27
- // One implementation, shared with the overlay's RenderCauseDetector — the two
28
- // classifiers are separate on purpose, but this question is not. See the file
29
- // for why the obvious identity check is wrong.
30
-
31
- // React work tags for components that render user code.
32
- const FunctionComponent = 0;
33
- const ClassComponent = 1;
34
- const ForwardRef = 11;
35
- const MemoComponent = 14;
36
- const SimpleMemoComponent = 15;
37
-
38
- // React fiber flag: this fiber rendered (performed work) in the last commit.
39
- // Stable value 0b1 across React 16-19 (effectTag pre-18, flags after).
40
- const PerformedWork = 0b1;
41
-
42
- /** Overhead guardrail: stop walking a commit past this many visited fibers. */
43
- const MAX_FIBERS_PER_COMMIT = 10_000;
44
-
45
- /** Bound on distinct component names tracked; overflow still counts in totals. */
46
- const MAX_TRACKED_NAMES = 300;
47
-
48
- /** Components returned in the summary. */
49
- const TOP_COMPONENTS_CAP = 20;
50
- let hookInstalled = false;
51
- let active = false;
52
- let sawDurations = false;
53
- let captureDetail = false;
54
- let totalCommits = 0;
55
- let totalRenders = 0;
56
- let totalRenderMs = 0;
57
- let totalCauses = {};
58
- let buckets = new Map();
59
- function getHook() {
60
- if (typeof global === "undefined") return null;
61
- return global.__REACT_DEVTOOLS_GLOBAL_HOOK__ ?? null;
62
- }
63
- function getFlags(fiber) {
64
- const f = fiber.flags ?? fiber.effectTag;
65
- return typeof f === "number" ? f : 0;
66
- }
67
- function isCompositeTag(tag) {
68
- return tag === FunctionComponent || tag === ClassComponent || tag === ForwardRef || tag === MemoComponent || tag === SimpleMemoComponent;
69
- }
70
-
71
- /** Resolve a composite fiber's display name (DevTools conventions). */
72
- function getComponentName(fiber) {
73
- const type = fiber.type;
74
- try {
75
- switch (fiber.tag) {
76
- case FunctionComponent:
77
- case ClassComponent:
78
- return type?.displayName || type?.name || "Anonymous";
79
- case ForwardRef:
80
- return type?.displayName || type?.render?.displayName || type?.render?.name || "ForwardRef";
81
- case MemoComponent:
82
- case SimpleMemoComponent:
83
- {
84
- const inner = type?.type ?? fiber.elementType?.type;
85
- return type?.displayName || inner?.displayName || inner?.name || "Memo";
86
- }
87
- default:
88
- return "Anonymous";
89
- }
90
- } catch {
91
- return "Anonymous";
92
- }
93
- }
94
-
95
- /**
96
- * Self duration = own actualDuration minus direct children's. React's
97
- * bubbleProperties adds every direct child's actualDuration into the parent,
98
- * so subtracting them all back out is symmetric and exact.
99
- */
100
- function computeSelfMs(fiber) {
101
- const actual = fiber.actualDuration;
102
- if (typeof actual !== "number") return null;
103
- let self = actual;
104
- for (let child = fiber.child; child != null; child = child.sibling) {
105
- if (typeof child.actualDuration === "number") self -= child.actualDuration;
106
- }
107
- return self > 0 ? self : 0;
108
- }
109
-
110
- // React Native framework wrapper components. Their renders are real work
111
- // but the actionable signal lives on the OWNING component (a Touchable
112
- // re-rendering is already visible as its parent re-rendering), so they're
113
- // folded into the capture totals and kept OUT of the per-component list.
114
- // This is a report-level filter only — unlike isDevToolsComponentName it
115
- // must NOT prune subtrees (their children are user components).
116
- const FRAMEWORK_WRAPPER_NAMES = new Set(["View", "Text", "Image", "ImageBackground", "ScrollView", "FlatList", "SectionList", "SafeAreaView", "KeyboardAvoidingView", "StatusBar", "ActivityIndicator", "RefreshControl", "Modal", "Switch", "Pressable", "PlatformPressable", "TouchableOpacity", "TouchableHighlight", "TouchableWithoutFeedback", "TouchableNativeFeedback", "PressabilityDebugView", "AnimatedComponent", "Wrapper"]);
117
- const FRAMEWORK_WRAPPER_PREFIXES = ["Animated(",
118
- // Animated(View), Animated(Text), Animated(FlatList), …
119
- "Pressability", "Virtualized",
120
- // VirtualizedList + its context providers
121
- "CellRenderer"];
122
- function isFrameworkWrapperName(name) {
123
- if (FRAMEWORK_WRAPPER_NAMES.has(name)) return true;
124
- for (let i = 0; i < FRAMEWORK_WRAPPER_PREFIXES.length; i++) {
125
- if (name.startsWith(FRAMEWORK_WRAPPER_PREFIXES[i])) return true;
126
- }
127
- return false;
128
- }
129
-
130
- /** Bound on prop keys compared per render, so a fat props object can't stall a commit. */
131
- const MAX_PROPS_COMPARED = 40;
132
- /** Bound on distinct changed-prop key names remembered per component (detail tier). */
133
- const MAX_CHANGED_KEYS = 5;
134
-
135
- /**
136
- * Are two values for the same prop key equivalent despite differing identity?
137
- *
138
- * Functions always count as equivalent: a fresh arrow per render is THE
139
- * unstable-prop bug, and there's no way to compare behaviour anyway. Plain
140
- * objects and arrays compare one level deep, which catches `style={{...}}`
141
- * and `data={[...]}`. Anything else falls through to a real value change.
142
- */
143
- function isEquivalentValue(a, b) {
144
- if (Object.is(a, b)) return true;
145
- const ta = typeof a;
146
- if (ta !== typeof b) return false;
147
- if (ta === "function") return true;
148
- if (a === null || b === null || ta !== "object") return false;
149
- const aIsArray = Array.isArray(a);
150
- if (aIsArray !== Array.isArray(b)) return false;
151
- if (aIsArray) {
152
- if (a.length !== b.length) return false;
153
- for (let i = 0; i < a.length; i++) if (!Object.is(a[i], b[i])) return false;
154
- return true;
155
- }
156
- const keysA = Object.keys(a);
157
- const keysB = Object.keys(b);
158
- if (keysA.length !== keysB.length) return false;
159
- for (let i = 0; i < keysA.length; i++) {
160
- const k = keysA[i];
161
- if (!Object.prototype.hasOwnProperty.call(b, k)) return false;
162
- if (!Object.is(a[k], b[k])) return false;
163
- }
164
- return true;
165
- }
166
- /**
167
- * Why did this fiber render?
168
- *
169
- * Simpler than the overlay's RenderCauseDetector, and deliberately so: that
170
- * one carries WeakMap fallbacks and fiber-swap detection because traceUpdates
171
- * hands it fibers at an ambiguous moment. Here we're inside onCommitFiberRoot
172
- * walking the committed tree, so `fiber` is current and `fiber.alternate` is
173
- * previous by definition. Porting the swap logic would be cargo-cult.
174
- */
175
- function detectCause(fiber, wantKeys) {
176
- const prev = fiber.alternate;
177
- if (!prev) return {
178
- cause: "mount"
179
- };
180
- const nextProps = fiber.memoizedProps;
181
- const prevProps = prev.memoizedProps;
182
- if (nextProps !== prevProps && nextProps && prevProps) {
183
- let sawChange = false;
184
- let sawValueChange = false;
185
- let changedKeys;
186
- const keys = Object.keys(nextProps);
187
- const limit = keys.length < MAX_PROPS_COMPARED ? keys.length : MAX_PROPS_COMPARED;
188
- for (let i = 0; i < limit; i++) {
189
- const k = keys[i];
190
- const a = prevProps[k];
191
- const b = nextProps[k];
192
- if (Object.is(a, b)) continue;
193
- sawChange = true;
194
- if (wantKeys) {
195
- if (!changedKeys) changedKeys = [];
196
- if (changedKeys.length < MAX_CHANGED_KEYS) changedKeys.push(k);
197
- }
198
- if (!isEquivalentValue(a, b)) {
199
- sawValueChange = true;
200
- if (!wantKeys) break; // cause is settled; only keys need the full pass
201
- }
202
- }
203
- // A removed key counts as a real change; cheap to detect via key count.
204
- if (!sawValueChange && Object.keys(prevProps).length !== keys.length) {
205
- sawChange = true;
206
- sawValueChange = true;
207
- }
208
- if (sawChange) {
209
- return {
210
- cause: sawValueChange ? "props" : "propsUnstable",
211
- changedKeys
212
- };
213
- }
214
- }
215
-
216
- // No prop change of substance. Own state/hooks next.
217
- if ((0, _ownStateChanged.ownStateChanged)(prev, fiber)) return {
218
- cause: "hooks"
219
- };
220
- return {
221
- cause: "parent"
222
- };
223
- }
224
-
225
- /**
226
- * Nearest ancestor that ALSO rendered this commit and is worth naming — i.e.
227
- * skipping framework wrappers, which are never where a fix goes. Answers "who
228
- * dragged this along", which is the actionable half of a cascade.
229
- */
230
- function nearestRenderedAncestorName(fiber) {
231
- for (let p = fiber.return; p != null; p = p.return) {
232
- if (!isCompositeTag(p.tag)) continue;
233
- if ((getFlags(p) & PerformedWork) === 0) continue;
234
- const name = getComponentName(p);
235
- if (isFrameworkWrapperName(name)) continue;
236
- return name;
237
- }
238
- return undefined;
239
- }
240
- function recordRender(fiber, parentName) {
241
- const name = getComponentName(fiber);
242
- const selfMs = computeSelfMs(fiber);
243
- if (selfMs !== null) sawDurations = true;
244
- const ms = selfMs ?? 0;
245
- totalRenders += 1;
246
- totalRenderMs += ms;
247
- const {
248
- cause,
249
- changedKeys
250
- } = detectCause(fiber, captureDetail);
251
- totalCauses[cause] = (totalCauses[cause] ?? 0) + 1;
252
-
253
- // Framework wrappers count toward totals but never surface as a row.
254
- if (isFrameworkWrapperName(name)) return;
255
- let bucket = buckets.get(name);
256
- if (!bucket) {
257
- // Bounded map: overflow renders still count toward the totals above.
258
- if (buckets.size >= MAX_TRACKED_NAMES) return;
259
- bucket = {
260
- renders: 0,
261
- totalSelfMs: 0,
262
- maxSelfMs: 0,
263
- causes: {}
264
- };
265
- buckets.set(name, bucket);
266
- }
267
- bucket.renders += 1;
268
- bucket.totalSelfMs += ms;
269
- if (ms > bucket.maxSelfMs) bucket.maxSelfMs = ms;
270
- bucket.causes[cause] = (bucket.causes[cause] ?? 0) + 1;
271
- if (captureDetail) {
272
- if (changedKeys) {
273
- if (!bucket.changedProps) bucket.changedProps = new Set();
274
- for (let i = 0; i < changedKeys.length; i++) {
275
- if (bucket.changedProps.size >= MAX_CHANGED_KEYS) break;
276
- bucket.changedProps.add(changedKeys[i]);
277
- }
278
- }
279
- // First parent wins — a component rendered under several parents is rare,
280
- // and the first is the one that showed up in the heaviest commit.
281
- if (parentName && !bucket.parentName) bucket.parentName = parentName;
282
- }
283
- }
284
-
285
- /**
286
- * Iterative walk (child → sibling → return) of the committed tree. Never
287
- * throws upward — a broken walk must not break React's commit.
288
- */
289
- function handleCommit(root) {
290
- const start = root?.current;
291
- if (!start) return;
292
- const rendersBefore = totalRenders;
293
- let visited = 0;
294
- let fiber = start;
295
- while (fiber != null) {
296
- visited += 1;
297
- if (visited > MAX_FIBERS_PER_COMMIT) break;
298
- let skipSubtree = false;
299
- const composite = isCompositeTag(fiber.tag);
300
- if (composite) {
301
- const compositeName = getComponentName(fiber);
302
- // Only Buoy's render-tracking UI gets its SUBTREE pruned — walking it
303
- // feeds its own renders back in and loops. Everything else that's merely
304
- // devtools noise is skipped as a NODE and we keep descending: app
305
- // components live underneath Buoy chrome, and pruning a subtree on a name
306
- // match is what made a stray "Virtualized" prefix hide every FlatList in
307
- // every app.
308
- if ((0, _devtoolsComponentNames.isRenderLoopUnsafeName)(compositeName)) {
309
- skipSubtree = true;
310
- } else if ((0, _devtoolsComponentNames.isDevToolsComponentName)(compositeName)) {
311
- // skip this node, keep walking its children
312
- } else if ((getFlags(fiber) & PerformedWork) !== 0) {
313
- // PerformedWork is the only trustworthy "rendered this commit"
314
- // signal — actualDuration is stale on bailed-out fibers.
315
- recordRender(fiber, captureDetail ? nearestRenderedAncestorName(fiber) : undefined);
316
- }
317
- }
318
- if (!skipSubtree) {
319
- // Prune subtrees with no work. subtreeFlags only exists on React 18+;
320
- // when absent, always descend.
321
- const subtreeFlags = fiber.subtreeFlags;
322
- const noSubtreeWork = typeof subtreeFlags === "number" && (subtreeFlags & PerformedWork) === 0 && (getFlags(fiber) & PerformedWork) === 0;
323
- if (!noSubtreeWork && fiber.child != null) {
324
- fiber = fiber.child;
325
- continue;
326
- }
327
- }
328
-
329
- // No descend: advance to sibling, climbing up as needed.
330
- while (fiber != null) {
331
- if (fiber === start) return finalizeCommit(rendersBefore);
332
- if (fiber.sibling != null) {
333
- fiber = fiber.sibling;
334
- break;
335
- }
336
- fiber = fiber.return;
337
- }
338
- }
339
- finalizeCommit(rendersBefore);
340
- }
341
- function finalizeCommit(rendersBefore) {
342
- if (totalRenders > rendersBefore) totalCommits += 1;
343
- }
344
-
345
- /**
346
- * Install the onCommitFiberRoot wrapper once and never restore it — the
347
- * `active` flag gates all work, and never restoring avoids clobber races if
348
- * something else (e.g. React DevTools attaching mid-session) wraps the hook
349
- * after us. The wrapper dies with the JS realm on reload.
350
- */
351
- function ensureHookInstalled() {
352
- if (hookInstalled) return true;
353
- const hook = getHook();
354
- if (!hook) return false;
355
- const original = hook.onCommitFiberRoot;
356
- hook.onCommitFiberRoot = function (rendererID, root, ...rest) {
357
- if (active) {
358
- try {
359
- handleCommit(root);
360
- } catch {
361
- // Capture must never break React's commit path.
362
- }
363
- }
364
- return typeof original === "function" ? original.call(hook, rendererID, root, ...rest) : undefined;
365
- };
366
- hookInstalled = true;
367
- return true;
368
- }
369
- function isSupported() {
370
- if (typeof __DEV__ !== "undefined" && !__DEV__) return false;
371
- const hook = getHook();
372
- return Boolean(hook && hook.renderers && hook.renderers.size > 0);
373
- }
374
- function isCapturing() {
375
- return active;
376
- }
377
-
378
- /**
379
- * Begin a capture window, discarding any previous unclaimed data.
380
- *
381
- * `detail` turns on the allocating tier — changed prop key names and parent
382
- * component names. Causes themselves are always classified: it costs a shallow
383
- * prop compare, against a `computeSelfMs` that already walks every direct
384
- * child on every render, and the wasted-render total is the number most worth
385
- * seeing without having to know to ask for it.
386
- */
387
- function startCapture(opts) {
388
- if (!ensureHookInstalled()) return;
389
- buckets = new Map();
390
- sawDurations = false;
391
- captureDetail = opts?.detail === true;
392
- totalCommits = 0;
393
- totalRenders = 0;
394
- totalRenderMs = 0;
395
- totalCauses = {};
396
- active = true;
397
- }
398
-
399
- /** End the window and return the aggregate (null if never started). */
400
- function stopCapture() {
401
- if (!active && totalRenders === 0 && buckets.size === 0) return null;
402
- active = false;
403
- const topComponents = Array.from(buckets.entries()).sort((a, b) => {
404
- const byMs = b[1].totalSelfMs - a[1].totalSelfMs;
405
- return byMs !== 0 ? byMs : b[1].renders - a[1].renders;
406
- }).slice(0, TOP_COMPONENTS_CAP).map(([name, b]) => ({
407
- name,
408
- renders: b.renders,
409
- totalMs: round2(b.totalSelfMs),
410
- maxMs: round2(b.maxSelfMs),
411
- avgMs: b.renders > 0 ? round2(b.totalSelfMs / b.renders) : 0,
412
- causes: b.causes,
413
- ...(b.changedProps && b.changedProps.size > 0 ? {
414
- changedProps: Array.from(b.changedProps)
415
- } : null),
416
- ...(b.parentName ? {
417
- parentName: b.parentName
418
- } : null)
419
- }));
420
- const summary = {
421
- totalCommits,
422
- totalRenders,
423
- capturedDurations: sawDurations,
424
- totalRenderMs: round2(totalRenderMs),
425
- // Parent cascades and identity-only prop churn are both removable without
426
- // changing behaviour — together they're the recoverable share.
427
- wastedRenders: (totalCauses.parent ?? 0) + (totalCauses.propsUnstable ?? 0),
428
- causes: totalCauses,
429
- topComponents
430
- };
431
- buckets = new Map();
432
- totalCommits = 0;
433
- totalRenders = 0;
434
- totalRenderMs = 0;
435
- totalCauses = {};
436
- return summary;
437
- }
438
- function round2(n) {
439
- return Math.round(n * 100) / 100;
440
- }
441
- const CommitProfiler = exports.CommitProfiler = {
442
- isSupported,
443
- isCapturing,
444
- startCapture,
445
- stopCapture
446
- };
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0}),exports.CommitProfiler=void 0;var _devtoolsComponentNames=require("./devtoolsComponentNames"),_ownStateChanged=require("./ownStateChanged");const FunctionComponent=0,ClassComponent=1,ForwardRef=11,MemoComponent=14,SimpleMemoComponent=15,PerformedWork=1,MAX_FIBERS_PER_COMMIT=1e4,MAX_TRACKED_NAMES=300,TOP_COMPONENTS_CAP=20;let hookInstalled=!1,active=!1,sawDurations=!1,captureDetail=!1,totalCommits=0,totalRenders=0,totalRenderMs=0,totalCauses={},buckets=new Map;function getHook(){return"undefined"==typeof global?null:global.__REACT_DEVTOOLS_GLOBAL_HOOK__??null}function getFlags(e){const t=e.flags??e.effectTag;return"number"==typeof t?t:0}function isCompositeTag(e){return 0===e||1===e||11===e||14===e||15===e}function getComponentName(e){const t=e.type;try{switch(e.tag){case 0:case 1:return t?.displayName||t?.name||"Anonymous";case 11:return t?.displayName||t?.render?.displayName||t?.render?.name||"ForwardRef";case 14:case 15:{const n=t?.type??e.elementType?.type;return t?.displayName||n?.displayName||n?.name||"Memo"}default:return"Anonymous"}}catch{return"Anonymous"}}function computeSelfMs(e){const t=e.actualDuration;if("number"!=typeof t)return null;let n=t;for(let t=e.child;null!=t;t=t.sibling)"number"==typeof t.actualDuration&&(n-=t.actualDuration);return n>0?n:0}const FRAMEWORK_WRAPPER_NAMES=new Set(["View","Text","Image","ImageBackground","ScrollView","FlatList","SectionList","SafeAreaView","KeyboardAvoidingView","StatusBar","ActivityIndicator","RefreshControl","Modal","Switch","Pressable","PlatformPressable","TouchableOpacity","TouchableHighlight","TouchableWithoutFeedback","TouchableNativeFeedback","PressabilityDebugView","AnimatedComponent","Wrapper"]),FRAMEWORK_WRAPPER_PREFIXES=["Animated(","Pressability","Virtualized","CellRenderer"];function isFrameworkWrapperName(e){if(FRAMEWORK_WRAPPER_NAMES.has(e))return!0;for(let t=0;t<FRAMEWORK_WRAPPER_PREFIXES.length;t++)if(e.startsWith(FRAMEWORK_WRAPPER_PREFIXES[t]))return!0;return!1}const MAX_PROPS_COMPARED=40,MAX_CHANGED_KEYS=5;function isEquivalentValue(e,t){if(Object.is(e,t))return!0;const n=typeof e;if(n!==typeof t)return!1;if("function"===n)return!0;if(null===e||null===t||"object"!==n)return!1;const r=Array.isArray(e);if(r!==Array.isArray(t))return!1;if(r){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(!Object.is(e[n],t[n]))return!1;return!0}const o=Object.keys(e),a=Object.keys(t);if(o.length!==a.length)return!1;for(let n=0;n<o.length;n++){const r=o[n];if(!Object.prototype.hasOwnProperty.call(t,r))return!1;if(!Object.is(e[r],t[r]))return!1}return!0}function detectCause(e,t){const n=e.alternate;if(!n)return{cause:"mount"};const r=e.memoizedProps,o=n.memoizedProps;if(r!==o&&r&&o){let e,n=!1,a=!1;const s=Object.keys(r),i=s.length<40?s.length:40;for(let l=0;l<i;l++){const i=s[l],u=o[i],c=r[i];if(!Object.is(u,c)&&(n=!0,t&&(e||(e=[]),e.length<5&&e.push(i)),!isEquivalentValue(u,c)&&(a=!0,!t)))break}if(a||Object.keys(o).length===s.length||(n=!0,a=!0),n)return{cause:a?"props":"propsUnstable",changedKeys:e}}return(0,_ownStateChanged.ownStateChanged)(n,e)?{cause:"hooks"}:{cause:"parent"}}function nearestRenderedAncestorName(e){for(let t=e.return;null!=t;t=t.return){if(!isCompositeTag(t.tag))continue;if(!(1&getFlags(t)))continue;const e=getComponentName(t);if(!isFrameworkWrapperName(e))return e}}function recordRender(e,t){const n=getComponentName(e),r=computeSelfMs(e);null!==r&&(sawDurations=!0);const o=r??0;totalRenders+=1,totalRenderMs+=o;const{cause:a,changedKeys:s}=detectCause(e,captureDetail);if(totalCauses[a]=(totalCauses[a]??0)+1,isFrameworkWrapperName(n))return;let i=buckets.get(n);if(!i){if(buckets.size>=300)return;i={renders:0,totalSelfMs:0,maxSelfMs:0,causes:{}},buckets.set(n,i)}if(i.renders+=1,i.totalSelfMs+=o,o>i.maxSelfMs&&(i.maxSelfMs=o),i.causes[a]=(i.causes[a]??0)+1,captureDetail){if(s){i.changedProps||(i.changedProps=new Set);for(let e=0;e<s.length&&!(i.changedProps.size>=5);e++)i.changedProps.add(s[e])}t&&!i.parentName&&(i.parentName=t)}}function handleCommit(e){const t=e?.current;if(!t)return;const n=totalRenders;let r=0,o=t;for(;null!=o&&(r+=1,!(r>1e4));){let e=!1;if(isCompositeTag(o.tag)){const t=getComponentName(o);(0,_devtoolsComponentNames.isRenderLoopUnsafeName)(t)?e=!0:(0,_devtoolsComponentNames.isDevToolsComponentName)(t)||1&getFlags(o)&&recordRender(o,captureDetail?nearestRenderedAncestorName(o):void 0)}if(!e){const e=o.subtreeFlags;if(("number"!=typeof e||1&e||1&getFlags(o))&&null!=o.child){o=o.child;continue}}for(;null!=o;){if(o===t)return finalizeCommit(n);if(null!=o.sibling){o=o.sibling;break}o=o.return}}finalizeCommit(n)}function finalizeCommit(e){totalRenders>e&&(totalCommits+=1)}function ensureHookInstalled(){if(hookInstalled)return!0;const e=getHook();if(!e)return!1;const t=e.onCommitFiberRoot;return e.onCommitFiberRoot=function(n,r,...o){if(active)try{handleCommit(r)}catch{}return"function"==typeof t?t.call(e,n,r,...o):void 0},hookInstalled=!0,!0}function isSupported(){if("undefined"!=typeof __DEV__&&!__DEV__)return!1;const e=getHook();return Boolean(e&&e.renderers&&e.renderers.size>0)}function isCapturing(){return active}function startCapture(e){ensureHookInstalled()&&(buckets=new Map,sawDurations=!1,captureDetail=!0===e?.detail,totalCommits=0,totalRenders=0,totalRenderMs=0,totalCauses={},active=!0)}function stopCapture(){if(!active&&0===totalRenders&&0===buckets.size)return null;active=!1;const e=Array.from(buckets.entries()).sort((e,t)=>{const n=t[1].totalSelfMs-e[1].totalSelfMs;return 0!==n?n:t[1].renders-e[1].renders}).slice(0,20).map(([e,t])=>({name:e,renders:t.renders,totalMs:round2(t.totalSelfMs),maxMs:round2(t.maxSelfMs),avgMs:t.renders>0?round2(t.totalSelfMs/t.renders):0,causes:t.causes,...t.changedProps&&t.changedProps.size>0?{changedProps:Array.from(t.changedProps)}:null,...t.parentName?{parentName:t.parentName}:null})),t={totalCommits:totalCommits,totalRenders:totalRenders,capturedDurations:sawDurations,totalRenderMs:round2(totalRenderMs),wastedRenders:(totalCauses.parent??0)+(totalCauses.propsUnstable??0),causes:totalCauses,topComponents:e};return buckets=new Map,totalCommits=0,totalRenders=0,totalRenderMs=0,totalCauses={},t}function round2(e){return Math.round(100*e)/100}const CommitProfiler=exports.CommitProfiler={isSupported:isSupported,isCapturing:isCapturing,startCapture:startCapture,stopCapture:stopCapture};