From 44174cda0061506c1526797981f41c4f17f4d0ab Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 7 Apr 2021 17:16:09 -0400 Subject: [PATCH] Offscreen: Use JS stack to track hidden/unhidden subtree state --- .../src/ReactFiberCommitWork.new.js | 91 ++++++++++++------- .../src/ReactFiberCommitWork.old.js | 91 ++++++++++++------- 2 files changed, 112 insertions(+), 70 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index 48150f021a32b..536b880d98596 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -152,14 +152,6 @@ if (__DEV__) { didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); } -// Used during the commit phase to track the state of the Offscreen component stack. -// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor. -// Only used when enableSuspenseLayoutEffectSemantics is enabled. -let offscreenSubtreeIsHidden: boolean = false; -const offscreenSubtreeIsHiddenStack: Array = []; -let offscreenSubtreeWasHidden: boolean = false; -const offscreenSubtreeWasHiddenStack: Array = []; - const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; let nextEffect: Fiber | null = null; @@ -2283,13 +2275,38 @@ export function commitLayoutEffects( committedLanes: Lanes, ): void { nextEffect = finishedWork; - commitLayoutEffects_begin(finishedWork, root, committedLanes); + commitLayoutEffects_intermediate( + finishedWork, + root, + committedLanes, + false, + false, + ); +} + +export function commitLayoutEffects_intermediate( + finishedWork: Fiber, + root: FiberRoot, + committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, +): void { + nextEffect = finishedWork; + commitLayoutEffects_begin( + finishedWork, + root, + committedLanes, + subtreeWasHidden, + subtreeIsHidden, + ); } function commitLayoutEffects_begin( subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, ) { // Suspense layout effects semantics don't change for legacy roots. const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode; @@ -2305,11 +2322,25 @@ function commitLayoutEffects_begin( const wasHidden = current !== null && current.memoizedState !== null; const isHidden = fiber.memoizedState !== null; - offscreenSubtreeWasHidden = wasHidden || offscreenSubtreeWasHidden; - offscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; + const newOffscreenSubtreeWasHidden = wasHidden || subtreeWasHidden; + const newOffscreenSubtreeIsHidden = isHidden || subtreeIsHidden; - offscreenSubtreeWasHiddenStack.push(wasHidden); - offscreenSubtreeIsHiddenStack.push(isHidden); + if ( + newOffscreenSubtreeWasHidden !== subtreeWasHidden || + newOffscreenSubtreeIsHidden !== subtreeIsHidden + ) { + // Traverse the Offscreen subtree with the current Offscreen as the root. + nextEffect = fiber.child; + commitLayoutEffects_intermediate( + fiber, // New root; bubble back up to here and stop. + root, + committedLanes, + newOffscreenSubtreeWasHidden, + newOffscreenSubtreeIsHidden, + ); + nextEffect = fiber.sibling; + continue; + } } } @@ -2318,8 +2349,7 @@ function commitLayoutEffects_begin( nextEffect = firstChild; } else { if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - const visibilityChanged = - !offscreenSubtreeIsHidden && offscreenSubtreeWasHidden; + const visibilityChanged = !subtreeIsHidden && subtreeWasHidden; if ( visibilityChanged && (fiber.subtreeFlags & LayoutStatic) !== NoFlags && @@ -2334,7 +2364,13 @@ function commitLayoutEffects_begin( } } - commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); + commitLayoutMountEffects_complete( + subtreeRoot, + root, + committedLanes, + subtreeWasHidden, + subtreeIsHidden, + ); } } } @@ -2343,6 +2379,8 @@ function commitLayoutMountEffects_complete( subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, ) { // Suspense layout effects semantics don't change for legacy roots. const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode; @@ -2350,28 +2388,11 @@ function commitLayoutMountEffects_complete( while (nextEffect !== null) { const fiber = nextEffect; - if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - if (fiber.tag === OffscreenComponent) { - offscreenSubtreeWasHiddenStack.pop(); - offscreenSubtreeIsHiddenStack.pop(); - offscreenSubtreeWasHidden = - offscreenSubtreeWasHiddenStack.length > 0 && - offscreenSubtreeWasHiddenStack[ - offscreenSubtreeWasHiddenStack.length - 1 - ]; - offscreenSubtreeIsHidden = - offscreenSubtreeIsHiddenStack.length > 0 && - offscreenSubtreeIsHiddenStack[ - offscreenSubtreeIsHiddenStack.length - 1 - ]; - } - } - if ( enableSuspenseLayoutEffectSemantics && isModernRoot && - offscreenSubtreeWasHidden && - !offscreenSubtreeIsHidden + subtreeWasHidden && + !subtreeIsHidden ) { // Inside of an Offscreen subtree that changed visibility during this commit. // If this subtree was hidden, layout effects will have already been destroyed (during mutation phase) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index ca3dbe363f071..1d57cd2ae7754 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -152,14 +152,6 @@ if (__DEV__) { didWarnAboutUndefinedSnapshotBeforeUpdate = new Set(); } -// Used during the commit phase to track the state of the Offscreen component stack. -// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor. -// Only used when enableSuspenseLayoutEffectSemantics is enabled. -let offscreenSubtreeIsHidden: boolean = false; -const offscreenSubtreeIsHiddenStack: Array = []; -let offscreenSubtreeWasHidden: boolean = false; -const offscreenSubtreeWasHiddenStack: Array = []; - const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set; let nextEffect: Fiber | null = null; @@ -2283,13 +2275,38 @@ export function commitLayoutEffects( committedLanes: Lanes, ): void { nextEffect = finishedWork; - commitLayoutEffects_begin(finishedWork, root, committedLanes); + commitLayoutEffects_intermediate( + finishedWork, + root, + committedLanes, + false, + false, + ); +} + +export function commitLayoutEffects_intermediate( + finishedWork: Fiber, + root: FiberRoot, + committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, +): void { + nextEffect = finishedWork; + commitLayoutEffects_begin( + finishedWork, + root, + committedLanes, + subtreeWasHidden, + subtreeIsHidden, + ); } function commitLayoutEffects_begin( subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, ) { // Suspense layout effects semantics don't change for legacy roots. const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode; @@ -2305,11 +2322,25 @@ function commitLayoutEffects_begin( const wasHidden = current !== null && current.memoizedState !== null; const isHidden = fiber.memoizedState !== null; - offscreenSubtreeWasHidden = wasHidden || offscreenSubtreeWasHidden; - offscreenSubtreeIsHidden = isHidden || offscreenSubtreeIsHidden; + const newOffscreenSubtreeWasHidden = wasHidden || subtreeWasHidden; + const newOffscreenSubtreeIsHidden = isHidden || subtreeIsHidden; - offscreenSubtreeWasHiddenStack.push(wasHidden); - offscreenSubtreeIsHiddenStack.push(isHidden); + if ( + newOffscreenSubtreeWasHidden !== subtreeWasHidden || + newOffscreenSubtreeIsHidden !== subtreeIsHidden + ) { + // Traverse the Offscreen subtree with the current Offscreen as the root. + nextEffect = fiber.child; + commitLayoutEffects_intermediate( + fiber, // New root; bubble back up to here and stop. + root, + committedLanes, + newOffscreenSubtreeWasHidden, + newOffscreenSubtreeIsHidden, + ); + nextEffect = fiber.sibling; + continue; + } } } @@ -2318,8 +2349,7 @@ function commitLayoutEffects_begin( nextEffect = firstChild; } else { if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - const visibilityChanged = - !offscreenSubtreeIsHidden && offscreenSubtreeWasHidden; + const visibilityChanged = !subtreeIsHidden && subtreeWasHidden; if ( visibilityChanged && (fiber.subtreeFlags & LayoutStatic) !== NoFlags && @@ -2334,7 +2364,13 @@ function commitLayoutEffects_begin( } } - commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); + commitLayoutMountEffects_complete( + subtreeRoot, + root, + committedLanes, + subtreeWasHidden, + subtreeIsHidden, + ); } } } @@ -2343,6 +2379,8 @@ function commitLayoutMountEffects_complete( subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes, + subtreeWasHidden: boolean, + subtreeIsHidden: boolean, ) { // Suspense layout effects semantics don't change for legacy roots. const isModernRoot = (subtreeRoot.mode & ConcurrentMode) !== NoMode; @@ -2350,28 +2388,11 @@ function commitLayoutMountEffects_complete( while (nextEffect !== null) { const fiber = nextEffect; - if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - if (fiber.tag === OffscreenComponent) { - offscreenSubtreeWasHiddenStack.pop(); - offscreenSubtreeIsHiddenStack.pop(); - offscreenSubtreeWasHidden = - offscreenSubtreeWasHiddenStack.length > 0 && - offscreenSubtreeWasHiddenStack[ - offscreenSubtreeWasHiddenStack.length - 1 - ]; - offscreenSubtreeIsHidden = - offscreenSubtreeIsHiddenStack.length > 0 && - offscreenSubtreeIsHiddenStack[ - offscreenSubtreeIsHiddenStack.length - 1 - ]; - } - } - if ( enableSuspenseLayoutEffectSemantics && isModernRoot && - offscreenSubtreeWasHidden && - !offscreenSubtreeIsHidden + subtreeWasHidden && + !subtreeIsHidden ) { // Inside of an Offscreen subtree that changed visibility during this commit. // If this subtree was hidden, layout effects will have already been destroyed (during mutation phase)