diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js index cd9081d8b95b2..23ad7f657ba33 100644 --- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js +++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js @@ -153,7 +153,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", @@ -183,7 +183,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -244,7 +244,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", @@ -286,7 +286,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", @@ -337,7 +337,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -392,7 +392,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -446,7 +446,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-state-update-2-Example", "--layout-effects-stop", @@ -459,7 +459,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] @@ -499,7 +499,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-forced-update-2-Example", "--layout-effects-stop", @@ -512,7 +512,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] @@ -564,7 +564,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -618,7 +618,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -658,7 +658,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--component-layout-effect-mount-start-Example", "--schedule-state-update-2-Example", @@ -673,7 +673,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] @@ -705,7 +705,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -723,7 +723,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", ] `); @@ -755,7 +755,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--layout-effects-stop", "--commit-stop", @@ -805,7 +805,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--schedule-state-update-2-ErrorBoundary", "--layout-effects-stop", @@ -819,7 +819,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", ] `); @@ -885,7 +885,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--schedule-state-update-2-ErrorBoundary", "--layout-effects-stop", @@ -898,7 +898,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--commit-stop", "--commit-stop", ] @@ -961,7 +961,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-32", "--component-layout-effect-mount-start-ComponentWithEffects", "--component-layout-effect-mount-stop", @@ -1017,7 +1017,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--component-layout-effect-unmount-start-ComponentWithEffects", "--component-layout-effect-unmount-stop", "--component-layout-effect-unmount-start-ComponentWithEffects", @@ -1051,7 +1051,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", @@ -1101,7 +1101,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-2", "--layout-effects-stop", "--commit-stop", @@ -1141,7 +1141,7 @@ describe('Timeline profiler', () => { "--profiler-version-1", "--react-internal-module-start- at filtered (:0:0)", "--react-internal-module-stop- at filtered (:1:1)", - "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen", + "--react-lane-labels-SyncHydrationLane,Sync,InputContinuousHydration,InputContinuous,DefaultHydration,Default,TransitionHydration,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Transition,Retry,Retry,Retry,Retry,SelectiveHydration,IdleHydration,Idle,Offscreen,Deferred", "--layout-effects-start-8", "--layout-effects-stop", "--commit-stop", diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js index b822663c93861..52eb869e9d17a 100644 --- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js +++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js @@ -2001,15 +2001,15 @@ describe('Timeline profiler', () => { 524288 => "Transition", 1048576 => "Transition", 2097152 => "Transition", - 4194304 => "Transition", + 4194304 => "Retry", 8388608 => "Retry", 16777216 => "Retry", 33554432 => "Retry", - 67108864 => "Retry", - 134217728 => "SelectiveHydration", - 268435456 => "IdleHydration", - 536870912 => "Idle", - 1073741824 => "Offscreen", + 67108864 => "SelectiveHydration", + 134217728 => "IdleHydration", + 268435456 => "Idle", + 536870912 => "Offscreen", + 1073741824 => "Deferred", }, "laneToReactMeasureMap": Map { 1 => [], @@ -2269,15 +2269,15 @@ describe('Timeline profiler', () => { 524288 => "Transition", 1048576 => "Transition", 2097152 => "Transition", - 4194304 => "Transition", + 4194304 => "Retry", 8388608 => "Retry", 16777216 => "Retry", 33554432 => "Retry", - 67108864 => "Retry", - 134217728 => "SelectiveHydration", - 268435456 => "IdleHydration", - 536870912 => "Idle", - 1073741824 => "Offscreen", + 67108864 => "SelectiveHydration", + 134217728 => "IdleHydration", + 268435456 => "Idle", + 536870912 => "Offscreen", + 1073741824 => "Deferred", }, "laneToReactMeasureMap": Map { 1 => [], diff --git a/packages/react-reconciler/src/ReactFiberHooks.js b/packages/react-reconciler/src/ReactFiberHooks.js index d2c456a5f9c9a..c3c85af085932 100644 --- a/packages/react-reconciler/src/ReactFiberHooks.js +++ b/packages/react-reconciler/src/ReactFiberHooks.js @@ -61,16 +61,17 @@ import { NoLane, SyncLane, OffscreenLane, + DeferredLane, NoLanes, isSubsetOfLanes, includesBlockingLane, includesOnlyNonUrgentLanes, - claimNextTransitionLane, mergeLanes, removeLanes, intersectLanes, isTransitionLane, markRootEntangled, + includesSomeLane, } from './ReactFiberLane'; import { ContinuousEventPriority, @@ -101,6 +102,7 @@ import { getWorkInProgressRootRenderLanes, scheduleUpdateOnFiber, requestUpdateLane, + requestDeferredLane, markSkippedUpdateLanes, isInvalidExecutionContextForEventFunction, } from './ReactFiberWorkLoop'; @@ -2665,16 +2667,21 @@ function rerenderDeferredValue(value: T, initialValue?: T): T { } function mountDeferredValueImpl(hook: Hook, value: T, initialValue?: T): T { - if (enableUseDeferredValueInitialArg && initialValue !== undefined) { + if ( + enableUseDeferredValueInitialArg && // When `initialValue` is provided, we defer the initial render even if the // current render is not synchronous. - // TODO: However, to avoid waterfalls, we should not defer if this render - // was itself spawned by an earlier useDeferredValue. Plan is to add a - // Deferred lane to track this. + initialValue !== undefined && + // However, to avoid waterfalls, we do not defer if this render + // was itself spawned by an earlier useDeferredValue. Check if DeferredLane + // is part of the render lanes. + !includesSomeLane(renderLanes, DeferredLane) + ) { + // Render with the initial value hook.memoizedState = initialValue; - // Schedule a deferred render - const deferredLane = claimNextTransitionLane(); + // Schedule a deferred render to switch to the final value. + const deferredLane = requestDeferredLane(); currentlyRenderingFiber.lanes = mergeLanes( currentlyRenderingFiber.lanes, deferredLane, @@ -2710,7 +2717,7 @@ function updateDeferredValueImpl( if (!is(value, prevValue)) { // Schedule a deferred render - const deferredLane = claimNextTransitionLane(); + const deferredLane = requestDeferredLane(); currentlyRenderingFiber.lanes = mergeLanes( currentlyRenderingFiber.lanes, deferredLane, diff --git a/packages/react-reconciler/src/ReactFiberLane.js b/packages/react-reconciler/src/ReactFiberLane.js index 21055ac82f7e9..864edf6ee676b 100644 --- a/packages/react-reconciler/src/ReactFiberLane.js +++ b/packages/react-reconciler/src/ReactFiberLane.js @@ -52,7 +52,7 @@ export const SyncUpdateLanes: Lane = enableUnifiedSyncLane : SyncLane; const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000001000000; -const TransitionLanes: Lanes = /* */ 0b0000000011111111111111110000000; +const TransitionLanes: Lanes = /* */ 0b0000000001111111111111110000000; const TransitionLane1: Lane = /* */ 0b0000000000000000000000010000000; const TransitionLane2: Lane = /* */ 0b0000000000000000000000100000000; const TransitionLane3: Lane = /* */ 0b0000000000000000000001000000000; @@ -68,24 +68,24 @@ const TransitionLane12: Lane = /* */ 0b0000000000001000000 const TransitionLane13: Lane = /* */ 0b0000000000010000000000000000000; const TransitionLane14: Lane = /* */ 0b0000000000100000000000000000000; const TransitionLane15: Lane = /* */ 0b0000000001000000000000000000000; -const TransitionLane16: Lane = /* */ 0b0000000010000000000000000000000; -const RetryLanes: Lanes = /* */ 0b0000111100000000000000000000000; -const RetryLane1: Lane = /* */ 0b0000000100000000000000000000000; -const RetryLane2: Lane = /* */ 0b0000001000000000000000000000000; -const RetryLane3: Lane = /* */ 0b0000010000000000000000000000000; -const RetryLane4: Lane = /* */ 0b0000100000000000000000000000000; +const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000; +const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000; +const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000; +const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000; +const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000; export const SomeRetryLane: Lane = RetryLane1; -export const SelectiveHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; +export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000; -const NonIdleLanes: Lanes = /* */ 0b0001111111111111111111111111111; +const NonIdleLanes: Lanes = /* */ 0b0000111111111111111111111111111; -export const IdleHydrationLane: Lane = /* */ 0b0010000000000000000000000000000; -export const IdleLane: Lane = /* */ 0b0100000000000000000000000000000; +export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000; +export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000; -export const OffscreenLane: Lane = /* */ 0b1000000000000000000000000000000; +export const OffscreenLane: Lane = /* */ 0b0100000000000000000000000000000; +export const DeferredLane: Lane = /* */ 0b1000000000000000000000000000000; // Any lane that might schedule an update. This is used to detect infinite // update loops, so it doesn't include hydration lanes or retries. @@ -135,6 +135,9 @@ export function getLabelForLane(lane: Lane): string | void { if (lane & OffscreenLane) { return 'Offscreen'; } + if (lane & DeferredLane) { + return 'Deferred'; + } } } @@ -180,7 +183,6 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { case TransitionLane13: case TransitionLane14: case TransitionLane15: - case TransitionLane16: return lanes & TransitionLanes; case RetryLane1: case RetryLane2: @@ -195,6 +197,10 @@ function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes { return IdleLane; case OffscreenLane: return OffscreenLane; + case DeferredLane: + // This shouldn't be reachable because deferred work is always entangled + // with something else. + return NoLanes; default: if (__DEV__) { console.error( @@ -367,7 +373,6 @@ function computeExpirationTime(lane: Lane, currentTime: number) { case TransitionLane13: case TransitionLane14: case TransitionLane15: - case TransitionLane16: return currentTime + 5000; case RetryLane1: case RetryLane2: @@ -383,6 +388,7 @@ function computeExpirationTime(lane: Lane, currentTime: number) { case IdleHydrationLane: case IdleLane: case OffscreenLane: + case DeferredLane: // Anything idle priority or lower should never expire. return NoTimestamp; default: @@ -616,7 +622,11 @@ export function markRootUpdated(root: FiberRoot, updateLane: Lane) { } } -export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { +export function markRootSuspended( + root: FiberRoot, + suspendedLanes: Lanes, + spawnedLane: Lane, +) { root.suspendedLanes |= suspendedLanes; root.pingedLanes &= ~suspendedLanes; @@ -631,13 +641,21 @@ export function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { lanes &= ~lane; } + + if (spawnedLane !== NoLane) { + markSpawnedDeferredLane(root, spawnedLane, suspendedLanes); + } } export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) { root.pingedLanes |= root.suspendedLanes & pingedLanes; } -export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { +export function markRootFinished( + root: FiberRoot, + remainingLanes: Lanes, + spawnedLane: Lane, +) { const noLongerPendingLanes = root.pendingLanes & ~remainingLanes; root.pendingLanes = remainingLanes; @@ -683,6 +701,37 @@ export function markRootFinished(root: FiberRoot, remainingLanes: Lanes) { lanes &= ~lane; } + + if (spawnedLane !== NoLane) { + markSpawnedDeferredLane( + root, + spawnedLane, + // This render finished successfully without suspending, so we don't need + // to entangle the spawned task with the parent task. + NoLanes, + ); + } +} + +function markSpawnedDeferredLane( + root: FiberRoot, + spawnedLane: Lane, + entangledLanes: Lanes, +) { + // This render spawned a deferred task. Mark it as pending. + root.pendingLanes |= spawnedLane; + root.suspendedLanes &= ~spawnedLane; + + // Entangle the spawned lane with the DeferredLane bit so that we know it + // was the result of another render. This lets us avoid a useDeferredValue + // waterfall — only the first level will defer. + const spawnedLaneIndex = laneToIndex(spawnedLane); + root.entangledLanes |= spawnedLane; + root.entanglements[spawnedLaneIndex] |= + DeferredLane | + // If the parent render task suspended, we must also entangle those lanes + // with the spawned task. + entangledLanes; } export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) { @@ -795,7 +844,6 @@ export function getBumpedLaneForHydration( case TransitionLane13: case TransitionLane14: case TransitionLane15: - case TransitionLane16: case RetryLane1: case RetryLane2: case RetryLane3: diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index cb217d04eba19..fbfa153b71785 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -366,6 +366,8 @@ let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes; let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes; // Lanes that were pinged (in an interleaved event) during this render. let workInProgressRootPingedLanes: Lanes = NoLanes; +// If this lane scheduled deferred work, this is the lane of the deferred task. +let workInProgressDeferredLane: Lane = NoLane; // Errors that are thrown during the render phase. let workInProgressRootConcurrentErrors: Array> | null = null; @@ -683,6 +685,27 @@ function requestRetryLane(fiber: Fiber) { return claimNextRetryLane(); } +export function requestDeferredLane(): Lane { + if (workInProgressDeferredLane === NoLane) { + // If there are multiple useDeferredValue hooks in the same render, the + // tasks that they spawn should all be batched together, so they should all + // receive the same lane. + if (includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)) { + // There's only one OffscreenLane, so if it contains deferred work, we + // should just reschedule using the same lane. + // TODO: We also use OffscreenLane for hydration, on the basis that the + // initial HTML is the same as the hydrated UI, but since the deferred + // task will change the UI, it should be treated like an update. Use + // TransitionHydrationLane to trigger selective hydration. + workInProgressDeferredLane = OffscreenLane; + } else { + // Everything else is spawned as a transition. + workInProgressDeferredLane = requestTransitionLane(); + } + } + return workInProgressDeferredLane; +} + export function scheduleUpdateOnFiber( root: FiberRoot, fiber: Fiber, @@ -712,7 +735,11 @@ export function scheduleUpdateOnFiber( // The incoming update might unblock the current render. Interrupt the // current attempt and restart from the top. prepareFreshStack(root, NoLanes); - markRootSuspended(root, workInProgressRootRenderLanes); + markRootSuspended( + root, + workInProgressRootRenderLanes, + workInProgressDeferredLane, + ); } // Mark that the root has a pending update. @@ -792,7 +819,11 @@ export function scheduleUpdateOnFiber( // effect of interrupting the current render and switching to the update. // TODO: Make sure this doesn't override pings that happen while we've // already started rendering. - markRootSuspended(root, workInProgressRootRenderLanes); + markRootSuspended( + root, + workInProgressRootRenderLanes, + workInProgressDeferredLane, + ); } } @@ -903,7 +934,7 @@ export function performConcurrentWorkOnRoot( // The render unwound without completing the tree. This happens in special // cases where need to exit the current render without producing a // consistent tree or committing. - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, NoLane); } else { // The render completed. @@ -947,7 +978,7 @@ export function performConcurrentWorkOnRoot( if (exitStatus === RootFatalErrored) { const fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, NoLane); ensureRootIsScheduled(root); throw fatalError; } @@ -1074,7 +1105,7 @@ function finishConcurrentRender( // This is a transition, so we should exit without committing a // placeholder and without scheduling a timeout. Delay indefinitely // until we receive more data. - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, workInProgressDeferredLane); return; } // Commit the placeholder. @@ -1096,6 +1127,7 @@ function finishConcurrentRender( root, workInProgressRootRecoverableErrors, workInProgressTransitions, + workInProgressDeferredLane, ); } else { if ( @@ -1109,7 +1141,7 @@ function finishConcurrentRender( // Don't bother with a very short suspense time. if (msUntilTimeout > 10) { - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, workInProgressDeferredLane); const nextLanes = getNextLanes(root, NoLanes); if (nextLanes !== NoLanes) { @@ -1131,6 +1163,7 @@ function finishConcurrentRender( workInProgressRootRecoverableErrors, workInProgressTransitions, lanes, + workInProgressDeferredLane, ), msUntilTimeout, ); @@ -1143,6 +1176,7 @@ function finishConcurrentRender( workInProgressRootRecoverableErrors, workInProgressTransitions, lanes, + workInProgressDeferredLane, ); } } @@ -1153,6 +1187,7 @@ function commitRootWhenReady( recoverableErrors: Array> | null, transitions: Array | null, lanes: Lanes, + spawnedLane: Lane, ) { // TODO: Combine retry throttling with Suspensey commits. Right now they run // one after the other. @@ -1180,13 +1215,13 @@ function commitRootWhenReady( root.cancelPendingCommit = schedulePendingCommit( commitRoot.bind(null, root, recoverableErrors, transitions), ); - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, spawnedLane); return; } } // Otherwise, commit immediately. - commitRoot(root, recoverableErrors, transitions); + commitRoot(root, recoverableErrors, transitions, spawnedLane); } function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean { @@ -1242,7 +1277,11 @@ function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean { return true; } -function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { +function markRootSuspended( + root: FiberRoot, + suspendedLanes: Lanes, + spawnedLane: Lane, +) { // When suspending, we should always exclude lanes that were pinged or (more // rarely, since we try to avoid it) updated during the render phase. // TODO: Lol maybe there's a better way to factor this besides this @@ -1252,7 +1291,7 @@ function markRootSuspended(root: FiberRoot, suspendedLanes: Lanes) { suspendedLanes, workInProgressRootInterleavedUpdatedLanes, ); - markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes); + markRootSuspended_dontCallThisOneDirectly(root, suspendedLanes, spawnedLane); } // This is the entry point for synchronous tasks that don't go @@ -1302,7 +1341,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null { if (exitStatus === RootFatalErrored) { const fatalError = workInProgressRootFatalError; prepareFreshStack(root, NoLanes); - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, NoLane); ensureRootIsScheduled(root); throw fatalError; } @@ -1311,7 +1350,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null { // The render unwound without completing the tree. This happens in special // cases where need to exit the current render without producing a // consistent tree or committing. - markRootSuspended(root, lanes); + markRootSuspended(root, lanes, NoLane); ensureRootIsScheduled(root); return null; } @@ -1325,6 +1364,7 @@ export function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes): null { root, workInProgressRootRecoverableErrors, workInProgressTransitions, + workInProgressDeferredLane, ); // Before exiting, make sure there's a callback scheduled for the next @@ -1537,6 +1577,7 @@ function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber { workInProgressRootInterleavedUpdatedLanes = NoLanes; workInProgressRootRenderPhaseUpdatedLanes = NoLanes; workInProgressRootPingedLanes = NoLanes; + workInProgressDeferredLane = NoLane; workInProgressRootConcurrentErrors = null; workInProgressRootRecoverableErrors = null; @@ -1808,9 +1849,9 @@ export function renderDidSuspendDelayIfPossible(): void { // Check if there are updates that we skipped tree that might have unblocked // this render. if ( - workInProgressRoot !== null && (includesNonIdleWork(workInProgressRootSkippedLanes) || - includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) + includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) && + workInProgressRoot !== null ) { // Mark the current render as suspended so that we switch to working on // the updates that were skipped. Usually we only suspend at the end of @@ -1821,8 +1862,11 @@ export function renderDidSuspendDelayIfPossible(): void { // pinged or updated while we were rendering. // TODO: Consider unwinding immediately, using the // SuspendedOnHydration mechanism. - // $FlowFixMe[incompatible-call] need null check workInProgressRoot - markRootSuspended(workInProgressRoot, workInProgressRootRenderLanes); + markRootSuspended( + workInProgressRoot, + workInProgressRootRenderLanes, + workInProgressDeferredLane, + ); } } @@ -2592,6 +2636,7 @@ function commitRoot( root: FiberRoot, recoverableErrors: null | Array>, transitions: Array | null, + spawnedLane: Lane, ) { // TODO: This no longer makes any sense. We already wrap the mutation and // layout phases. Should be able to remove. @@ -2606,6 +2651,7 @@ function commitRoot( recoverableErrors, transitions, previousUpdateLanePriority, + spawnedLane, ); } finally { ReactCurrentBatchConfig.transition = prevTransition; @@ -2620,6 +2666,7 @@ function commitRootImpl( recoverableErrors: null | Array>, transitions: Array | null, renderPriorityLevel: EventPriority, + spawnedLane: Lane, ) { do { // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which @@ -2696,7 +2743,7 @@ function commitRootImpl( const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes(); remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes); - markRootFinished(root, remainingLanes); + markRootFinished(root, remainingLanes, spawnedLane); if (root === workInProgressRoot) { // We can reset these now that they are finished. diff --git a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js index 39d42d254f610..f8026d6ea9fab 100644 --- a/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js +++ b/packages/react-reconciler/src/__tests__/DebugTracing-test.internal.js @@ -19,7 +19,7 @@ describe('DebugTracing', () => { const SYNC_LANE_STRING = '0b0000000000000000000000000000010'; const DEFAULT_LANE_STRING = '0b0000000000000000000000000100000'; - const RETRY_LANE_STRING = '0b0000000100000000000000000000000'; + const RETRY_LANE_STRING = '0b0000000010000000000000000000000'; global.IS_REACT_ACT_ENVIRONMENT = true; diff --git a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js index c29a9c4287275..76d90f39cf276 100644 --- a/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js +++ b/packages/react-reconciler/src/__tests__/ReactDeferredValue-test.js @@ -15,8 +15,11 @@ let startTransition; let useDeferredValue; let useMemo; let useState; +let Suspense; +let Offscreen; let assertLog; let waitForPaint; +let textCache; describe('ReactDeferredValue', () => { beforeEach(() => { @@ -30,17 +33,78 @@ describe('ReactDeferredValue', () => { useDeferredValue = React.useDeferredValue; useMemo = React.useMemo; useState = React.useState; + Suspense = React.Suspense; + Offscreen = React.unstable_Offscreen; const InternalTestUtils = require('internal-test-utils'); assertLog = InternalTestUtils.assertLog; waitForPaint = InternalTestUtils.waitForPaint; + + textCache = new Map(); }); + function resolveText(text) { + const record = textCache.get(text); + if (record === undefined) { + const newRecord = { + status: 'resolved', + value: text, + }; + textCache.set(text, newRecord); + } else if (record.status === 'pending') { + const thenable = record.value; + record.status = 'resolved'; + record.value = text; + thenable.pings.forEach(t => t()); + } + } + + function readText(text) { + const record = textCache.get(text); + if (record !== undefined) { + switch (record.status) { + case 'pending': + Scheduler.log(`Suspend! [${text}]`); + throw record.value; + case 'rejected': + throw record.value; + case 'resolved': + return record.value; + } + } else { + Scheduler.log(`Suspend! [${text}]`); + const thenable = { + pings: [], + then(resolve) { + if (newRecord.status === 'pending') { + thenable.pings.push(resolve); + } else { + Promise.resolve().then(() => resolve(newRecord.value)); + } + }, + }; + + const newRecord = { + status: 'pending', + value: thenable, + }; + textCache.set(text, newRecord); + + throw thenable; + } + } + function Text({text}) { Scheduler.log(text); return text; } + function AsyncText({text}) { + readText(text); + Scheduler.log(text); + return text; + } + it('does not cause an infinite defer loop if the original value isn\t memoized', async () => { function App({value}) { // The object passed to useDeferredValue is never the same as the previous @@ -341,4 +405,215 @@ describe('ReactDeferredValue', () => { assertLog(['Final']); expect(root).toMatchRenderedOutput('Final'); }); + + // @gate enableUseDeferredValueInitialArg + it( + 'if a suspended render spawns a deferred task, we can switch to the ' + + 'deferred task without finishing the original one', + async () => { + function App() { + const text = useDeferredValue('Final', 'Loading...'); + return ; + } + + const root = ReactNoop.createRoot(); + await act(() => root.render()); + assertLog([ + 'Suspend! [Loading...]', + // The initial value suspended, so we attempt the final value, which + // also suspends. + 'Suspend! [Final]', + ]); + expect(root).toMatchRenderedOutput(null); + + // The final value loads, so we can skip the initial value entirely. + await act(() => resolveText('Final')); + assertLog(['Final']); + expect(root).toMatchRenderedOutput('Final'); + + // When the initial value finally loads, nothing happens because we no + // longer need it. + await act(() => resolveText('Loading...')); + assertLog([]); + expect(root).toMatchRenderedOutput('Final'); + }, + ); + + // @gate enableUseDeferredValueInitialArg + it( + 'if a suspended render spawns a deferred task that also suspends, we can ' + + 'finish the original task if that one loads first', + async () => { + function App() { + const text = useDeferredValue('Final', 'Loading...'); + return ; + } + + const root = ReactNoop.createRoot(); + await act(() => root.render()); + assertLog([ + 'Suspend! [Loading...]', + // The initial value suspended, so we attempt the final value, which + // also suspends. + 'Suspend! [Final]', + ]); + expect(root).toMatchRenderedOutput(null); + + // The initial value resolves first, so we render that. + await act(() => resolveText('Loading...')); + assertLog([ + 'Loading...', + // Still waiting for the final value. + 'Suspend! [Final]', + ]); + expect(root).toMatchRenderedOutput('Loading...'); + + // The final value loads, so we can switch to that. + await act(() => resolveText('Final')); + assertLog(['Final']); + expect(root).toMatchRenderedOutput('Final'); + }, + ); + + // @gate enableUseDeferredValueInitialArg + it( + 'if there are multiple useDeferredValues in the same tree, only the ' + + 'first level defers; subsequent ones go straight to the final value, to ' + + 'avoid a waterfall', + async () => { + function App() { + const showContent = useDeferredValue(true, false); + if (!showContent) { + return ; + } + return ; + } + + function Content() { + const text = useDeferredValue('Content', 'Content Preview'); + return ; + } + + const root = ReactNoop.createRoot(); + resolveText('App Preview'); + + await act(() => root.render()); + assertLog([ + // The App shows an immediate preview + 'App Preview', + // Then we switch to showing the content. The Content component also + // contains a useDeferredValue, but since we already showed a preview + // in a parent component, we skip the preview in the inner one and + // go straight to attempting the final value. + // + // (Note that this is intentionally different from how nested Suspense + // boundaries work, where we always prefer to show the innermost + // loading state.) + 'Suspend! [Content]', + ]); + // Still showing the App preview state because the inner + // content suspended. + expect(root).toMatchRenderedOutput('App Preview'); + + // Finish loading the content + await act(() => resolveText('Content')); + // We didn't even attempt to render Content Preview. + assertLog(['Content']); + expect(root).toMatchRenderedOutput('Content'); + }, + ); + + // @gate enableUseDeferredValueInitialArg + it('avoids a useDeferredValue waterfall when separated by a Suspense boundary', async () => { + // Same as the previous test but with a Suspense boundary separating the + // two useDeferredValue hooks. + function App() { + const showContent = useDeferredValue(true, false); + if (!showContent) { + return ; + } + return ( + }> + + + ); + } + + function Content() { + const text = useDeferredValue('Content', 'Content Preview'); + return ; + } + + const root = ReactNoop.createRoot(); + resolveText('App Preview'); + + await act(() => root.render()); + assertLog([ + // The App shows an immediate preview + 'App Preview', + // Then we switch to showing the content. The Content component also + // contains a useDeferredValue, but since we already showed a preview + // in a parent component, we skip the preview in the inner one and + // go straight to attempting the final value. + 'Suspend! [Content]', + 'Loading...', + ]); + // The content suspended, so we show a Suspense fallback + expect(root).toMatchRenderedOutput('Loading...'); + + // Finish loading the content + await act(() => resolveText('Content')); + // We didn't even attempt to render Content Preview. + assertLog(['Content']); + expect(root).toMatchRenderedOutput('Content'); + }); + + // @gate enableUseDeferredValueInitialArg + // @gate enableOffscreen + it('useDeferredValue can spawn a deferred task while prerendering a hidden tree', async () => { + function App() { + const text = useDeferredValue('Final', 'Preview'); + return ( +
+ +
+ ); + } + + let revealContent; + function Container({children}) { + const [shouldShow, setState] = useState(false); + revealContent = () => setState(true); + return ( + + {children} + + ); + } + + const root = ReactNoop.createRoot(); + + // Prerender a hidden tree + resolveText('Preview'); + await act(() => + root.render( + + + , + ), + ); + assertLog(['Preview', 'Suspend! [Final]']); + expect(root).toMatchRenderedOutput(); + + // Finish loading the content + await act(() => resolveText('Final')); + assertLog(['Final']); + expect(root).toMatchRenderedOutput(); + + // Now reveal the hidden tree. It should toggle the visibility without + // having to re-render anything inside the prerendered tree. + await act(() => revealContent()); + assertLog([]); + expect(root).toMatchRenderedOutput(
Final
); + }); });