88 */
99
1010import type { FiberRoot } from './ReactInternalTypes' ;
11- import type { Lane } from './ReactFiberLane' ;
11+ import type { Lane , Lanes } from './ReactFiberLane' ;
1212import type { PriorityLevel } from 'scheduler/src/SchedulerPriorities' ;
1313import type { BatchConfigTransition } from './ReactFiberTracingMarkerComponent' ;
1414
@@ -24,8 +24,8 @@ import {
2424 getNextLanes ,
2525 includesSyncLane ,
2626 markStarvedLanesAsExpired ,
27- upgradePendingLaneToSync ,
2827 claimNextTransitionLane ,
28+ getNextLanesToFlushSync ,
2929} from './ReactFiberLane' ;
3030import {
3131 CommitContext ,
@@ -145,18 +145,21 @@ export function ensureRootIsScheduled(root: FiberRoot): void {
145145export function flushSyncWorkOnAllRoots ( ) {
146146 // This is allowed to be called synchronously, but the caller should check
147147 // the execution context first.
148- flushSyncWorkAcrossRoots_impl ( false ) ;
148+ flushSyncWorkAcrossRoots_impl ( NoLanes , false ) ;
149149}
150150
151151export function flushSyncWorkOnLegacyRootsOnly ( ) {
152152 // This is allowed to be called synchronously, but the caller should check
153153 // the execution context first.
154154 if ( ! disableLegacyMode ) {
155- flushSyncWorkAcrossRoots_impl ( true ) ;
155+ flushSyncWorkAcrossRoots_impl ( NoLanes , true ) ;
156156 }
157157}
158158
159- function flushSyncWorkAcrossRoots_impl ( onlyLegacy : boolean ) {
159+ function flushSyncWorkAcrossRoots_impl (
160+ syncTransitionLanes : Lanes | Lane ,
161+ onlyLegacy : boolean ,
162+ ) {
160163 if ( isFlushingWork ) {
161164 // Prevent reentrancy.
162165 // TODO: Is this overly defensive? The callers must check the execution
@@ -179,17 +182,28 @@ function flushSyncWorkAcrossRoots_impl(onlyLegacy: boolean) {
179182 if ( onlyLegacy && ( disableLegacyMode || root . tag !== LegacyRoot ) ) {
180183 // Skip non-legacy roots.
181184 } 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+ }
193207 }
194208 }
195209 root = root . next ;
@@ -209,23 +223,23 @@ function processRootScheduleInMicrotask() {
209223 // We'll recompute this as we iterate through all the roots and schedule them.
210224 mightHavePendingSyncWork = false ;
211225
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+
212237 const currentTime = now ( ) ;
213238
214239 let prev = null ;
215240 let root = firstScheduledRoot ;
216241 while ( root !== null ) {
217242 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-
229243 const nextLanes = scheduleTaskForRootDuringMicrotask ( root , currentTime ) ;
230244 if ( nextLanes === NoLane ) {
231245 // This root has no more pending work. Remove it from the schedule. To
@@ -248,18 +262,27 @@ function processRootScheduleInMicrotask() {
248262 } else {
249263 // This root still has work. Keep it in the list.
250264 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+ ) {
252277 mightHavePendingSyncWork = true ;
253278 }
254279 }
255280 root = next ;
256281 }
257282
258- currentEventTransitionLane = NoLane ;
259-
260283 // At the end of the microtask, flush any pending synchronous work. This has
261284 // to come at the end, because it does actual rendering work that might throw.
262- flushSyncWorkOnAllRoots ( ) ;
285+ flushSyncWorkAcrossRoots_impl ( syncTransitionLanes , false ) ;
263286}
264287
265288function scheduleTaskForRootDuringMicrotask (
0 commit comments