From 236edd98a19496ca7440640ea9afdc6bb80b6b19 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 1 Aug 2025 13:33:00 -0400 Subject: [PATCH 1/4] Fix restore --- .../react-devtools-shared/src/backend/fiber/renderer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 5ffa5251dcae9..8d6fddf8e09a1 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3919,9 +3919,11 @@ export function attach( reconcilingParent = stashedParent; previouslyReconciledSibling = stashedPrevious; remainingReconcilingChildren = stashedRemaining; - reconcilingParentSuspenseNode = stashedSuspenseParent; - previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; - remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + if (fiberInstance.suspenseNode !== null) { + reconcilingParentSuspenseNode = stashedSuspenseParent; + previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; + remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + } } } } From 0795d106ea700c098b823b709cdae0ef8f00dc07 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 1 Aug 2025 19:00:38 -0400 Subject: [PATCH 2/4] Consume SuspenseNodes that were skipped when we're bailing out of a subtree --- .../src/backend/fiber/renderer.js | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 8d6fddf8e09a1..837ebc3f752ec 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -2701,6 +2701,76 @@ export function attach( } } + function isChildOf( + parentInstance: DevToolsInstance, + childInstance: DevToolsInstance, + grandParent: DevToolsInstance, + ): boolean { + let instance = childInstance.parent; + while (instance !== null) { + if (parentInstance === instance) { + return true; + } + if (instance === parentInstance.parent || instance === grandParent) { + // This was a sibling but not inside the FiberInstance. We can bail out. + break; + } + instance = instance.parent; + } + return false; + } + + function consumeSuspenseNodesOfExistingInstance( + instance: DevToolsInstance, + ): void { + // We need to also consume any unchanged Suspense boundaries. + let suspenseNode = remainingReconcilingChildrenSuspenseNodes; + if (suspenseNode === null) { + return; + } + const parentSuspenseNode = reconcilingParentSuspenseNode; + if (parentSuspenseNode === null) { + throw new Error( + 'The should not be any remaining suspense node children if there is no parent.', + ); + } + let foundOne = false; + let previousSkippedSibling = null; + while (suspenseNode !== null) { + // Check if this SuspenseNode was a child of the bailed out FiberInstance. + if ( + isChildOf(instance, suspenseNode.instance, parentSuspenseNode.instance) + ) { + foundOne = true; + // The suspenseNode was child of the bailed out Fiber. + // First, remove it from the remaining children set. + const nextRemainingSibling = suspenseNode.nextSibling; + if (previousSkippedSibling === null) { + remainingReconcilingChildrenSuspenseNodes = nextRemainingSibling; + } else { + previousSkippedSibling.nextSibling = nextRemainingSibling; + } + suspenseNode.nextSibling = null; + // Then, re-insert it into the newly reconciled set. + if (previouslyReconciledSiblingSuspenseNode === null) { + parentSuspenseNode.firstChild = suspenseNode; + } else { + previouslyReconciledSiblingSuspenseNode.nextSibling = suspenseNode; + } + previouslyReconciledSiblingSuspenseNode = suspenseNode; + // Continue + suspenseNode = nextRemainingSibling; + } else if (foundOne) { + // If we found one and then hit a miss, we assume that we're passed the sequence because + // they should've all been consecutive. + break; + } else { + previousSkippedSibling = suspenseNode; + suspenseNode = suspenseNode.nextSibling; + } + } + } + function mountVirtualInstanceRecursively( virtualInstance: VirtualInstance, firstChild: Fiber, @@ -3849,6 +3919,8 @@ export function attach( fiberInstance.firstChild = remainingReconcilingChildren; remainingReconcilingChildren = null; + consumeSuspenseNodesOfExistingInstance(fiberInstance); + if (traceUpdatesEnabled) { // If we're tracing updates and we've bailed out before reaching a host node, // we should fall back to recursively marking the nearest host descendants for highlight. From 07250282e63e6a18b2116aefa1a77c283f87ddb5 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sun, 3 Aug 2025 19:49:50 -0400 Subject: [PATCH 3/4] Clear firstChild of SuspenseNode and re-add it as we go --- .../react-devtools-shared/src/backend/fiber/renderer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 837ebc3f752ec..120f0b88701c6 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3758,10 +3758,12 @@ export function attach( fiberInstance.firstChild = null; fiberInstance.suspendedBy = null; - if (fiberInstance.suspenseNode !== null) { - reconcilingParentSuspenseNode = fiberInstance.suspenseNode; + const suspenseNode = fiberInstance.suspenseNode; + if (suspenseNode !== null) { + reconcilingParentSuspenseNode = suspenseNode; previouslyReconciledSiblingSuspenseNode = null; - remainingReconcilingChildrenSuspenseNodes = null; + remainingReconcilingChildrenSuspenseNodes = suspenseNode.firstChild; + suspenseNode.firstChild = null; } } try { From 4b8e0b0653e5a8b96a7921a2bdcc7cef9241b2ae Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 4 Aug 2025 12:53:18 -0400 Subject: [PATCH 4/4] Conditionally pop --- .../react-devtools-shared/src/backend/fiber/renderer.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 120f0b88701c6..b6bc24dd01b4c 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -3164,9 +3164,11 @@ export function attach( reconcilingParent = stashedParent; previouslyReconciledSibling = stashedPrevious; remainingReconcilingChildren = stashedRemaining; - reconcilingParentSuspenseNode = stashedSuspenseParent; - previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; - remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + if (instance.suspenseNode !== null) { + reconcilingParentSuspenseNode = stashedSuspenseParent; + previouslyReconciledSiblingSuspenseNode = stashedSuspensePrevious; + remainingReconcilingChildrenSuspenseNodes = stashedSuspenseRemaining; + } } if (instance.kind === FIBER_INSTANCE) { recordUnmount(instance);