bippy 0.6.1-dev.3a24abf → 0.6.1-dev.d7876ea

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.
package/src/core.ts CHANGED
@@ -12,76 +12,41 @@ import type {
12
12
  ReactRenderer,
13
13
  } from "./types.js";
14
14
 
15
+ import { BippyInstrumentationError, BippyReactDevToolsError, runWithBippyError } from "./errors.js";
16
+
15
17
  import {
16
18
  _onActiveListeners,
17
19
  BIPPY_INSTRUMENTATION_STRING,
18
20
  getRDTHook,
19
21
  hasRDTHook,
20
22
  isRealReactDevtools,
23
+ onRDTHookReplace,
21
24
  onRendererInject,
22
25
  } from "./rdt-hook.js";
23
26
  import {
24
- ClassComponentTag,
25
- CONCURRENT_MODE_NUMBER,
26
- CONCURRENT_MODE_SYMBOL_DESCRIPTION,
27
- CONCURRENT_MODE_SYMBOL_STRING,
28
- ContextConsumerTag,
29
- DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION,
30
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
31
- DehydratedSuspenseComponentTag,
32
- ELEMENT_TYPE_SYMBOL_STRING,
33
- ForwardRefTag,
34
- FragmentTag,
35
- FunctionComponentTag,
36
- HostComponentTag,
37
- HostHoistableTag,
38
- HostRootTag,
39
- HostSingletonTag,
40
- HostTextTag,
41
- LegacyHiddenComponentTag,
42
- MemoComponentTag,
27
+ getReactWorkTagsForFiber,
43
28
  MutationMask,
44
- OffscreenComponentTag,
45
29
  ReactBuildType,
46
30
  ReactFiberFlags,
47
- SimpleMemoComponentTag,
48
- SuspenseComponentTag,
49
- TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING,
31
+ ReactSymbols,
32
+ setReactWorkTagsForFiber,
50
33
  } from "./react-internals.js";
51
34
  import { toUnsubscribe, type Unsubscribe } from "./unsubscribe.js";
52
35
 
36
+ export { getReactWorkTags, ReactSymbols } from "./react-internals.js";
37
+ export type { ReactWorkTagMap, ReactWorkTagVersion } from "./react-internals.js";
53
38
  export {
54
- ActivityComponentTag,
55
- ClassComponentTag,
56
- CONCURRENT_MODE_NUMBER,
57
- CONCURRENT_MODE_SYMBOL_DESCRIPTION,
58
- CONCURRENT_MODE_SYMBOL_STRING,
59
- ContextConsumerTag,
60
- DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION,
61
- DEPRECATED_ASYNC_MODE_SYMBOL_STRING,
62
- DehydratedSuspenseComponentTag,
63
- ELEMENT_TYPE_SYMBOL_STRING,
64
- ForwardRefTag,
65
- FragmentTag,
66
- FunctionComponentTag,
67
- HostComponentTag,
68
- HostHoistableTag,
69
- HostPortalTag,
70
- HostRootTag,
71
- HostSingletonTag,
72
- HostTextTag,
73
- LazyComponentTag,
74
- LegacyHiddenComponentTag,
75
- MemoComponentTag,
76
- OffscreenComponentTag,
77
- SimpleMemoComponentTag,
78
- SuspenseComponentTag,
79
- SuspenseListComponentTag,
80
- TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING,
81
- ViewTransitionComponentTag,
82
- } from "./react-internals.js";
83
-
84
- const { Cloned, PerformedWork } = ReactFiberFlags;
39
+ BippyError,
40
+ BippyHookInstallationError,
41
+ BippyHookInspectionError,
42
+ BippyHookListenerError,
43
+ BippyHookRenderError,
44
+ BippyInstrumentationError,
45
+ BippyReactBuildError,
46
+ BippyReactDevToolsError,
47
+ BippySourceMapError,
48
+ BippyUnsupportedHookError,
49
+ } from "./errors.js";
85
50
 
86
51
  const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
87
52
  typeof value === "object" && value !== null;
@@ -113,6 +78,20 @@ interface FiberSelector {
113
78
  (node: Fiber): boolean | Promise<boolean | void> | void;
114
79
  }
115
80
 
81
+ export interface FiberTimings {
82
+ selfTime: number;
83
+ totalTime: number;
84
+ }
85
+
86
+ export interface RenderHandler {
87
+ <State>(fiber: Fiber, phase: RenderPhase, state?: State): unknown;
88
+ }
89
+
90
+ interface ValueWrite {
91
+ path: string[];
92
+ value: unknown;
93
+ }
94
+
116
95
  /**
117
96
  * Returns `true` if object is a React Element.
118
97
  *
@@ -122,7 +101,7 @@ export const isValidElement = (element: unknown): element is React.ReactElement
122
101
  typeof element === "object" &&
123
102
  element !== null &&
124
103
  "$$typeof" in element &&
125
- [ELEMENT_TYPE_SYMBOL_STRING, TRANSITIONAL_ELEMENT_TYPE_SYMBOL_STRING].includes(
104
+ [ReactSymbols.LEGACY_ELEMENT_SYMBOL_STRING, ReactSymbols.ELEMENT_SYMBOL_STRING].includes(
126
105
  String(element.$$typeof),
127
106
  );
128
107
 
@@ -145,11 +124,12 @@ export const isValidFiber = (fiber: unknown): fiber is Fiber =>
145
124
  * @see https://reactnative.dev/architecture/glossary#host-view-tree-and-host-view
146
125
  */
147
126
  export const isHostFiber = (fiber: Fiber): fiber is HostFiber => {
127
+ const workTags = getReactWorkTagsForFiber(fiber);
148
128
  switch (fiber.tag) {
149
- case HostComponentTag:
150
- case HostTextTag:
151
- case HostHoistableTag:
152
- case HostSingletonTag:
129
+ case workTags.HostComponent:
130
+ case workTags.HostText:
131
+ case workTags.HostHoistable:
132
+ case workTags.HostSingleton:
153
133
  return true;
154
134
  default:
155
135
  return false;
@@ -162,12 +142,13 @@ export const isHostFiber = (fiber: Fiber): fiber is HostFiber => {
162
142
  * @see https://reactnative.dev/architecture/glossary#react-composite-components
163
143
  */
164
144
  export const isCompositeFiber = (fiber: Fiber): boolean => {
145
+ const workTags = getReactWorkTagsForFiber(fiber);
165
146
  switch (fiber.tag) {
166
- case ClassComponentTag:
167
- case ForwardRefTag:
168
- case FunctionComponentTag:
169
- case MemoComponentTag:
170
- case SimpleMemoComponentTag:
147
+ case workTags.ClassComponent:
148
+ case workTags.ForwardRef:
149
+ case workTags.FunctionComponent:
150
+ case workTags.MemoComponent:
151
+ case workTags.SimpleMemoComponent:
171
152
  return true;
172
153
  default:
173
154
  return false;
@@ -275,15 +256,16 @@ export const didFiberRender = (fiber: Fiber): boolean => {
275
256
  const nextProps = fiber.memoizedProps;
276
257
  const prevProps = fiber.alternate?.memoizedProps || {};
277
258
  const flags = fiber.flags ?? fiber.effectTag ?? 0;
259
+ const workTags = getReactWorkTagsForFiber(fiber);
278
260
 
279
261
  switch (fiber.tag) {
280
- case ClassComponentTag:
281
- case ContextConsumerTag:
282
- case ForwardRefTag:
283
- case FunctionComponentTag:
284
- case MemoComponentTag:
285
- case SimpleMemoComponentTag: {
286
- return (flags & PerformedWork) === PerformedWork;
262
+ case workTags.ClassComponent:
263
+ case workTags.ContextConsumer:
264
+ case workTags.ForwardRef:
265
+ case workTags.FunctionComponent:
266
+ case workTags.MemoComponent:
267
+ case workTags.SimpleMemoComponent: {
268
+ return (flags & ReactFiberFlags.PerformedWork) === ReactFiberFlags.PerformedWork;
287
269
  }
288
270
  default:
289
271
  // Host nodes (DOM, root, etc.)
@@ -301,8 +283,8 @@ export const didFiberRender = (fiber: Fiber): boolean => {
301
283
  */
302
284
  export const didFiberCommit = (fiber: Fiber): boolean => {
303
285
  return Boolean(
304
- (fiber.flags & (MutationMask | Cloned)) !== 0 ||
305
- (fiber.subtreeFlags & (MutationMask | Cloned)) !== 0,
286
+ (fiber.flags & (MutationMask | ReactFiberFlags.Cloned)) !== 0 ||
287
+ (fiber.subtreeFlags & (MutationMask | ReactFiberFlags.Cloned)) !== 0,
306
288
  );
307
289
  };
308
290
 
@@ -351,8 +333,9 @@ export const getFiberStack = (fiber: Fiber): Fiber[] => {
351
333
  * Returns `true` if the {@link Fiber} should be filtered out during reconciliation.
352
334
  */
353
335
  const shouldFilterFiber = (fiber: Fiber): boolean => {
336
+ const workTags = getReactWorkTagsForFiber(fiber);
354
337
  switch (fiber.tag) {
355
- case DehydratedSuspenseComponentTag:
338
+ case workTags.DehydratedSuspenseComponent:
356
339
  // TODO: ideally we would show dehydrated Suspense immediately.
357
340
  // However, it has some special behavior (like disconnecting
358
341
  // an alternate and turning into real Suspense) which breaks DevTools.
@@ -360,13 +343,13 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
360
343
  // https://github.com/bvaughn/react-devtools-experimental/issues/197
361
344
  return true;
362
345
 
363
- case FragmentTag:
364
- case HostTextTag:
365
- case LegacyHiddenComponentTag:
366
- case OffscreenComponentTag:
346
+ case workTags.Fragment:
347
+ case workTags.HostText:
348
+ case workTags.LegacyHiddenComponent:
349
+ case workTags.OffscreenComponent:
367
350
  return true;
368
351
 
369
- case HostRootTag:
352
+ case workTags.HostRoot:
370
353
  // It is never valid to filter the root element.
371
354
  return false;
372
355
 
@@ -376,15 +359,15 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
376
359
 
377
360
  if (typeof symbolOrNumber === "symbol") {
378
361
  return (
379
- symbolOrNumber.description === CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
380
- symbolOrNumber.description === DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
362
+ symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
363
+ symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
381
364
  );
382
365
  }
383
366
 
384
367
  switch (symbolOrNumber) {
385
- case CONCURRENT_MODE_NUMBER:
386
- case CONCURRENT_MODE_SYMBOL_STRING:
387
- case DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
368
+ case ReactSymbols.CONCURRENT_MODE_NUMBER:
369
+ case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
370
+ case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
388
371
  return true;
389
372
 
390
373
  default:
@@ -528,7 +511,7 @@ const traverseFiberAsync = async (
528
511
  * console.log(selfTime, totalTime);
529
512
  * ```
530
513
  */
531
- export const getTimings = (fiber?: Fiber | null): { selfTime: number; totalTime: number } => {
514
+ export const getTimings = (fiber?: Fiber | null): FiberTimings => {
532
515
  const totalTime = fiber?.actualDuration ?? 0;
533
516
  let selfTime = totalTime;
534
517
  // TODO: calculate a DOM time, which is just host component summed up
@@ -612,28 +595,25 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
612
595
  return fiber;
613
596
  };
614
597
 
615
- export type RenderHandler = <S>(fiber: Fiber, phase: RenderPhase, state?: S) => unknown;
616
-
617
598
  export type RenderPhase = "mount" | "unmount" | "update";
618
599
 
619
- let fiberId = 0;
600
+ let nextFiberId = 0;
620
601
  const fiberIdMap = new WeakMap<Fiber, number>();
621
602
 
622
- export const setFiberId = (fiber: Fiber, id: number = fiberId++): void => {
623
- fiberIdMap.set(fiber, id);
603
+ export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
604
+ fiberIdMap.set(fiber, fiberId);
624
605
  };
625
606
 
626
- // Fiber alternates share an identity across double-buffered commits.
627
607
  export const getFiberId = (fiber: Fiber): number => {
628
- let id = fiberIdMap.get(fiber);
629
- if (id === undefined && fiber.alternate) {
630
- id = fiberIdMap.get(fiber.alternate);
608
+ let currentFiberId = fiberIdMap.get(fiber);
609
+ if (currentFiberId === undefined && fiber.alternate) {
610
+ currentFiberId = fiberIdMap.get(fiber.alternate);
631
611
  }
632
- if (id === undefined) {
633
- id = fiberId++;
634
- setFiberId(fiber, id);
612
+ if (currentFiberId === undefined) {
613
+ currentFiberId = nextFiberId++;
614
+ setFiberId(fiber, currentFiberId);
635
615
  }
636
- return id;
616
+ return currentFiberId;
637
617
  };
638
618
 
639
619
  const mountFiberRecursively = (
@@ -652,7 +632,7 @@ const mountFiberRecursively = (
652
632
  onRender(fiber, "mount");
653
633
  }
654
634
 
655
- if (fiber.tag === SuspenseComponentTag) {
635
+ if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
656
636
  const isTimedOut = fiber.memoizedState !== null;
657
637
  if (isTimedOut) {
658
638
  // Special case: if Suspense mounts in a timed-out state,
@@ -693,7 +673,7 @@ const updateFiberRecursively = (
693
673
  getFiberId(prevFiber);
694
674
  }
695
675
 
696
- const isSuspense = nextFiber.tag === SuspenseComponentTag;
676
+ const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
697
677
 
698
678
  const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
699
679
  if (shouldIncludeInTree && didFiberRender(nextFiber)) {
@@ -780,7 +760,7 @@ const updateFiberRecursively = (
780
760
  };
781
761
 
782
762
  const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
783
- const isRoot = fiber.tag === HostRootTag;
763
+ const isRoot = fiber.tag === getReactWorkTagsForFiber(fiber).HostRoot;
784
764
 
785
765
  if (isRoot || !shouldFilterFiber(fiber)) {
786
766
  onRender(fiber, "unmount");
@@ -789,7 +769,8 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
789
769
 
790
770
  const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber): void => {
791
771
  // We might meet a nested Suspense on our way.
792
- const isTimedOutSuspense = fiber.tag === SuspenseComponentTag && fiber.memoizedState !== null;
772
+ const isTimedOutSuspense =
773
+ fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent && fiber.memoizedState !== null;
793
774
  let child = fiber.child;
794
775
 
795
776
  if (isTimedOutSuspense) {
@@ -878,9 +859,8 @@ let areOverrideRenderersWired = false;
878
859
  const wireOverrideRenderers = (): void => {
879
860
  if (!hasRDTHook()) return;
880
861
  const rdtHook = getRDTHook();
881
- if (!rdtHook?.renderers) return;
882
862
 
883
- ensureHookDispatchesToListeners(rdtHook);
863
+ setHookEventDispatchers(rdtHook);
884
864
  for (const renderer of rdtHook.renderers.values()) {
885
865
  overrideRenderers.add(renderer);
886
866
  }
@@ -899,12 +879,9 @@ const getRootRenderer = (fiber: Fiber): ReactRenderer | null => {
899
879
  }
900
880
  const rendererId = rootRendererIds.get(hostRootFiber.stateNode);
901
881
  if (rendererId === undefined) return null;
902
- return getRDTHook().renderers?.get(rendererId) ?? null;
882
+ return getRDTHook().renderers.get(rendererId) ?? null;
903
883
  };
904
884
 
905
- // dispatch to the renderer that owns the fiber's root when known; renderers
906
- // that never committed through bippy's hook patch fall back to "try all",
907
- // which matches the pre-ownership behavior for single-renderer apps
908
885
  const resolveOverrideRenderers = (fiber: Fiber): ReactRenderer[] => {
909
886
  wireOverrideRenderers();
910
887
  const rootRenderer = getRootRenderer(fiber);
@@ -930,7 +907,7 @@ const getHookStateDispatch = (
930
907
  hookIndex: number,
931
908
  ): ((value: unknown) => void) | null => {
932
909
  let hookState = fiber.memoizedState;
933
- for (let i = 0; i < hookIndex; i++) {
910
+ for (let currentHookIndex = 0; currentHookIndex < hookIndex; currentHookIndex++) {
934
911
  if (!hookState?.next) return null;
935
912
  hookState = hookState.next;
936
913
  }
@@ -963,12 +940,12 @@ const isPOJO = (maybePOJO: unknown): maybePOJO is Record<string, unknown> => {
963
940
  const buildPathsFromValue = (
964
941
  maybePOJO: Record<string, unknown>,
965
942
  basePath: string[] = [],
966
- ): Array<{ path: string[]; value: unknown }> => {
943
+ ): ValueWrite[] => {
967
944
  if (!isPOJO(maybePOJO)) {
968
945
  return [{ path: basePath, value: maybePOJO }];
969
946
  }
970
947
 
971
- const paths: Array<{ path: string[]; value: unknown }> = [];
948
+ const paths: ValueWrite[] = [];
972
949
 
973
950
  for (const key in maybePOJO) {
974
951
  const value = maybePOJO[key];
@@ -984,7 +961,7 @@ const buildPathsFromValue = (
984
961
  return paths;
985
962
  };
986
963
 
987
- const buildValueWrites = (partialValue: unknown): Array<{ path: string[]; value: unknown }> =>
964
+ const buildValueWrites = (partialValue: unknown): ValueWrite[] =>
988
965
  isPOJO(partialValue) ? buildPathsFromValue(partialValue) : [{ path: [], value: partialValue }];
989
966
 
990
967
  export const overrideProps = (fiber: Fiber, partialValue: Record<string, unknown>) => {
@@ -1048,18 +1025,24 @@ export interface InstrumentationOptions {
1048
1025
  onScheduleFiberRoot?: (rendererID: number, root: FiberRoot, children: React.ReactNode) => unknown;
1049
1026
  }
1050
1027
 
1051
- const commitFiberRootListeners = new Set<
1052
- NonNullable<InstrumentationOptions["onCommitFiberRoot"]>
1053
- >();
1054
- const commitFiberUnmountListeners = new Set<
1055
- NonNullable<InstrumentationOptions["onCommitFiberUnmount"]>
1056
- >();
1057
- const postCommitFiberRootListeners = new Set<
1058
- NonNullable<InstrumentationOptions["onPostCommitFiberRoot"]>
1059
- >();
1060
- const scheduleFiberRootListeners = new Set<
1061
- NonNullable<InstrumentationOptions["onScheduleFiberRoot"]>
1062
- >();
1028
+ interface InstrumentationSubscription {
1029
+ options: InstrumentationOptions;
1030
+ }
1031
+
1032
+ const instrumentationSubscriptions = new Set<InstrumentationSubscription>();
1033
+
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));
1063
1046
 
1064
1047
  interface HookDispatchers {
1065
1048
  onCommitFiberRoot: ReactDevToolsGlobalHook["onCommitFiberRoot"];
@@ -1069,6 +1052,7 @@ interface HookDispatchers {
1069
1052
  }
1070
1053
 
1071
1054
  const hookDispatchers = new WeakMap<ReactDevToolsGlobalHook, Partial<HookDispatchers>>();
1055
+ let didSubscribeToHookReplacements = false;
1072
1056
 
1073
1057
  const rootRendererIds = new WeakMap<FiberRoot, number>();
1074
1058
 
@@ -1077,7 +1061,7 @@ const rootRendererIds = new WeakMap<FiberRoot, number>();
1077
1061
  // next instrument() call installs a fresh wrapper over it; a superseded
1078
1062
  // wrapper still forwards the previous chain but skips the listeners so
1079
1063
  // they never fire twice.
1080
- const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void => {
1064
+ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
1081
1065
  const dispatchers = hookDispatchers.get(rdtHook) ?? {};
1082
1066
  hookDispatchers.set(rdtHook, dispatchers);
1083
1067
 
@@ -1092,8 +1076,13 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1092
1076
  priority,
1093
1077
  didError,
1094
1078
  ) => {
1095
- prevOnCommitFiberRoot?.(rendererID, root, priority, didError);
1079
+ if (prevOnCommitFiberRoot) {
1080
+ runReactDevToolsCallback("onCommitFiberRoot", () =>
1081
+ prevOnCommitFiberRoot(rendererID, root, priority, didError),
1082
+ );
1083
+ }
1096
1084
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
1085
+ setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
1097
1086
  const rootMemoizedState = root.current.memoizedState;
1098
1087
  const isUnmounting =
1099
1088
  rootMemoizedState === null ||
@@ -1106,8 +1095,14 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1106
1095
  _fiberRoots.add(root);
1107
1096
  rootRendererIds.set(root, rendererID);
1108
1097
  }
1109
- for (const listener of commitFiberRootListeners) {
1110
- listener(rendererID, root, priority, didError);
1098
+ for (const { options } of instrumentationSubscriptions) {
1099
+ if (options.onCommitFiberRoot) {
1100
+ runInstrumentationCallback(
1101
+ options.name ?? BIPPY_INSTRUMENTATION_STRING,
1102
+ "onCommitFiberRoot",
1103
+ () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
1104
+ );
1105
+ }
1111
1106
  }
1112
1107
  };
1113
1108
  dispatchers.onCommitFiberRoot = dispatchCommitFiberRoot;
@@ -1123,12 +1118,23 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1123
1118
  rendererID,
1124
1119
  fiber,
1125
1120
  ) => {
1126
- prevOnCommitFiberUnmount?.(rendererID, fiber);
1121
+ setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
1122
+ if (prevOnCommitFiberUnmount) {
1123
+ runReactDevToolsCallback("onCommitFiberUnmount", () =>
1124
+ prevOnCommitFiberUnmount(rendererID, fiber),
1125
+ );
1126
+ }
1127
1127
  if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
1128
1128
  return;
1129
1129
  }
1130
- for (const listener of commitFiberUnmountListeners) {
1131
- listener(rendererID, fiber);
1130
+ for (const { options } of instrumentationSubscriptions) {
1131
+ if (options.onCommitFiberUnmount) {
1132
+ runInstrumentationCallback(
1133
+ options.name ?? BIPPY_INSTRUMENTATION_STRING,
1134
+ "onCommitFiberUnmount",
1135
+ () => options.onCommitFiberUnmount?.(rendererID, fiber),
1136
+ );
1137
+ }
1132
1138
  }
1133
1139
  };
1134
1140
  dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
@@ -1144,12 +1150,22 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1144
1150
  rendererID,
1145
1151
  root,
1146
1152
  ) => {
1147
- prevOnPostCommitFiberRoot?.(rendererID, root);
1153
+ if (prevOnPostCommitFiberRoot) {
1154
+ runReactDevToolsCallback("onPostCommitFiberRoot", () =>
1155
+ prevOnPostCommitFiberRoot(rendererID, root),
1156
+ );
1157
+ }
1148
1158
  if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
1149
1159
  return;
1150
1160
  }
1151
- for (const listener of postCommitFiberRootListeners) {
1152
- listener(rendererID, root);
1161
+ for (const { options } of instrumentationSubscriptions) {
1162
+ if (options.onPostCommitFiberRoot) {
1163
+ runInstrumentationCallback(
1164
+ options.name ?? BIPPY_INSTRUMENTATION_STRING,
1165
+ "onPostCommitFiberRoot",
1166
+ () => options.onPostCommitFiberRoot?.(rendererID, root),
1167
+ );
1168
+ }
1153
1169
  }
1154
1170
  };
1155
1171
  dispatchers.onPostCommitFiberRoot = dispatchPostCommitFiberRoot;
@@ -1166,10 +1182,20 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1166
1182
  root,
1167
1183
  children,
1168
1184
  ) => {
1169
- prevOnScheduleFiberRoot?.(rendererID, root, children);
1185
+ if (prevOnScheduleFiberRoot) {
1186
+ runReactDevToolsCallback("onScheduleFiberRoot", () =>
1187
+ prevOnScheduleFiberRoot(rendererID, root, children),
1188
+ );
1189
+ }
1170
1190
  if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
1171
- for (const listener of scheduleFiberRootListeners) {
1172
- listener(rendererID, root, children);
1191
+ for (const { options } of instrumentationSubscriptions) {
1192
+ if (options.onScheduleFiberRoot) {
1193
+ runInstrumentationCallback(
1194
+ options.name ?? BIPPY_INSTRUMENTATION_STRING,
1195
+ "onScheduleFiberRoot",
1196
+ () => options.onScheduleFiberRoot?.(rendererID, root, children),
1197
+ );
1198
+ }
1173
1199
  }
1174
1200
  };
1175
1201
  dispatchers.onScheduleFiberRoot = dispatchScheduleFiberRoot;
@@ -1196,30 +1222,31 @@ const ensureHookDispatchesToListeners = (rdtHook: ReactDevToolsGlobalHook): void
1196
1222
  * unsubscribe();
1197
1223
  */
1198
1224
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
1199
- const rdtHook = getRDTHook(options.onActive);
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
+ }
1200
1240
 
1201
1241
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
1202
1242
 
1203
- ensureHookDispatchesToListeners(rdtHook);
1204
-
1205
- const {
1206
- onActive,
1207
- onCommitFiberRoot,
1208
- onCommitFiberUnmount,
1209
- onPostCommitFiberRoot,
1210
- onScheduleFiberRoot,
1211
- } = options;
1212
- if (onCommitFiberRoot) commitFiberRootListeners.add(onCommitFiberRoot);
1213
- if (onCommitFiberUnmount) commitFiberUnmountListeners.add(onCommitFiberUnmount);
1214
- if (onPostCommitFiberRoot) postCommitFiberRootListeners.add(onPostCommitFiberRoot);
1215
- if (onScheduleFiberRoot) scheduleFiberRootListeners.add(onScheduleFiberRoot);
1243
+ setHookEventDispatchers(rdtHook);
1244
+ const subscription: InstrumentationSubscription = { options };
1245
+ instrumentationSubscriptions.add(subscription);
1216
1246
 
1217
1247
  return toUnsubscribe(() => {
1218
1248
  if (onActive) _onActiveListeners.delete(onActive);
1219
- if (onCommitFiberRoot) commitFiberRootListeners.delete(onCommitFiberRoot);
1220
- if (onCommitFiberUnmount) commitFiberUnmountListeners.delete(onCommitFiberUnmount);
1221
- if (onPostCommitFiberRoot) postCommitFiberRootListeners.delete(onPostCommitFiberRoot);
1222
- if (onScheduleFiberRoot) scheduleFiberRootListeners.delete(onScheduleFiberRoot);
1249
+ instrumentationSubscriptions.delete(subscription);
1223
1250
  });
1224
1251
  };
1225
1252
 
@@ -1307,6 +1334,18 @@ export const getFiberFromHostInstance = <T>(hostInstance: T): Fiber | null => {
1307
1334
  return null;
1308
1335
  };
1309
1336
 
1310
- export * from "./rdt-hook.js";
1337
+ export {
1338
+ BIPPY_INSTRUMENTATION_STRING,
1339
+ _onActiveListeners,
1340
+ _renderers,
1341
+ getRDTHook,
1342
+ hasRDTHook,
1343
+ installRDTHook,
1344
+ isRealReactDevtools,
1345
+ onRendererInject,
1346
+ patchRDTHook,
1347
+ safelyInstallRDTHook,
1348
+ version,
1349
+ } from "./rdt-hook.js";
1311
1350
  export * from "./unsubscribe.js";
1312
1351
  export type * from "./types.js";