@@ -66,6 +66,11 @@ import {
66
66
} from './ReactFiberHostConfig' ;
67
67
68
68
import { createWorkInProgress , assignFiberPropertiesInDEV } from './ReactFiber' ;
69
+ import {
70
+ isRootSuspendedAtTime ,
71
+ markRootSuspendedAtTime ,
72
+ markRootUnsuspendedAtTime ,
73
+ } from './ReactFiberRoot' ;
69
74
import {
70
75
NoMode ,
71
76
StrictMode ,
@@ -377,8 +382,6 @@ export function scheduleUpdateOnFiber(
377
382
return ;
378
383
}
379
384
380
- root . pingTime = NoWork ;
381
-
382
385
checkForInterruption ( fiber , expirationTime ) ;
383
386
recordScheduleUpdate ( ) ;
384
387
@@ -492,6 +495,9 @@ function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
492
495
if ( lastPendingTime === NoWork || expirationTime < lastPendingTime ) {
493
496
root . lastPendingTime = expirationTime ;
494
497
}
498
+
499
+ // Mark that the root is no longer suspended at this time.
500
+ markRootUnsuspendedAtTime ( root , expirationTime ) ;
495
501
}
496
502
497
503
return root ;
@@ -807,13 +813,6 @@ function renderRoot(
807
813
'Should not already be working.' ,
808
814
) ;
809
815
810
- if ( root . firstPendingTime < expirationTime ) {
811
- // If there's no work left at this expiration time, exit immediately. This
812
- // happens when multiple callbacks are scheduled for a single root, but an
813
- // earlier callback flushes the work of a later one.
814
- return null ;
815
- }
816
-
817
816
if ( isSync && root . finishedExpirationTime === expirationTime ) {
818
817
// There's already a pending commit at this expiration time.
819
818
// TODO: This is poorly factored. This case only exists for the
@@ -831,21 +830,25 @@ function renderRoot(
831
830
} else if ( workInProgressRootExitStatus === RootSuspendedWithDelay ) {
832
831
// We could've received an update at a lower priority while we yielded.
833
832
// We're suspended in a delayed state. Once we complete this render we're
834
- // just going to try to recover at the last pending time anyway so we might
835
- // as well start doing that eagerly.
833
+ // just going to try to recover at the pending time anyway so we might as
834
+ // well start doing that eagerly.
835
+ //
836
836
// Ideally we should be able to do this even for retries but we don't yet
837
837
// know if we're going to process an update which wants to commit earlier,
838
838
// and this path happens very early so it would happen too often. Instead,
839
839
// for that case, we'll wait until we complete.
840
840
if ( workInProgressRootHasPendingPing ) {
841
841
// We have a ping at this expiration. Let's restart to see if we get unblocked.
842
842
prepareFreshStack ( root , expirationTime ) ;
843
- } else {
844
- const lastPendingTime = root . lastPendingTime ;
845
- if ( lastPendingTime < expirationTime ) {
846
- // There's lower priority work. It might be unsuspended. Try rendering
847
- // at that level immediately, while preserving the position in the queue.
848
- return renderRoot . bind ( null , root , lastPendingTime ) ;
843
+ } else if ( ! isSync ) {
844
+ // Check if there's work that isn't in the suspended range
845
+ const firstPendingTime = root . firstPendingTime ;
846
+ if ( ! isRootSuspendedAtTime ( root , firstPendingTime ) ) {
847
+ // There's a pending update that falls outside the range of
848
+ // suspended work.
849
+ if ( firstPendingTime > expirationTime ) {
850
+ return renderRoot . bind ( null , root , firstPendingTime ) ;
851
+ }
849
852
}
850
853
}
851
854
}
@@ -958,7 +961,8 @@ function renderRoot(
958
961
// something suspended, wait to commit it after a timeout.
959
962
stopFinishedWorkLoopTimer ( ) ;
960
963
961
- root . finishedWork = root . current . alternate ;
964
+ const finishedWork : Fiber = ( ( root . finishedWork =
965
+ root . current . alternate ) : any ) ;
962
966
root . finishedExpirationTime = expirationTime ;
963
967
964
968
const isLocked = resolveLocksOnRoot ( root , expirationTime ) ;
@@ -1002,6 +1006,11 @@ function renderRoot(
1002
1006
return commitRoot . bind ( null , root) ;
1003
1007
}
1004
1008
case RootSuspended : {
1009
+ markRootSuspendedAtTime ( root , expirationTime ) ;
1010
+ const lastSuspendedTime = root . lastSuspendedTime ;
1011
+ if ( expirationTime === lastSuspendedTime ) {
1012
+ root . nextAfterSuspendedTime = getRemainingExpirationTime ( finishedWork ) ;
1013
+ }
1005
1014
flushSuspensePriorityWarningInDEV ( ) ;
1006
1015
1007
1016
// We have an acceptable loading state. We need to figure out if we should
@@ -1038,11 +1047,20 @@ function renderRoot(
1038
1047
prepareFreshStack ( root , expirationTime ) ;
1039
1048
return renderRoot . bind ( null , root , expirationTime ) ;
1040
1049
}
1041
- const lastPendingTime = root . lastPendingTime ;
1042
- if ( lastPendingTime < expirationTime ) {
1050
+
1051
+ const nextAfterSuspendedTime = root . nextAfterSuspendedTime ;
1052
+ if ( nextAfterSuspendedTime !== NoWork ) {
1043
1053
// There's lower priority work. It might be unsuspended. Try rendering
1044
1054
// at that level.
1045
- return renderRoot . bind ( null , root , lastPendingTime ) ;
1055
+ return renderRoot . bind ( null , root , nextAfterSuspendedTime ) ;
1056
+ }
1057
+ if (
1058
+ lastSuspendedTime !== NoWork &&
1059
+ lastSuspendedTime !== expirationTime
1060
+ ) {
1061
+ // We should prefer to render the fallback of at the last
1062
+ // suspended level.
1063
+ return renderRoot . bind ( null , root , lastSuspendedTime ) ;
1046
1064
}
1047
1065
// The render is suspended, it hasn't timed out, and there's no lower
1048
1066
// priority work to do. Instead of committing the fallback
@@ -1058,6 +1076,11 @@ function renderRoot(
1058
1076
return commitRoot . bind ( null , root ) ;
1059
1077
}
1060
1078
case RootSuspendedWithDelay : {
1079
+ markRootSuspendedAtTime ( root , expirationTime ) ;
1080
+ const lastSuspendedTime = root . lastSuspendedTime ;
1081
+ if ( expirationTime === lastSuspendedTime ) {
1082
+ root . nextAfterSuspendedTime = getRemainingExpirationTime ( finishedWork ) ;
1083
+ }
1061
1084
flushSuspensePriorityWarningInDEV ( ) ;
1062
1085
1063
1086
if (
@@ -1077,11 +1100,20 @@ function renderRoot(
1077
1100
prepareFreshStack ( root , expirationTime ) ;
1078
1101
return renderRoot . bind ( null , root , expirationTime ) ;
1079
1102
}
1080
- const lastPendingTime = root . lastPendingTime ;
1081
- if ( lastPendingTime < expirationTime ) {
1103
+
1104
+ const nextAfterSuspendedTime = root . nextAfterSuspendedTime ;
1105
+ if ( nextAfterSuspendedTime !== NoWork ) {
1082
1106
// There's lower priority work. It might be unsuspended. Try rendering
1083
- // at that level immediately.
1084
- return renderRoot . bind ( null , root , lastPendingTime ) ;
1107
+ // at that level.
1108
+ return renderRoot . bind ( null , root , nextAfterSuspendedTime ) ;
1109
+ }
1110
+ if (
1111
+ lastSuspendedTime !== NoWork &&
1112
+ lastSuspendedTime !== expirationTime
1113
+ ) {
1114
+ // We should prefer to render the fallback of at the last
1115
+ // suspended level.
1116
+ return renderRoot . bind ( null , root , lastSuspendedTime ) ;
1085
1117
}
1086
1118
1087
1119
let msUntilTimeout ;
@@ -1425,6 +1457,14 @@ function completeUnitOfWork(unitOfWork: Fiber): Fiber | null {
1425
1457
return null ;
1426
1458
}
1427
1459
1460
+ function getRemainingExpirationTime ( fiber : Fiber ) {
1461
+ const updateExpirationTime = fiber . expirationTime ;
1462
+ const childExpirationTime = fiber . childExpirationTime ;
1463
+ return updateExpirationTime > childExpirationTime
1464
+ ? updateExpirationTime
1465
+ : childExpirationTime ;
1466
+ }
1467
+
1428
1468
function resetChildExpirationTime ( completedWork : Fiber ) {
1429
1469
if (
1430
1470
renderExpirationTime !== Never &&
@@ -1540,19 +1580,19 @@ function commitRootImpl(root, renderPriorityLevel) {
1540
1580
1541
1581
// Update the first and last pending times on this root. The new first
1542
1582
// pending time is whatever is left on the root fiber.
1543
- const updateExpirationTimeBeforeCommit = finishedWork . expirationTime ;
1544
- const childExpirationTimeBeforeCommit = finishedWork . childExpirationTime ;
1545
- const firstPendingTimeBeforeCommit =
1546
- childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
1547
- ? childExpirationTimeBeforeCommit
1548
- : updateExpirationTimeBeforeCommit ;
1549
- root . firstPendingTime = firstPendingTimeBeforeCommit ;
1550
- if ( firstPendingTimeBeforeCommit < root . lastPendingTime ) {
1583
+ const remainingExpirationTimeBeforeCommit = getRemainingExpirationTime (
1584
+ finishedWork ,
1585
+ ) ;
1586
+ root . firstPendingTime = remainingExpirationTimeBeforeCommit ;
1587
+ if ( remainingExpirationTimeBeforeCommit < root . lastPendingTime ) {
1551
1588
// This usually means we've finished all the work, but it can also happen
1552
1589
// when something gets downprioritized during render, like a hidden tree.
1553
- root . lastPendingTime = firstPendingTimeBeforeCommit ;
1590
+ root . lastPendingTime = remainingExpirationTimeBeforeCommit ;
1554
1591
}
1555
1592
1593
+ // Mark that the root is no longer suspended at the finished time
1594
+ markRootUnsuspendedAtTime ( root , expirationTime ) ;
1595
+
1556
1596
if ( root === workInProgressRoot ) {
1557
1597
// We can reset these now that they are finished.
1558
1598
workInProgressRoot = null ;
@@ -2148,20 +2188,19 @@ export function pingSuspendedRoot(
2148
2188
return ;
2149
2189
}
2150
2190
2151
- const lastPendingTime = root . lastPendingTime ;
2152
- if ( lastPendingTime < suspendedTime ) {
2191
+ if ( ! isRootSuspendedAtTime ( root , suspendedTime ) ) {
2153
2192
// The root is no longer suspended at this time.
2154
2193
return ;
2155
2194
}
2156
2195
2157
- const pingTime = root . pingTime ;
2158
- if ( pingTime !== NoWork && pingTime < suspendedTime ) {
2196
+ const lastPingedTime = root . lastPingedTime ;
2197
+ if ( lastPingedTime !== NoWork && lastPingedTime < suspendedTime ) {
2159
2198
// There's already a lower priority ping scheduled.
2160
2199
return ;
2161
2200
}
2162
2201
2163
2202
// Mark the time at which this ping was scheduled.
2164
- root . pingTime = suspendedTime ;
2203
+ root . lastPingedTime = suspendedTime ;
2165
2204
2166
2205
if ( root . finishedExpirationTime === suspendedTime ) {
2167
2206
// If there's a pending fallback waiting to commit, throw it away.
0 commit comments