Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Land forked reconciler changes #24878

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
359 changes: 223 additions & 136 deletions packages/react-reconciler/src/ReactFiberBeginWork.old.js

Large diffs are not rendered by default.

105 changes: 75 additions & 30 deletions packages/react-reconciler/src/ReactFiberCommitWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -1953,40 +1953,65 @@ function commitSuspenseHydrationCallbacks(
}
}

function attachSuspenseRetryListeners(finishedWork: Fiber) {
function getRetryCache(finishedWork) {
// TODO: Unify the interface for the retry cache so we don't have to switch
// on the tag like this.
switch (finishedWork.tag) {
case SuspenseComponent:
case SuspenseListComponent: {
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}
return retryCache;
}
case OffscreenComponent: {
const instance: OffscreenInstance = finishedWork.stateNode;
let retryCache = instance.retryCache;
if (retryCache === null) {
retryCache = instance.retryCache = new PossiblyWeakSet();
}
return retryCache;
}
default: {
throw new Error(
`Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +
'bug in React.',
);
}
}
}

function attachSuspenseRetryListeners(
finishedWork: Fiber,
wakeables: Set<Wakeable>,
) {
// If this boundary just timed out, then it will have a set of wakeables.
// For each wakeable, attach a listener so that when it resolves, React
// attempts to re-render the boundary in the primary (pre-timeout) state.
const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
if (wakeables !== null) {
finishedWork.updateQueue = null;
let retryCache = finishedWork.stateNode;
if (retryCache === null) {
retryCache = finishedWork.stateNode = new PossiblyWeakSet();
}
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
if (!retryCache.has(wakeable)) {
retryCache.add(wakeable);

if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);
}
const retryCache = getRetryCache(finishedWork);
wakeables.forEach(wakeable => {
// Memoize using the boundary fiber to prevent redundant listeners.
const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
if (!retryCache.has(wakeable)) {
retryCache.add(wakeable);

if (enableUpdaterTracking) {
if (isDevToolsPresent) {
if (inProgressLanes !== null && inProgressRoot !== null) {
// If we have pending work still, associate the original updaters with it.
restorePendingUpdaters(inProgressRoot, inProgressLanes);
} else {
throw Error(
'Expected finished root and lanes to be set. This is a bug in React.',
);
}
}

wakeable.then(retry, retry);
}
});
}

wakeable.then(retry, retry);
}
});
}

// This function detects when a Suspense boundary goes from visible to hidden.
Expand Down Expand Up @@ -2307,7 +2332,11 @@ function commitMutationEffectsOnFiber(
} catch (error) {
captureCommitPhaseError(finishedWork, finishedWork.return, error);
}
attachSuspenseRetryListeners(finishedWork);
const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
if (wakeables !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, wakeables);
}
}
return;
}
Expand Down Expand Up @@ -2362,14 +2391,30 @@ function commitMutationEffectsOnFiber(
hideOrUnhideAllChildren(offscreenBoundary, isHidden);
}
}

// TODO: Move to passive phase
if (flags & Update) {
const offscreenQueue: OffscreenQueue | null = (finishedWork.updateQueue: any);
if (offscreenQueue !== null) {
const wakeables = offscreenQueue.wakeables;
if (wakeables !== null) {
offscreenQueue.wakeables = null;
attachSuspenseRetryListeners(finishedWork, wakeables);
}
}
}
return;
}
case SuspenseListComponent: {
recursivelyTraverseMutationEffects(root, finishedWork, lanes);
commitReconciliationEffects(finishedWork);

if (flags & Update) {
attachSuspenseRetryListeners(finishedWork);
const wakeables: Set<Wakeable> | null = (finishedWork.updateQueue: any);
if (wakeables !== null) {
finishedWork.updateQueue = null;
attachSuspenseRetryListeners(finishedWork, wakeables);
}
}
return;
}
Expand Down
87 changes: 49 additions & 38 deletions packages/react-reconciler/src/ReactFiberCompleteWork.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import type {
SuspenseState,
SuspenseListRenderState,
} from './ReactFiberSuspenseComponent.old';
import type {SuspenseContext} from './ReactFiberSuspenseContext.old';
import type {OffscreenState} from './ReactFiberOffscreenComponent';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
import type {Cache} from './ReactFiberCacheComponent.old';
Expand Down Expand Up @@ -110,14 +109,17 @@ import {
} from './ReactFiberHostContext.old';
import {
suspenseStackCursor,
InvisibleParentSuspenseContext,
hasSuspenseContext,
popSuspenseContext,
pushSuspenseContext,
setShallowSuspenseContext,
popSuspenseListContext,
popSuspenseHandler,
pushSuspenseListContext,
setShallowSuspenseListContext,
ForceSuspenseFallback,
setDefaultShallowSuspenseContext,
setDefaultShallowSuspenseListContext,
} from './ReactFiberSuspenseContext.old';
import {
popHiddenContext,
isCurrentTreeHidden,
} from './ReactFiberHiddenContext.old';
import {findFirstSuspended} from './ReactFiberSuspenseComponent.old';
import {
isContextProvider as isLegacyContextProvider,
Expand Down Expand Up @@ -147,9 +149,7 @@ import {
renderDidSuspend,
renderDidSuspendDelayIfPossible,
renderHasNotSuspendedYet,
popRenderLanes,
getRenderTargetTime,
subtreeRenderLanes,
getWorkInProgressTransitions,
} from './ReactFiberWorkLoop.old';
import {
Expand Down Expand Up @@ -1086,7 +1086,7 @@ function completeWork(
return null;
}
case SuspenseComponent: {
popSuspenseContext(workInProgress);
popSuspenseHandler(workInProgress);
const nextState: null | SuspenseState = workInProgress.memoizedState;

// Special path for dehydrated boundaries. We may eventually move this
Expand Down Expand Up @@ -1195,25 +1195,23 @@ function completeWork(
// If this render already had a ping or lower pri updates,
// and this is the first time we know we're going to suspend we
// should be able to immediately restart from within throwException.
const hasInvisibleChildContext =
current === null &&
(workInProgress.memoizedProps.unstable_avoidThisFallback !==
true ||
!enableSuspenseAvoidThisFallback);
if (
hasInvisibleChildContext ||
hasSuspenseContext(
suspenseStackCursor.current,
(InvisibleParentSuspenseContext: SuspenseContext),
)
) {
// If this was in an invisible tree or a new render, then showing
// this boundary is ok.
renderDidSuspend();
} else {
// Otherwise, we're going to have to hide content so we should
// suspend for longer if possible.

// Check if this is a "bad" fallback state or a good one. A bad
// fallback state is one that we only show as a last resort; if this
// is a transition, we'll block it from displaying, and wait for
// more data to arrive.
const isBadFallback =
// It's bad to switch to a fallback if content is already visible
(current !== null && !prevDidTimeout && !isCurrentTreeHidden()) ||
// Experimental: Some fallbacks are always bad
(enableSuspenseAvoidThisFallback &&
workInProgress.memoizedProps.unstable_avoidThisFallback ===
true);

if (isBadFallback) {
renderDidSuspendDelayIfPossible();
} else {
renderDidSuspend();
}
}
}
Expand Down Expand Up @@ -1275,7 +1273,7 @@ function completeWork(
return null;
}
case SuspenseListComponent: {
popSuspenseContext(workInProgress);
popSuspenseListContext(workInProgress);

const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;
Expand Down Expand Up @@ -1341,11 +1339,11 @@ function completeWork(
workInProgress.subtreeFlags = NoFlags;
resetChildFibers(workInProgress, renderLanes);

// Set up the Suspense Context to force suspense and immediately
// rerender the children.
pushSuspenseContext(
// Set up the Suspense List Context to force suspense and
// immediately rerender the children.
pushSuspenseListContext(
workInProgress,
setShallowSuspenseContext(
setShallowSuspenseListContext(
suspenseStackCursor.current,
ForceSuspenseFallback,
),
Expand Down Expand Up @@ -1468,14 +1466,16 @@ function completeWork(
// setting it the first time we go from not suspended to suspended.
let suspenseContext = suspenseStackCursor.current;
if (didSuspendAlready) {
suspenseContext = setShallowSuspenseContext(
suspenseContext = setShallowSuspenseListContext(
suspenseContext,
ForceSuspenseFallback,
);
} else {
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
suspenseContext = setDefaultShallowSuspenseListContext(
suspenseContext,
);
}
pushSuspenseContext(workInProgress, suspenseContext);
pushSuspenseListContext(workInProgress, suspenseContext);
// Do a pass over the next row.
// Don't bubble properties in this case.
return next;
Expand Down Expand Up @@ -1508,7 +1508,8 @@ function completeWork(
}
case OffscreenComponent:
case LegacyHiddenComponent: {
popRenderLanes(workInProgress);
popSuspenseHandler(workInProgress);
popHiddenContext(workInProgress);
const nextState: OffscreenState | null = workInProgress.memoizedState;
const nextIsHidden = nextState !== null;

Expand All @@ -1529,7 +1530,11 @@ function completeWork(
} else {
// Don't bubble properties for hidden children unless we're rendering
// at offscreen priority.
if (includesSomeLane(subtreeRenderLanes, (OffscreenLane: Lane))) {
if (
includesSomeLane(renderLanes, (OffscreenLane: Lane)) &&
// Also don't bubble if the tree suspended
(workInProgress.flags & DidCapture) === NoLanes
) {
bubbleProperties(workInProgress);
// Check if there was an insertion or update in the hidden subtree.
// If so, we need to hide those nodes in the commit phase, so
Expand All @@ -1544,6 +1549,12 @@ function completeWork(
}
}

if (workInProgress.updateQueue !== null) {
// Schedule an effect to attach Suspense retry listeners
// TODO: Move to passive phase
workInProgress.flags |= Update;
}

if (enableCache) {
let previousCache: Cache | null = null;
if (
Expand Down
71 changes: 70 additions & 1 deletion packages/react-reconciler/src/ReactFiberHiddenContext.old.js
Original file line number Diff line number Diff line change
@@ -1 +1,70 @@
// Intentionally blank. File only exists in new reconciler fork.
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Fiber} from './ReactInternalTypes';
import type {StackCursor} from './ReactFiberStack.old';
import type {Lanes} from './ReactFiberLane.old';

import {createCursor, push, pop} from './ReactFiberStack.old';

import {getRenderLanes, setRenderLanes} from './ReactFiberWorkLoop.old';
import {NoLanes, mergeLanes} from './ReactFiberLane.old';

// TODO: Remove `renderLanes` context in favor of hidden context
type HiddenContext = {
// Represents the lanes that must be included when processing updates in
// order to reveal the hidden content.
// TODO: Remove `subtreeLanes` context from work loop in favor of this one.
baseLanes: number,
};

// TODO: This isn't being used yet, but it's intended to replace the
// InvisibleParentContext that is currently managed by SuspenseContext.
export const currentTreeHiddenStackCursor: StackCursor<HiddenContext | null> = createCursor(
null,
);
export const prevRenderLanesStackCursor: StackCursor<Lanes> = createCursor(
NoLanes,
);

export function pushHiddenContext(fiber: Fiber, context: HiddenContext): void {
const prevRenderLanes = getRenderLanes();
push(prevRenderLanesStackCursor, prevRenderLanes, fiber);
push(currentTreeHiddenStackCursor, context, fiber);

// When rendering a subtree that's currently hidden, we must include all
// lanes that would have rendered if the hidden subtree hadn't been deferred.
// That is, in order to reveal content from hidden -> visible, we must commit
// all the updates that we skipped when we originally hid the tree.
setRenderLanes(mergeLanes(prevRenderLanes, context.baseLanes));
}

export function reuseHiddenContextOnStack(fiber: Fiber): void {
// This subtree is not currently hidden, so we don't need to add any lanes
// to the render lanes. But we still need to push something to avoid a
// context mismatch. Reuse the existing context on the stack.
push(prevRenderLanesStackCursor, getRenderLanes(), fiber);
push(
currentTreeHiddenStackCursor,
currentTreeHiddenStackCursor.current,
fiber,
);
}

export function popHiddenContext(fiber: Fiber): void {
// Restore the previous render lanes from the stack
setRenderLanes(prevRenderLanesStackCursor.current);

pop(currentTreeHiddenStackCursor, fiber);
pop(prevRenderLanesStackCursor, fiber);
}

export function isCurrentTreeHidden() {
return currentTreeHiddenStackCursor.current !== null;
}
Loading