8
8
*/
9
9
10
10
import type { FiberRoot } from './ReactInternalTypes' ;
11
- import type { Lane } from './ReactFiberLane' ;
11
+ import type { Lane , Lanes } from './ReactFiberLane' ;
12
12
import type { PriorityLevel } from 'scheduler/src/SchedulerPriorities' ;
13
13
import type { BatchConfigTransition } from './ReactFiberTracingMarkerComponent' ;
14
14
@@ -24,8 +24,8 @@ import {
24
24
getNextLanes ,
25
25
includesSyncLane ,
26
26
markStarvedLanesAsExpired ,
27
- upgradePendingLaneToSync ,
28
27
claimNextTransitionLane ,
28
+ getNextLanesToFlushSync ,
29
29
} from './ReactFiberLane' ;
30
30
import {
31
31
CommitContext ,
@@ -145,18 +145,21 @@ export function ensureRootIsScheduled(root: FiberRoot): void {
145
145
export function flushSyncWorkOnAllRoots ( ) {
146
146
// This is allowed to be called synchronously, but the caller should check
147
147
// the execution context first.
148
- flushSyncWorkAcrossRoots_impl ( false ) ;
148
+ flushSyncWorkAcrossRoots_impl ( NoLanes , false ) ;
149
149
}
150
150
151
151
export function flushSyncWorkOnLegacyRootsOnly ( ) {
152
152
// This is allowed to be called synchronously, but the caller should check
153
153
// the execution context first.
154
154
if ( ! disableLegacyMode ) {
155
- flushSyncWorkAcrossRoots_impl ( true ) ;
155
+ flushSyncWorkAcrossRoots_impl ( NoLanes , true ) ;
156
156
}
157
157
}
158
158
159
- function flushSyncWorkAcrossRoots_impl ( onlyLegacy : boolean ) {
159
+ function flushSyncWorkAcrossRoots_impl (
160
+ syncTransitionLanes : Lanes | Lane ,
161
+ onlyLegacy : boolean ,
162
+ ) {
160
163
if ( isFlushingWork ) {
161
164
// Prevent reentrancy.
162
165
// TODO: Is this overly defensive? The callers must check the execution
@@ -179,17 +182,28 @@ function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
179
182
if ( onlyLegacy && ( disableLegacyMode || root . tag !== LegacyRoot ) ) {
180
183
// Skip non-legacy roots.
181
184
} else {
182
- const workInProgressRoot = getWorkInProgressRoot ( ) ;
183
- const workInProgressRootRenderLanes =
184
- getWorkInProgressRootRenderLanes ( ) ;
185
- const nextLanes = getNextLanes (
186
- root ,
187
- root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes ,
188
- ) ;
189
- if ( includesSyncLane ( nextLanes ) ) {
190
- // This root has pending sync work. Flush it now.
191
- didPerformSomeWork = true ;
192
- performSyncWorkOnRoot ( root , nextLanes ) ;
185
+ if ( syncTransitionLanes !== NoLanes ) {
186
+ const nextLanes = getNextLanesToFlushSync ( root , syncTransitionLanes ) ;
187
+ if ( nextLanes !== NoLanes ) {
188
+ // This root has pending sync work. Flush it now.
189
+ didPerformSomeWork = true ;
190
+ performSyncWorkOnRoot ( root , nextLanes ) ;
191
+ }
192
+ } else {
193
+ const workInProgressRoot = getWorkInProgressRoot ( ) ;
194
+ const workInProgressRootRenderLanes =
195
+ getWorkInProgressRootRenderLanes ( ) ;
196
+ const nextLanes = getNextLanes (
197
+ root ,
198
+ root === workInProgressRoot
199
+ ? workInProgressRootRenderLanes
200
+ : NoLanes ,
201
+ ) ;
202
+ if ( includesSyncLane ( nextLanes ) ) {
203
+ // This root has pending sync work. Flush it now.
204
+ didPerformSomeWork = true ;
205
+ performSyncWorkOnRoot ( root , nextLanes ) ;
206
+ }
193
207
}
194
208
}
195
209
root = root . next ;
@@ -209,23 +223,23 @@ function processRootScheduleInMicrotask() {
209
223
// We'll recompute this as we iterate through all the roots and schedule them.
210
224
mightHavePendingSyncWork = false ;
211
225
226
+ let syncTransitionLanes = NoLanes ;
227
+ if ( currentEventTransitionLane !== NoLane ) {
228
+ if ( shouldAttemptEagerTransition ( ) ) {
229
+ // A transition was scheduled during an event, but we're going to try to
230
+ // render it synchronously anyway. We do this during a popstate event to
231
+ // preserve the scroll position of the previous page.
232
+ syncTransitionLanes = currentEventTransitionLane ;
233
+ }
234
+ currentEventTransitionLane = NoLane ;
235
+ }
236
+
212
237
const currentTime = now ( ) ;
213
238
214
239
let prev = null ;
215
240
let root = firstScheduledRoot ;
216
241
while ( root !== null ) {
217
242
const next = root . next ;
218
-
219
- if (
220
- currentEventTransitionLane !== NoLane &&
221
- shouldAttemptEagerTransition ( )
222
- ) {
223
- // A transition was scheduled during an event, but we're going to try to
224
- // render it synchronously anyway. We do this during a popstate event to
225
- // preserve the scroll position of the previous page.
226
- upgradePendingLaneToSync ( root , currentEventTransitionLane ) ;
227
- }
228
-
229
243
const nextLanes = scheduleTaskForRootDuringMicrotask ( root , currentTime ) ;
230
244
if ( nextLanes === NoLane ) {
231
245
// This root has no more pending work. Remove it from the schedule. To
@@ -248,18 +262,27 @@ function processRootScheduleInMicrotask() {
248
262
} else {
249
263
// This root still has work. Keep it in the list.
250
264
prev = root ;
251
- if ( includesSyncLane ( nextLanes ) ) {
265
+
266
+ // This is a fast-path optimization to early exit from
267
+ // flushSyncWorkOnAllRoots if we can be certain that there is no remaining
268
+ // synchronous work to perform. Set this to true if there might be sync
269
+ // work left.
270
+ if (
271
+ // Skip the optimization if syncTransitionLanes is set
272
+ syncTransitionLanes !== NoLanes ||
273
+ // Common case: we're not treating any extra lanes as synchronous, so we
274
+ // can just check if the next lanes are sync.
275
+ includesSyncLane ( nextLanes )
276
+ ) {
252
277
mightHavePendingSyncWork = true ;
253
278
}
254
279
}
255
280
root = next ;
256
281
}
257
282
258
- currentEventTransitionLane = NoLane ;
259
-
260
283
// At the end of the microtask, flush any pending synchronous work. This has
261
284
// to come at the end, because it does actual rendering work that might throw.
262
- flushSyncWorkOnAllRoots ( ) ;
285
+ flushSyncWorkAcrossRoots_impl ( syncTransitionLanes , false ) ;
263
286
}
264
287
265
288
function scheduleTaskForRootDuringMicrotask (
0 commit comments