bippy 0.6.1-dev.b88fcb4 → 0.6.1-dev.f9a65c0

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 +211 -445
  2. package/dist/core.cjs +1 -1
  3. package/dist/core.d.cts +9 -59
  4. package/dist/core.d.ts +9 -59
  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 +106 -545
  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
  /**
@@ -108,7 +70,7 @@ export const isValidElement = (element: unknown): element is React.ReactElement
108
70
  /**
109
71
  * Returns `true` if object is a React Fiber.
110
72
  */
111
- export const isValidFiber = (fiber: unknown): fiber is Fiber =>
73
+ export const isFiber = (fiber: unknown): fiber is Fiber =>
112
74
  typeof fiber === "object" &&
113
75
  fiber !== null &&
114
76
  "tag" in fiber &&
@@ -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
+ isFiber(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.
@@ -158,100 +123,6 @@ export const isCompositeFiber = (fiber: Fiber): boolean => {
158
123
  }
159
124
  };
160
125
 
161
- /**
162
- * Returns `true` if the object is a {@link Fiber}
163
- */
164
- export const isFiber = (maybeFiber: unknown): maybeFiber is Fiber => {
165
- if (!maybeFiber || typeof maybeFiber !== "object") return false;
166
- // this is a fast check. pendingProps will ALWAYS exist in fiber
167
- // `containerInfo` is in FiberRootNode, not FiberNode
168
- return "pendingProps" in maybeFiber && !("containerInfo" in maybeFiber);
169
- };
170
-
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
126
  /**
256
127
  * 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
128
  */
@@ -291,47 +162,6 @@ export const didFiberCommit = (fiber: Fiber): boolean => {
291
162
  );
292
163
  };
293
164
 
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
165
  /**
336
166
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
337
167
  */
@@ -380,47 +210,6 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
380
210
  }
381
211
  };
382
212
 
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
213
  /**
425
214
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
426
215
  */
@@ -444,7 +233,17 @@ export function traverseFiber(
444
233
  selector: FiberSelector,
445
234
  ascending = false,
446
235
  ): Fiber | null | Promise<Fiber | null> {
447
- return traverseFiberInternal(fiber, selector, ascending);
236
+ if (!fiber) return null;
237
+
238
+ const selection = selector(fiber);
239
+ if (isPromiseLike<boolean | void>(selection)) {
240
+ return Promise.resolve(selection).then((didSelectFiber) =>
241
+ didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
242
+ );
243
+ }
244
+ if (selection === true) return fiber;
245
+
246
+ return traverseFiberChildren(fiber, selector, ascending);
448
247
  }
449
248
 
450
249
  const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
@@ -470,7 +269,7 @@ const traverseFiberSiblings = (
470
269
  if (!fiber) return null;
471
270
 
472
271
  const nextSibling = ascending ? null : fiber.sibling;
473
- const match = traverseFiberInternal(fiber, selector, ascending);
272
+ const match = traverseFiber(fiber, selector, ascending);
474
273
  if (isPromiseLike<Fiber | null>(match)) {
475
274
  return Promise.resolve(match).then(
476
275
  (resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
@@ -479,45 +278,6 @@ const traverseFiberSiblings = (
479
278
  return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
480
279
  };
481
280
 
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
281
  /**
522
282
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
523
283
  */
@@ -530,8 +290,8 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
530
290
  */
531
291
  export const getType = (type: unknown): null | React.ComponentType<unknown> => {
532
292
  if (isComponentType(type)) return type;
533
- if (!isObjectRecord(type)) return null;
534
- return getType(type.type ?? type.render);
293
+ if (typeof type !== "object" || type === null) return null;
294
+ return getType(Reflect.get(type, "type") ?? Reflect.get(type, "render"));
535
295
  };
536
296
 
537
297
  /**
@@ -571,6 +331,7 @@ export const isInstrumentationActive = (): boolean => {
571
331
  };
572
332
 
573
333
  export const _fiberRoots = new Set<FiberRoot>();
334
+ const rootRendererIds = new WeakMap<FiberRoot, number>();
574
335
 
575
336
  /**
576
337
  * Returns the latest fiber (since it may be double-buffered).
@@ -590,6 +351,25 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
590
351
  return fiber;
591
352
  };
592
353
 
354
+ /**
355
+ * Returns the renderer that owns the {@link Fiber}.
356
+ */
357
+ export const getRenderer = (fiber: Fiber): ReactRenderer | null => {
358
+ if (!hasRDTHook()) return null;
359
+
360
+ let rootFiber = fiber;
361
+ while (rootFiber.return) {
362
+ rootFiber = rootFiber.return;
363
+ }
364
+
365
+ const fiberRoot = rootFiber.stateNode;
366
+ if (!isFiberRoot(fiberRoot)) return null;
367
+
368
+ const rendererID = rootRendererIds.get(fiberRoot);
369
+ if (rendererID === undefined) return null;
370
+ return getRDTHook().renderers.get(rendererID) ?? null;
371
+ };
372
+
593
373
  export type RenderPhase = "mount" | "unmount" | "update";
594
374
 
595
375
  let nextFiberId = 0;
@@ -622,9 +402,7 @@ const mountFiberRecursively = (
622
402
  let fiber: Fiber | null = firstChild;
623
403
 
624
404
  while (fiber !== null) {
625
- if (!fiberIdMap.has(fiber)) {
626
- getFiberId(fiber);
627
- }
405
+ getFiberId(fiber);
628
406
  const shouldIncludeInTree = !shouldFilterFiber(fiber);
629
407
  if (shouldIncludeInTree && didFiberRender(fiber)) {
630
408
  onRender(fiber, "mount");
@@ -661,15 +439,10 @@ const updateFiberRecursively = (
661
439
  onRender: RenderHandler,
662
440
  nextFiber: Fiber,
663
441
  prevFiber: Fiber | null,
664
- parentFiber: Fiber | null,
665
442
  ): void => {
666
- if (!fiberIdMap.has(nextFiber)) {
667
- getFiberId(nextFiber);
668
- }
443
+ getFiberId(nextFiber);
669
444
  if (!prevFiber) return;
670
- if (!fiberIdMap.has(prevFiber)) {
671
- getFiberId(prevFiber);
672
- }
445
+ getFiberId(prevFiber);
673
446
 
674
447
  const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
675
448
 
@@ -700,7 +473,7 @@ const updateFiberRecursively = (
700
473
  const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
701
474
 
702
475
  if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
703
- updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet, nextFiber);
476
+ updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
704
477
  }
705
478
  } else if (prevDidTimeout && !nextDidTimeOut) {
706
479
  // Fallback -> Primary:
@@ -739,14 +512,7 @@ const updateFiberRecursively = (
739
512
  // Schedule updates and mounts depending on whether alternates exist.
740
513
  // We don't track deletions here because they are reported separately.
741
514
  if (nextChild.alternate) {
742
- const prevChild = nextChild.alternate;
743
-
744
- updateFiberRecursively(
745
- onRender,
746
- nextChild,
747
- prevChild,
748
- shouldIncludeInTree ? nextFiber : parentFiber,
749
- );
515
+ updateFiberRecursively(onRender, nextChild, nextChild.alternate);
750
516
  } else {
751
517
  mountFiberRecursively(onRender, nextChild, false);
752
518
  }
@@ -792,11 +558,9 @@ const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber):
792
558
  }
793
559
  };
794
560
 
795
- let commitId = 0;
796
561
  const rootInstanceMap = new WeakMap<
797
562
  Fiber | FiberRoot,
798
563
  {
799
- id: number;
800
564
  prevFiber: Fiber | null;
801
565
  }
802
566
  >();
@@ -814,17 +578,17 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
814
578
  let rootInstance = rootInstanceMap.get(root);
815
579
 
816
580
  if (!rootInstance) {
817
- rootInstance = { id: commitId++, prevFiber: null };
581
+ rootInstance = { prevFiber: null };
818
582
  rootInstanceMap.set(root, rootInstance);
819
583
  }
820
584
 
821
585
  const { prevFiber } = rootInstance;
822
- // if fiberRoot don't have current instance, means it's been unmounted
823
586
  if (!fiber) {
824
- unmountFiber(onRender, fiber);
587
+ if (prevFiber) {
588
+ unmountFiber(onRender, prevFiber);
589
+ }
825
590
  } else if (prevFiber !== null) {
826
591
  const wasMounted =
827
- prevFiber &&
828
592
  prevFiber.memoizedState !== null &&
829
593
  prevFiber.memoizedState.element !== null &&
830
594
  prevFiber.memoizedState.element !== undefined &&
@@ -840,7 +604,7 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
840
604
  if (!wasMounted && isMounted) {
841
605
  mountFiberRecursively(onRender, fiber, false);
842
606
  } else if (wasMounted && isMounted) {
843
- updateFiberRecursively(onRender, fiber, fiber.alternate, null);
607
+ updateFiberRecursively(onRender, fiber, fiber.alternate);
844
608
  } else if (wasMounted && !isMounted) {
845
609
  unmountFiber(onRender, fiber);
846
610
  }
@@ -851,171 +615,6 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
851
615
  rootInstance.prevFiber = fiber;
852
616
  };
853
617
 
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
618
  export interface InstrumentationOptions {
1020
619
  name?: string;
1021
620
  onActive?: () => unknown;
@@ -1036,21 +635,6 @@ interface InstrumentationSubscription {
1036
635
 
1037
636
  const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
1038
637
 
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
638
  interface HookDispatchers {
1055
639
  onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
1056
640
  onCommitFiberUnmount: ReactDevToolsGlobalHook["onCommitFiberUnmount"];
@@ -1061,8 +645,6 @@ interface HookDispatchers {
1061
645
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
1062
646
  let didSubscribeToHookReplacements = false;
1063
647
 
1064
- const rootRendererIds = new WeakMap<FiberRoot, number>();
1065
-
1066
648
  // each hook event is dispatched from a single re-installable wrapper. If
1067
649
  // something overwrites the hook method (devtools, direct assignment), the
1068
650
  // next instrument() call installs a fresh wrapper over it; a superseded
@@ -1084,9 +666,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1084
666
  didError,
1085
667
  ) => {
1086
668
  if (prevOnCommitFiberRoot) {
1087
- runReactDevToolsCallback("onCommitFiberRoot", () =>
1088
- prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError),
1089
- );
669
+ prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
1090
670
  }
1091
671
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
1092
672
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
@@ -1104,11 +684,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1104
684
  }
1105
685
  for (const { options } of instrumentationSubscriptions) {
1106
686
  if (options.onCommitFiberRoot) {
1107
- runInstrumentationCallback(
1108
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1109
- "onCommitFiberRoot",
1110
- () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
1111
- );
687
+ options.onCommitFiberRoot(rendererID, root, priority, didError);
1112
688
  }
1113
689
  }
1114
690
  };
@@ -1127,20 +703,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1127
703
  ) => {
1128
704
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
1129
705
  if (prevOnCommitFiberUnmount) {
1130
- runReactDevToolsCallback("onCommitFiberUnmount", () =>
1131
- prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber),
1132
- );
706
+ prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
1133
707
  }
1134
708
  if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
1135
709
  return;
1136
710
  }
1137
711
  for (const { options } of instrumentationSubscriptions) {
1138
712
  if (options.onCommitFiberUnmount) {
1139
- runInstrumentationCallback(
1140
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1141
- "onCommitFiberUnmount",
1142
- () => options.onCommitFiberUnmount?.(rendererID, fiber),
1143
- );
713
+ options.onCommitFiberUnmount(rendererID, fiber);
1144
714
  }
1145
715
  }
1146
716
  };
@@ -1158,20 +728,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1158
728
  root,
1159
729
  ) => {
1160
730
  if (prevOnPostCommitFiberRoot) {
1161
- runReactDevToolsCallback("onPostCommitFiberRoot", () =>
1162
- prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root),
1163
- );
731
+ prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
1164
732
  }
1165
733
  if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
1166
734
  return;
1167
735
  }
1168
736
  for (const { options } of instrumentationSubscriptions) {
1169
737
  if (options.onPostCommitFiberRoot) {
1170
- runInstrumentationCallback(
1171
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1172
- "onPostCommitFiberRoot",
1173
- () => options.onPostCommitFiberRoot?.(rendererID, root),
1174
- );
738
+ options.onPostCommitFiberRoot(rendererID, root);
1175
739
  }
1176
740
  }
1177
741
  };
@@ -1190,18 +754,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1190
754
  children,
1191
755
  ) => {
1192
756
  if (prevOnScheduleFiberRoot) {
1193
- runReactDevToolsCallback("onScheduleFiberRoot", () =>
1194
- prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children),
1195
- );
757
+ prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
1196
758
  }
1197
759
  if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
1198
760
  for (const { options } of instrumentationSubscriptions) {
1199
761
  if (options.onScheduleFiberRoot) {
1200
- runInstrumentationCallback(
1201
- options.name ?? BIPPY_INSTRUMENTATION_STRING,
1202
- "onScheduleFiberRoot",
1203
- () => options.onScheduleFiberRoot?.(rendererID, root, children),
1204
- );
762
+ options.onScheduleFiberRoot(rendererID, root, children);
1205
763
  }
1206
764
  }
1207
765
  };
@@ -1210,6 +768,18 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1210
768
  }
1211
769
  };
1212
770
 
771
+ const wireHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
772
+ if (!didSubscribeToHookReplacements) {
773
+ onRDTHookReplace(setHookEventDispatchers);
774
+ didSubscribeToHookReplacements = true;
775
+ }
776
+ setHookEventDispatchers(rdtHook);
777
+ };
778
+
779
+ if (hasRDTHook()) {
780
+ wireHookEventDispatchers(getRDTHook());
781
+ }
782
+
1213
783
  /**
1214
784
  * Instruments the DevTools hook. Each hook event is patched once and
1215
785
  * dispatches to a set of listeners, so multiple `instrument` calls compose
@@ -1228,30 +798,15 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1228
798
  * unsubscribe();
1229
799
  */
1230
800
  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
-
801
+ const rdtHook = getRDTHook(options.onActive);
1247
802
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1248
803
 
1249
- setHookEventDispatchers(rdtHook);
804
+ wireHookEventDispatchers(rdtHook);
1250
805
  const subscription: InstrumentationSubscription = { options };
1251
806
  instrumentationSubscriptions.add(subscription);
1252
807
 
1253
808
  return createUnsubscribe(() => {
1254
- if (onActive) _onActiveListeners.delete(onActive);
809
+ if (options.onActive) _onActiveListeners.delete(options.onActive);
1255
810
  instrumentationSubscriptions.delete(subscription);
1256
811
  });
1257
812
  };
@@ -1264,33 +819,38 @@ const isFiberPropertyKey = (key: string): boolean =>
1264
819
  key.startsWith("__reactInternalInstance$") ||
1265
820
  key.startsWith("__reactFiber");
1266
821
 
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;
1275
- return isValidFiber(child) ? child : null;
822
+ const getLegacyRootFiber = (hostInstance: object): Fiber | null => {
823
+ const reactRootContainer = Reflect.get(hostInstance, "_reactRootContainer");
824
+ if (typeof reactRootContainer !== "object" || reactRootContainer === null) return null;
825
+ const internalRoot = Reflect.get(reactRootContainer, "_internalRoot");
826
+ if (typeof internalRoot !== "object" || internalRoot === null) return null;
827
+ const current = Reflect.get(internalRoot, "current");
828
+ if (typeof current !== "object" || current === null) return null;
829
+ const child = Reflect.get(current, "child");
830
+ return isFiber(child) ? child : null;
1276
831
  };
1277
832
 
1278
- const getInternalInstanceHandle = (hostInstance: Record<string, unknown>): Fiber | null => {
833
+ const getInternalInstanceHandle = (hostInstance: object): Fiber | null => {
1279
834
  const internalInstanceHandle =
1280
- hostInstance.__internalInstanceHandle ?? hostInstance._internalInstanceHandle;
1281
- return isValidFiber(internalInstanceHandle) ? internalInstanceHandle : null;
835
+ Reflect.get(hostInstance, "__internalInstanceHandle") ??
836
+ Reflect.get(hostInstance, "_internalInstanceHandle");
837
+ return isFiber(internalInstanceHandle) ? internalInstanceHandle : null;
1282
838
  };
1283
839
 
1284
840
  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;
841
+ if (typeof stateNode !== "object" || stateNode === null) return stateNode;
842
+ const canonical = Reflect.get(stateNode, "canonical");
843
+ if (typeof canonical === "object" && canonical !== null) {
844
+ const publicInstance = Reflect.get(canonical, "publicInstance");
845
+ if (typeof publicInstance === "object" && publicInstance !== null) {
846
+ return publicInstance;
847
+ }
1289
848
  }
1290
- return typeof stateNode._nativeTag === "number" ? stateNode._nativeTag : stateNode;
849
+ const nativeTag = Reflect.get(stateNode, "_nativeTag");
850
+ return typeof nativeTag === "number" ? nativeTag : stateNode;
1291
851
  };
1292
852
 
1293
- export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
853
+ export const getFiber = <T>(hostInstance: T): Fiber | null => {
1294
854
  const rdtHook = globalThis.__REACT_DEVTOOLS_GLOBAL_HOOK__;
1295
855
  if (rdtHook?.renderers) {
1296
856
  for (const renderer of rdtHook.renderers.values()) {
@@ -1301,7 +861,7 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1301
861
  }
1302
862
  }
1303
863
 
1304
- if (isObjectRecord(hostInstance)) {
864
+ if (typeof hostInstance === "object" && hostInstance !== null) {
1305
865
  const legacyRootFiber = getLegacyRootFiber(hostInstance);
1306
866
  if (legacyRootFiber) return legacyRootFiber;
1307
867
 
@@ -1309,15 +869,15 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1309
869
  if (internalInstanceHandle) return internalInstanceHandle;
1310
870
 
1311
871
  for (const knownKey of knownFiberPropertyKeys) {
1312
- const fiber = hostInstance[knownKey];
1313
- if (isValidFiber(fiber)) return fiber;
872
+ const fiber = Reflect.get(hostInstance, knownKey);
873
+ if (isFiber(fiber)) return fiber;
1314
874
  }
1315
875
 
1316
876
  for (const key of Object.keys(hostInstance)) {
1317
877
  if (isFiberPropertyKey(key)) {
1318
878
  knownFiberPropertyKeys.add(key);
1319
- const fiber = hostInstance[key];
1320
- if (isValidFiber(fiber)) return fiber;
879
+ const fiber = Reflect.get(hostInstance, key);
880
+ if (isFiber(fiber)) return fiber;
1321
881
  }
1322
882
  }
1323
883
  }
@@ -1340,6 +900,8 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1340
900
  return null;
1341
901
  };
1342
902
 
903
+ export const getFiberFromHostInstance = getFiber;
904
+
1343
905
  export {
1344
906
  BIPPY_INSTRUMENTATION_STRING,
1345
907
  _onActiveListeners,
@@ -1350,8 +912,7 @@ export {
1350
912
  isRealReactDevtools,
1351
913
  onRendererInject,
1352
914
  patchRDTHook,
1353
- safelyInstallRDTHook,
1354
915
  version,
1355
916
  } from "./rdt-hook.js";
1356
917
  export type { Unsubscribe } from "./rdt-hook.js";
1357
- export type * from "./react-internals/index.js";
918
+ export * from "./react-internals/index.js";