bippy 0.6.1-dev.b88fcb4 → 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 (45) hide show
  1. package/README.md +159 -487
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +8 -54
  4. package/dist/core.d.ts +8 -54
  5. package/dist/core.js +1 -1
  6. package/dist/core2.cjs +1 -1
  7. package/dist/core2.d.cts +3 -3
  8. package/dist/core2.d.ts +3 -3
  9. package/dist/core2.js +1 -1
  10. package/dist/errors.d.cts +2 -57
  11. package/dist/errors.d.ts +2 -57
  12. package/dist/index.cjs +1 -1
  13. package/dist/index.d.cts +6 -3
  14. package/dist/index.d.ts +6 -3
  15. package/dist/index.js +1 -1
  16. package/dist/install-hook-only.cjs +1 -1
  17. package/dist/install-hook-only.js +1 -1
  18. package/dist/rdt-hook.cjs +1 -1
  19. package/dist/rdt-hook.js +1 -1
  20. package/dist/source.cjs +12 -13
  21. package/dist/source.d.cts +77 -78
  22. package/dist/source.d.ts +77 -78
  23. package/dist/source.js +12 -13
  24. package/package.json +6 -3
  25. package/src/core.ts +101 -530
  26. package/src/errors.ts +0 -65
  27. package/src/index.ts +1 -0
  28. package/src/install-hook-only.ts +2 -2
  29. package/src/rdt-hook.ts +30 -43
  30. package/src/react-internals/generated/react-work-tags.ts +6 -2
  31. package/src/react-internals/index.ts +4 -4
  32. package/src/react-internals/semver.ts +2 -0
  33. package/src/react-internals/types.ts +0 -83
  34. package/src/react.ts +76 -0
  35. package/src/source/get-display-name-from-source.ts +44 -40
  36. package/src/source/get-source.ts +40 -24
  37. package/src/source/index.ts +2 -0
  38. package/src/source/inspect-hooks.ts +70 -91
  39. package/src/source/owner-stack.ts +60 -89
  40. package/src/source/parse-hook-names.ts +14 -53
  41. package/src/source/parse-stack.ts +14 -31
  42. package/src/source/renderer-dispatchers.ts +30 -0
  43. package/src/source/symbolication.ts +312 -111
  44. package/dist/index.iife.js +0 -9
  45. package/dist/install-hook-only.iife.js +0 -9
package/src/core.ts CHANGED
@@ -3,17 +3,13 @@
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
11
  } from "./react-internals/index.js";
14
12
 
15
- import { BippyInstrumentationError, BippyReactDevToolsError, runWithBippyError } from "./errors.js";
16
-
17
13
  import {
18
14
  _onActiveListeners,
19
15
  BIPPY_INSTRUMENTATION_STRING,
@@ -22,7 +18,6 @@ import {
22
18
  hasRDTHook,
23
19
  isRealReactDevtools,
24
20
  onRDTHookReplace,
25
- onRendererInject,
26
21
  } from "./rdt-hook.js";
27
22
  import type { Unsubscribe } from "./rdt-hook.js";
28
23
  import {
@@ -34,44 +29,21 @@ import {
34
29
  setReactWorkTagsForFiber,
35
30
  } from "./react-internals/index.js";
36
31
 
37
- export { getReactWorkTags, ReactSymbols } from "./react-internals/index.js";
38
- export type { ReactWorkTagMap, ReactWorkTagVersion } from "./react-internals/index.js";
39
32
  export {
40
33
  BippyError,
41
- BippyHookInstallationError,
42
34
  BippyHookInspectionError,
43
- BippyHookListenerError,
44
35
  BippyHookRenderError,
45
- BippyInstrumentationError,
46
- BippyReactBuildError,
47
- BippyReactDevToolsError,
48
36
  BippySourceMapError,
49
37
  BippyUnsupportedHookError,
50
38
  } from "./errors.js";
51
39
 
52
- const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
53
- typeof value === "object" && value !== null;
54
-
55
40
  const isComponentType = (value: unknown): value is React.ComponentType<unknown> =>
56
41
  typeof value === "function";
57
42
 
58
- const getPropertyValue = (value: object, key: PropertyKey): unknown => {
59
- let currentValue: object | null = value;
60
- while (currentValue) {
61
- const descriptor = Object.getOwnPropertyDescriptor(currentValue, key);
62
- if (descriptor) {
63
- if ("value" in descriptor) return descriptor.value;
64
- return descriptor.get?.call(value);
65
- }
66
- currentValue = Object.getPrototypeOf(currentValue);
67
- }
68
- return undefined;
69
- };
70
-
71
43
  const getTypeName = (value: object): string | null => {
72
- const displayName = getPropertyValue(value, "displayName");
44
+ const displayName = "displayName" in value ? value.displayName : null;
73
45
  if (typeof displayName === "string" && displayName) return displayName;
74
- const name = getPropertyValue(value, "name");
46
+ const name = "name" in value ? value.name : null;
75
47
  return typeof name === "string" && name ? name : null;
76
48
  };
77
49
 
@@ -79,18 +51,8 @@ interface FiberSelector {
79
51
  (node: Fiber): boolean | Promise<boolean | void> | void;
80
52
  }
81
53
 
82
- export interface FiberTimings {
83
- selfTime: number;
84
- totalTime: number;
85
- }
86
-
87
54
  export interface RenderHandler {
88
- <State>(fiber: Fiber, phase: RenderPhase, state?: State): unknown;
89
- }
90
-
91
- interface ValueWrite {
92
- path: string[];
93
- value: unknown;
55
+ (fiber: Fiber, phase: RenderPhase): unknown;
94
56
  }
95
57
 
96
58
  /**
@@ -119,7 +81,10 @@ export const isValidFiber = (fiber: unknown): fiber is Fiber =>
119
81
  "flags" in fiber;
120
82
 
121
83
  const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
122
- isObjectRecord(fiberRoot) && isValidFiber(fiberRoot.current);
84
+ typeof fiberRoot === "object" &&
85
+ fiberRoot !== null &&
86
+ "current" in fiberRoot &&
87
+ isValidFiber(fiberRoot.current);
123
88
 
124
89
  /**
125
90
  * Returns `true` if fiber is a host fiber. Host fibers are DOM nodes in react-dom, `View` in react-native, etc.
@@ -168,90 +133,6 @@ export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
168
133
  return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
169
134
  };
170
135
 
171
- /**
172
- * Traverses up or down a {@link Fiber}'s contexts, return `true` to stop and select the current and previous context value.
173
- */
174
- export const traverseContexts = (
175
- fiber: Fiber,
176
- selector: (
177
- nextValue: ContextDependency<unknown> | null | undefined,
178
- prevValue: ContextDependency<unknown> | null | undefined,
179
- ) => boolean | void,
180
- ): boolean => {
181
- try {
182
- const nextDependencies = fiber.dependencies;
183
- const prevDependencies = fiber.alternate?.dependencies;
184
-
185
- if (!nextDependencies || !prevDependencies) return false;
186
- if (
187
- typeof nextDependencies !== "object" ||
188
- !("firstContext" in nextDependencies) ||
189
- typeof prevDependencies !== "object" ||
190
- !("firstContext" in prevDependencies)
191
- ) {
192
- return false;
193
- }
194
- let nextContext: ContextDependency<unknown> | null | undefined = nextDependencies.firstContext;
195
- let prevContext: ContextDependency<unknown> | null | undefined = prevDependencies.firstContext;
196
- while (
197
- (nextContext && typeof nextContext === "object" && "memoizedValue" in nextContext) ||
198
- (prevContext && typeof prevContext === "object" && "memoizedValue" in prevContext)
199
- ) {
200
- if (selector(nextContext, prevContext) === true) return true;
201
-
202
- nextContext = nextContext?.next;
203
- prevContext = prevContext?.next;
204
- }
205
- } catch {}
206
- return false;
207
- };
208
-
209
- /**
210
- * 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.
211
- */
212
- export const traverseState = (
213
- fiber: Fiber,
214
- selector: (
215
- nextValue: MemoizedState | null | undefined,
216
- prevValue: MemoizedState | null | undefined,
217
- ) => boolean | void,
218
- ): boolean => {
219
- try {
220
- let nextState: MemoizedState | null | undefined = fiber.memoizedState;
221
- let prevState: MemoizedState | null | undefined = fiber.alternate?.memoizedState;
222
-
223
- while (nextState || prevState) {
224
- if (selector(nextState, prevState) === true) return true;
225
-
226
- nextState = nextState?.next;
227
- prevState = prevState?.next;
228
- }
229
- } catch {}
230
- return false;
231
- };
232
-
233
- /**
234
- * Traverses up or down a {@link Fiber}'s props, return `true` to stop and select the current and previous props value.
235
- */
236
- export const traverseProps = (
237
- fiber: Fiber,
238
- selector: (propName: string, nextValue: unknown, prevValue: unknown) => boolean | void,
239
- ): boolean => {
240
- try {
241
- const nextProps = fiber.memoizedProps;
242
- const prevProps = fiber.alternate?.memoizedProps || {};
243
-
244
- for (const propName of Object.keys(nextProps)) {
245
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
246
- }
247
- for (const propName of Object.keys(prevProps)) {
248
- if (propName in nextProps) continue;
249
- if (selector(propName, nextProps[propName], prevProps[propName]) === true) return true;
250
- }
251
- } catch {}
252
- return false;
253
- };
254
-
255
136
  /**
256
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.
257
138
  */
@@ -291,47 +172,6 @@ export const didFiberCommit = (fiber: Fiber): boolean => {
291
172
  );
292
173
  };
293
174
 
294
- /**
295
- * Returns all host {@link Fiber}s that have committed and rendered.
296
- */
297
- export const getMutatedHostFibers = (fiber: Fiber): Fiber[] => {
298
- const mutations: Fiber[] = [];
299
- const stack: Fiber[] = [fiber];
300
-
301
- while (stack.length) {
302
- const node = stack.pop();
303
- if (!node) continue;
304
-
305
- if (isHostFiber(node) && didFiberCommit(node) && didFiberRender(node)) {
306
- mutations.push(node);
307
- }
308
-
309
- if (node.child) stack.push(node.child);
310
- if (node.sibling) stack.push(node.sibling);
311
- }
312
-
313
- return mutations;
314
- };
315
-
316
- /**
317
- * Returns the stack of {@link Fiber}s from the current fiber to the root fiber.
318
- *
319
- * @example
320
- * ```ts
321
- * [fiber, fiber.return, fiber.return.return, ...]
322
- * ```
323
- */
324
- export const getFiberStack = (fiber: Fiber): Fiber[] => {
325
- const stack: Fiber[] = [];
326
- let currentFiber = fiber;
327
- while (currentFiber.return) {
328
- stack.push(currentFiber);
329
- const parentFiber = currentFiber.return;
330
- currentFiber = parentFiber;
331
- }
332
- return stack;
333
- };
334
-
335
175
  /**
336
176
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
337
177
  */
@@ -380,47 +220,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
380
220
  }
381
221
  };
382
222
 
383
- /**
384
- * Returns the nearest host {@link Fiber} to the current {@link Fiber}.
385
- */
386
- export const getNearestHostFiber = (fiber: Fiber, ascending = false): Fiber | null => {
387
- let hostFiber = traverseFiber(fiber, isHostFiber, ascending);
388
- if (!hostFiber) {
389
- hostFiber = traverseFiber(fiber, isHostFiber, !ascending);
390
- }
391
- return hostFiber;
392
- };
393
-
394
- /**
395
- * Returns all host {@link Fiber}s in the tree that are associated with the current {@link Fiber}.
396
- */
397
- export const getNearestHostFibers = (fiber: Fiber): Fiber[] => {
398
- const hostFibers: Fiber[] = [];
399
- const stack: Fiber[] = [];
400
-
401
- if (isHostFiber(fiber)) {
402
- hostFibers.push(fiber);
403
- } else if (fiber.child) {
404
- stack.push(fiber.child);
405
- }
406
-
407
- while (stack.length) {
408
- const currentNode = stack.pop();
409
- if (!currentNode) break;
410
- if (isHostFiber(currentNode)) {
411
- hostFibers.push(currentNode);
412
- } else if (currentNode.child) {
413
- stack.push(currentNode.child);
414
- }
415
-
416
- if (currentNode.sibling) {
417
- stack.push(currentNode.sibling);
418
- }
419
- }
420
-
421
- return hostFibers;
422
- };
423
-
424
223
  /**
425
224
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
426
225
  */
@@ -444,7 +243,17 @@ export function traverseFiber(
444
243
  selector: FiberSelector,
445
244
  ascending = false,
446
245
  ): Fiber | null | Promise<Fiber | null> {
447
- return traverseFiberInternal(fiber, selector, ascending);
246
+ if (!fiber) return null;
247
+
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
+ );
253
+ }
254
+ if (selection === true) return fiber;
255
+
256
+ return traverseFiberChildren(fiber, selector, ascending);
448
257
  }
449
258
 
450
259
  const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
@@ -470,7 +279,7 @@ const traverseFiberSiblings = (
470
279
  if (!fiber) return null;
471
280
 
472
281
  const nextSibling = ascending ? null : fiber.sibling;
473
- const match = traverseFiberInternal(fiber, selector, ascending);
282
+ const match = traverseFiber(fiber, selector, ascending);
474
283
  if (isPromiseLike<Fiber | null>(match)) {
475
284
  return Promise.resolve(match).then(
476
285
  (resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
@@ -479,45 +288,6 @@ const traverseFiberSiblings = (
479
288
  return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
480
289
  };
481
290
 
482
- const traverseFiberInternal = (
483
- fiber: Fiber | null,
484
- selector: FiberSelector,
485
- ascending: boolean,
486
- ): Fiber | null | Promise<Fiber | null> => {
487
- if (!fiber) return null;
488
-
489
- const selection = selector(fiber);
490
- if (isPromiseLike<boolean | void>(selection)) {
491
- return Promise.resolve(selection).then((didSelectFiber) =>
492
- didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
493
- );
494
- }
495
- if (selection === true) return fiber;
496
-
497
- return traverseFiberChildren(fiber, selector, ascending);
498
- };
499
-
500
- /**
501
- * Returns the timings of the {@link Fiber}.
502
- *
503
- * @example
504
- * ```ts
505
- * const { selfTime, totalTime } = getTimings(fiber);
506
- * console.log(selfTime, totalTime);
507
- * ```
508
- */
509
- export const getTimings = (fiber?: Fiber | null): FiberTimings => {
510
- const totalTime = fiber?.actualDuration ?? 0;
511
- let selfTime = totalTime;
512
- // TODO: calculate a DOM time, which is just host component summed up
513
- let child = fiber?.child ?? null;
514
- while (totalTime > 0 && child !== null) {
515
- selfTime -= child.actualDuration ?? 0;
516
- child = child.sibling;
517
- }
518
- return { selfTime, totalTime };
519
- };
520
-
521
291
  /**
522
292
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
523
293
  */
@@ -530,8 +300,8 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
530
300
  */
531
301
  export const getType = (type: unknown): null | React.ComponentType<unknown> => {
532
302
  if (isComponentType(type)) return type;
533
- if (!isObjectRecord(type)) return null;
534
- 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"));
535
305
  };
536
306
 
537
307
  /**
@@ -571,6 +341,7 @@ export const isInstrumentationActive = (): boolean => {
571
341
  };
572
342
 
573
343
  export const _fiberRoots = new Set<FiberRoot>();
344
+ const rootRendererIds = new WeakMap<FiberRoot, number>();
574
345
 
575
346
  /**
576
347
  * Returns the latest fiber (since it may be double-buffered).
@@ -590,6 +361,25 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
590
361
  return fiber;
591
362
  };
592
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
+
593
383
  export type RenderPhase = "mount" | "unmount" | "update";
594
384
 
595
385
  let nextFiberId = 0;
@@ -622,9 +412,7 @@ const mountFiberRecursively = (
622
412
  let fiber: Fiber | null = firstChild;
623
413
 
624
414
  while (fiber !== null) {
625
- if (!fiberIdMap.has(fiber)) {
626
- getFiberId(fiber);
627
- }
415
+ getFiberId(fiber);
628
416
  const shouldIncludeInTree = !shouldFilterFiber(fiber);
629
417
  if (shouldIncludeInTree && didFiberRender(fiber)) {
630
418
  onRender(fiber, "mount");
@@ -661,15 +449,10 @@ const updateFiberRecursively = (
661
449
  onRender: RenderHandler,
662
450
  nextFiber: Fiber,
663
451
  prevFiber: Fiber | null,
664
- parentFiber: Fiber | null,
665
452
  ): void => {
666
- if (!fiberIdMap.has(nextFiber)) {
667
- getFiberId(nextFiber);
668
- }
453
+ getFiberId(nextFiber);
669
454
  if (!prevFiber) return;
670
- if (!fiberIdMap.has(prevFiber)) {
671
- getFiberId(prevFiber);
672
- }
455
+ getFiberId(prevFiber);
673
456
 
674
457
  const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
675
458
 
@@ -700,7 +483,7 @@ const updateFiberRecursively = (
700
483
  const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
701
484
 
702
485
  if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
703
- updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet, nextFiber);
486
+ updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
704
487
  }
705
488
  } else if (prevDidTimeout && !nextDidTimeOut) {
706
489
  // Fallback -> Primary:
@@ -739,14 +522,7 @@ const updateFiberRecursively = (
739
522
  // Schedule updates and mounts depending on whether alternates exist.
740
523
  // We don't track deletions here because they are reported separately.
741
524
  if (nextChild.alternate) {
742
- const prevChild = nextChild.alternate;
743
-
744
- updateFiberRecursively(
745
- onRender,
746
- nextChild,
747
- prevChild,
748
- shouldIncludeInTree ? nextFiber : parentFiber,
749
- );
525
+ updateFiberRecursively(onRender, nextChild, nextChild.alternate);
750
526
  } else {
751
527
  mountFiberRecursively(onRender, nextChild, false);
752
528
  }
@@ -792,11 +568,9 @@ const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber):
792
568
  }
793
569
  };
794
570
 
795
- let commitId = 0;
796
571
  const rootInstanceMap = new WeakMap<
797
572
  Fiber | FiberRoot,
798
573
  {
799
- id: number;
800
574
  prevFiber: Fiber | null;
801
575
  }
802
576
  >();
@@ -814,17 +588,17 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
814
588
  let rootInstance = rootInstanceMap.get(root);
815
589
 
816
590
  if (!rootInstance) {
817
- rootInstance = { id: commitId++, prevFiber: null };
591
+ rootInstance = { prevFiber: null };
818
592
  rootInstanceMap.set(root, rootInstance);
819
593
  }
820
594
 
821
595
  const { prevFiber } = rootInstance;
822
- // if fiberRoot don't have current instance, means it's been unmounted
823
596
  if (!fiber) {
824
- unmountFiber(onRender, fiber);
597
+ if (prevFiber) {
598
+ unmountFiber(onRender, prevFiber);
599
+ }
825
600
  } else if (prevFiber !== null) {
826
601
  const wasMounted =
827
- prevFiber &&
828
602
  prevFiber.memoizedState !== null &&
829
603
  prevFiber.memoizedState.element !== null &&
830
604
  prevFiber.memoizedState.element !== undefined &&
@@ -840,7 +614,7 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
840
614
  if (!wasMounted && isMounted) {
841
615
  mountFiberRecursively(onRender, fiber, false);
842
616
  } else if (wasMounted && isMounted) {
843
- updateFiberRecursively(onRender, fiber, fiber.alternate, null);
617
+ updateFiberRecursively(onRender, fiber, fiber.alternate);
844
618
  } else if (wasMounted && !isMounted) {
845
619
  unmountFiber(onRender, fiber);
846
620
  }
@@ -851,171 +625,6 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
851
625
  rootInstance.prevFiber = fiber;
852
626
  };
853
627
 
854
- const overrideRenderers = new Set<ReactRenderer>();
855
- let areOverrideRenderersWired = false;
856
-
857
- const wireOverrideRenderers = (): void => {
858
- if (!hasRDTHook()) return;
859
- const rdtHook = getRDTHook();
860
-
861
- setHookEventDispatchers(rdtHook);
862
- for (const renderer of rdtHook.renderers.values()) {
863
- overrideRenderers.add(renderer);
864
- }
865
- if (areOverrideRenderersWired) return;
866
- areOverrideRenderersWired = true;
867
- onRendererInject((renderer) => {
868
- overrideRenderers.add(renderer);
869
- });
870
- };
871
-
872
- const getRootRenderer = (fiber: Fiber): ReactRenderer | null => {
873
- if (!hasRDTHook()) return null;
874
- let hostRootFiber = fiber;
875
- while (hostRootFiber.return) {
876
- hostRootFiber = hostRootFiber.return;
877
- }
878
- const fiberRoot = hostRootFiber.stateNode;
879
- if (!isFiberRoot(fiberRoot)) return null;
880
- const rendererId = rootRendererIds.get(fiberRoot);
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
- ancestors = new WeakSet<object>(),
944
- ): ValueWrite[] => {
945
- if (!isPOJO(maybePOJO)) {
946
- return [{ path: basePath, value: maybePOJO }];
947
- }
948
- if (ancestors.has(maybePOJO)) {
949
- return [{ path: basePath, value: maybePOJO }];
950
- }
951
-
952
- ancestors.add(maybePOJO);
953
- const paths: ValueWrite[] = [];
954
-
955
- for (const [key, value] of Object.entries(maybePOJO)) {
956
- const path = basePath.concat(key);
957
-
958
- if (isPOJO(value)) {
959
- paths.push(...buildPathsFromValue(value, path, ancestors));
960
- } else {
961
- paths.push({ path, value });
962
- }
963
- }
964
-
965
- ancestors.delete(maybePOJO);
966
- return paths;
967
- };
968
-
969
- const buildValueWrites = (partialValue: unknown): ValueWrite[] =>
970
- isPOJO(partialValue) ? buildPathsFromValue(partialValue) : [{ path: [], value: partialValue }];
971
-
972
- export const overrideProps = (fiber: Fiber, partialValue: Record<string, unknown>) => {
973
- const renderers = resolveOverrideRenderers(fiber);
974
- for (const { path, value } of buildValueWrites(partialValue)) {
975
- applyPropsOverride(renderers, fiber, path, value);
976
- }
977
- };
978
-
979
- export const overrideHookState = (fiber: Fiber, id: number, partialValue: unknown) => {
980
- const renderers = resolveOverrideRenderers(fiber).filter((renderer) =>
981
- Boolean(renderer.overrideHookState),
982
- );
983
- const writes = buildValueWrites(partialValue);
984
-
985
- if (renderers.length > 0) {
986
- for (const renderer of renderers) {
987
- for (const { path, value } of writes) {
988
- try {
989
- renderer.overrideHookState?.(fiber, id, path, value);
990
- } catch {}
991
- }
992
- }
993
- return;
994
- }
995
-
996
- // production renderers don't expose overrideHookState; dispatching through
997
- // the hook's own queue still works there, but only for whole-value writes
998
- // (a path write through dispatch would replace the entire hook state)
999
- if (isPOJO(partialValue)) return;
1000
- const dispatch = getHookStateDispatch(fiber, id);
1001
- if (!dispatch) return;
1002
- try {
1003
- dispatch(partialValue);
1004
- } catch {}
1005
- };
1006
-
1007
- export const overrideContext = (fiber: Fiber, contextType: unknown, partialValue: unknown) => {
1008
- const providerFiber = findContextProviderFiber(fiber, contextType);
1009
- if (!providerFiber) return;
1010
- const renderers = resolveOverrideRenderers(providerFiber);
1011
- for (const { path, value } of buildValueWrites(partialValue)) {
1012
- applyPropsOverride(renderers, providerFiber, ["value", ...path], value);
1013
- if (providerFiber.alternate) {
1014
- applyPropsOverride(renderers, providerFiber.alternate, ["value", ...path], value);
1015
- }
1016
- }
1017
- };
1018
-
1019
628
  export interface InstrumentationOptions {
1020
629
  name?: string;
1021
630
  onActive?: () => unknown;
@@ -1036,21 +645,6 @@ interface InstrumentationSubscription {
1036
645
 
1037
646
  const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
1038
647
 
1039
- const runInstrumentationCallback = (
1040
- instrumentationName: string,
1041
- callbackName: string,
1042
- callback: () => unknown,
1043
- ): void => {
1044
- runWithBippyError(
1045
- callback,
1046
- (cause) => new BippyInstrumentationError(instrumentationName, callbackName, cause),
1047
- );
1048
- };
1049
-
1050
- const runReactDevToolsCallback = (callbackName: string, callback: () => unknown): void => {
1051
- runWithBippyError(callback, (cause) => new BippyReactDevToolsError(callbackName, cause));
1052
- };
1053
-
1054
648
  interface HookDispatchers {
1055
649
  onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
1056
650
  onCommitFiberUnmount: ReactDevToolsGlobalHook["onCommitFiberUnmount"];
@@ -1061,8 +655,6 @@ interface HookDispatchers {
1061
655
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
1062
656
  let didSubscribeToHookReplacements = false;
1063
657
 
1064
- const rootRendererIds = new WeakMap<FiberRoot, number>();
1065
-
1066
658
  // each hook event is dispatched from a single re-installable wrapper. If
1067
659
  // something overwrites the hook method (devtools, direct assignment), the
1068
660
  // next instrument() call installs a fresh wrapper over it; a superseded
@@ -1084,9 +676,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1084
676
  didError,
1085
677
  ) => {
1086
678
  if (prevOnCommitFiberRoot) {
1087
- runReactDevToolsCallback("onCommitFiberRoot", () =>
1088
- prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError),
1089
- );
679
+ prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
1090
680
  }
1091
681
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
1092
682
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
@@ -1104,11 +694,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1104
694
  }
1105
695
  for (const { options } of instrumentationSubscriptions) {
1106
696
  if (options.onCommitFiberRoot) {
1107
- runInstrumentationCallback(
1108
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1109
- "onCommitFiberRoot",
1110
- () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
1111
- );
697
+ options.onCommitFiberRoot(rendererID, root, priority, didError);
1112
698
  }
1113
699
  }
1114
700
  };
@@ -1127,20 +713,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1127
713
  ) => {
1128
714
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
1129
715
  if (prevOnCommitFiberUnmount) {
1130
- runReactDevToolsCallback("onCommitFiberUnmount", () =>
1131
- prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber),
1132
- );
716
+ prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
1133
717
  }
1134
718
  if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
1135
719
  return;
1136
720
  }
1137
721
  for (const { options } of instrumentationSubscriptions) {
1138
722
  if (options.onCommitFiberUnmount) {
1139
- runInstrumentationCallback(
1140
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1141
- "onCommitFiberUnmount",
1142
- () => options.onCommitFiberUnmount?.(rendererID, fiber),
1143
- );
723
+ options.onCommitFiberUnmount(rendererID, fiber);
1144
724
  }
1145
725
  }
1146
726
  };
@@ -1158,20 +738,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1158
738
  root,
1159
739
  ) => {
1160
740
  if (prevOnPostCommitFiberRoot) {
1161
- runReactDevToolsCallback("onPostCommitFiberRoot", () =>
1162
- prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root),
1163
- );
741
+ prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
1164
742
  }
1165
743
  if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
1166
744
  return;
1167
745
  }
1168
746
  for (const { options } of instrumentationSubscriptions) {
1169
747
  if (options.onPostCommitFiberRoot) {
1170
- runInstrumentationCallback(
1171
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1172
- "onPostCommitFiberRoot",
1173
- () => options.onPostCommitFiberRoot?.(rendererID, root),
1174
- );
748
+ options.onPostCommitFiberRoot(rendererID, root);
1175
749
  }
1176
750
  }
1177
751
  };
@@ -1190,18 +764,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1190
764
  children,
1191
765
  ) => {
1192
766
  if (prevOnScheduleFiberRoot) {
1193
- runReactDevToolsCallback("onScheduleFiberRoot", () =>
1194
- prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children),
1195
- );
767
+ prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
1196
768
  }
1197
769
  if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
1198
770
  for (const { options } of instrumentationSubscriptions) {
1199
771
  if (options.onScheduleFiberRoot) {
1200
- runInstrumentationCallback(
1201
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1202
- "onScheduleFiberRoot",
1203
- () => options.onScheduleFiberRoot?.(rendererID, root, children),
1204
- );
772
+ options.onScheduleFiberRoot(rendererID, root, children);
1205
773
  }
1206
774
  }
1207
775
  };
@@ -1210,6 +778,18 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1210
778
  }
1211
779
  };
1212
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
+
1213
793
  /**
1214
794
  * Instruments the DevTools hook. Each hook event is patched once and
1215
795
  * dispatches to a set of listeners, so multiple `instrument` calls compose
@@ -1228,30 +808,15 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1228
808
  * unsubscribe();
1229
809
  */
1230
810
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1231
- const onActiveListener = options.onActive;
1232
- const onActive = onActiveListener
1233
- ? () =>
1234
- runInstrumentationCallback(
1235
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1236
- "onActive",
1237
- onActiveListener,
1238
- )
1239
- : undefined;
1240
- const rdtHook = getRDTHook(onActive);
1241
-
1242
- if (!didSubscribeToHookReplacements) {
1243
- onRDTHookReplace(setHookEventDispatchers);
1244
- didSubscribeToHookReplacements = true;
1245
- }
1246
-
811
+ const rdtHook = getRDTHook(options.onActive);
1247
812
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1248
813
 
1249
- setHookEventDispatchers(rdtHook);
814
+ wireHookEventDispatchers(rdtHook);
1250
815
  const subscription: InstrumentationSubscription = { options };
1251
816
  instrumentationSubscriptions.add(subscription);
1252
817
 
1253
818
  return createUnsubscribe(() => {
1254
- if (onActive) _onActiveListeners.delete(onActive);
819
+ if (options.onActive) _onActiveListeners.delete(options.onActive);
1255
820
  instrumentationSubscriptions.delete(subscription);
1256
821
  });
1257
822
  };
@@ -1264,33 +829,38 @@ const isFiberPropertyKey = (key: string): boolean =>
1264
829
  key.startsWith("__reactInternalInstance$") ||
1265
830
  key.startsWith("__reactFiber");
1266
831
 
1267
- const getLegacyRootFiber = (hostInstance: Record<string, unknown>): Fiber | null => {
1268
- const reactRootContainer = hostInstance._reactRootContainer;
1269
- if (!isObjectRecord(reactRootContainer)) return null;
1270
- const internalRoot = reactRootContainer._internalRoot;
1271
- if (!isObjectRecord(internalRoot)) return null;
1272
- const current = internalRoot.current;
1273
- if (!isObjectRecord(current)) return null;
1274
- 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");
1275
840
  return isValidFiber(child) ? child : null;
1276
841
  };
1277
842
 
1278
- const getInternalInstanceHandle = (hostInstance: Record<string, unknown>): Fiber | null => {
843
+ const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
1279
844
  const internalInstanceHandle =
1280
- hostInstance.__internalInstanceHandle ?? hostInstance._internalInstanceHandle;
845
+ Reflect.get(hostInstance, "__internalInstanceHandle") ??
846
+ Reflect.get(hostInstance, "_internalInstanceHandle");
1281
847
  return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
1282
848
  };
1283
849
 
1284
850
  const getPublicHostInstance = (stateNode: unknown): unknown => {
1285
- if (!isObjectRecord(stateNode)) return stateNode;
1286
- const canonical = stateNode.canonical;
1287
- if (isObjectRecord(canonical) && isObjectRecord(canonical.publicInstance)) {
1288
- 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
+ }
1289
858
  }
1290
- return typeof stateNode._nativeTag === "number" ? stateNode._nativeTag : stateNode;
859
+ const nativeTag = Reflect.get(stateNode, "_nativeTag");
860
+ return typeof nativeTag === "number" ? nativeTag : stateNode;
1291
861
  };
1292
862
 
1293
- export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
863
+ export const getFiber = <T>(hostInstance: T): Fiber | null => {
1294
864
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1295
865
  if (rdtHook?.renderers) {
1296
866
  for (const renderer of rdtHook.renderers.values()) {
@@ -1301,7 +871,7 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1301
871
  }
1302
872
  }
1303
873
 
1304
- if (isObjectRecord(hostInstance)) {
874
+ if (typeof hostInstance === "object" && hostInstance !== null) {
1305
875
  const legacyRootFiber = getLegacyRootFiber(hostInstance);
1306
876
  if (legacyRootFiber) return legacyRootFiber;
1307
877
 
@@ -1309,14 +879,14 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1309
879
  if (internalInstanceHandle) return internalInstanceHandle;
1310
880
 
1311
881
  for (const knownKey of knownFiberPropertyKeys) {
1312
- const fiber = hostInstance[knownKey];
882
+ const fiber = Reflect.get(hostInstance, knownKey);
1313
883
  if (isValidFiber(fiber)) return fiber;
1314
884
  }
1315
885
 
1316
886
  for (const key of Object.keys(hostInstance)) {
1317
887
  if (isFiberPropertyKey(key)) {
1318
888
  knownFiberPropertyKeys.add(key);
1319
- const fiber = hostInstance[key];
889
+ const fiber = Reflect.get(hostInstance, key);
1320
890
  if (isValidFiber(fiber)) return fiber;
1321
891
  }
1322
892
  }
@@ -1340,6 +910,8 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1340
910
  return null;
1341
911
  };
1342
912
 
913
+ export const getFiberFromHostInstance = getFiber;
914
+
1343
915
  export {
1344
916
  BIPPY_INSTRUMENTATION_STRING,
1345
917
  _onActiveListeners,
@@ -1350,8 +922,7 @@ export {
1350
922
  isRealReactDevtools,
1351
923
  onRendererInject,
1352
924
  patchRDTHook,
1353
- safelyInstallRDTHook,
1354
925
  version,
1355
926
  } from "./rdt-hook.js";
1356
927
  export type { Unsubscribe } from "./rdt-hook.js";
1357
- export type * from "./react-internals/index.js";
928
+ export * from "./react-internals/index.js";