bippy 0.7.3-dev.049a43d → 0.7.3-dev.0ac832d

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.
@@ -0,0 +1,13 @@
1
+ export const callListener = <Arguments extends unknown[]>(
2
+ listener: (...listenerArguments: Arguments) => unknown,
3
+ receiver: unknown,
4
+ ...listenerArguments: Arguments
5
+ ): void => {
6
+ try {
7
+ listener.apply(receiver, listenerArguments);
8
+ } catch (error) {
9
+ try {
10
+ console.error("Bippy instrumentation encountered an error:", error);
11
+ } catch {}
12
+ }
13
+ };
package/src/core.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // React must remain a type-only import because this module loads immediately after the DevTools hook.
2
2
  import type * as React from "react";
3
+ import { callListener } from "./call-listener.js";
3
4
 
4
5
  import type {
5
6
  Fiber,
@@ -21,6 +22,7 @@ import {
21
22
  removeActiveListener,
22
23
  } from "./rdt-hook.js";
23
24
  import type { ReactDevToolsTarget, Unsubscribe } from "./rdt-hook.js";
25
+ import { getCurrentFiberFromRoot } from "./react-internals/current-fiber.js";
24
26
  import {
25
27
  getReactWorkTagsForFiber,
26
28
  ReactBuildType,
@@ -64,8 +66,8 @@ export const isValidElement = (element: unknown): element is React.ReactElement
64
66
  typeof element === "object" &&
65
67
  element !== null &&
66
68
  "$$typeof" in element &&
67
- (String(element.$$typeof) === ReactSymbols.LEGACY_ELEMENT_SYMBOL_STRING ||
68
- String(element.$$typeof) === ReactSymbols.ELEMENT_SYMBOL_STRING);
69
+ (element.$$typeof === Symbol.for("react.element") ||
70
+ element.$$typeof === Symbol.for("react.transitional.element"));
69
71
 
70
72
  /**
71
73
  * Returns `true` if object is a React Fiber.
@@ -78,7 +80,7 @@ export const isFiber = (fiber: unknown): fiber is Fiber =>
78
80
  "return" in fiber &&
79
81
  "child" in fiber &&
80
82
  "sibling" in fiber &&
81
- "flags" in fiber;
83
+ ("flags" in fiber || "effectTag" in fiber);
82
84
 
83
85
  const isFiberRoot = (fiberRoot: unknown): fiberRoot is FiberRoot =>
84
86
  typeof fiberRoot === "object" &&
@@ -128,7 +130,7 @@ export const isCompositeFiber = (fiber: Fiber): boolean => {
128
130
  */
129
131
  export const didFiberRender = (fiber: Fiber): boolean => {
130
132
  const nextProps = fiber.memoizedProps;
131
- const prevProps = fiber.alternate?.memoizedProps || {};
133
+ const prevProps = fiber.alternate?.memoizedProps;
132
134
  const flags = fiber.flags ?? fiber.effectTag ?? 0;
133
135
  const workTags = getReactWorkTagsForFiber(fiber);
134
136
 
@@ -200,41 +202,57 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
200
202
  }
201
203
  };
202
204
 
205
+ interface TraverseFiber {
206
+ (
207
+ fiber: Fiber | null,
208
+ selector: (node: Fiber) => boolean | void,
209
+ ascending?: boolean,
210
+ ): Fiber | null;
211
+ (
212
+ fiber: Fiber | null,
213
+ selector: (node: Fiber) => Promise<boolean | void>,
214
+ ascending?: boolean,
215
+ ): Promise<Fiber | null>;
216
+ (
217
+ fiber: Fiber | null,
218
+ selector: FiberSelector,
219
+ ascending?: boolean,
220
+ ): Fiber | null | Promise<Fiber | null>;
221
+ }
222
+
203
223
  /**
204
224
  * Traverses up or down a {@link Fiber}, return `true` to stop and select a node.
205
225
  */
206
- export function traverseFiber(
207
- fiber: Fiber | null,
208
- selector: (node: Fiber) => boolean | void,
209
- ascending?: boolean,
210
- ): Fiber | null;
211
- export function traverseFiber(
212
- fiber: Fiber | null,
213
- selector: (node: Fiber) => Promise<boolean | void>,
214
- ascending?: boolean,
215
- ): Promise<Fiber | null>;
216
- export function traverseFiber(
217
- fiber: Fiber | null,
218
- selector: FiberSelector,
219
- ascending?: boolean,
220
- ): Fiber | null | Promise<Fiber | null>;
221
- export function traverseFiber(
226
+ export const traverseFiber = ((
222
227
  fiber: Fiber | null,
223
228
  selector: FiberSelector,
224
229
  ascending = false,
225
- ): Fiber | null | Promise<Fiber | null> {
226
- if (!fiber) return null;
227
-
228
- const selection = selector(fiber);
229
- if (isPromiseLike<boolean | void>(selection)) {
230
- return Promise.resolve(selection).then((didSelectFiber) =>
231
- didSelectFiber === true ? fiber : traverseFiberChildren(fiber, selector, ascending),
232
- );
233
- }
234
- if (selection === true) return fiber;
235
-
236
- return traverseFiberChildren(fiber, selector, ascending);
237
- }
230
+ ): Fiber | null | Promise<Fiber | null> => {
231
+ const pendingSiblings: Fiber[] = [];
232
+ const getNextFiber = (currentFiber: Fiber): Fiber | null => {
233
+ if (ascending) return currentFiber.return;
234
+ if (currentFiber !== fiber && currentFiber.sibling) {
235
+ pendingSiblings.push(currentFiber.sibling);
236
+ }
237
+ return currentFiber.child ?? pendingSiblings.pop() ?? null;
238
+ };
239
+ const visit = (firstFiber: Fiber | null): Fiber | null | Promise<Fiber | null> => {
240
+ let currentFiber = firstFiber;
241
+ while (currentFiber) {
242
+ const selectedFiber = currentFiber;
243
+ const selection = selector(selectedFiber);
244
+ if (isPromiseLike<boolean | void>(selection)) {
245
+ return Promise.resolve(selection).then((didSelectFiber) =>
246
+ didSelectFiber === true ? selectedFiber : visit(getNextFiber(selectedFiber)),
247
+ );
248
+ }
249
+ if (selection === true) return selectedFiber;
250
+ currentFiber = getNextFiber(selectedFiber);
251
+ }
252
+ return null;
253
+ };
254
+ return visit(fiber);
255
+ }) as TraverseFiber;
238
256
 
239
257
  const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
240
258
  (typeof value === "object" || typeof value === "function") &&
@@ -242,32 +260,6 @@ const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
242
260
  "then" in value &&
243
261
  typeof value.then === "function";
244
262
 
245
- const traverseFiberChildren = (
246
- fiber: Fiber,
247
- selector: FiberSelector,
248
- ascending: boolean,
249
- ): Fiber | null | Promise<Fiber | null> => {
250
- const firstChild = ascending ? fiber.return : fiber.child;
251
- return traverseFiberSiblings(firstChild, selector, ascending);
252
- };
253
-
254
- const traverseFiberSiblings = (
255
- fiber: Fiber | null,
256
- selector: FiberSelector,
257
- ascending: boolean,
258
- ): Fiber | null | Promise<Fiber | null> => {
259
- if (!fiber) return null;
260
-
261
- const nextSibling = ascending ? null : fiber.sibling;
262
- const match = traverseFiber(fiber, selector, ascending);
263
- if (isPromiseLike<Fiber | null>(match)) {
264
- return Promise.resolve(match).then(
265
- (resolvedMatch) => resolvedMatch ?? traverseFiberSiblings(nextSibling, selector, ascending),
266
- );
267
- }
268
- return match ?? traverseFiberSiblings(nextSibling, selector, ascending);
269
- };
270
-
271
263
  /**
272
264
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
273
265
  */
@@ -281,7 +273,14 @@ export const hasMemoCache = (fiber: Fiber): boolean => {
281
273
  export const getType = (type: unknown): null | React.ComponentType<unknown> => {
282
274
  if (isComponentType(type)) return type;
283
275
  if (typeof type !== "object" || type === null) return null;
284
- return getType(Reflect.get(type, "type") ?? Reflect.get(type, "render"));
276
+ const visitedTypes = new Set<object>();
277
+ let currentType: unknown = type;
278
+ while (typeof currentType === "object" && currentType !== null) {
279
+ if (visitedTypes.has(currentType)) return null;
280
+ visitedTypes.add(currentType);
281
+ currentType = Reflect.get(currentType, "type") ?? Reflect.get(currentType, "render");
282
+ }
283
+ return isComponentType(currentType) ? currentType : null;
285
284
  };
286
285
 
287
286
  /**
@@ -331,6 +330,8 @@ const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
331
330
  export const getLatestFiber = (fiber: Fiber): Fiber => {
332
331
  const alternate = fiber.alternate;
333
332
  if (!alternate) return fiber;
333
+ const currentFiber = getCurrentFiberFromRoot(fiber);
334
+ if (currentFiber) return currentFiber;
334
335
 
335
336
  let rootFiber = fiber;
336
337
  while (rootFiber.return) {
@@ -458,131 +459,91 @@ const releaseFiberId = (fiber: Fiber): void => {
458
459
  }
459
460
  };
460
461
 
461
- const mountFiberRecursively = (
462
+ const getRenderedChild = (fiber: Fiber, skipPrimaryWrapper: boolean): Fiber | null => {
463
+ const workTags = getReactWorkTagsForFiber(fiber);
464
+ if (fiber.tag !== workTags.SuspenseComponent) return fiber.child;
465
+ if (fiber.memoizedState !== null) return fiber.child?.sibling?.child ?? null;
466
+ return skipPrimaryWrapper && workTags.OffscreenComponent !== -1
467
+ ? (fiber.child?.child ?? null)
468
+ : fiber.child;
469
+ };
470
+
471
+ const mountFiberTree = (
462
472
  onRender: RenderHandler,
463
473
  firstChild: Fiber,
464
474
  traverseSiblings: boolean,
465
475
  ): void => {
476
+ const pendingSiblings: Fiber[] = [];
466
477
  let fiber: Fiber | null = firstChild;
467
-
468
- while (fiber !== null) {
478
+ while (fiber) {
469
479
  getFiberId(fiber);
470
- const shouldIncludeInTree = !shouldFilterFiber(fiber);
471
- if (shouldIncludeInTree && didFiberRender(fiber)) {
472
- onRender(fiber, "mount");
473
- }
474
-
475
- if (fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent) {
476
- const isTimedOut = fiber.memoizedState !== null;
477
- if (isTimedOut) {
478
- // Special case: if Suspense mounts in a timed-out state,
479
- // get the fallback child from the inner fragment and mount
480
- // it as if it was our own child. Updates handle this too.
481
- const primaryChildFragment = fiber.child;
482
- const fallbackChildFragment = primaryChildFragment ? primaryChildFragment.sibling : null;
483
- if (fallbackChildFragment) {
484
- const fallbackChild = fallbackChildFragment.child;
485
- if (fallbackChild !== null) {
486
- mountFiberRecursively(onRender, fallbackChild, false);
487
- }
488
- }
489
- } else {
490
- const primaryChild = fiber.child?.child ?? null;
491
- if (primaryChild !== null) {
492
- mountFiberRecursively(onRender, primaryChild, false);
493
- }
494
- }
495
- } else if (fiber.child !== null) {
496
- mountFiberRecursively(onRender, fiber.child, true);
497
- }
498
- fiber = traverseSiblings ? fiber.sibling : null;
480
+ if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "mount");
481
+ if (traverseSiblings && fiber.sibling) pendingSiblings.push(fiber.sibling);
482
+ fiber = getRenderedChild(fiber, true) ?? pendingSiblings.pop() ?? null;
483
+ traverseSiblings = true;
499
484
  }
500
485
  };
501
486
 
502
- const updateFiberRecursively = (
487
+ interface FiberUpdate {
488
+ fiber: Fiber;
489
+ previousFiber: Fiber | null;
490
+ traverseSiblings: boolean;
491
+ }
492
+
493
+ const updateFiberTree = (
503
494
  onRender: RenderHandler,
504
495
  nextFiber: Fiber,
505
496
  prevFiber: Fiber | null,
506
497
  ): void => {
507
- getFiberId(nextFiber);
508
- if (!prevFiber) return;
509
- getFiberId(prevFiber);
510
-
511
- const isSuspense = nextFiber.tag === getReactWorkTagsForFiber(nextFiber).SuspenseComponent;
512
-
513
- const shouldIncludeInTree = !shouldFilterFiber(nextFiber);
514
- if (shouldIncludeInTree && didFiberRender(nextFiber)) {
515
- onRender(nextFiber, "update");
516
- }
517
-
518
- // The behavior of timed-out Suspense trees is unique.
519
- // Rather than unmount the timed out content (and possibly lose important state),
520
- // React re-parents this content within a hidden Fragment while the fallback is showing.
521
- // This behavior doesn't need to be observable in the DevTools though.
522
- // It might even result in a bad user experience for e.g. node selection in the Elements panel.
523
- // The easiest fix is to strip out the intermediate Fragment fibers,
524
- // so the Elements panel and Profiler don't need to special case them.
525
- // Suspense components only have a non-null memoizedState if they're timed-out.
526
- const prevDidTimeout = isSuspense && prevFiber.memoizedState !== null;
527
- const nextDidTimeOut = isSuspense && nextFiber.memoizedState !== null;
528
-
529
- // The logic below is inspired by the code paths in updateSuspenseComponent()
530
- // inside ReactFiberBeginWork in the React source code.
531
- if (prevDidTimeout && nextDidTimeOut) {
532
- // Fallback -> Fallback:
533
- // 1. Reconcile fallback set.
534
- const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
535
- // Note: We can't use nextFiber.child.sibling.alternate
536
- // because the set is special and alternate may not exist.
537
- const prevFallbackChildSet = prevFiber.child?.sibling ?? null;
538
-
539
- if (nextFallbackChildSet !== null && prevFallbackChildSet !== null) {
540
- updateFiberRecursively(onRender, nextFallbackChildSet, prevFallbackChildSet);
541
- }
542
- } else if (prevDidTimeout && !nextDidTimeOut) {
543
- // Fallback -> Primary:
544
- // 1. Unmount fallback set
545
- // Note: don't emulate fallback unmount because React actually did it.
546
- // 2. Mount primary set
547
- const nextPrimaryChildSet = nextFiber.child;
548
-
549
- if (nextPrimaryChildSet !== null) {
550
- mountFiberRecursively(onRender, nextPrimaryChildSet, true);
498
+ if (!prevFiber) {
499
+ getFiberId(nextFiber);
500
+ return;
501
+ }
502
+ const pendingUpdates: FiberUpdate[] = [
503
+ { fiber: nextFiber, previousFiber: prevFiber, traverseSiblings: false },
504
+ ];
505
+ let update: FiberUpdate | undefined;
506
+ while ((update = pendingUpdates.pop())) {
507
+ const { fiber, previousFiber, traverseSiblings } = update;
508
+ if (traverseSiblings && fiber.sibling) {
509
+ pendingUpdates.push({
510
+ fiber: fiber.sibling,
511
+ previousFiber: fiber.sibling.alternate,
512
+ traverseSiblings: true,
513
+ });
551
514
  }
552
- } else if (!prevDidTimeout && nextDidTimeOut) {
553
- // Primary -> Fallback:
554
- // 1. Hide primary set
555
- // This is not a real unmount, so it won't get reported by React.
556
- // We need to manually walk the previous tree and record unmounts.
557
- unmountFiberChildrenRecursively(onRender, prevFiber);
558
-
559
- // 2. Mount fallback set
560
- const nextFallbackChildSet = nextFiber.child?.sibling ?? null;
561
-
562
- if (nextFallbackChildSet !== null) {
563
- mountFiberRecursively(onRender, nextFallbackChildSet, true);
515
+ if (!previousFiber) {
516
+ mountFiberTree(onRender, fiber, false);
517
+ continue;
564
518
  }
565
- } else if (nextFiber.child !== prevFiber.child) {
566
- // Common case: Primary -> Primary.
567
- // This is the same code path as for non-Suspense fibers.
568
-
569
- // If the first child is different, we need to traverse them.
570
- // Each next child will be either a new child (mount) or an alternate (update).
571
- let nextChild = nextFiber.child;
572
-
573
- while (nextChild) {
574
- // We already know children will be referentially different because
575
- // they are either new mounts or alternates of previous children.
576
- // Schedule updates and mounts depending on whether alternates exist.
577
- // We don't track deletions here because they are reported separately.
578
- if (nextChild.alternate) {
579
- updateFiberRecursively(onRender, nextChild, nextChild.alternate);
580
- } else {
581
- mountFiberRecursively(onRender, nextChild, false);
519
+ getFiberId(fiber);
520
+ getFiberId(previousFiber);
521
+ if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "update");
522
+ const isSuspense = fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent;
523
+ const wasTimedOut = isSuspense && previousFiber.memoizedState !== null;
524
+ const isTimedOut = isSuspense && fiber.memoizedState !== null;
525
+ if (wasTimedOut && isTimedOut) {
526
+ const nextFallback = fiber.child?.sibling;
527
+ const previousFallback = previousFiber.child?.sibling;
528
+ if (nextFallback && previousFallback) {
529
+ pendingUpdates.push({
530
+ fiber: nextFallback,
531
+ previousFiber: previousFallback,
532
+ traverseSiblings: false,
533
+ });
582
534
  }
583
-
584
- // Try the next child.
585
- nextChild = nextChild.sibling;
535
+ } else if (wasTimedOut && !isTimedOut) {
536
+ if (fiber.child) mountFiberTree(onRender, fiber.child, true);
537
+ } else if (!wasTimedOut && isTimedOut) {
538
+ unmountFiberChildren(onRender, previousFiber);
539
+ const fallback = fiber.child?.sibling;
540
+ if (fallback) mountFiberTree(onRender, fallback, true);
541
+ } else if (fiber.child && fiber.child !== previousFiber.child) {
542
+ pendingUpdates.push({
543
+ fiber: fiber.child,
544
+ previousFiber: fiber.child.alternate,
545
+ traverseSiblings: true,
546
+ });
586
547
  }
587
548
  }
588
549
  };
@@ -595,30 +556,18 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
595
556
  }
596
557
  };
597
558
 
598
- const unmountFiberChildrenRecursively = (onRender: RenderHandler, fiber: Fiber): void => {
599
- // We might meet a nested Suspense on our way.
600
- const isTimedOutSuspense =
601
- fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent && fiber.memoizedState !== null;
602
- let child = fiber.child;
603
-
604
- if (isTimedOutSuspense) {
605
- // If it's showing fallback tree, let's traverse it instead.
606
- const primaryChildFragment = fiber.child;
607
- const fallbackChildFragment = primaryChildFragment?.sibling ?? null;
608
-
609
- // Skip over to the real Fiber child.
610
- child = fallbackChildFragment?.child ?? null;
611
- }
612
-
613
- while (child !== null) {
614
- // Record simulated unmounts children-first.
615
- // We skip nodes without return because those are real unmounts.
559
+ const unmountFiberChildren = (onRender: RenderHandler, fiber: Fiber): void => {
560
+ const pendingSiblings: Fiber[] = [];
561
+ let child = getRenderedChild(fiber, false);
562
+ while (child) {
563
+ if (child.sibling) pendingSiblings.push(child.sibling);
616
564
  if (child.return !== null) {
617
565
  unmountFiber(onRender, child);
618
- unmountFiberChildrenRecursively(onRender, child);
566
+ child = getRenderedChild(child, false);
567
+ } else {
568
+ child = null;
619
569
  }
620
-
621
- child = child.sibling;
570
+ child ??= pendingSiblings.pop() ?? null;
622
571
  }
623
572
  };
624
573
 
@@ -668,14 +617,14 @@ export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: Render
668
617
  const isMounted = isRootFiberMounted(fiber);
669
618
 
670
619
  if (!wasMounted && isMounted) {
671
- mountFiberRecursively(onRender, fiber, false);
620
+ mountFiberTree(onRender, fiber, false);
672
621
  } else if (wasMounted && isMounted) {
673
- updateFiberRecursively(onRender, fiber, fiber.alternate);
622
+ updateFiberTree(onRender, fiber, fiber.alternate);
674
623
  } else if (wasMounted && !isMounted) {
675
624
  unmountFiber(onRender, fiber);
676
625
  }
677
626
  } else {
678
- mountFiberRecursively(onRender, fiber, true);
627
+ mountFiberTree(onRender, fiber, true);
679
628
  }
680
629
 
681
630
  rootInstance.prevFiber = fiber;
@@ -735,7 +684,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
735
684
  didError,
736
685
  ) => {
737
686
  if (prevOnCommitFiberRoot) {
738
- prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
687
+ callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
739
688
  }
740
689
  if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
741
690
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
@@ -752,7 +701,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
752
701
  }
753
702
  for (const { options, target } of instrumentationSubscriptions) {
754
703
  if (target === hookTargets.get(rdtHook) && options.onCommitFiberRoot) {
755
- options.onCommitFiberRoot(rendererID, root, priority, didError);
704
+ callListener(options.onCommitFiberRoot, options, rendererID, root, priority, didError);
756
705
  }
757
706
  }
758
707
  };
@@ -771,7 +720,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
771
720
  ) => {
772
721
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
773
722
  if (prevOnCommitFiberUnmount) {
774
- prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
723
+ callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
775
724
  }
776
725
  if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
777
726
  return;
@@ -779,7 +728,7 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
779
728
  try {
780
729
  for (const { options, target } of instrumentationSubscriptions) {
781
730
  if (target === hookTargets.get(rdtHook) && options.onCommitFiberUnmount) {
782
- options.onCommitFiberUnmount(rendererID, fiber);
731
+ callListener(options.onCommitFiberUnmount, options, rendererID, fiber);
783
732
  }
784
733
  }
785
734
  } finally {
@@ -800,14 +749,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
800
749
  root,
801
750
  ) => {
802
751
  if (prevOnPostCommitFiberRoot) {
803
- prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
752
+ callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
804
753
  }
805
754
  if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
806
755
  return;
807
756
  }
808
757
  for (const { options, target } of instrumentationSubscriptions) {
809
758
  if (target === hookTargets.get(rdtHook) && options.onPostCommitFiberRoot) {
810
- options.onPostCommitFiberRoot(rendererID, root);
759
+ callListener(options.onPostCommitFiberRoot, options, rendererID, root);
811
760
  }
812
761
  }
813
762
  };
@@ -826,12 +775,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
826
775
  children,
827
776
  ) => {
828
777
  if (prevOnScheduleFiberRoot) {
829
- prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
778
+ callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
830
779
  }
831
780
  if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
832
781
  for (const { options, target } of instrumentationSubscriptions) {
833
782
  if (target === hookTargets.get(rdtHook) && options.onScheduleFiberRoot) {
834
- options.onScheduleFiberRoot(rendererID, root, children);
783
+ callListener(options.onScheduleFiberRoot, options, rendererID, root, children);
835
784
  }
836
785
  }
837
786
  };
@@ -967,9 +916,11 @@ export const getFiber = <HostInstance>(
967
916
 
968
917
  for (const key of Object.keys(hostInstance)) {
969
918
  if (isFiberPropertyKey(key)) {
970
- knownFiberPropertyKeys.add(key);
971
919
  const fiber = Reflect.get(hostInstance, key);
972
- if (isFiber(fiber)) return fiber;
920
+ if (isFiber(fiber)) {
921
+ knownFiberPropertyKeys.add(key);
922
+ return fiber;
923
+ }
973
924
  }
974
925
  }
975
926
  }
package/src/rdt-hook.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // This module must load before React so renderers can inject into the hook.
2
2
 
3
+ import { callListener } from "./call-listener.js";
3
4
  import type { FiberRoot, ReactDevToolsGlobalHook, ReactRenderer } from "./react-internals/index.js";
4
5
 
5
6
  export interface Unsubscribe extends Disposable {
@@ -49,6 +50,8 @@ export const isFiberRootUnmounted = (fiberRoot: FiberRoot): boolean => {
49
50
  };
50
51
 
51
52
  const checkDCE = (functionToCheck: unknown): void => {
53
+ // HACK: Node loads React's unbundled entrypoints, where development guards are intentionally not eliminated.
54
+ if (typeof process !== "undefined" && process.versions?.node) return;
52
55
  try {
53
56
  const code = Function.prototype.toString.call(functionToCheck);
54
57
  if (code.indexOf("^_^") > -1) {
@@ -115,7 +118,7 @@ export const removeActiveListener = (
115
118
 
116
119
  const notifyActiveListeners = (target: ReactDevToolsTarget): void => {
117
120
  for (const listener of _onActiveListeners) {
118
- if (activeListenerTargets.get(listener)?.has(target)) listener();
121
+ if (activeListenerTargets.get(listener)?.has(target)) callListener(listener, undefined);
119
122
  }
120
123
  };
121
124
 
@@ -124,7 +127,7 @@ const notifyRendererInjectListeners = (
124
127
  renderer: ReactRenderer,
125
128
  ): void => {
126
129
  for (const subscription of rendererInjectSubscriptions) {
127
- if (subscription.target === target) subscription.listener(renderer);
130
+ if (subscription.target === target) callListener(subscription.listener, subscription, renderer);
128
131
  }
129
132
  };
130
133
 
@@ -133,7 +136,7 @@ const notifyRDTHookReplaceListeners = (
133
136
  target: ReactDevToolsTarget,
134
137
  ): void => {
135
138
  for (const listener of rdtHookReplaceListeners) {
136
- listener(rdtHook, target);
139
+ callListener(listener, undefined, rdtHook, target);
137
140
  }
138
141
  };
139
142
 
@@ -324,7 +327,7 @@ export const patchRDTHook = (
324
327
  };
325
328
  }
326
329
  if (!didNotifyActiveListeners && (renderers.size || rdtHook._instrumentationIsActive)) {
327
- onActive?.();
330
+ if (onActive) callListener(onActive, undefined);
328
331
  }
329
332
  };
330
333
 
@@ -0,0 +1,84 @@
1
+ // Adapted from ReactFiberTreeReflection.js. Copyright (c) Meta Platforms, Inc. and affiliates. MIT licensed.
2
+ import type { Fiber } from "./types.js";
3
+ import { getReactWorkTagsForFiber } from "./index.js";
4
+
5
+ export const getCurrentFiberFromRoot = (fiber: Fiber): Fiber | null => {
6
+ const alternate = fiber.alternate;
7
+ if (!alternate) return null;
8
+ let firstBranch = fiber;
9
+ let secondBranch = alternate;
10
+
11
+ while (firstBranch.return) {
12
+ const firstParent = firstBranch.return;
13
+ const secondParent = firstParent.alternate;
14
+ if (!secondParent) {
15
+ const nextParent = firstParent.return;
16
+ if (!nextParent) return null;
17
+ firstBranch = secondBranch = nextParent;
18
+ continue;
19
+ }
20
+
21
+ if (firstParent.child === secondParent.child) {
22
+ let child = firstParent.child;
23
+ while (child) {
24
+ if (child === firstBranch || child === secondBranch) {
25
+ let root = firstParent;
26
+ while (root.return) root = root.return;
27
+ if (root.tag !== getReactWorkTagsForFiber(root).HostRoot) return null;
28
+ return child === firstBranch ? fiber : alternate;
29
+ }
30
+ child = child.sibling;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ if (firstBranch.return !== secondBranch.return) {
36
+ firstBranch = firstParent;
37
+ secondBranch = secondParent;
38
+ } else {
39
+ let didFindChild = false;
40
+ let child = firstParent.child;
41
+ while (child) {
42
+ if (child === firstBranch) {
43
+ firstBranch = firstParent;
44
+ secondBranch = secondParent;
45
+ didFindChild = true;
46
+ break;
47
+ }
48
+ if (child === secondBranch) {
49
+ firstBranch = secondParent;
50
+ secondBranch = firstParent;
51
+ didFindChild = true;
52
+ break;
53
+ }
54
+ child = child.sibling;
55
+ }
56
+ if (!didFindChild) {
57
+ child = secondParent.child;
58
+ while (child) {
59
+ if (child === firstBranch) {
60
+ firstBranch = secondParent;
61
+ secondBranch = firstParent;
62
+ didFindChild = true;
63
+ break;
64
+ }
65
+ if (child === secondBranch) {
66
+ firstBranch = firstParent;
67
+ secondBranch = secondParent;
68
+ didFindChild = true;
69
+ break;
70
+ }
71
+ child = child.sibling;
72
+ }
73
+ if (!didFindChild) return null;
74
+ }
75
+ }
76
+ if (firstBranch.alternate !== secondBranch) return null;
77
+ }
78
+
79
+ if (firstBranch.tag !== getReactWorkTagsForFiber(firstBranch).HostRoot) return null;
80
+ const root = firstBranch.stateNode;
81
+ if (typeof root !== "object" || root === null || !("current" in root)) return null;
82
+ if (root.current === firstBranch) return fiber;
83
+ return root.current === secondBranch ? alternate : null;
84
+ };