bippy 0.7.3-dev.775b69e → 0.7.3-dev.7d8af8d

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
@@ -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,
@@ -167,16 +168,20 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
167
168
  // https://github.com/bvaughn/react-devtools-experimental/issues/197
168
169
  return true;
169
170
 
170
- case workTags.Fragment:
171
+ case workTags.HostPortal:
171
172
  case workTags.HostText:
172
173
  case workTags.LegacyHiddenComponent:
173
174
  case workTags.OffscreenComponent:
175
+ case workTags.Throw:
174
176
  return true;
175
177
 
176
178
  case workTags.HostRoot:
177
179
  // It is never valid to filter the root element.
178
180
  return false;
179
181
 
182
+ case workTags.Fragment:
183
+ return fiber.key === null;
184
+
180
185
  default: {
181
186
  const symbolOrNumber =
182
187
  typeof fiber.type === "object" && fiber.type !== null ? fiber.type.$$typeof : fiber.type;
@@ -184,7 +189,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
184
189
  if (typeof symbolOrNumber === "symbol") {
185
190
  return (
186
191
  symbolOrNumber.description === ReactSymbols.CONCURRENT_MODE_SYMBOL_DESCRIPTION ||
187
- symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION
192
+ symbolOrNumber.description === ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_DESCRIPTION ||
193
+ symbolOrNumber.description === ReactSymbols.STRICT_MODE_SYMBOL_DESCRIPTION
188
194
  );
189
195
  }
190
196
 
@@ -192,6 +198,8 @@ const shouldFilterFiber = (fiber: Fiber): boolean => {
192
198
  case ReactSymbols.CONCURRENT_MODE_NUMBER:
193
199
  case ReactSymbols.CONCURRENT_MODE_SYMBOL_STRING:
194
200
  case ReactSymbols.DEPRECATED_ASYNC_MODE_SYMBOL_STRING:
201
+ case ReactSymbols.STRICT_MODE_NUMBER:
202
+ case ReactSymbols.STRICT_MODE_SYMBOL_STRING:
195
203
  return true;
196
204
 
197
205
  default:
@@ -240,8 +248,15 @@ export const traverseFiber = ((
240
248
  while (currentFiber) {
241
249
  const selectedFiber = currentFiber;
242
250
  const selection = selector(selectedFiber);
243
- if (isPromiseLike<boolean | void>(selection)) {
244
- return Promise.resolve(selection).then((didSelectFiber) =>
251
+ const then =
252
+ selection !== null && (typeof selection === "object" || typeof selection === "function")
253
+ ? selection.then
254
+ : null;
255
+ if (typeof then === "function") {
256
+ return Promise.resolve<boolean | void>({
257
+ // oxlint-disable-next-line unicorn/no-thenable -- Assimilate the captured method without reading the accessor twice.
258
+ then: (resolve, reject) => Reflect.apply(then, selection, [resolve, reject]),
259
+ }).then((didSelectFiber) =>
245
260
  didSelectFiber === true ? selectedFiber : visit(getNextFiber(selectedFiber)),
246
261
  );
247
262
  }
@@ -253,12 +268,6 @@ export const traverseFiber = ((
253
268
  return visit(fiber);
254
269
  }) as TraverseFiber;
255
270
 
256
- const isPromiseLike = <Result>(value: unknown): value is PromiseLike<Result> =>
257
- (typeof value === "object" || typeof value === "function") &&
258
- value !== null &&
259
- "then" in value &&
260
- typeof value.then === "function";
261
-
262
271
  /**
263
272
  * Returns `true` if the {@link Fiber} uses React Compiler's memo cache.
264
273
  */
@@ -322,6 +331,7 @@ export const isInstrumentationActive = (target: ReactDevToolsTarget = globalThis
322
331
  export const _fiberRoots = new Set<FiberRoot>();
323
332
  const rootRendererIds = new WeakMap<FiberRoot, number>();
324
333
  const rootHooks = new WeakMap<FiberRoot, ReactDevToolsGlobalHook>();
334
+ let currentUnmountingFiber: Fiber | null = null;
325
335
 
326
336
  /**
327
337
  * Returns the latest fiber (since it may be double-buffered).
@@ -330,7 +340,9 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
330
340
  const alternate = fiber.alternate;
331
341
  if (!alternate) return fiber;
332
342
  const currentFiber = getCurrentFiberFromRoot(fiber);
333
- if (currentFiber) return currentFiber;
343
+ const isUnmountingPair = fiber === currentUnmountingFiber || alternate === currentUnmountingFiber;
344
+ if (currentFiber && (!isUnmountingPair || currentFiber === currentUnmountingFiber))
345
+ return currentFiber;
334
346
 
335
347
  let rootFiber = fiber;
336
348
  while (rootFiber.return) {
@@ -342,6 +354,7 @@ export const getLatestFiber = (fiber: Fiber): Fiber => {
342
354
  });
343
355
  if (latestFiber) return latestFiber;
344
356
  }
357
+ if (currentFiber) return currentFiber;
345
358
 
346
359
  if (alternate.actualStartTime && fiber.actualStartTime) {
347
360
  return alternate.actualStartTime > fiber.actualStartTime ? alternate : fiber;
@@ -405,14 +418,24 @@ const fiberIdFinalizationRegistry =
405
418
  const createFiberReference = (fiber: Fiber): FiberReference =>
406
419
  typeof WeakRef === "function" ? new WeakRef(fiber) : { deref: () => fiber };
407
420
 
408
- export const setFiberId = (fiber: Fiber, fiberId: number = nextFiberId++): void => {
421
+ const getNextFiberId = (): number => {
422
+ if (!Number.isSafeInteger(nextFiberId)) throw new RangeError("Fiber ID space exhausted");
423
+ return nextFiberId++;
424
+ };
425
+
426
+ export const setFiberId = (fiber: Fiber, fiberId: number = getNextFiberId()): void => {
409
427
  const previousFiberId = fiberIdMap.get(fiber);
410
- if (previousFiberId !== undefined && previousFiberId !== fiberId) {
428
+ if (
429
+ previousFiberId !== undefined &&
430
+ previousFiberId !== fiberId &&
431
+ fiberByIdMap.get(previousFiberId)?.deref() === fiber
432
+ ) {
411
433
  fiberByIdMap.delete(previousFiberId);
412
434
  }
413
435
  fiberIdMap.set(fiber, fiberId);
414
436
  fiberByIdMap.set(fiberId, createFiberReference(fiber));
415
- fiberIdFinalizationRegistry?.register(fiber, fiberId);
437
+ if (previousFiberId !== undefined) fiberIdFinalizationRegistry?.unregister(fiber);
438
+ fiberIdFinalizationRegistry?.register(fiber, fiberId, fiber);
416
439
  if (Number.isSafeInteger(fiberId) && fiberId >= nextFiberId) {
417
440
  nextFiberId = fiberId + 1;
418
441
  }
@@ -422,10 +445,17 @@ export const getFiberId = (fiber: Fiber): number => {
422
445
  let currentFiberId = fiberIdMap.get(fiber);
423
446
  if (currentFiberId === undefined && fiber.alternate) {
424
447
  currentFiberId = fiberIdMap.get(fiber.alternate);
425
- if (currentFiberId !== undefined) setFiberId(fiber, currentFiberId);
448
+ if (currentFiberId !== undefined) {
449
+ const assignedFiber = fiberByIdMap.get(currentFiberId)?.deref();
450
+ if (assignedFiber && assignedFiber !== fiber.alternate) {
451
+ fiberIdMap.set(fiber, currentFiberId);
452
+ } else {
453
+ setFiberId(fiber, currentFiberId);
454
+ }
455
+ }
426
456
  }
427
457
  if (currentFiberId === undefined) {
428
- currentFiberId = nextFiberId++;
458
+ currentFiberId = getNextFiberId();
429
459
  setFiberId(fiber, currentFiberId);
430
460
  }
431
461
  return currentFiberId;
@@ -448,6 +478,7 @@ const releaseFiberId = (fiber: Fiber): void => {
448
478
  const fiberId = fiberIdMap.get(relatedFiber);
449
479
  if (fiberId !== undefined) fiberIds.add(fiberId);
450
480
  fiberIdMap.delete(relatedFiber);
481
+ fiberIdFinalizationRegistry?.unregister(relatedFiber);
451
482
  }
452
483
 
453
484
  for (const fiberId of fiberIds) {
@@ -458,131 +489,91 @@ const releaseFiberId = (fiber: Fiber): void => {
458
489
  }
459
490
  };
460
491
 
461
- const mountFiberRecursively = (
492
+ const getRenderedChild = (fiber: Fiber, skipPrimaryWrapper: boolean): Fiber | null => {
493
+ const workTags = getReactWorkTagsForFiber(fiber);
494
+ if (fiber.tag !== workTags.SuspenseComponent) return fiber.child;
495
+ if (fiber.memoizedState !== null) return fiber.child?.sibling?.child ?? null;
496
+ return skipPrimaryWrapper && workTags.OffscreenComponent !== -1
497
+ ? (fiber.child?.child ?? null)
498
+ : fiber.child;
499
+ };
500
+
501
+ const mountFiberTree = (
462
502
  onRender: RenderHandler,
463
503
  firstChild: Fiber,
464
504
  traverseSiblings: boolean,
465
505
  ): void => {
506
+ const pendingSiblings: Fiber[] = [];
466
507
  let fiber: Fiber | null = firstChild;
467
-
468
- while (fiber !== null) {
508
+ while (fiber) {
469
509
  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, true);
487
- }
488
- }
489
- } else {
490
- const primaryChild = fiber.child?.child ?? null;
491
- if (primaryChild !== null) {
492
- mountFiberRecursively(onRender, primaryChild, true);
493
- }
494
- }
495
- } else if (fiber.child !== null) {
496
- mountFiberRecursively(onRender, fiber.child, true);
497
- }
498
- fiber = traverseSiblings ? fiber.sibling : null;
510
+ if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "mount");
511
+ if (traverseSiblings && fiber.sibling) pendingSiblings.push(fiber.sibling);
512
+ fiber = getRenderedChild(fiber, true) ?? pendingSiblings.pop() ?? null;
513
+ traverseSiblings = true;
499
514
  }
500
515
  };
501
516
 
502
- const updateFiberRecursively = (
517
+ interface FiberUpdate {
518
+ fiber: Fiber;
519
+ previousFiber: Fiber | null;
520
+ traverseSiblings: boolean;
521
+ }
522
+
523
+ const updateFiberTree = (
503
524
  onRender: RenderHandler,
504
525
  nextFiber: Fiber,
505
526
  prevFiber: Fiber | null,
506
527
  ): 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);
528
+ if (!prevFiber) {
529
+ getFiberId(nextFiber);
530
+ return;
531
+ }
532
+ const pendingUpdates: FiberUpdate[] = [
533
+ { fiber: nextFiber, previousFiber: prevFiber, traverseSiblings: false },
534
+ ];
535
+ let update: FiberUpdate | undefined;
536
+ while ((update = pendingUpdates.pop())) {
537
+ const { fiber, previousFiber, traverseSiblings } = update;
538
+ if (traverseSiblings && fiber.sibling) {
539
+ pendingUpdates.push({
540
+ fiber: fiber.sibling,
541
+ previousFiber: fiber.sibling.alternate,
542
+ traverseSiblings: true,
543
+ });
551
544
  }
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);
545
+ if (!previousFiber) {
546
+ mountFiberTree(onRender, fiber, false);
547
+ continue;
564
548
  }
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);
549
+ getFiberId(fiber);
550
+ getFiberId(previousFiber);
551
+ if (!shouldFilterFiber(fiber) && didFiberRender(fiber)) onRender(fiber, "update");
552
+ const isSuspense = fiber.tag === getReactWorkTagsForFiber(fiber).SuspenseComponent;
553
+ const wasTimedOut = isSuspense && previousFiber.memoizedState !== null;
554
+ const isTimedOut = isSuspense && fiber.memoizedState !== null;
555
+ if (wasTimedOut && isTimedOut) {
556
+ const nextFallback = fiber.child?.sibling;
557
+ const previousFallback = previousFiber.child?.sibling;
558
+ if (nextFallback) {
559
+ pendingUpdates.push({
560
+ fiber: nextFallback,
561
+ previousFiber: previousFallback ?? null,
562
+ traverseSiblings: false,
563
+ });
582
564
  }
583
-
584
- // Try the next child.
585
- nextChild = nextChild.sibling;
565
+ } else if (wasTimedOut && !isTimedOut) {
566
+ if (fiber.child) mountFiberTree(onRender, fiber.child, true);
567
+ } else if (!wasTimedOut && isTimedOut) {
568
+ unmountFiberChildren(onRender, previousFiber);
569
+ const fallback = fiber.child?.sibling;
570
+ if (fallback) mountFiberTree(onRender, fallback, true);
571
+ } else if (fiber.child && fiber.child !== previousFiber.child) {
572
+ pendingUpdates.push({
573
+ fiber: fiber.child,
574
+ previousFiber: fiber.child.alternate,
575
+ traverseSiblings: true,
576
+ });
586
577
  }
587
578
  }
588
579
  };
@@ -595,30 +586,18 @@ const unmountFiber = (onRender: RenderHandler, fiber: Fiber): void => {
595
586
  }
596
587
  };
597
588
 
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.
589
+ const unmountFiberChildren = (onRender: RenderHandler, fiber: Fiber): void => {
590
+ const pendingSiblings: Fiber[] = [];
591
+ let child = getRenderedChild(fiber, false);
592
+ while (child) {
593
+ if (child.sibling) pendingSiblings.push(child.sibling);
616
594
  if (child.return !== null) {
617
595
  unmountFiber(onRender, child);
618
- unmountFiberChildrenRecursively(onRender, child);
596
+ child = getRenderedChild(child, false);
597
+ } else {
598
+ child = null;
619
599
  }
620
-
621
- child = child.sibling;
600
+ child ??= pendingSiblings.pop() ?? null;
622
601
  }
623
602
  };
624
603
 
@@ -650,35 +629,35 @@ const isRootFiberMounted = (fiber: Fiber): boolean => {
650
629
  */
651
630
  export const traverseRenderedFibers = (root: Fiber | FiberRoot, onRender: RenderHandler): void => {
652
631
  const fiber = "current" in root ? root.current : root;
632
+ const rootKey = "current" in root || !isFiberRoot(root.stateNode) ? root : root.stateNode;
653
633
 
654
- let rootInstance = rootInstanceMap.get(root);
634
+ let rootInstance = rootInstanceMap.get(rootKey);
655
635
 
656
636
  if (!rootInstance) {
657
637
  rootInstance = { prevFiber: null };
658
- rootInstanceMap.set(root, rootInstance);
638
+ rootInstanceMap.set(rootKey, rootInstance);
659
639
  }
660
640
 
661
641
  const { prevFiber } = rootInstance;
642
+ rootInstance.prevFiber = fiber;
662
643
  if (!fiber) {
663
644
  if (prevFiber) {
664
645
  unmountFiber(onRender, prevFiber);
665
646
  }
666
647
  } else if (prevFiber !== null) {
667
- const wasMounted = isRootFiberMounted(prevFiber);
648
+ const wasMounted = isRootFiberMounted(fiber.alternate ?? prevFiber);
668
649
  const isMounted = isRootFiberMounted(fiber);
669
650
 
670
651
  if (!wasMounted && isMounted) {
671
- mountFiberRecursively(onRender, fiber, false);
652
+ mountFiberTree(onRender, fiber, false);
672
653
  } else if (wasMounted && isMounted) {
673
- updateFiberRecursively(onRender, fiber, fiber.alternate);
654
+ updateFiberTree(onRender, fiber, fiber.alternate);
674
655
  } else if (wasMounted && !isMounted) {
675
656
  unmountFiber(onRender, fiber);
676
657
  }
677
658
  } else {
678
- mountFiberRecursively(onRender, fiber, true);
659
+ mountFiberTree(onRender, fiber, true);
679
660
  }
680
-
681
- rootInstance.prevFiber = fiber;
682
661
  };
683
662
 
684
663
  export interface InstrumentationOptions {
@@ -734,10 +713,12 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
734
713
  priority,
735
714
  didError,
736
715
  ) => {
716
+ const isCurrentDispatcher =
717
+ hookDispatchers.get(rdtHook)?.onCommitFiberRoot === dispatchCommitFiberRoot;
737
718
  if (prevOnCommitFiberRoot) {
738
- prevOnCommitFiberRoot.call(rdtHook, rendererID, root, priority, didError);
719
+ callListener(prevOnCommitFiberRoot, rdtHook, rendererID, root, priority, didError);
739
720
  }
740
- if (hookDispatchers.get(rdtHook)?.onCommitFiberRoot !== dispatchCommitFiberRoot) return;
721
+ if (!isCurrentDispatcher) return;
741
722
  setReactWorkTagsForFiber(root.current, rdtHook.renderers.get(rendererID));
742
723
  // Custom renderers and test harnesses commit roots without a memoizedState;
743
724
  // those must stay tracked, so only explicit unmount evidence removes a root.
@@ -750,9 +731,14 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
750
731
  rootRendererIds.set(root, rendererID);
751
732
  rootHooks.set(root, rdtHook);
752
733
  }
753
- for (const { options, target } of instrumentationSubscriptions) {
754
- if (target === hookTargets.get(rdtHook) && options.onCommitFiberRoot) {
755
- options.onCommitFiberRoot(rendererID, root, priority, didError);
734
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
735
+ for (const subscription of subscriptionSnapshot) {
736
+ const { options, target } = subscription;
737
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
738
+ callListener(
739
+ () => options.onCommitFiberRoot?.(rendererID, root, priority, didError),
740
+ undefined,
741
+ );
756
742
  }
757
743
  }
758
744
  };
@@ -769,21 +755,29 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
769
755
  rendererID,
770
756
  fiber,
771
757
  ) => {
758
+ const isCurrentDispatcher =
759
+ hookDispatchers.get(rdtHook)?.onCommitFiberUnmount === dispatchCommitFiberUnmount;
772
760
  setReactWorkTagsForFiber(fiber, rdtHook.renderers.get(rendererID));
773
- if (prevOnCommitFiberUnmount) {
774
- prevOnCommitFiberUnmount.call(rdtHook, rendererID, fiber);
775
- }
776
- if (hookDispatchers.get(rdtHook)?.onCommitFiberUnmount !== dispatchCommitFiberUnmount) {
777
- return;
778
- }
761
+ const previousUnmountingFiber = currentUnmountingFiber;
762
+ currentUnmountingFiber = fiber;
779
763
  try {
780
- for (const { options, target } of instrumentationSubscriptions) {
781
- if (target === hookTargets.get(rdtHook) && options.onCommitFiberUnmount) {
782
- options.onCommitFiberUnmount(rendererID, fiber);
764
+ if (prevOnCommitFiberUnmount) {
765
+ callListener(prevOnCommitFiberUnmount, rdtHook, rendererID, fiber);
766
+ }
767
+ if (!isCurrentDispatcher) return;
768
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
769
+ for (const subscription of subscriptionSnapshot) {
770
+ const { options, target } = subscription;
771
+ if (
772
+ instrumentationSubscriptions.has(subscription) &&
773
+ target === hookTargets.get(rdtHook)
774
+ ) {
775
+ callListener(() => options.onCommitFiberUnmount?.(rendererID, fiber), undefined);
783
776
  }
784
777
  }
785
778
  } finally {
786
- releaseFiberId(fiber);
779
+ currentUnmountingFiber = previousUnmountingFiber;
780
+ if (isCurrentDispatcher) releaseFiberId(fiber);
787
781
  }
788
782
  };
789
783
  dispatchers.onCommitFiberUnmount = dispatchCommitFiberUnmount;
@@ -799,15 +793,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
799
793
  rendererID,
800
794
  root,
801
795
  ) => {
796
+ const isCurrentDispatcher =
797
+ hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot === dispatchPostCommitFiberRoot;
802
798
  if (prevOnPostCommitFiberRoot) {
803
- prevOnPostCommitFiberRoot.call(rdtHook, rendererID, root);
804
- }
805
- if (hookDispatchers.get(rdtHook)?.onPostCommitFiberRoot !== dispatchPostCommitFiberRoot) {
806
- return;
799
+ callListener(prevOnPostCommitFiberRoot, rdtHook, rendererID, root);
807
800
  }
808
- for (const { options, target } of instrumentationSubscriptions) {
809
- if (target === hookTargets.get(rdtHook) && options.onPostCommitFiberRoot) {
810
- options.onPostCommitFiberRoot(rendererID, root);
801
+ if (!isCurrentDispatcher) return;
802
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
803
+ for (const subscription of subscriptionSnapshot) {
804
+ const { options, target } = subscription;
805
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
806
+ callListener(() => options.onPostCommitFiberRoot?.(rendererID, root), undefined);
811
807
  }
812
808
  }
813
809
  };
@@ -825,13 +821,17 @@ const setHookEventDispatchers = (rdtHook: ReactDevToolsGlobalHook): void => {
825
821
  root,
826
822
  children,
827
823
  ) => {
824
+ const isCurrentDispatcher =
825
+ hookDispatchers.get(rdtHook)?.onScheduleFiberRoot === dispatchScheduleFiberRoot;
828
826
  if (prevOnScheduleFiberRoot) {
829
- prevOnScheduleFiberRoot.call(rdtHook, rendererID, root, children);
827
+ callListener(prevOnScheduleFiberRoot, rdtHook, rendererID, root, children);
830
828
  }
831
- if (hookDispatchers.get(rdtHook)?.onScheduleFiberRoot !== dispatchScheduleFiberRoot) return;
832
- for (const { options, target } of instrumentationSubscriptions) {
833
- if (target === hookTargets.get(rdtHook) && options.onScheduleFiberRoot) {
834
- options.onScheduleFiberRoot(rendererID, root, children);
829
+ if (!isCurrentDispatcher) return;
830
+ const subscriptionSnapshot = [...instrumentationSubscriptions];
831
+ for (const subscription of subscriptionSnapshot) {
832
+ const { options, target } = subscription;
833
+ if (instrumentationSubscriptions.has(subscription) && target === hookTargets.get(rdtHook)) {
834
+ callListener(() => options.onScheduleFiberRoot?.(rendererID, root, children), undefined);
835
835
  }
836
836
  }
837
837
  };
@@ -887,15 +887,20 @@ try {
887
887
  */
888
888
  export const instrument = (options: InstrumentationOptions): Unsubscribe => {
889
889
  const target = options.target ?? globalThis;
890
- const rdtHook = getRDTHook(options.onActive, target);
890
+ const activeListener = options.onActive;
891
+ const onActive = activeListener ? () => activeListener() : undefined;
892
+ const existingHook = target.__REACT_DEVTOOLS_GLOBAL_HOOK__;
893
+ if (existingHook) wireHookEventDispatchers(existingHook, target);
894
+ const rdtHook = getRDTHook(undefined, target);
891
895
  rdtHook._instrumentationSource = options.name ?? BIPPY_INSTRUMENTATION_STRING;
892
896
 
893
897
  wireHookEventDispatchers(rdtHook, target);
894
898
  const subscription: InstrumentationSubscription = { options, target };
895
899
  instrumentationSubscriptions.add(subscription);
900
+ if (onActive) getRDTHook(onActive, target);
896
901
 
897
902
  return createUnsubscribe(() => {
898
- if (options.onActive) removeActiveListener(options.onActive, target);
903
+ if (onActive) removeActiveListener(onActive, target);
899
904
  instrumentationSubscriptions.delete(subscription);
900
905
  });
901
906
  };