Use strict equality (===) to prevent type coercion bugs
if (root === lastScheduledRoot || root.next !== null) {
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {FiberRoot} from './ReactInternalTypes';11import type {Lane, Lanes} from './ReactFiberLane';12import type {PriorityLevel} from 'scheduler/src/SchedulerPriorities';13import type {Transition} from 'react/src/ReactStartTransition';1415import {16 disableLegacyMode,17 disableSchedulerTimeoutInWorkLoop,18 enableProfilerTimer,19 enableProfilerNestedUpdatePhase,20 enableComponentPerformanceTrack,21 enableYieldingBeforePassive,22 enableGestureTransition,23 enableDefaultTransitionIndicator,24} from 'shared/ReactFeatureFlags';25import {26 NoLane,27 NoLanes,28 SyncLane,29 DefaultLane,30 getHighestPriorityLane,31 getNextLanes,32 includesSyncLane,33 markStarvedLanesAsExpired,34 claimNextTransitionUpdateLane,35 getNextLanesToFlushSync,36 checkIfRootIsPrerendering,37 isGestureRender,38} from './ReactFiberLane';39import {40 CommitContext,41 NoContext,42 RenderContext,43 flushPendingEffects,44 flushPendingEffectsDelayed,45 getExecutionContext,46 getWorkInProgressRoot,47 getWorkInProgressRootRenderLanes,48 getRootWithPendingPassiveEffects,49 getPendingPassiveEffectsLanes,50 hasPendingCommitEffects,51 isWorkLoopSuspendedOnData,52 performWorkOnRoot,53} from './ReactFiberWorkLoop';54import {LegacyRoot} from './ReactRootTags';55import {56 ImmediatePriority as ImmediateSchedulerPriority,57 UserBlockingPriority as UserBlockingSchedulerPriority,58 NormalPriority as NormalSchedulerPriority,59 IdlePriority as IdleSchedulerPriority,60 cancelCallback as Scheduler_cancelCallback,61 scheduleCallback as Scheduler_scheduleCallback,62 now,63} from './Scheduler';64import {65 DiscreteEventPriority,66 ContinuousEventPriority,67 DefaultEventPriority,68 IdleEventPriority,69 lanesToEventPriority,70} from './ReactEventPriorities';71import {72 supportsMicrotasks,73 scheduleMicrotask,74 shouldAttemptEagerTransition,75 trackSchedulerEvent,76 noTimeout,77} from './ReactFiberConfig';7879import ReactSharedInternals from 'shared/ReactSharedInternals';80import {81 resetNestedUpdateFlag,82 syncNestedUpdateFlag,83} from './ReactProfilerTimer';84import {peekEntangledActionLane} from './ReactFiberAsyncAction';8586import noop from 'shared/noop';87import reportGlobalError from 'shared/reportGlobalError';8889import {90 startIsomorphicDefaultIndicatorIfNeeded,91 hasOngoingIsomorphicIndicator,92 retainIsomorphicIndicator,93 markIsomorphicIndicatorHandled,94} from './ReactFiberAsyncAction';9596// A linked list of all the roots with pending work. In an idiomatic app,97// there's only a single root, but we do support multi root apps, hence this98// extra complexity. But this module is optimized for the single root case.99export let firstScheduledRoot: FiberRoot | null = null;100let lastScheduledRoot: FiberRoot | null = null;101102// Used to prevent redundant mircotasks from being scheduled.103let didScheduleMicrotask: boolean = false;104// `act` "microtasks" are scheduled on the `act` queue instead of an actual105// microtask, so we have to dedupe those separately. This wouldn't be an issue106// if we required all `act` calls to be awaited, which we might in the future.107let didScheduleMicrotask_act: boolean = false;108109// Used to quickly bail out of flushSync if there's no sync work to do.110let mightHavePendingSyncWork: boolean = false;111112let isFlushingWork: boolean = false;113114let currentEventTransitionLane: Lane = NoLane;115116export function ensureRootIsScheduled(root: FiberRoot): void {117 // This function is called whenever a root receives an update. It does two118 // things 1) it ensures the root is in the root schedule, and 2) it ensures119 // there's a pending microtask to process the root schedule.120 //121 // Most of the actual scheduling logic does not happen until122 // `scheduleTaskForRootDuringMicrotask` runs.123124 // Add the root to the schedule125 if (root === lastScheduledRoot || root.next !== null) {126 // Fast path. This root is already scheduled.127 } else {128 if (lastScheduledRoot === null) {129 firstScheduledRoot = lastScheduledRoot = root;130 } else {131 lastScheduledRoot.next = root;132 lastScheduledRoot = root;133 }134 }135136 // Any time a root received an update, we set this to true until the next time137 // we process the schedule. If it's false, then we can quickly exit flushSync138 // without consulting the schedule.139 mightHavePendingSyncWork = true;140141 ensureScheduleIsScheduled();142143 if (144 __DEV__ &&145 !disableLegacyMode &&146 ReactSharedInternals.isBatchingLegacy &&147 root.tag === LegacyRoot148 ) {149 // Special `act` case: Record whenever a legacy update is scheduled.150 ReactSharedInternals.didScheduleLegacyUpdate = true;151 }152}153154export function ensureScheduleIsScheduled(): void {155 // At the end of the current event, go through each of the roots and ensure156 // there's a task scheduled for each one at the correct priority.157 if (__DEV__ && ReactSharedInternals.actQueue !== null) {158 // We're inside an `act` scope.159 if (!didScheduleMicrotask_act) {160 didScheduleMicrotask_act = true;161 scheduleImmediateRootScheduleTask();162 }163 } else {164 if (!didScheduleMicrotask) {165 didScheduleMicrotask = true;166 scheduleImmediateRootScheduleTask();167 }168 }169}170171export function flushSyncWorkOnAllRoots() {172 // This is allowed to be called synchronously, but the caller should check173 // the execution context first.174 flushSyncWorkAcrossRoots_impl(NoLanes, false);175}176177export function flushSyncWorkOnLegacyRootsOnly() {178 // This is allowed to be called synchronously, but the caller should check179 // the execution context first.180 if (!disableLegacyMode) {181 flushSyncWorkAcrossRoots_impl(NoLanes, true);182 }183}184185function flushSyncWorkAcrossRoots_impl(186 syncTransitionLanes: Lanes | Lane,187 onlyLegacy: boolean,188) {189 if (isFlushingWork) {190 // Prevent reentrancy.191 // TODO: Is this overly defensive? The callers must check the execution192 // context first regardless.193 return;194 }195196 if (!mightHavePendingSyncWork) {197 // Fast path. There's no sync work to do.198 return;199 }200201 // There may or may not be synchronous work scheduled. Let's check.202 let didPerformSomeWork;203 isFlushingWork = true;204 do {205 didPerformSomeWork = false;206 let root = firstScheduledRoot;207 while (root !== null) {208 if (onlyLegacy && (disableLegacyMode || root.tag !== LegacyRoot)) {209 // Skip non-legacy roots.210 } else {211 if (syncTransitionLanes !== NoLanes) {212 const nextLanes = getNextLanesToFlushSync(root, syncTransitionLanes);213 if (nextLanes !== NoLanes) {214 // This root has pending sync work. Flush it now.215 didPerformSomeWork = true;216 performSyncWorkOnRoot(root, nextLanes);217 }218 } else {219 const workInProgressRoot = getWorkInProgressRoot();220 const workInProgressRootRenderLanes =221 getWorkInProgressRootRenderLanes();222 const rootHasPendingCommit =223 root.cancelPendingCommit !== null ||224 root.timeoutHandle !== noTimeout;225 const nextLanes = getNextLanes(226 root,227 root === workInProgressRoot228 ? workInProgressRootRenderLanes229 : NoLanes,230 rootHasPendingCommit,231 );232 if (233 (includesSyncLane(nextLanes) ||234 (enableGestureTransition && isGestureRender(nextLanes))) &&235 !checkIfRootIsPrerendering(root, nextLanes)236 ) {237 // This root has pending sync work. Flush it now.238 didPerformSomeWork = true;239 performSyncWorkOnRoot(root, nextLanes);240 }241 }242 }243 root = root.next;244 }245 } while (didPerformSomeWork);246 isFlushingWork = false;247}248249function processRootScheduleInImmediateTask() {250 if (enableProfilerTimer && enableComponentPerformanceTrack) {251 // Track the currently executing event if there is one so we can ignore this252 // event when logging events.253 trackSchedulerEvent();254 }255256 processRootScheduleInMicrotask();257}258259function processRootScheduleInMicrotask() {260 // This function is always called inside a microtask. It should never be261 // called synchronously.262 didScheduleMicrotask = false;263 if (__DEV__) {264 didScheduleMicrotask_act = false;265 }266267 // We'll recompute this as we iterate through all the roots and schedule them.268 mightHavePendingSyncWork = false;269270 let syncTransitionLanes = NoLanes;271 if (currentEventTransitionLane !== NoLane) {272 if (shouldAttemptEagerTransition()) {273 // A transition was scheduled during an event, but we're going to try to274 // render it synchronously anyway. We do this during a popstate event to275 // preserve the scroll position of the previous page.276 syncTransitionLanes = currentEventTransitionLane;277 } else if (enableDefaultTransitionIndicator) {278 // If we have a Transition scheduled by this event it might be paired279 // with Default lane scheduled loading indicators. To unbatch it from280 // other events later on, flush it early to determine whether it281 // rendered an indicator. This ensures that setState in default priority282 // event doesn't trigger onDefaultTransitionIndicator.283 syncTransitionLanes = DefaultLane;284 }285 }286287 const currentTime = now();288289 let prev = null;290 let root = firstScheduledRoot;291 while (root !== null) {292 const next = root.next;293 const nextLanes = scheduleTaskForRootDuringMicrotask(root, currentTime);294 if (nextLanes === NoLane) {295 // This root has no more pending work. Remove it from the schedule. To296 // guard against subtle reentrancy bugs, this microtask is the only place297 // we do this — you can add roots to the schedule whenever, but you can298 // only remove them here.299300 // Null this out so we know it's been removed from the schedule.301 root.next = null;302 if (prev === null) {303 // This is the new head of the list304 firstScheduledRoot = next;305 } else {306 prev.next = next;307 }308 if (next === null) {309 // This is the new tail of the list310 lastScheduledRoot = prev;311 }312 } else {313 // This root still has work. Keep it in the list.314 prev = root;315316 // This is a fast-path optimization to early exit from317 // flushSyncWorkOnAllRoots if we can be certain that there is no remaining318 // synchronous work to perform. Set this to true if there might be sync319 // work left.320 if (321 // Skip the optimization if syncTransitionLanes is set322 syncTransitionLanes !== NoLanes ||323 // Common case: we're not treating any extra lanes as synchronous, so we324 // can just check if the next lanes are sync.325 includesSyncLane(nextLanes) ||326 (enableGestureTransition && isGestureRender(nextLanes))327 ) {328 mightHavePendingSyncWork = true;329 }330 }331 root = next;332 }333334 // At the end of the microtask, flush any pending synchronous work. This has335 // to come at the end, because it does actual rendering work that might throw.336 // If we're in the middle of a View Transition async sequence, we don't want to337 // interrupt that sequence. Instead, we'll flush any remaining work when it338 // completes.339 if (!hasPendingCommitEffects()) {340 flushSyncWorkAcrossRoots_impl(syncTransitionLanes, false);341 }342343 if (currentEventTransitionLane !== NoLane) {344 // Reset Event Transition Lane so that we allocate a new one next time.345 currentEventTransitionLane = NoLane;346 startDefaultTransitionIndicatorIfNeeded();347 }348}349350function startDefaultTransitionIndicatorIfNeeded() {351 if (!enableDefaultTransitionIndicator) {352 return;353 }354 // Check if we need to start an isomorphic indicator like if an async action355 // was started.356 startIsomorphicDefaultIndicatorIfNeeded();357 // Check all the roots if there are any new indicators needed.358 let root = firstScheduledRoot;359 while (root !== null) {360 if (root.indicatorLanes !== NoLanes && root.pendingIndicator === null) {361 // We have new indicator lanes that requires a loading state. Start the362 // default transition indicator.363 if (hasOngoingIsomorphicIndicator()) {364 // We already have an isomorphic indicator going which means it has to365 // also apply to this root since it implies all roots have the same one.366 // We retain this indicator so that it keeps going until we commit this367 // root.368 root.pendingIndicator = retainIsomorphicIndicator();369 } else {370 try {371 const onDefaultTransitionIndicator =372 root.onDefaultTransitionIndicator;373 root.pendingIndicator = onDefaultTransitionIndicator() || noop;374 } catch (x) {375 root.pendingIndicator = noop;376 reportGlobalError(x);377 }378 }379 }380 root = root.next;381 }382}383384function scheduleTaskForRootDuringMicrotask(385 root: FiberRoot,386 currentTime: number,387): Lane {388 // This function is always called inside a microtask, or at the very end of a389 // rendering task right before we yield to the main thread. It should never be390 // called synchronously.391392 // This function also never performs React work synchronously; it should393 // only schedule work to be performed later, in a separate task or microtask.394395 // Check if any lanes are being starved by other work. If so, mark them as396 // expired so we know to work on those next.397 markStarvedLanesAsExpired(root, currentTime);398399 // Determine the next lanes to work on, and their priority.400 const rootWithPendingPassiveEffects = getRootWithPendingPassiveEffects();401 const pendingPassiveEffectsLanes = getPendingPassiveEffectsLanes();402 const workInProgressRoot = getWorkInProgressRoot();403 const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();404 const rootHasPendingCommit =405 root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;406 const nextLanes =407 enableYieldingBeforePassive && root === rootWithPendingPassiveEffects408 ? // This will schedule the callback at the priority of the lane but we used to409 // always schedule it at NormalPriority. Discrete will flush it sync anyway.410 // So the only difference is Idle and it doesn't seem necessarily right for that411 // to get upgraded beyond something important just because we're past commit.412 pendingPassiveEffectsLanes413 : getNextLanes(414 root,415 root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,416 rootHasPendingCommit,417 );418419 const existingCallbackNode = root.callbackNode;420 if (421 // Check if there's nothing to work on422 nextLanes === NoLanes ||423 // If this root is currently suspended and waiting for data to resolve, don't424 // schedule a task to render it. We'll either wait for a ping, or wait to425 // receive an update.426 //427 // Suspended render phase428 (root === workInProgressRoot && isWorkLoopSuspendedOnData()) ||429 // Suspended commit phase430 root.cancelPendingCommit !== null431 ) {432 // Fast path: There's nothing to work on.433 if (existingCallbackNode !== null) {434 cancelCallback(existingCallbackNode);435 }436 root.callbackNode = null;437 root.callbackPriority = NoLane;438 return NoLane;439 }440441 // Schedule a new callback in the host environment.442 if (443 includesSyncLane(nextLanes) &&444 // If we're prerendering, then we should use the concurrent work loop445 // even if the lanes are synchronous, so that prerendering never blocks446 // the main thread.447 !checkIfRootIsPrerendering(root, nextLanes)448 ) {449 // Synchronous work is always flushed at the end of the microtask, so we450 // don't need to schedule an additional task.451 if (existingCallbackNode !== null) {452 cancelCallback(existingCallbackNode);453 }454 root.callbackPriority = SyncLane;455 root.callbackNode = null;456 return SyncLane;457 } else {458 // We use the highest priority lane to represent the priority of the callback.459 const existingCallbackPriority = root.callbackPriority;460 const newCallbackPriority = getHighestPriorityLane(nextLanes);461462 if (463 newCallbackPriority === existingCallbackPriority &&464 // Special case related to `act`. If the currently scheduled task is a465 // Scheduler task, rather than an `act` task, cancel it and re-schedule466 // on the `act` queue.467 !(468 __DEV__ &&469 ReactSharedInternals.actQueue !== null &&470 existingCallbackNode !== fakeActCallbackNode471 )472 ) {473 // The priority hasn't changed. We can reuse the existing task.474 return newCallbackPriority;475 } else {476 // Cancel the existing callback. We'll schedule a new one below.477 cancelCallback(existingCallbackNode);478 }479480 let schedulerPriorityLevel;481 switch (lanesToEventPriority(nextLanes)) {482 // Scheduler does have an "ImmediatePriority", but now that we use483 // microtasks for sync work we no longer use that. Any sync work that484 // reaches this path is meant to be time sliced.485 case DiscreteEventPriority:486 case ContinuousEventPriority:487 schedulerPriorityLevel = UserBlockingSchedulerPriority;488 break;489 case DefaultEventPriority:490 schedulerPriorityLevel = NormalSchedulerPriority;491 break;492 case IdleEventPriority:493 schedulerPriorityLevel = IdleSchedulerPriority;494 break;495 default:496 schedulerPriorityLevel = NormalSchedulerPriority;497 break;498 }499500 const newCallbackNode = scheduleCallback(501 schedulerPriorityLevel,502 performWorkOnRootViaSchedulerTask.bind(null, root),503 );504505 root.callbackPriority = newCallbackPriority;506 root.callbackNode = newCallbackNode;507 return newCallbackPriority;508 }509}510511type RenderTaskFn = (didTimeout: boolean) => RenderTaskFn | null;512513function performWorkOnRootViaSchedulerTask(514 root: FiberRoot,515 didTimeout: boolean,516): RenderTaskFn | null {517 // This is the entry point for concurrent tasks scheduled via Scheduler (and518 // postTask, in the future).519520 if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {521 resetNestedUpdateFlag();522 }523524 if (enableProfilerTimer && enableComponentPerformanceTrack) {525 // Track the currently executing event if there is one so we can ignore this526 // event when logging events.527 trackSchedulerEvent();528 }529530 if (hasPendingCommitEffects()) {531 // We are currently in the middle of an async committing (such as a View Transition).532 // We could force these to flush eagerly but it's better to defer any work until533 // it finishes. This may not be the same root as we're waiting on.534 // TODO: This relies on the commit eventually calling ensureRootIsScheduled which535 // always calls processRootScheduleInMicrotask which in turn always loops through536 // all the roots to figure out. This is all a bit inefficient and if optimized537 // it'll need to consider rescheduling a task for any skipped roots.538 root.callbackNode = null;539 root.callbackPriority = NoLane;540 return null;541 }542543 // Flush any pending passive effects before deciding which lanes to work on,544 // in case they schedule additional work.545 const originalCallbackNode = root.callbackNode;546 const didFlushPassiveEffects = flushPendingEffectsDelayed();547 if (didFlushPassiveEffects) {548 // Something in the passive effect phase may have canceled the current task.549 // Check if the task node for this root was changed.550 if (root.callbackNode !== originalCallbackNode) {551 // The current task was canceled. Exit. We don't need to call552 // `ensureRootIsScheduled` because the check above implies either that553 // there's a new task, or that there's no remaining work on this root.554 return null;555 } else {556 // Current task was not canceled. Continue.557 }558 }559560 // Determine the next lanes to work on, using the fields stored on the root.561 // TODO: We already called getNextLanes when we scheduled the callback; we562 // should be able to avoid calling it again by stashing the result on the563 // root object. However, because we always schedule the callback during564 // a microtask (scheduleTaskForRootDuringMicrotask), it's possible that565 // an update was scheduled earlier during this same browser task (and566 // therefore before the microtasks have run). That's because Scheduler batches567 // together multiple callbacks into a single browser macrotask, without568 // yielding to microtasks in between. We should probably change this to align569 // with the postTask behavior (and literally use postTask when570 // it's available).571 const workInProgressRoot = getWorkInProgressRoot();572 const workInProgressRootRenderLanes = getWorkInProgressRootRenderLanes();573 const rootHasPendingCommit =574 root.cancelPendingCommit !== null || root.timeoutHandle !== noTimeout;575 const lanes = getNextLanes(576 root,577 root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,578 rootHasPendingCommit,579 );580 if (lanes === NoLanes) {581 // No more work on this root.582 return null;583 }584585 // Enter the work loop.586 // TODO: We only check `didTimeout` defensively, to account for a Scheduler587 // bug we're still investigating. Once the bug in Scheduler is fixed,588 // we can remove this, since we track expiration ourselves.589 const forceSync = !disableSchedulerTimeoutInWorkLoop && didTimeout;590 performWorkOnRoot(root, lanes, forceSync);591592 // The work loop yielded, but there may or may not be work left at the current593 // priority. Need to determine whether we need to schedule a continuation.594 // Usually `scheduleTaskForRootDuringMicrotask` only runs inside a microtask;595 // however, since most of the logic for determining if we need a continuation596 // versus a new task is the same, we cheat a bit and call it here. This is597 // only safe to do because we know we're at the end of the browser task.598 // So although it's not an actual microtask, it might as well be.599 scheduleTaskForRootDuringMicrotask(root, now());600 if (root.callbackNode != null && root.callbackNode === originalCallbackNode) {601 // The task node scheduled for this root is the same one that's602 // currently executed. Need to return a continuation.603 return performWorkOnRootViaSchedulerTask.bind(null, root);604 }605 return null;606}607608function performSyncWorkOnRoot(root: FiberRoot, lanes: Lanes) {609 // This is the entry point for synchronous tasks that don't go610 // through Scheduler.611 const didFlushPassiveEffects = flushPendingEffects();612 if (didFlushPassiveEffects) {613 // If passive effects were flushed, exit to the outer work loop in the root614 // scheduler, so we can recompute the priority.615 return null;616 }617 if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {618 syncNestedUpdateFlag();619 }620 const forceSync = true;621 performWorkOnRoot(root, lanes, forceSync);622}623624const fakeActCallbackNode = {};625626function scheduleCallback(627 priorityLevel: PriorityLevel,628 callback: RenderTaskFn,629) {630 if (__DEV__ && ReactSharedInternals.actQueue !== null) {631 // Special case: We're inside an `act` scope (a testing utility).632 // Instead of scheduling work in the host environment, add it to a633 // fake internal queue that's managed by the `act` implementation.634 ReactSharedInternals.actQueue.push(callback);635 return fakeActCallbackNode;636 } else {637 return Scheduler_scheduleCallback(priorityLevel, callback);638 }639}640641function cancelCallback(callbackNode: mixed) {642 if (__DEV__ && callbackNode === fakeActCallbackNode) {643 // Special `act` case: check if this is the fake callback node used by644 // the `act` implementation.645 } else if (callbackNode !== null) {646 Scheduler_cancelCallback(callbackNode);647 }648}649650function scheduleImmediateRootScheduleTask() {651 if (__DEV__ && ReactSharedInternals.actQueue !== null) {652 // Special case: Inside an `act` scope, we push microtasks to the fake `act`653 // callback queue. This is because we currently support calling `act`654 // without awaiting the result. The plan is to deprecate that, and require655 // that you always await the result so that the microtasks have a chance to656 // run. But it hasn't happened yet.657 ReactSharedInternals.actQueue.push(() => {658 processRootScheduleInMicrotask();659 return null;660 });661 }662663 // TODO: Can we land supportsMicrotasks? Which environments don't support it?664 // Alternatively, can we move this check to the host config?665 // $FlowFixMe[constant-condition]666 if (supportsMicrotasks) {667 scheduleMicrotask(() => {668 // In Safari, appending an iframe forces microtasks to run.669 // https://github.com/facebook/react/issues/22459670 // We don't support running callbacks in the middle of render671 // or commit so we need to check against that.672 const executionContext = getExecutionContext();673 if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {674 // Note that this would still prematurely flush the callbacks675 // if this happens outside render or commit phase (e.g. in an event).676677 // Intentionally using a macrotask instead of a microtask here. This is678 // wrong semantically but it prevents an infinite loop. The bug is679 // Safari's, not ours, so we just do our best to not crash even though680 // the behavior isn't completely correct.681 Scheduler_scheduleCallback(682 ImmediateSchedulerPriority,683 processRootScheduleInImmediateTask,684 );685 return;686 }687 processRootScheduleInMicrotask();688 });689 } else {690 // If microtasks are not supported, use Scheduler.691 Scheduler_scheduleCallback(692 ImmediateSchedulerPriority,693 processRootScheduleInImmediateTask,694 );695 }696}697698export function requestTransitionLane(699 // This argument isn't used, it's only here to encourage the caller to700 // check that it's inside a transition before calling this function.701 // TODO: Make this non-nullable. Requires a tweak to useOptimistic.702 transition: Transition | null,703): Lane {704 // The algorithm for assigning an update to a lane should be stable for all705 // updates at the same priority within the same event. To do this, the706 // inputs to the algorithm must be the same.707 //708 // The trick we use is to cache the first of each of these inputs within an709 // event. Then reset the cached values once we can be sure the event is710 // over. Our heuristic for that is whenever we enter a concurrent work loop.711 if (currentEventTransitionLane === NoLane) {712 // All transitions within the same event are assigned the same lane.713 const actionScopeLane = peekEntangledActionLane();714 currentEventTransitionLane =715 actionScopeLane !== NoLane716 ? // We're inside an async action scope. Reuse the same lane.717 actionScopeLane718 : // We may or may not be inside an async action scope. If we are, this719 // is the first update in that scope. Either way, we need to get a720 // fresh transition lane.721 claimNextTransitionUpdateLane();722 }723 return currentEventTransitionLane;724}725726export function didCurrentEventScheduleTransition(): boolean {727 return currentEventTransitionLane !== NoLane;728}729730export function markIndicatorHandled(root: FiberRoot): void {731 if (enableDefaultTransitionIndicator) {732 // The current transition event rendered a synchronous loading state.733 // Clear it from the indicator lanes. We don't need to show a separate734 // loading state for this lane.735 root.indicatorLanes &= ~currentEventTransitionLane;736 markIsomorphicIndicatorHandled();737 }738}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.