@@ -245,6 +245,10 @@ let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
245
245
// Used for performance tracking.
246
246
let interruptedBy : Fiber | null = null ;
247
247
248
+ // Do not increment or decrement interaction counts in the event of suspense timeouts.
249
+ // This flag is only used when enableInteractionTracking is true.
250
+ let freezeInteractionCount : boolean = false ;
251
+
248
252
let stashedWorkInProgressProperties ;
249
253
let replayUnitOfWork ;
250
254
let isReplayingFailedUnitOfWork ;
@@ -772,24 +776,28 @@ function commitRoot(root: FiberRoot, finishedWork: Fiber): void {
772
776
unhandledError = error ;
773
777
}
774
778
} finally {
775
- // Now that we're done, check the completed batch of interactions.
776
- // If no more work is outstanding for a given interaction,
777
- // We need to notify the subscribers that it's finished.
778
- committedInteractions . forEach ( interaction => {
779
- interaction . __count -- ;
780
- if ( subscriber !== null && interaction . __count === 0 ) {
781
- try {
782
- subscriber . onInteractionScheduledWorkCompleted ( interaction ) ;
783
- } catch ( error ) {
784
- // It's not safe for commitRoot() to throw.
785
- // Store the error for now and we'll re-throw in finishRendering().
786
- if ( ! hasUnhandledError ) {
787
- hasUnhandledError = true ;
788
- unhandledError = error ;
779
+ // Don't update interaction counts if we're frozen due to suspense.
780
+ // In this case, we can skip the completed-work check entirely.
781
+ if ( ! freezeInteractionCount ) {
782
+ // Now that we're done, check the completed batch of interactions.
783
+ // If no more work is outstanding for a given interaction,
784
+ // We need to notify the subscribers that it's finished.
785
+ committedInteractions . forEach ( interaction => {
786
+ interaction . __count -- ;
787
+ if ( subscriber !== null && interaction . __count === 0 ) {
788
+ try {
789
+ subscriber . onInteractionScheduledWorkCompleted ( interaction ) ;
790
+ } catch ( error ) {
791
+ // It's not safe for commitRoot() to throw.
792
+ // Store the error for now and we'll re-throw in finishRendering().
793
+ if ( ! hasUnhandledError ) {
794
+ hasUnhandledError = true ;
795
+ unhandledError = error ;
796
+ }
789
797
}
790
798
}
791
- }
792
- } ) ;
799
+ } ) ;
800
+ }
793
801
}
794
802
}
795
803
}
@@ -1178,6 +1186,49 @@ function renderRoot(
1178
1186
nextRenderExpirationTime ,
1179
1187
) ;
1180
1188
root . pendingCommitExpirationTime = NoWork ;
1189
+
1190
+ if ( enableInteractionTracking ) {
1191
+ // Determine which interactions this batch of work currently includes,
1192
+ // So that we can accurately attribute time spent working on it,
1193
+ // And so that cascading work triggered during the render phase will be associated with it.
1194
+ const interactions : Set < Interaction > = new Set();
1195
+ root.pendingInteractionMap.forEach(
1196
+ (scheduledInteractions, scheduledExpirationTime) => {
1197
+ if ( scheduledExpirationTime <= expirationTime ) {
1198
+ scheduledInteractions . forEach ( interaction =>
1199
+ interactions . add ( interaction ) ,
1200
+ ) ;
1201
+ }
1202
+ } ,
1203
+ ) ;
1204
+
1205
+ // Store the current set of interactions on the FiberRoot for a few reasons:
1206
+ // We can re-use it in hot functions like renderRoot() without having to recalculate it.
1207
+ // We will also use it in commitWork() to pass to any Profiler onRender() hooks.
1208
+ // This also provides DevTools with a way to access it when the onCommitRoot() hook is called.
1209
+ root . memoizedInteractions = interactions ;
1210
+
1211
+ if ( interactions . size > 0 ) {
1212
+ const subscriber = __subscriberRef . current ;
1213
+ if ( subscriber !== null ) {
1214
+ const threadID = computeThreadID (
1215
+ expirationTime ,
1216
+ root . interactionThreadID ,
1217
+ ) ;
1218
+ try {
1219
+ subscriber . onWorkStarted ( interactions , threadID ) ;
1220
+ } catch ( error ) {
1221
+ // Work thrown by a interaction-tracking subscriber should be rethrown,
1222
+ // But only once it's safe (to avoid leaveing the scheduler in an invalid state).
1223
+ // Store the error for now and we'll re-throw in finishRendering().
1224
+ if ( ! hasUnhandledError ) {
1225
+ hasUnhandledError = true ;
1226
+ unhandledError = error ;
1227
+ }
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1181
1232
}
1182
1233
1183
1234
let didFatal = false ;
@@ -1550,7 +1601,19 @@ function retrySuspendedRoot(
1550
1601
scheduleWorkToRoot ( fiber , retryTime ) ;
1551
1602
const rootExpirationTime = root . expirationTime ;
1552
1603
if ( rootExpirationTime !== NoWork ) {
1553
- requestWork ( root , rootExpirationTime ) ;
1604
+ if ( enableInteractionTracking ) {
1605
+ // Restore previous interactions so that new work is associated with them.
1606
+ let prevInteractions = __interactionsRef . current ;
1607
+ __interactionsRef . current = root . memoizedInteractions ;
1608
+ // Because suspense timeouts do not decrement the interaction count,
1609
+ // Continued suspense work should also not increment the count.
1610
+ freezeInteractionCount = true ;
1611
+ requestWork ( root , rootExpirationTime ) ;
1612
+ freezeInteractionCount = false ;
1613
+ __interactionsRef . current = prevInteractions ;
1614
+ } else {
1615
+ requestWork ( root , rootExpirationTime ) ;
1616
+ }
1554
1617
}
1555
1618
}
1556
1619
}
@@ -1618,7 +1681,7 @@ function storeInteractionsForExpirationTime(
1618
1681
const pendingInteractions = root . pendingInteractionMap . get ( expirationTime ) ;
1619
1682
if ( pendingInteractions != null ) {
1620
1683
interactions . forEach ( interaction => {
1621
- if ( ! pendingInteractions . has ( interaction ) ) {
1684
+ if ( ! freezeInteractionCount && ! pendingInteractions . has ( interaction ) ) {
1622
1685
// Update the pending async work count for previously unscheduled interaction.
1623
1686
interaction . __count ++ ;
1624
1687
}
@@ -1629,9 +1692,11 @@ function storeInteractionsForExpirationTime(
1629
1692
root . pendingInteractionMap . set ( expirationTime , new Set ( interactions ) ) ;
1630
1693
1631
1694
// Update the pending async work count for the current interactions.
1632
- interactions . forEach ( interaction => {
1633
- interaction . __count ++ ;
1634
- } ) ;
1695
+ if ( ! freezeInteractionCount ) {
1696
+ interactions . forEach ( interaction => {
1697
+ interaction . __count ++ ;
1698
+ } ) ;
1699
+ }
1635
1700
}
1636
1701
1637
1702
const subscriber = __subscriberRef . current ;
@@ -1860,7 +1925,16 @@ function onTimeout(root, finishedWork, suspendedExpirationTime) {
1860
1925
// because we're at the top of a timer event.
1861
1926
recomputeCurrentRendererTime ( ) ;
1862
1927
currentSchedulerTime = currentRendererTime ;
1863
- flushRoot ( root , suspendedExpirationTime ) ;
1928
+
1929
+ if ( enableInteractionTracking ) {
1930
+ // Don't update pending interaction counts for suspense timeouts,
1931
+ // Because we know we still need to do more work in this case.
1932
+ freezeInteractionCount = true ;
1933
+ flushRoot ( root , suspendedExpirationTime ) ;
1934
+ freezeInteractionCount = false ;
1935
+ } else {
1936
+ flushRoot ( root , suspendedExpirationTime ) ;
1937
+ }
1864
1938
}
1865
1939
}
1866
1940
@@ -2171,49 +2245,6 @@ function performWorkOnRoot(
2171
2245
2172
2246
isRendering = true ;
2173
2247
2174
- if ( enableInteractionTracking ) {
2175
- // Determine which interactions this batch of work currently includes,
2176
- // So that we can accurately attribute time spent working on it,
2177
- // And so that cascading work triggered during the render phase will be associated with it.
2178
- const interactions : Set < Interaction > = new Set();
2179
- root.pendingInteractionMap.forEach(
2180
- (scheduledInteractions, scheduledExpirationTime) => {
2181
- if ( scheduledExpirationTime <= expirationTime ) {
2182
- scheduledInteractions . forEach ( interaction =>
2183
- interactions . add ( interaction ) ,
2184
- ) ;
2185
- }
2186
- } ,
2187
- ) ;
2188
-
2189
- // Store the current set of interactions on the FiberRoot for a few reasons:
2190
- // We can re-use it in hot functions like renderRoot() without having to recalculate it.
2191
- // We will also use it in commitWork() to pass to any Profiler onRender() hooks.
2192
- // This also provides DevTools with a way to access it when the onCommitRoot() hook is called.
2193
- root . memoizedInteractions = interactions ;
2194
-
2195
- if ( interactions . size > 0 ) {
2196
- const subscriber = __subscriberRef . current ;
2197
- if ( subscriber !== null ) {
2198
- const threadID = computeThreadID (
2199
- expirationTime ,
2200
- root . interactionThreadID ,
2201
- ) ;
2202
- try {
2203
- subscriber . onWorkStarted ( interactions , threadID ) ;
2204
- } catch ( error ) {
2205
- // Work thrown by a interaction-tracking subscriber should be rethrown,
2206
- // But only once it's safe (to avoid leaveing the scheduler in an invalid state).
2207
- // Store the error for now and we'll re-throw in finishRendering().
2208
- if ( ! hasUnhandledError ) {
2209
- hasUnhandledError = true ;
2210
- unhandledError = error ;
2211
- }
2212
- }
2213
- }
2214
- }
2215
- }
2216
-
2217
2248
// Check if this is async work or sync/expired work.
2218
2249
if ( deadline === null || isExpired ) {
2219
2250
// Flush work without yielding.
@@ -2477,5 +2508,4 @@ export {
2477
2508
interactiveUpdates,
2478
2509
flushInteractiveUpdates,
2479
2510
computeUniqueAsyncExpiration,
2480
- computeThreadID,
2481
2511
} ;
0 commit comments