@samitouri / QOS-React / commits / e0a07e9738

[DevTools] Support VirtualInstances in findAllCurrentHostInstances (#30853)

This lets us highlight Server Components. However, there is a problem with this because if the actual nearest Fiber is filtered, there's no FiberInstance and so we might skip past it and maybe never find a child while walking the whole tree. This is very common in the case where you have just Server Components and Host Components which are filtered by default. Note how the DOM nodes that are just plain host instances without client component wrappers are not highlighted here: <img width="1102" alt="Screenshot 2024-08-30 at 4 33 55 PM" src="https://github.com/user-attachments/assets/c9a7b91e-5faf-4c60-99a8-1195539ff8b5"> Fixing that needs a separate refactor though and related to several other features that already have a similar issue without VirtualInstances like Suspense/Error Boundaries (triggering suspense/error on a filtered Suspense/ErrorBoundary doesn't work correctly). So this first PR just adds the feature for the common case where there's at least some Fibers.

Sebastian Markbåge committed Sep 3, 2024 at 12:29 UTC e0a07e9738b2ec2ea5cf4872406b465d677bfe2c
1 file changed +45 -35
packages/react-devtools-shared/src/backend/fiber/renderer.js
+45 -35
@@ -3393,6 +3393,18 @@ export function attach(
3393 // I.e. we just restore them by undoing what we did above.
3394 fiberInstance.firstChild = remainingReconcilingChildren;
3395 remainingReconcilingChildren = null;
3396 +
3397 + if (traceUpdatesEnabled) {
3398 + // If we're tracing updates and we've bailed out before reaching a host node,
3399 + // we should fall back to recursively marking the nearest host descendants for highlight.
3400 + if (traceNearestHostComponentUpdate) {
3401 + const hostInstances =
3402 + findAllCurrentHostInstances(fiberInstance);
3403 + hostInstances.forEach(hostInstance => {
3404 + traceUpdatesForNodes.add(hostInstance);
3405 + });
3406 + }
3407 + }
3408 } else {
3409 // If this fiber is filtered there might be changes to this set elsewhere so we have
3410 // to visit each child to place it back in the set. We let the child bail out instead.
@@ -3404,19 +3416,6 @@ export function attach(
3416 );
3417 }
3418 }
3407 -
3408 - if (traceUpdatesEnabled) {
3409 - // If we're tracing updates and we've bailed out before reaching a host node,
3410 - // we should fall back to recursively marking the nearest host descendants for highlight.
3411 - if (traceNearestHostComponentUpdate) {
3412 - const hostInstances = findAllCurrentHostInstances(
3413 - getFiberInstanceThrows(nextFiber),
3414 - );
3415 - hostInstances.forEach(hostInstance => {
3416 - traceUpdatesForNodes.add(hostInstance);
3417 - });
3418 - }
3419 - }
3419 }
3420 }
3421
@@ -3690,15 +3689,31 @@ export function attach(
3689 return null;
3690 }
3691
3693 - function findAllCurrentHostInstances(
3694 - fiberInstance: FiberInstance,
3695 - ): $ReadOnlyArray<HostInstance> {
3696 - const hostInstances = [];
3697 - const fiber = fiberInstance.data;
3698 - if (!fiber) {
3699 - return hostInstances;
3692 + function appendHostInstancesByDevToolsInstance(
3693 + devtoolsInstance: DevToolsInstance,
3694 + hostInstances: Array<HostInstance>,
3695 + ) {
3696 + if (devtoolsInstance.kind === FIBER_INSTANCE) {
3697 + const fiber = devtoolsInstance.data;
3698 + appendHostInstancesByFiber(fiber, hostInstances);
3699 + return;
3700 }
3701 + // Search the tree for the nearest child Fiber and add all its host instances.
3702 + // TODO: If the true nearest Fiber is filtered, we might skip it and instead include all
3703 + // the children below it. In the extreme case, searching the whole tree.
3704 + for (
3705 + let child = devtoolsInstance.firstChild;
3706 + child !== null;
3707 + child = child.nextSibling
3708 + ) {
3709 + appendHostInstancesByDevToolsInstance(child, hostInstances);
3710 + }
3711 + }
3712
3713 + function appendHostInstancesByFiber(
3714 + fiber: Fiber,
3715 + hostInstances: Array<HostInstance>,
3716 + ): void {
3717 // Next we'll drill down this component to find all HostComponent/Text.
3718 let node: Fiber = fiber;
3719 while (true) {
@@ -3718,19 +3733,24 @@ export function attach(
3733 continue;
3734 }
3735 if (node === fiber) {
3721 - return hostInstances;
3736 + return;
3737 }
3738 while (!node.sibling) {
3739 if (!node.return || node.return === fiber) {
3725 - return hostInstances;
3740 + return;
3741 }
3742 node = node.return;
3743 }
3744 node.sibling.return = node.return;
3745 node = node.sibling;
3746 }
3732 - // Flow needs the return here, but ESLint complains about it.
3733 - // eslint-disable-next-line no-unreachable
3747 + }
3748 +
3749 + function findAllCurrentHostInstances(
3750 + devtoolsInstance: DevToolsInstance,
3751 + ): $ReadOnlyArray<HostInstance> {
3752 + const hostInstances: Array<HostInstance> = [];
3753 + appendHostInstancesByDevToolsInstance(devtoolsInstance, hostInstances);
3754 return hostInstances;
3755 }
3756
@@ -3741,17 +3761,7 @@ export function attach(
3761 console.warn(`Could not find DevToolsInstance with id "${id}"`);
3762 return null;
3763 }
3744 - if (devtoolsInstance.kind !== FIBER_INSTANCE) {
3745 - // TODO: Handle VirtualInstance.
3746 - return null;
3747 - }
3748 - const fiber = devtoolsInstance.data;
3749 - if (fiber === null) {
3750 - return null;
3751 - }
3752 -
3753 - const hostInstances = findAllCurrentHostInstances(devtoolsInstance);
3754 - return hostInstances;
3764 + return findAllCurrentHostInstances(devtoolsInstance);
3765 } catch (err) {
3766 // The fiber might have unmounted by now.
3767 return null;