39
enableCache,
40
enableTransitionTracing,
41
useModernStrictMode,
42
+ revertRemovalOfSiblingPrerendering,
43
} from 'shared/ReactFeatureFlags';
44
import ReactSharedInternals from 'shared/ReactSharedInternals';
45
import is from 'shared/objectIs';
2174
break outer;
2175
}
2176
default: {
2176
- // Continue with the normal work loop.
2177
+ // Unwind then continue with the normal work loop.
2178
workInProgressSuspendedReason = NotSuspended;
2179
workInProgressThrownValue = null;
2179
- unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2180
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
2181
break;
2182
}
2183
}
2286
// Unwind then continue with the normal work loop.
2287
workInProgressSuspendedReason = NotSuspended;
2288
workInProgressThrownValue = null;
2288
- unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2289
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
2290
break;
2291
}
2292
case SuspendedOnData: {
2344
// Otherwise, unwind then continue with the normal work loop.
2345
workInProgressSuspendedReason = NotSuspended;
2346
workInProgressThrownValue = null;
2346
- unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2347
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
2348
}
2349
break;
2350
}
2401
// Otherwise, unwind then continue with the normal work loop.
2402
workInProgressSuspendedReason = NotSuspended;
2403
workInProgressThrownValue = null;
2403
- unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2404
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
2405
break;
2406
}
2407
case SuspendedOnDeprecatedThrowPromise: {
2411
// always unwind.
2412
workInProgressSuspendedReason = NotSuspended;
2413
workInProgressThrownValue = null;
2413
- unwindSuspendedUnitOfWork(unitOfWork, thrownValue);
2414
+ throwAndUnwindWorkLoop(unitOfWork, thrownValue);
2415
break;
2416
}
2417
case SuspendedOnHydration: {
2611
ReactCurrentOwner.current = null;
2612
}
2613
2613
-function unwindSuspendedUnitOfWork(unitOfWork: Fiber, thrownValue: mixed) {
2614
+function throwAndUnwindWorkLoop(unitOfWork: Fiber, thrownValue: mixed) {
2615
// This is a fork of performUnitOfWork specifcally for unwinding a fiber
2616
// that threw an exception.
2617
//
2656
throw error;
2657
}
2658
2658
- // Return to the normal work loop.
2659
- completeUnitOfWork(unitOfWork);
2659
+ if (unitOfWork.flags & Incomplete) {
2660
+ // Unwind the stack until we reach the nearest boundary.
2661
+ unwindUnitOfWork(unitOfWork);
2662
+ } else {
2663
+ // Although the fiber suspended, we're intentionally going to commit it in
2664
+ // an inconsistent state. We can do this safely in cases where we know the
2665
+ // inconsistent tree will be hidden.
2666
+ //
2667
+ // This currently only applies to Legacy Suspense implementation, but we may
2668
+ // port a version of this to concurrent roots, too, when performing a
2669
+ // synchronous render. Because that will allow us to mutate the tree as we
2670
+ // go instead of buffering mutations until the end. Though it's unclear if
2671
+ // this particular path is how that would be implemented.
2672
+ completeUnitOfWork(unitOfWork);
2673
+ }
2674
}
2675
2676
function completeUnitOfWork(unitOfWork: Fiber): void {
2678
// sibling. If there are no more siblings, return to the parent fiber.
2679
let completedWork: Fiber = unitOfWork;
2680
do {
2681
+ if (revertRemovalOfSiblingPrerendering) {
2682
+ if ((completedWork.flags & Incomplete) !== NoFlags) {
2683
+ // This fiber did not complete, because one of its children did not
2684
+ // complete. Switch to unwinding the stack instead of completing it.
2685
+ //
2686
+ // The reason "unwind" and "complete" is interleaved is because when
2687
+ // something suspends, we continue rendering the siblings even though
2688
+ // they will be replaced by a fallback.
2689
+ // TODO: Disable sibling prerendering, then remove this branch.
2690
+ unwindUnitOfWork(completedWork);
2691
+ return;
2692
+ }
2693
+ } else {
2694
+ if (__DEV__) {
2695
+ if ((completedWork.flags & Incomplete) !== NoFlags) {
2696
+ // NOTE: If we re-enable sibling prerendering in some cases, this branch
2697
+ // is where we would switch to the unwinding path.
2698
+ console.error(
2699
+ 'Internal React error: Expected this fiber to be complete, but ' +
2700
+ "it isn't. It should have been unwound. This is a bug in React.",
2701
+ );
2702
+ }
2703
+ }
2704
+ }
2705
+
2706
// The current, flushed, state of this fiber is the alternate. Ideally
2707
// nothing should rely on this, but relying on it here means that we don't
2708
// need an additional field on the work in progress.
2709
const current = completedWork.alternate;
2710
const returnFiber = completedWork.return;
2711
2673
- // Check if the work completed or if something threw.
2674
- if ((completedWork.flags & Incomplete) === NoFlags) {
2675
- setCurrentDebugFiberInDEV(completedWork);
2676
- let next;
2677
- if (
2678
- !enableProfilerTimer ||
2679
- (completedWork.mode & ProfileMode) === NoMode
2680
- ) {
2681
- next = completeWork(current, completedWork, renderLanes);
2682
- } else {
2683
- startProfilerTimer(completedWork);
2684
- next = completeWork(current, completedWork, renderLanes);
2685
- // Update render duration assuming we didn't error.
2686
- stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
2687
- }
2688
- resetCurrentDebugFiberInDEV();
2689
-
2690
- if (next !== null) {
2691
- // Completing this fiber spawned new work. Work on that next.
2692
- workInProgress = next;
2693
- return;
2694
- }
2712
+ setCurrentDebugFiberInDEV(completedWork);
2713
+ let next;
2714
+ if (!enableProfilerTimer || (completedWork.mode & ProfileMode) === NoMode) {
2715
+ next = completeWork(current, completedWork, renderLanes);
2716
} else {
2696
- // This fiber did not complete because something threw. Pop values off
2697
- // the stack without entering the complete phase. If this is a boundary,
2698
- // capture values if possible.
2699
- const next = unwindWork(current, completedWork, renderLanes);
2700
-
2701
- // Because this fiber did not complete, don't reset its lanes.
2702
-
2703
- if (next !== null) {
2704
- // If completing this work spawned new work, do that next. We'll come
2705
- // back here again.
2706
- // Since we're restarting, remove anything that is not a host effect
2707
- // from the effect tag.
2708
- next.flags &= HostEffectMask;
2709
- workInProgress = next;
2710
- return;
2711
- }
2712
-
2713
- if (
2714
- enableProfilerTimer &&
2715
- (completedWork.mode & ProfileMode) !== NoMode
2716
- ) {
2717
- // Record the render duration for the fiber that errored.
2718
- stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
2719
-
2720
- // Include the time spent working on failed children before continuing.
2721
- let actualDuration = completedWork.actualDuration;
2722
- let child = completedWork.child;
2723
- while (child !== null) {
2724
- // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2725
- actualDuration += child.actualDuration;
2726
- child = child.sibling;
2727
- }
2728
- completedWork.actualDuration = actualDuration;
2729
- }
2717
+ startProfilerTimer(completedWork);
2718
+ next = completeWork(current, completedWork, renderLanes);
2719
+ // Update render duration assuming we didn't error.
2720
+ stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
2721
+ }
2722
+ resetCurrentDebugFiberInDEV();
2723
2731
- if (returnFiber !== null) {
2732
- // Mark the parent fiber as incomplete and clear its subtree flags.
2733
- returnFiber.flags |= Incomplete;
2734
- returnFiber.subtreeFlags = NoFlags;
2735
- returnFiber.deletions = null;
2736
- } else {
2737
- // We've unwound all the way to the root.
2738
- workInProgressRootExitStatus = RootDidNotComplete;
2739
- workInProgress = null;
2740
- return;
2741
- }
2724
+ if (next !== null) {
2725
+ // Completing this fiber spawned new work. Work on that next.
2726
+ workInProgress = next;
2727
+ return;
2728
}
2729
2730
const siblingFiber = completedWork.sibling;
2746
}
2747
}
2748
2749
+function unwindUnitOfWork(unitOfWork: Fiber): void {
2750
+ let incompleteWork: Fiber = unitOfWork;
2751
+ do {
2752
+ // The current, flushed, state of this fiber is the alternate. Ideally
2753
+ // nothing should rely on this, but relying on it here means that we don't
2754
+ // need an additional field on the work in progress.
2755
+ const current = incompleteWork.alternate;
2756
+
2757
+ // This fiber did not complete because something threw. Pop values off
2758
+ // the stack without entering the complete phase. If this is a boundary,
2759
+ // capture values if possible.
2760
+ const next = unwindWork(current, incompleteWork, renderLanes);
2761
+
2762
+ // Because this fiber did not complete, don't reset its lanes.
2763
+
2764
+ if (next !== null) {
2765
+ // Found a boundary that can handle this exception. Re-renter the
2766
+ // begin phase. This branch will return us to the normal work loop.
2767
+ //
2768
+ // Since we're restarting, remove anything that is not a host effect
2769
+ // from the effect tag.
2770
+ next.flags &= HostEffectMask;
2771
+ workInProgress = next;
2772
+ return;
2773
+ }
2774
+
2775
+ // Keep unwinding until we reach either a boundary or the root.
2776
+
2777
+ if (enableProfilerTimer && (incompleteWork.mode & ProfileMode) !== NoMode) {
2778
+ // Record the render duration for the fiber that errored.
2779
+ stopProfilerTimerIfRunningAndRecordDelta(incompleteWork, false);
2780
+
2781
+ // Include the time spent working on failed children before continuing.
2782
+ let actualDuration = incompleteWork.actualDuration;
2783
+ let child = incompleteWork.child;
2784
+ while (child !== null) {
2785
+ // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
2786
+ actualDuration += child.actualDuration;
2787
+ child = child.sibling;
2788
+ }
2789
+ incompleteWork.actualDuration = actualDuration;
2790
+ }
2791
+
2792
+ // TODO: Once we stop prerendering siblings, instead of resetting the parent
2793
+ // of the node being unwound, we should be able to reset node itself as we
2794
+ // unwind the stack. Saves an additional null check.
2795
+ const returnFiber = incompleteWork.return;
2796
+ if (returnFiber !== null) {
2797
+ // Mark the parent fiber as incomplete and clear its subtree flags.
2798
+ // TODO: Once we stop prerendering siblings, we may be able to get rid of
2799
+ // the Incomplete flag because unwinding to the nearest boundary will
2800
+ // happen synchronously.
2801
+ returnFiber.flags |= Incomplete;
2802
+ returnFiber.subtreeFlags = NoFlags;
2803
+ returnFiber.deletions = null;
2804
+ }
2805
+
2806
+ if (revertRemovalOfSiblingPrerendering) {
2807
+ // If there are siblings, work on them now even though they're going to be
2808
+ // replaced by a fallback. We're "prerendering" them. Historically our
2809
+ // rationale for this behavior has been to initiate any lazy data requests
2810
+ // in the siblings, and also to warm up the CPU cache.
2811
+ // TODO: Don't prerender siblings. With `use`, we suspend the work loop
2812
+ // until the data has resolved, anyway.
2813
+ const siblingFiber = incompleteWork.sibling;
2814
+ if (siblingFiber !== null) {
2815
+ // This branch will return us to the normal work loop.
2816
+ workInProgress = siblingFiber;
2817
+ return;
2818
+ }
2819
+ } else {
2820
+ // NOTE: If we re-enable sibling prerendering in some cases, this branch
2821
+ // is where we would switch to the normal completion path: check if a
2822
+ // sibling exists, and if so, begin work on it.
2823
+ }
2824
+
2825
+ // Otherwise, return to the parent
2826
+ // $FlowFixMe[incompatible-type] we bail out when we get a null
2827
+ incompleteWork = returnFiber;
2828
+ // Update the next thing we're working on in case something throws.
2829
+ workInProgress = incompleteWork;
2830
+ } while (incompleteWork !== null);
2831
+
2832
+ // We've unwound all the way to the root.
2833
+ workInProgressRootExitStatus = RootDidNotComplete;
2834
+ workInProgress = null;
2835
+}
2836
+
2837
function commitRoot(
2838
root: FiberRoot,
2839
recoverableErrors: null | Array<CapturedValue<mixed>>,