bippy 0.6.1-dev.d7876ea → 0.6.1-dev.f9e6c65

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 (50) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +159 -487
  3. package/dist/core.cjs +1 -1
  4. package/dist/core.d.cts +24 -70
  5. package/dist/core.d.ts +24 -70
  6. package/dist/core.js +1 -1
  7. package/dist/core2.cjs +1 -1
  8. package/dist/core2.d.cts +11 -3
  9. package/dist/core2.d.ts +11 -3
  10. package/dist/core2.js +1 -1
  11. package/dist/errors.d.cts +33 -59
  12. package/dist/errors.d.ts +33 -59
  13. package/dist/index.cjs +1 -1
  14. package/dist/index.d.cts +14 -3
  15. package/dist/index.d.ts +14 -3
  16. package/dist/index.js +1 -1
  17. package/dist/install-hook-only.cjs +1 -1
  18. package/dist/install-hook-only.d.cts +8 -0
  19. package/dist/install-hook-only.d.ts +8 -0
  20. package/dist/install-hook-only.js +1 -1
  21. package/dist/rdt-hook.cjs +1 -1
  22. package/dist/rdt-hook.js +1 -1
  23. package/dist/source.cjs +12 -13
  24. package/dist/source.d.cts +85 -77
  25. package/dist/source.d.ts +85 -77
  26. package/dist/source.js +12 -13
  27. package/package.json +6 -3
  28. package/src/core.ts +141 -564
  29. package/src/errors.ts +0 -65
  30. package/src/index.ts +1 -0
  31. package/src/install-hook-only.ts +2 -2
  32. package/src/rdt-hook.ts +39 -45
  33. package/src/{generated/react-work-tags.js → react-internals/generated/react-work-tags.ts} +85 -7
  34. package/src/{react-internals.ts → react-internals/index.ts} +6 -4
  35. package/src/{semver.ts → react-internals/semver.ts} +2 -0
  36. package/src/{types.ts → react-internals/types.ts} +1 -84
  37. package/src/react.ts +76 -0
  38. package/src/source/get-display-name-from-source.ts +44 -40
  39. package/src/source/get-source.ts +42 -26
  40. package/src/source/index.ts +2 -0
  41. package/src/source/inspect-hooks.ts +74 -96
  42. package/src/source/owner-stack.ts +66 -91
  43. package/src/source/parse-hook-names.ts +14 -53
  44. package/src/source/parse-stack.ts +14 -31
  45. package/src/source/renderer-dispatchers.ts +30 -0
  46. package/src/source/symbolication.ts +421 -108
  47. package/dist/index.iife.js +0 -9
  48. package/dist/install-hook-only.iife.js +0 -9
  49. package/src/generated/react-work-tags.d.ts +0 -261
  50. package/src/unsubscribe.ts +0 -17
package/src/core.ts CHANGED
@@ -3,26 +3,23 @@
3
3
  import type * as React from "react";
4
4
 
5
5
  import type {
6
- ContextDependency,
7
6
  Fiber,
8
7
  FiberRoot,
9
8
  HostFiber,
10
- MemoizedState,
11
9
  ReactDevToolsGlobalHook,
12
10
  ReactRenderer,
13
- } from "./types.js";
14
-
15
- import { BippyInstrumentationError, BippyReactDevToolsError, runWithBippyError } from "./errors.js";
11
+ } from "./react-internals/index.js";
16
12
 
17
13
  import {
18
14
  _onActiveListeners,
19
15
  BIPPY_INSTRUMENTATION_STRING,
16
+ createUnsubscribe,
20
17
  getRDTHook,
21
18
  hasRDTHook,
22
19
  isRealReactDevtools,
23
20
  onRDTHookReplace,
24
- onRendererInject,
25
21
  } from "./rdt-hook.js";
22
+ import type { Unsubscribe } from "./rdt-hook.js";
26
23
  import {
27
24
  getReactWorkTagsForFiber,
28
25
  MutationMask,
@@ -30,47 +27,23 @@ import {
30
27
  ReactFiberFlags,
31
28
  ReactSymbols,
32
29
  setReactWorkTagsForFiber,
33
- } from "./react-internals.js";
34
- import { toUnsubscribe, type Unsubscribe } from "./unsubscribe.js";
30
+ } from "./react-internals/index.js";
35
31
 
36
- export { getReactWorkTags, ReactSymbols } from "./react-internals.js";
37
- export type { ReactWorkTagMap, ReactWorkTagVersion } from "./react-internals.js";
38
32
  export {
39
33
  BippyError,
40
- BippyHookInstallationError,
41
34
  BippyHookInspectionError,
42
- BippyHookListenerError,
43
35
  BippyHookRenderError,
44
- BippyInstrumentationError,
45
- BippyReactBuildError,
46
- BippyReactDevToolsError,
47
36
  BippySourceMapError,
48
37
  BippyUnsupportedHookError,
49
38
  } from "./errors.js";
50
39
 
51
- const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
52
- typeof value === "object" && value !== null;
53
-
54
40
  const isComponentType = (value: unknown): value is React.ComponentType<unknown> =>
55
41
  typeof value === "function";
56
42
 
57
- const getPropertyValue = (value: object, key: PropertyKey): unknown => {
58
- let currentValue: object | null = value;
59
- while (currentValue) {
60
- const descriptor = Object.getOwnPropertyDescriptor(currentValue, key);
61
- if (descriptor) {
62
- if ("value" in descriptor) return descriptor.value;
63
- return descriptor.get?.call(value);
64
- }
65
- currentValue = Object.getPrototypeOf(currentValue);
66
- }
67
- return undefined;
68
- };
69
-
70
43
  const getTypeName = (value: object): string | null => {
71
- const displayName = getPropertyValue(value, "displayName");
44
+ const displayName = "displayName" in value ? value.displayName : null;
72
45
  if (typeof displayName === "string" && displayName) return displayName;
73
- const name = getPropertyValue(value, "name");
46
+ const name = "name" in value ? value.name : null;
74
47
  return typeof name === "string" && name ? name : null;
75
48
  };
76
49
 
@@ -78,18 +51,8 @@ interface FiberSelector {
78
51
  (node: Fiber): boolean | Promise<boolean | void> | void;
79
52
  }
80
53
 
81
- export interface FiberTimings {
82
- selfTime: number;
83
- totalTime: number;
84
- }
85
-
86
54
  export interface RenderHandler {
87
- <State>(fiber: Fiber, phase: RenderPhase, state?: State): unknown;
88
- }
89
-
90
- interface ValueWrite {
91
- path: string[];
92
- value: unknown;
55
+ (fiber: Fiber, phase: RenderPhase): unknown;
93
56
  }
94
57
 
95
58
  /**
@@ -101,9 +64,8 @@ export const isValidElement = (element: unknown): element is React.ReactElement
101
64
  typeof element === "object" &&
102
65
  element !== null &&
103
66
  "$$typeof" in element &&
104
- [ReactSymbols.LEGACY_ELEMENT_SYMBOL_STRING, ReactSymbols.ELEMENT_SYMBOL_STRING].includes(
105
- String(element.$$typeof),
106
- );
67
+ (String(element.$$typeof) === ReactSymbols.LEGACY_ELEMENT_SYMBOL_STRING ||
68
+ String(element.$$typeof) === ReactSymbols.ELEMENT_SYMBOL_STRING);
107
69
 
108
70
  /**
109
71
  * Returns `true` if object is a React Fiber.
@@ -118,6 +80,12 @@ export const isValidFiber = (fiber: unknown): fiber is Fiber =>
118
80
  "sibling" in fiber &&
119
81
  "flags" in fiber;
120
82
 
83
+ const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
84
+ typeof fiberRoot === "object" &&
85
+ fiberRoot !== null &&
86
+ "current" in fiberRoot &&
87
+ isValidFiber(fiberRoot.current);
88
+
121
89
  /**
122
90
  * Returns `true` if fiber is a host fiber. Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
123
91
  *
@@ -165,90 +133,6 @@ export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
165
133
  return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
166
134
  };
167
135
 
168
- /**
169
- * Traverses up or down a {@link Fiber}'s contexts, return `true` to stop and select the current and previous context value.
170
- */
171
- export const traverseContexts = (
172
- fiber: Fiber,
173
- selector: (
174
- nextValue: ContextDependency<unknown> | null | undefined,
175
- prevValue: ContextDependency<unknown> | null | undefined,
176
- ) => boolean | void,
177
- ): boolean => {
178
- try {
179
- const nextDependencies = fiber.dependencies;
180
- const prevDependencies = fiber.alternate?.dependencies;
181
-
182
- if (!nextDependencies || !prevDependencies) return false;
183
- if (
184
- typeof nextDependencies !== "object" ||
185
- !("firstContext" in nextDependencies) ||
186
- typeof prevDependencies !== "object" ||
187
- !("firstContext" in prevDependencies)
188
- ) {
189
- return false;
190
- }
191
- let nextContext: ContextDependency<unknown> | null | undefined = nextDependencies.firstContext;
192
- let prevContext: ContextDependency<unknown> | null | undefined = prevDependencies.firstContext;
193
- while (
194
- (nextContext && typeof nextContext === "object" && "memoizedValue" in nextContext) ||
195
- (prevContext && typeof prevContext === "object" && "memoizedValue" in prevContext)
196
- ) {
197
- if (selector(nextContext, prevContext) === true) return true;
198
-
199
- nextContext = nextContext?.next;
200
- prevContext = prevContext?.next;
201
- }
202
- } catch {}
203
- return false;
204
- };
205
-
206
- /**
207
- * Traverses up or down a {@link Fiber}'s states, return `true` to stop and select the current and previous state value. This stores both state values and effects.
208
- */
209
- export const traverseState = (
210
- fiber: Fiber,
211
- selector: (
212
- nextValue: MemoizedState | null | undefined,
213
- prevValue: MemoizedState | null | undefined,
214
- ) => boolean | void,
215
- ): boolean => {
216
- try {
217
- let nextState: MemoizedState | null | undefined = fiber.memoizedState;
218
- let prevState: MemoizedState | null | undefined = fiber.alternate?.memoizedState;
219
-
220
- while (nextState || prevState) {
221
- if (selector(nextState, prevState) === true) return true;
222
-
223
- nextState = nextState?.next;
224
- prevState = prevState?.next;
225
- }
226
- } catch {}
227
- return false;
228
- };
229
-
230
- /**
231
- * Traverses up or down a {@link Fiber}'s props, return `true` to stop and select the current and previous props value.
232
- */
233
- export const traverseProps = (
234
- fiber: Fiber,
235
- selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void,
236
- ): boolean => {
237
- try {
238
- const nextProps = fiber.memoizedProps;
239
- const prevProps = fiber.alternate?.memoizedProps || {};
240
-
241
- for (const propName of Object.keys(nextProps)) {
242
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
243
- }
244
- for (const propName of Object.keys(prevProps)) {
245
- if (propName in nextProps) continue;
246
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
247
- }
248
- } catch {}
249
- return false;
250
- };
251
-
252
136
  /**
253
137
  * Returns `true` if the {@link Fiber} has rendered. Note that this does not mean the fiber has rendered in the current commit, just that it has rendered in the past.
254
138
  */
@@ -288,47 +172,6 @@ export const didFiberCommit = (fiber: Fiber): boolean => {
288
172
  );
289
173
  };
290
174
 
291
- /**
292
- * Returns all host {@link Fiber}s that have committed and rendered.
293
- */
294
- export const getMutatedHostFibers = (fiber: Fiber): Fiber[] => {
295
- const mutations: Fiber[] = [];
296
- const stack: Fiber[] = [fiber];
297
-
298
- while (stack.length) {
299
- const node = stack.pop();
300
- if (!node) continue;
301
-
302
- if (isHostFiber(node) && didFiberCommit(node) && didFiberRender(node)) {
303
- mutations.push(node);
304
- }
305
-
306
- if (node.child) stack.push(node.child);
307
- if (node.sibling) stack.push(node.sibling);
308
- }
309
-
310
- return mutations;
311
- };
312
-
313
- /**
314
- * Returns the stack of {@link Fiber}s from the current fiber to the root fiber.
315
- *
316
- * @example
317
- * ```ts
318
- * [fiber, fiber.return, fiber.return.return, ...]
319
- * ```
320
- */
321
- export const getFiberStack = (fiber: Fiber): Fiber[] => {
322
- const stack: Fiber[] = [];
323
- let currentFiber = fiber;
324
- while (currentFiber.return) {
325
- stack.push(currentFiber);
326
- const parentFiber = currentFiber.return;
327
- currentFiber = parentFiber;
328
- }
329
- return stack;
330
- };
331
-
332
175
  /**
333
176
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
334
177
  */
@@ -377,47 +220,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
377
220
  }
378
221
  };
379
222
 
380
- /**
381
- * Returns the nearest host {@link Fiber} to the current {@link Fiber}.
382
- */
383
- export const getNearestHostFiber = (fiber: Fiber, ascending = false): Fiber | null => {
384
- let hostFiber = traverseFiber(fiber, isHostFiber, ascending);
385
- if (!hostFiber) {
386
- hostFiber = traverseFiber(fiber, isHostFiber, !ascending);
387
- }
388
- return hostFiber;
389
- };
390
-
391
- /**
392
- * Returns all host {@link Fiber}s in the tree that are associated with the current {@link Fiber}.
393
- */
394
- export const getNearestHostFibers = (fiber: Fiber): Fiber[] => {
395
- const hostFibers: Fiber[] = [];
396
- const stack: Fiber[] = [];
397
-
398
- if (isHostFiber(fiber)) {
399
- hostFibers.push(fiber);
400
- } else if (fiber.child) {
401
- stack.push(fiber.child);
402
- }
403
-
404
- while (stack.length) {
405
- const currentNode = stack.pop();
406
- if (!currentNode) break;
407
- if (isHostFiber(currentNode)) {
408
- hostFibers.push(currentNode);
409
- } else if (currentNode.child) {
410
- stack.push(currentNode.child);
411
- }
412
-
413
- if (currentNode.sibling) {
414
- stack.push(currentNode.sibling);
415
- }
416
- }
417
-
418
- return hostFibers;
419
- };
420
-
421
223
  /**
422
224
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
423
225
  */
@@ -431,6 +233,11 @@ export function traverseFiber(
431
233
  selector: (node: Fiber) => Promise<boolean | void>,
432
234
  ascending?: boolean,
433
235
  ): Promise<Fiber | null>;
236
+ export function traverseFiber(
237
+ fiber: Fiber | null,
238
+ selector: FiberSelector,
239
+ ascending?: boolean,
240
+ ): Fiber | null | Promise<Fiber | null>;
434
241
  export function traverseFiber(
435
242
  fiber: Fiber | null,
436
243
  selector: FiberSelector,
@@ -438,89 +245,47 @@ export function traverseFiber(
438
245
  ): Fiber | null | Promise<Fiber | null> {
439
246
  if (!fiber) return null;
440
247
 
441
- const firstResult = selector(fiber);
442
- if (firstResult instanceof Promise) {
443
- return (async () => {
444
- if ((await firstResult) === true) return fiber;
445
-
446
- let child = ascending ? fiber.return : fiber.child;
447
- while (child) {
448
- const match = await traverseFiberAsync(child, selector, ascending);
449
- if (match) return match;
450
- child = ascending ? null : child.sibling;
451
- }
452
- return null;
453
- })();
248
+ const selection = selector(fiber);
249
+ if (isPromiseLike<boolean | void>(selection)) {
250
+ return Promise.resolve(selection).then((didSelectFiber) =>
251
+ didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
252
+ );
454
253
  }
254
+ if (selection === true) return fiber;
455
255
 
456
- if (firstResult === true) return fiber;
457
-
458
- let child = ascending ? fiber.return : fiber.child;
459
- while (child) {
460
- const match = traverseFiberSync(child, selector, ascending);
461
- if (match) return match;
462
- child = ascending ? null : child.sibling;
463
- }
464
- return null;
256
+ return traverseFiberChildren(fiber, selector, ascending);
465
257
  }
466
258
 
467
- const traverseFiberSync = (
468
- fiber: Fiber | null,
469
- selector: FiberSelector,
470
- ascending = false,
471
- ): Fiber | null => {
472
- if (!fiber) return null;
473
- const selection = selector(fiber);
474
- if (selection instanceof Promise) return null;
475
- if (selection === true) return fiber;
476
-
477
- let child = ascending ? fiber.return : fiber.child;
478
- while (child) {
479
- const match = traverseFiberSync(child, selector, ascending);
480
- if (match) return match;
259
+ const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
260
+ (typeof value === "object" || typeof value === "function") &&
261
+ value !== null &&
262
+ "then" in value &&
263
+ typeof value.then === "function";
481
264
 
482
- child = ascending ? null : child.sibling;
483
- }
484
- return null;
265
+ const traverseFiberChildren = (
266
+ fiber: Fiber,
267
+ selector: FiberSelector,
268
+ ascending: boolean,
269
+ ): Fiber | null | Promise<Fiber | null> => {
270
+ const firstChild = ascending ? fiber.return : fiber.child;
271
+ return traverseFiberSiblings(firstChild, selector, ascending);
485
272
  };
486
273
 
487
- const traverseFiberAsync = async (
274
+ const traverseFiberSiblings = (
488
275
  fiber: Fiber | null,
489
276
  selector: FiberSelector,
490
- ascending = false,
491
- ): Promise<Fiber | null> => {
277
+ ascending: boolean,
278
+ ): Fiber | null | Promise<Fiber | null> => {
492
279
  if (!fiber) return null;
493
- if ((await selector(fiber)) === true) return fiber;
494
-
495
- let child = ascending ? fiber.return : fiber.child;
496
- while (child) {
497
- const match = await traverseFiberAsync(child, selector, ascending);
498
- if (match) return match;
499
280
 
500
- child = ascending ? null : child.sibling;
281
+ const nextSibling = ascending ? null : fiber.sibling;
282
+ const match = traverseFiber(fiber, selector, ascending);
283
+ if (isPromiseLike<Fiber | null>(match)) {
284
+ return Promise.resolve(match).then(
285
+ (resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
286
+ );
501
287
  }
502
- return null;
503
- };
504
-
505
- /**
506
- * Returns the timings of the {@link Fiber}.
507
- *
508
- * @example
509
- * ```ts
510
- * const { selfTime, totalTime } = getTimings(fiber);
511
- * console.log(selfTime, totalTime);
512
- * ```
513
- */
514
- export const getTimings = (fiber?: Fiber | null): FiberTimings => {
515
- const totalTime = fiber?.actualDuration ?? 0;
516
- let selfTime = totalTime;
517
- // TODO: calculate a DOM time, which is just host component summed up
518
- let child = fiber?.child ?? null;
519
- while (totalTime > 0 && child !== null) {
520
- selfTime -= child.actualDuration ?? 0;
521
- child = child.sibling;
522
- }
523
- return { selfTime, totalTime };
288
+ return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
524
289
  };
525
290
 
526
291
  /**
@@ -535,8 +300,8 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
535
300
  */
536
301
  export const getType = (type: unknown): null | React.ComponentType<unknown> => {
537
302
  if (isComponentType(type)) return type;
538
- if (!isObjectRecord(type)) return null;
539
- return getType(type.type ?? type.render);
303
+ if (typeof type !== "object" || type === null) return null;
304
+ return getType(Reflect.get(type, "type") ?? Reflect.get(type, "render"));
540
305
  };
541
306
 
542
307
  /**
@@ -576,6 +341,7 @@ export const isInstrumentationActive = (): boolean => {
576
341
  };
577
342
 
578
343
  export const _fiberRoots = new Set<FiberRoot>();
344
+ const rootRendererIds = new WeakMap<FiberRoot, number>();
579
345
 
580
346
  /**
581
347
  * Returns the latest fiber (since it may be double-buffered).
@@ -588,13 +354,32 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
588
354
  }
589
355
  for (const root of _fiberRoots) {
590
356
  const latestFiber = traverseFiber(root.current, (innerFiber) => {
591
- if (innerFiber === fiber) return true;
357
+ if (innerFiber === fiber || innerFiber === alternate) return true;
592
358
  });
593
359
  if (latestFiber) return latestFiber;
594
360
  }
595
361
  return fiber;
596
362
  };
597
363
 
364
+ /**
365
+ * Returns the renderer that owns the {@link Fiber}.
366
+ */
367
+ export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
368
+ if (!hasRDTHook()) return null;
369
+
370
+ let rootFiber = fiber;
371
+ while (rootFiber.return) {
372
+ rootFiber = rootFiber.return;
373
+ }
374
+
375
+ const fiberRoot = rootFiber.stateNode;
376
+ if (!isFiberRoot(fiberRoot)) return null;
377
+
378
+ const rendererID = rootRendererIds.get(fiberRoot);
379
+ if (rendererID === undefined) return null;
380
+ return getRDTHook().renderers.get(rendererID) ?? null;
381
+ };
382
+
598
383
  export type RenderPhase = "mount" | "unmount" | "update";
599
384
 
600
385
  let nextFiberId = 0;
@@ -602,6 +387,9 @@ const fiberIdMap = new WeakMap<Fiber, number>();
602
387
 
603
388
  export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
604
389
  fiberIdMap.set(fiber, fiberId);
390
+ if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
391
+ nextFiberId = fiberId + 1;
392
+ }
605
393
  };
606
394
 
607
395
  export const getFiberId = (fiber: Fiber): number => {
@@ -624,9 +412,7 @@ const mountFiberRecursively = (
624
412
  let fiber: Fiber | null = firstChild;
625
413
 
626
414
  while (fiber !== null) {
627
- if (!fiberIdMap.has(fiber)) {
628
- getFiberId(fiber);
629
- }
415
+ getFiberId(fiber);
630
416
  const shouldIncludeInTree = !shouldFilterFiber(fiber);
631
417
  if (shouldIncludeInTree && didFiberRender(fiber)) {
632
418
  onRender(fiber, "mount");
@@ -662,16 +448,11 @@ const mountFiberRecursively = (
662
448
  const updateFiberRecursively = (
663
449
  onRender: RenderHandler,
664
450
  nextFiber: Fiber,
665
- prevFiber: Fiber,
666
- parentFiber: Fiber | null,
451
+ prevFiber: Fiber | null,
667
452
  ): void => {
668
- if (!fiberIdMap.has(nextFiber)) {
669
- getFiberId(nextFiber);
670
- }
453
+ getFiberId(nextFiber);
671
454
  if (!prevFiber) return;
672
- if (!fiberIdMap.has(prevFiber)) {
673
- getFiberId(prevFiber);
674
- }
455
+ getFiberId(prevFiber);
675
456
 
676
457
  const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
677
458
 
@@ -702,7 +483,7 @@ const updateFiberRecursively = (
702
483
  const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
703
484
 
704
485
  if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
705
- updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet, nextFiber);
486
+ updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
706
487
  }
707
488
  } else if (prevDidTimeout && !nextDidTimeOut) {
708
489
  // Fallback -> Primary:
@@ -741,14 +522,7 @@ const updateFiberRecursively = (
741
522
  // Schedule updates and mounts depending on whether alternates exist.
742
523
  // We don't track deletions here because they are reported separately.
743
524
  if (nextChild.alternate) {
744
- const prevChild = nextChild.alternate;
745
-
746
- updateFiberRecursively(
747
- onRender,
748
- nextChild,
749
- prevChild,
750
- shouldIncludeInTree ? nextFiber : parentFiber,
751
- );
525
+ updateFiberRecursively(onRender, nextChild, nextChild.alternate);
752
526
  } else {
753
527
  mountFiberRecursively(onRender, nextChild, false);
754
528
  }
@@ -794,11 +568,9 @@ const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber):
794
568
  }
795
569
  };
796
570
 
797
- let commitId = 0;
798
571
  const rootInstanceMap = new WeakMap<
799
- FiberRoot,
572
+ Fiber | FiberRoot,
800
573
  {
801
- id: number;
802
574
  prevFiber: Fiber | null;
803
575
  }
804
576
  >();
@@ -810,23 +582,23 @@ const rootInstanceMap = new WeakMap<
810
582
  * console.log(phase)
811
583
  * })
812
584
  */
813
- export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler): void => {
585
+ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
814
586
  const fiber = "current" in root ? root.current : root;
815
587
 
816
588
  let rootInstance = rootInstanceMap.get(root);
817
589
 
818
590
  if (!rootInstance) {
819
- rootInstance = { id: commitId++, prevFiber: null };
591
+ rootInstance = { prevFiber: null };
820
592
  rootInstanceMap.set(root, rootInstance);
821
593
  }
822
594
 
823
595
  const { prevFiber } = rootInstance;
824
- // if fiberRoot don't have current instance, means it's been unmounted
825
596
  if (!fiber) {
826
- unmountFiber(onRender, fiber);
597
+ if (prevFiber) {
598
+ unmountFiber(onRender, prevFiber);
599
+ }
827
600
  } else if (prevFiber !== null) {
828
601
  const wasMounted =
829
- prevFiber &&
830
602
  prevFiber.memoizedState !== null &&
831
603
  prevFiber.memoizedState.element !== null &&
832
604
  prevFiber.memoizedState.element !== undefined &&
@@ -842,7 +614,7 @@ export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler)
842
614
  if (!wasMounted && isMounted) {
843
615
  mountFiberRecursively(onRender, fiber, false);
844
616
  } else if (wasMounted && isMounted) {
845
- updateFiberRecursively(onRender, fiber, fiber.alternate, null);
617
+ updateFiberRecursively(onRender, fiber, fiber.alternate);
846
618
  } else if (wasMounted && !isMounted) {
847
619
  unmountFiber(onRender, fiber);
848
620
  }
@@ -853,164 +625,6 @@ export const traverseRenderedFibers = (root: FiberRoot, onRender: RenderHandler)
853
625
  rootInstance.prevFiber = fiber;
854
626
  };
855
627
 
856
- const overrideRenderers = new Set<ReactRenderer>();
857
- let areOverrideRenderersWired = false;
858
-
859
- const wireOverrideRenderers = (): void => {
860
- if (!hasRDTHook()) return;
861
- const rdtHook = getRDTHook();
862
-
863
- setHookEventDispatchers(rdtHook);
864
- for (const renderer of rdtHook.renderers.values()) {
865
- overrideRenderers.add(renderer);
866
- }
867
- if (areOverrideRenderersWired) return;
868
- areOverrideRenderersWired = true;
869
- onRendererInject((renderer) => {
870
- overrideRenderers.add(renderer);
871
- });
872
- };
873
-
874
- const getRootRenderer = (fiber: Fiber): ReactRenderer | null => {
875
- if (!hasRDTHook()) return null;
876
- let hostRootFiber = fiber;
877
- while (hostRootFiber.return) {
878
- hostRootFiber = hostRootFiber.return;
879
- }
880
- const rendererId = rootRendererIds.get(hostRootFiber.stateNode);
881
- if (rendererId === undefined) return null;
882
- return getRDTHook().renderers.get(rendererId) ?? null;
883
- };
884
-
885
- const resolveOverrideRenderers = (fiber: Fiber): ReactRenderer[] => {
886
- wireOverrideRenderers();
887
- const rootRenderer = getRootRenderer(fiber);
888
- if (rootRenderer) return [rootRenderer];
889
- return Array.from(overrideRenderers);
890
- };
891
-
892
- const applyPropsOverride = (
893
- renderers: ReactRenderer[],
894
- fiber: Fiber,
895
- path: string[],
896
- value: unknown,
897
- ): void => {
898
- for (const renderer of renderers) {
899
- try {
900
- renderer.overrideProps?.(fiber, path, value);
901
- } catch {}
902
- }
903
- };
904
-
905
- const getHookStateDispatch = (
906
- fiber: Fiber,
907
- hookIndex: number,
908
- ): ((value: unknown) => void) | null => {
909
- let hookState = fiber.memoizedState;
910
- for (let currentHookIndex = 0; currentHookIndex < hookIndex; currentHookIndex++) {
911
- if (!hookState?.next) return null;
912
- hookState = hookState.next;
913
- }
914
- const queue = hookState?.queue;
915
- if (!isPOJO(queue)) return null;
916
- const dispatch = queue.dispatch;
917
- return typeof dispatch === "function" ? (value) => dispatch(value) : null;
918
- };
919
-
920
- const findContextProviderFiber = (fiber: Fiber, contextType: unknown): Fiber | null => {
921
- let currentFiber: Fiber | null = fiber;
922
- while (currentFiber) {
923
- const fiberType = currentFiber.type;
924
- if (fiberType === contextType || fiberType?.Provider === contextType) {
925
- return currentFiber;
926
- }
927
- currentFiber = currentFiber.return;
928
- }
929
- return null;
930
- };
931
-
932
- const isPOJO = (maybePOJO: unknown): maybePOJO is Record<string, unknown> => {
933
- return (
934
- Object.prototype.toString.call(maybePOJO) === "[object Object]" &&
935
- (Object.getPrototypeOf(maybePOJO) === Object.prototype ||
936
- Object.getPrototypeOf(maybePOJO) === null)
937
- );
938
- };
939
-
940
- const buildPathsFromValue = (
941
- maybePOJO: Record<string, unknown>,
942
- basePath: string[] = [],
943
- ): ValueWrite[] => {
944
- if (!isPOJO(maybePOJO)) {
945
- return [{ path: basePath, value: maybePOJO }];
946
- }
947
-
948
- const paths: ValueWrite[] = [];
949
-
950
- for (const key in maybePOJO) {
951
- const value = maybePOJO[key];
952
- const path = basePath.concat(key);
953
-
954
- if (isPOJO(value)) {
955
- paths.push(...buildPathsFromValue(value, path));
956
- } else {
957
- paths.push({ path, value });
958
- }
959
- }
960
-
961
- return paths;
962
- };
963
-
964
- const buildValueWrites = (partialValue: unknown): ValueWrite[] =>
965
- isPOJO(partialValue) ? buildPathsFromValue(partialValue) : [{ path: [], value: partialValue }];
966
-
967
- export const overrideProps = (fiber: Fiber, partialValue: Record<string, unknown>) => {
968
- const renderers = resolveOverrideRenderers(fiber);
969
- for (const { path, value } of buildValueWrites(partialValue)) {
970
- applyPropsOverride(renderers, fiber, path, value);
971
- }
972
- };
973
-
974
- export const overrideHookState = (fiber: Fiber, id: number, partialValue: unknown) => {
975
- const renderers = resolveOverrideRenderers(fiber).filter((renderer) =>
976
- Boolean(renderer.overrideHookState),
977
- );
978
- const writes = buildValueWrites(partialValue);
979
-
980
- if (renderers.length > 0) {
981
- for (const renderer of renderers) {
982
- for (const { path, value } of writes) {
983
- try {
984
- renderer.overrideHookState?.(fiber, id, path, value);
985
- } catch {}
986
- }
987
- }
988
- return;
989
- }
990
-
991
- // production renderers don't expose overrideHookState; dispatching through
992
- // the hook's own queue still works there, but only for whole-value writes
993
- // (a path write through dispatch would replace the entire hook state)
994
- if (isPOJO(partialValue)) return;
995
- const dispatch = getHookStateDispatch(fiber, id);
996
- if (!dispatch) return;
997
- try {
998
- dispatch(partialValue);
999
- } catch {}
1000
- };
1001
-
1002
- export const overrideContext = (fiber: Fiber, contextType: unknown, partialValue: unknown) => {
1003
- const providerFiber = findContextProviderFiber(fiber, contextType);
1004
- if (!providerFiber) return;
1005
- const renderers = resolveOverrideRenderers(providerFiber);
1006
- for (const { path, value } of buildValueWrites(partialValue)) {
1007
- applyPropsOverride(renderers, providerFiber, ["value", ...path], value);
1008
- if (providerFiber.alternate) {
1009
- applyPropsOverride(renderers, providerFiber.alternate, ["value", ...path], value);
1010
- }
1011
- }
1012
- };
1013
-
1014
628
  export interface InstrumentationOptions {
1015
629
  name?: string;
1016
630
  onActive?: () => unknown;
@@ -1031,19 +645,6 @@ interface InstrumentationSubscription {
1031
645
 
1032
646
  const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
1033
647
 
1034
- const runInstrumentationCallback = (
1035
- instrumentationName: string,
1036
- callbackName: string,
1037
- callback: () => unknown,
1038
- ): void =>
1039
- runWithBippyError(
1040
- callback,
1041
- (cause) => new BippyInstrumentationError(instrumentationName, callbackName, cause),
1042
- );
1043
-
1044
- const runReactDevToolsCallback = (callbackName: string, callback: () => unknown): void =>
1045
- runWithBippyError(callback, (cause) => new BippyReactDevToolsError(callbackName, cause));
1046
-
1047
648
  interface HookDispatchers {
1048
649
  onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
1049
650
  onCommitFiberUnmount: ReactDevToolsGlobalHook["onCommitFiberUnmount"];
@@ -1054,8 +655,6 @@ interface HookDispatchers {
1054
655
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
1055
656
  let didSubscribeToHookReplacements = false;
1056
657
 
1057
- const rootRendererIds = new WeakMap<FiberRoot, number>();
1058
-
1059
658
  // each hook event is dispatched from a single re-installable wrapper. If
1060
659
  // something overwrites the hook method (devtools, direct assignment), the
1061
660
  // next instrument() call installs a fresh wrapper over it; a superseded
@@ -1077,9 +676,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1077
676
  didError,
1078
677
  ) => {
1079
678
  if (prevOnCommitFiberRoot) {
1080
- runReactDevToolsCallback("onCommitFiberRoot", () =>
1081
- prevOnCommitFiberRoot(rendererID, root, priority, didError),
1082
- );
679
+ prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
1083
680
  }
1084
681
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
1085
682
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
@@ -1097,11 +694,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1097
694
  }
1098
695
  for (const { options } of instrumentationSubscriptions) {
1099
696
  if (options.onCommitFiberRoot) {
1100
- runInstrumentationCallback(
1101
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1102
- "onCommitFiberRoot",
1103
- () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
1104
- );
697
+ options.onCommitFiberRoot(rendererID, root, priority, didError);
1105
698
  }
1106
699
  }
1107
700
  };
@@ -1120,20 +713,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1120
713
  ) => {
1121
714
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
1122
715
  if (prevOnCommitFiberUnmount) {
1123
- runReactDevToolsCallback("onCommitFiberUnmount", () =>
1124
- prevOnCommitFiberUnmount(rendererID, fiber),
1125
- );
716
+ prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
1126
717
  }
1127
718
  if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
1128
719
  return;
1129
720
  }
1130
721
  for (const { options } of instrumentationSubscriptions) {
1131
722
  if (options.onCommitFiberUnmount) {
1132
- runInstrumentationCallback(
1133
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1134
- "onCommitFiberUnmount",
1135
- () => options.onCommitFiberUnmount?.(rendererID, fiber),
1136
- );
723
+ options.onCommitFiberUnmount(rendererID, fiber);
1137
724
  }
1138
725
  }
1139
726
  };
@@ -1151,20 +738,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1151
738
  root,
1152
739
  ) => {
1153
740
  if (prevOnPostCommitFiberRoot) {
1154
- runReactDevToolsCallback("onPostCommitFiberRoot", () =>
1155
- prevOnPostCommitFiberRoot(rendererID, root),
1156
- );
741
+ prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
1157
742
  }
1158
743
  if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
1159
744
  return;
1160
745
  }
1161
746
  for (const { options } of instrumentationSubscriptions) {
1162
747
  if (options.onPostCommitFiberRoot) {
1163
- runInstrumentationCallback(
1164
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1165
- "onPostCommitFiberRoot",
1166
- () => options.onPostCommitFiberRoot?.(rendererID, root),
1167
- );
748
+ options.onPostCommitFiberRoot(rendererID, root);
1168
749
  }
1169
750
  }
1170
751
  };
@@ -1183,18 +764,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1183
764
  children,
1184
765
  ) => {
1185
766
  if (prevOnScheduleFiberRoot) {
1186
- runReactDevToolsCallback("onScheduleFiberRoot", () =>
1187
- prevOnScheduleFiberRoot(rendererID, root, children),
1188
- );
767
+ prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
1189
768
  }
1190
769
  if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
1191
770
  for (const { options } of instrumentationSubscriptions) {
1192
771
  if (options.onScheduleFiberRoot) {
1193
- runInstrumentationCallback(
1194
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1195
- "onScheduleFiberRoot",
1196
- () => options.onScheduleFiberRoot?.(rendererID, root, children),
1197
- );
772
+ options.onScheduleFiberRoot(rendererID, root, children);
1198
773
  }
1199
774
  }
1200
775
  };
@@ -1203,13 +778,24 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1203
778
  }
1204
779
  };
1205
780
 
781
+ const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
782
+ if (!didSubscribeToHookReplacements) {
783
+ onRDTHookReplace(setHookEventDispatchers);
784
+ didSubscribeToHookReplacements = true;
785
+ }
786
+ setHookEventDispatchers(rdtHook);
787
+ };
788
+
789
+ if (hasRDTHook()) {
790
+ wireHookEventDispatchers(getRDTHook());
791
+ }
792
+
1206
793
  /**
1207
794
  * Instruments the DevTools hook. Each hook event is patched once and
1208
795
  * dispatches to a set of listeners, so multiple `instrument` calls compose
1209
796
  * without stacking patches. Returns an unsubscribe function that removes
1210
797
  * exactly the handlers this call registered.
1211
- * The returned function is also a `Disposable`, so it composes with other
1212
- * bippy subscriptions through `using`.
798
+ * The unsubscribe function can also be disposed through `using`.
1213
799
  * @example
1214
800
  * const unsubscribe = instrument({
1215
801
  * onActive() {
@@ -1222,30 +808,15 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1222
808
  * unsubscribe();
1223
809
  */
1224
810
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1225
- const onActiveListener = options.onActive;
1226
- const onActive = onActiveListener
1227
- ? () =>
1228
- runInstrumentationCallback(
1229
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1230
- "onActive",
1231
- onActiveListener,
1232
- )
1233
- : undefined;
1234
- const rdtHook = getRDTHook(onActive);
1235
-
1236
- if (!didSubscribeToHookReplacements) {
1237
- onRDTHookReplace(setHookEventDispatchers);
1238
- didSubscribeToHookReplacements = true;
1239
- }
1240
-
811
+ const rdtHook = getRDTHook(options.onActive);
1241
812
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1242
813
 
1243
- setHookEventDispatchers(rdtHook);
814
+ wireHookEventDispatchers(rdtHook);
1244
815
  const subscription: InstrumentationSubscription = { options };
1245
816
  instrumentationSubscriptions.add(subscription);
1246
817
 
1247
- return toUnsubscribe(() => {
1248
- if (onActive) _onActiveListeners.delete(onActive);
818
+ return createUnsubscribe(() => {
819
+ if (options.onActive) _onActiveListeners.delete(options.onActive);
1249
820
  instrumentationSubscriptions.delete(subscription);
1250
821
  });
1251
822
  };
@@ -1258,33 +829,38 @@ const isFiberPropertyKey = (key: string): boolean =>
1258
829
  key.startsWith("__reactInternalInstance$") ||
1259
830
  key.startsWith("__reactFiber");
1260
831
 
1261
- const getLegacyRootFiber = (hostInstance: Record<string, unknown>): Fiber | null => {
1262
- const reactRootContainer = hostInstance._reactRootContainer;
1263
- if (!isObjectRecord(reactRootContainer)) return null;
1264
- const internalRoot = reactRootContainer._internalRoot;
1265
- if (!isObjectRecord(internalRoot)) return null;
1266
- const current = internalRoot.current;
1267
- if (!isObjectRecord(current)) return null;
1268
- const child = current.child;
832
+ const getLegacyRootFiber = (hostInstance: object): Fiber | null => {
833
+ const reactRootContainer = Reflect.get(hostInstance, "_reactRootContainer");
834
+ if (typeof reactRootContainer !== "object" || reactRootContainer === null) return null;
835
+ const internalRoot = Reflect.get(reactRootContainer, "_internalRoot");
836
+ if (typeof internalRoot !== "object" || internalRoot === null) return null;
837
+ const current = Reflect.get(internalRoot, "current");
838
+ if (typeof current !== "object" || current === null) return null;
839
+ const child = Reflect.get(current, "child");
1269
840
  return isValidFiber(child) ? child : null;
1270
841
  };
1271
842
 
1272
- const getInternalInstanceHandle = (hostInstance: Record<string, unknown>): Fiber | null => {
843
+ const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
1273
844
  const internalInstanceHandle =
1274
- hostInstance.__internalInstanceHandle ?? hostInstance._internalInstanceHandle;
845
+ Reflect.get(hostInstance, "__internalInstanceHandle") ??
846
+ Reflect.get(hostInstance, "_internalInstanceHandle");
1275
847
  return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
1276
848
  };
1277
849
 
1278
850
  const getPublicHostInstance = (stateNode: unknown): unknown => {
1279
- if (!isObjectRecord(stateNode)) return stateNode;
1280
- const canonical = stateNode.canonical;
1281
- if (isObjectRecord(canonical) && isObjectRecord(canonical.publicInstance)) {
1282
- return canonical.publicInstance;
851
+ if (typeof stateNode !== "object" || stateNode === null) return stateNode;
852
+ const canonical = Reflect.get(stateNode, "canonical");
853
+ if (typeof canonical === "object" && canonical !== null) {
854
+ const publicInstance = Reflect.get(canonical, "publicInstance");
855
+ if (typeof publicInstance === "object" && publicInstance !== null) {
856
+ return publicInstance;
857
+ }
1283
858
  }
1284
- return typeof stateNode._nativeTag === "number" ? stateNode._nativeTag : stateNode;
859
+ const nativeTag = Reflect.get(stateNode, "_nativeTag");
860
+ return typeof nativeTag === "number" ? nativeTag : stateNode;
1285
861
  };
1286
862
 
1287
- export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
863
+ export const getFiber = <T>(hostInstance: T): Fiber | null => {
1288
864
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1289
865
  if (rdtHook?.renderers) {
1290
866
  for (const renderer of rdtHook.renderers.values()) {
@@ -1295,7 +871,7 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1295
871
  }
1296
872
  }
1297
873
 
1298
- if (isObjectRecord(hostInstance)) {
874
+ if (typeof hostInstance === "object" && hostInstance !== null) {
1299
875
  const legacyRootFiber = getLegacyRootFiber(hostInstance);
1300
876
  if (legacyRootFiber) return legacyRootFiber;
1301
877
 
@@ -1303,14 +879,14 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1303
879
  if (internalInstanceHandle) return internalInstanceHandle;
1304
880
 
1305
881
  for (const knownKey of knownFiberPropertyKeys) {
1306
- const fiber = hostInstance[knownKey];
882
+ const fiber = Reflect.get(hostInstance, knownKey);
1307
883
  if (isValidFiber(fiber)) return fiber;
1308
884
  }
1309
885
 
1310
886
  for (const key of Object.keys(hostInstance)) {
1311
887
  if (isFiberPropertyKey(key)) {
1312
888
  knownFiberPropertyKeys.add(key);
1313
- const fiber = hostInstance[key];
889
+ const fiber = Reflect.get(hostInstance, key);
1314
890
  if (isValidFiber(fiber)) return fiber;
1315
891
  }
1316
892
  }
@@ -1334,6 +910,8 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1334
910
  return null;
1335
911
  };
1336
912
 
913
+ export const getFiberFromHostInstance = getFiber;
914
+
1337
915
  export {
1338
916
  BIPPY_INSTRUMENTATION_STRING,
1339
917
  _onActiveListeners,
@@ -1344,8 +922,7 @@ export {
1344
922
  isRealReactDevtools,
1345
923
  onRendererInject,
1346
924
  patchRDTHook,
1347
- safelyInstallRDTHook,
1348
925
  version,
1349
926
  } from "./rdt-hook.js";
1350
- export * from "./unsubscribe.js";
1351
- export type * from "./types.js";
927
+ export type { Unsubscribe } from "./rdt-hook.js";
928
+ export * from "./react-internals/index.js";