Use strict equality (===) to prevent type coercion bugs
if (pendingSyncLanes !== 0) {
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 {Fiber, FiberRoot} from './ReactInternalTypes';11import type {Transition} from 'react/src/ReactStartTransition';12import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';1314// TODO: Ideally these types would be opaque but that doesn't work well with15// our reconciler fork infra, since these leak into non-reconciler packages.1617export type Lanes = number;18export type Lane = number;19export type LaneMap<T> = Array<T>;2021import {22 enableRetryLaneExpiration,23 enableSchedulingProfiler,24 enableTransitionTracing,25 enableUpdaterTracking,26 syncLaneExpirationMs,27 transitionLaneExpirationMs,28 retryLaneExpirationMs,29 disableLegacyMode,30 enableDefaultTransitionIndicator,31 enableGestureTransition,32 enableParallelTransitions,33} from 'shared/ReactFeatureFlags';34import {isDevToolsPresent} from './ReactFiberDevToolsHook';35import {clz32} from './clz32';36import {LegacyRoot} from './ReactRootTags';3738// Lane values below should be kept in sync with getLabelForLane(), used by react-devtools-timeline.39// If those values are changed that package should be rebuilt and redeployed.4041export const TotalLanes = 31;4243export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;44export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;4546export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;47export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;48export const SyncLaneIndex: number = 1;4950export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;51export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;5253export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;54export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;5556export const SyncUpdateLanes: Lane =57 SyncLane | InputContinuousLane | DefaultLane;5859export const GestureLane: Lane = /* */ 0b0000000000000000000000001000000;6061const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000010000000;62const TransitionLanes: Lanes = /* */ 0b0000000001111111111111100000000;63const TransitionLane1: Lane = /* */ 0b0000000000000000000000100000000;64const TransitionLane2: Lane = /* */ 0b0000000000000000000001000000000;65const TransitionLane3: Lane = /* */ 0b0000000000000000000010000000000;66const TransitionLane4: Lane = /* */ 0b0000000000000000000100000000000;67const TransitionLane5: Lane = /* */ 0b0000000000000000001000000000000;68const TransitionLane6: Lane = /* */ 0b0000000000000000010000000000000;69const TransitionLane7: Lane = /* */ 0b0000000000000000100000000000000;70const TransitionLane8: Lane = /* */ 0b0000000000000001000000000000000;71const TransitionLane9: Lane = /* */ 0b0000000000000010000000000000000;72const TransitionLane10: Lane = /* */ 0b0000000000000100000000000000000;73const TransitionLane11: Lane = /* */ 0b0000000000001000000000000000000;74const TransitionLane12: Lane = /* */ 0b0000000000010000000000000000000;75const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;76const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;7778export const SomeTransitionLane: Lane = TransitionLane1;7980const TransitionUpdateLanes =81 TransitionLane1 |82 TransitionLane2 |83 TransitionLane3 |84 TransitionLane4 |85 TransitionLane5 |86 TransitionLane6 |87 TransitionLane7 |88 TransitionLane8 |89 TransitionLane9 |90 TransitionLane10;91const TransitionDeferredLanes =92 TransitionLane11 | TransitionLane12 | TransitionLane13 | TransitionLane14;9394const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;95const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;96const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;97const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;98const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;99100export const SomeRetryLane: Lane = RetryLane1;101102export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;103104const NonIdleLanes: Lanes = /* */ 0b0000111111111111111111111111111;105106export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;107export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000;108109export const OffscreenLane: Lane = /* */ 0b0100000000000000000000000000000;110export const DeferredLane: Lane = /* */ 0b1000000000000000000000000000000;111112// Any lane that might schedule an update. This is used to detect infinite113// update loops, so it doesn't include hydration lanes or retries.114export const UpdateLanes: Lanes =115 SyncLane | InputContinuousLane | DefaultLane | TransitionUpdateLanes;116117export const HydrationLanes =118 SyncHydrationLane |119 InputContinuousHydrationLane |120 DefaultHydrationLane |121 TransitionHydrationLane |122 SelectiveHydrationLane |123 IdleHydrationLane;124125// This function is used for the experimental timeline (react-devtools-timeline)126// It should be kept in sync with the Lanes values above.127export function getLabelForLane(lane: Lane): string | void {128 if (enableSchedulingProfiler) {129 if (lane & SyncHydrationLane) {130 return 'SyncHydrationLane';131 }132 if (lane & SyncLane) {133 return 'Sync';134 }135 if (lane & InputContinuousHydrationLane) {136 return 'InputContinuousHydration';137 }138 if (lane & InputContinuousLane) {139 return 'InputContinuous';140 }141 if (lane & DefaultHydrationLane) {142 return 'DefaultHydration';143 }144 if (lane & DefaultLane) {145 return 'Default';146 }147 if (lane & TransitionHydrationLane) {148 return 'TransitionHydration';149 }150 if (lane & TransitionLanes) {151 return 'Transition';152 }153 if (lane & RetryLanes) {154 return 'Retry';155 }156 if (lane & SelectiveHydrationLane) {157 return 'SelectiveHydration';158 }159 if (lane & IdleHydrationLane) {160 return 'IdleHydration';161 }162 if (lane & IdleLane) {163 return 'Idle';164 }165 if (lane & OffscreenLane) {166 return 'Offscreen';167 }168 if (lane & DeferredLane) {169 return 'Deferred';170 }171 }172}173174export const NoTimestamp = -1;175176let nextTransitionUpdateLane: Lane = TransitionLane1;177let nextTransitionDeferredLane: Lane = TransitionLane11;178let nextRetryLane: Lane = RetryLane1;179180function getHighestPriorityLanes(lanes: Lanes | Lane): Lanes {181 const pendingSyncLanes = lanes & SyncUpdateLanes;182 if (pendingSyncLanes !== 0) {183 return pendingSyncLanes;184 }185 switch (getHighestPriorityLane(lanes)) {186 case SyncHydrationLane:187 return SyncHydrationLane;188 case SyncLane:189 return SyncLane;190 case InputContinuousHydrationLane:191 return InputContinuousHydrationLane;192 case InputContinuousLane:193 return InputContinuousLane;194 case DefaultHydrationLane:195 return DefaultHydrationLane;196 case DefaultLane:197 return DefaultLane;198 case GestureLane:199 return GestureLane;200 case TransitionHydrationLane:201 return TransitionHydrationLane;202 case TransitionLane1:203 case TransitionLane2:204 case TransitionLane3:205 case TransitionLane4:206 case TransitionLane5:207 case TransitionLane6:208 case TransitionLane7:209 case TransitionLane8:210 case TransitionLane9:211 case TransitionLane10:212 if (enableParallelTransitions) {213 return getHighestPriorityLane(lanes);214 }215 return lanes & TransitionUpdateLanes;216 case TransitionLane11:217 case TransitionLane12:218 case TransitionLane13:219 case TransitionLane14:220 return lanes & TransitionDeferredLanes;221 case RetryLane1:222 case RetryLane2:223 case RetryLane3:224 case RetryLane4:225 return lanes & RetryLanes;226 case SelectiveHydrationLane:227 return SelectiveHydrationLane;228 case IdleHydrationLane:229 return IdleHydrationLane;230 case IdleLane:231 return IdleLane;232 case OffscreenLane:233 return OffscreenLane;234 case DeferredLane:235 // This shouldn't be reachable because deferred work is always entangled236 // with something else.237 return NoLanes;238 default:239 if (__DEV__) {240 console.error(241 'Should have found matching lanes. This is a bug in React.',242 );243 }244 // This shouldn't be reachable, but as a fallback, return the entire bitmask.245 return lanes;246 }247}248249export function getNextLanes(250 root: FiberRoot,251 wipLanes: Lanes,252 rootHasPendingCommit: boolean,253): Lanes {254 // Early bailout if there's no pending work left.255 const pendingLanes = root.pendingLanes;256 if (pendingLanes === NoLanes) {257 return NoLanes;258 }259260 let nextLanes: Lanes = NoLanes;261262 const suspendedLanes = root.suspendedLanes;263 const pingedLanes = root.pingedLanes;264 const warmLanes = root.warmLanes;265266 // finishedLanes represents a completed tree that is ready to commit.267 //268 // It's not worth doing discarding the completed tree in favor of performing269 // speculative work. So always check this before deciding to warm up270 // the siblings.271 //272 // Note that this is not set in a "suspend indefinitely" scenario, like when273 // suspending outside of a Suspense boundary, or in the shell during a274 // transition — only in cases where we are very likely to commit the tree in275 // a brief amount of time (i.e. below the "Just Noticeable Difference"276 // threshold).277 //278279 // Do not work on any idle work until all the non-idle work has finished,280 // even if the work is suspended.281 const nonIdlePendingLanes = pendingLanes & NonIdleLanes;282 if (nonIdlePendingLanes !== NoLanes) {283 // First check for fresh updates.284 const nonIdleUnblockedLanes = nonIdlePendingLanes & ~suspendedLanes;285 if (nonIdleUnblockedLanes !== NoLanes) {286 nextLanes = getHighestPriorityLanes(nonIdleUnblockedLanes);287 } else {288 // No fresh updates. Check if suspended work has been pinged.289 const nonIdlePingedLanes = nonIdlePendingLanes & pingedLanes;290 if (nonIdlePingedLanes !== NoLanes) {291 nextLanes = getHighestPriorityLanes(nonIdlePingedLanes);292 } else {293 // Nothing has been pinged. Check for lanes that need to be prewarmed.294 if (!rootHasPendingCommit) {295 const lanesToPrewarm = nonIdlePendingLanes & ~warmLanes;296 if (lanesToPrewarm !== NoLanes) {297 nextLanes = getHighestPriorityLanes(lanesToPrewarm);298 }299 }300 }301 }302 } else {303 // The only remaining work is Idle.304 // TODO: Idle isn't really used anywhere, and the thinking around305 // speculative rendering has evolved since this was implemented. Consider306 // removing until we've thought about this again.307308 // First check for fresh updates.309 const unblockedLanes = pendingLanes & ~suspendedLanes;310 if (unblockedLanes !== NoLanes) {311 nextLanes = getHighestPriorityLanes(unblockedLanes);312 } else {313 // No fresh updates. Check if suspended work has been pinged.314 if (pingedLanes !== NoLanes) {315 nextLanes = getHighestPriorityLanes(pingedLanes);316 } else {317 // Nothing has been pinged. Check for lanes that need to be prewarmed.318 if (!rootHasPendingCommit) {319 const lanesToPrewarm = pendingLanes & ~warmLanes;320 if (lanesToPrewarm !== NoLanes) {321 nextLanes = getHighestPriorityLanes(lanesToPrewarm);322 }323 }324 }325 }326 }327328 if (nextLanes === NoLanes) {329 // This should only be reachable if we're suspended330 // TODO: Consider warning in this path if a fallback timer is not scheduled.331 return NoLanes;332 }333334 // If we're already in the middle of a render, switching lanes will interrupt335 // it and we'll lose our progress. We should only do this if the new lanes are336 // higher priority.337 if (338 wipLanes !== NoLanes &&339 wipLanes !== nextLanes &&340 // If we already suspended with a delay, then interrupting is fine. Don't341 // bother waiting until the root is complete.342 (wipLanes & suspendedLanes) === NoLanes343 ) {344 const nextLane = getHighestPriorityLane(nextLanes);345 const wipLane = getHighestPriorityLane(wipLanes);346 if (347 // Tests whether the next lane is equal or lower priority than the wip348 // one. This works because the bits decrease in priority as you go left.349 nextLane >= wipLane ||350 // Default priority updates should not interrupt transition updates. The351 // only difference between default updates and transition updates is that352 // default updates do not support refresh transitions.353 (nextLane === DefaultLane && (wipLane & TransitionLanes) !== NoLanes)354 ) {355 // Keep working on the existing in-progress tree. Do not interrupt.356 return wipLanes;357 }358 }359360 return nextLanes;361}362363export function getNextLanesToFlushSync(364 root: FiberRoot,365 extraLanesToForceSync: Lane | Lanes,366): Lanes {367 // Similar to getNextLanes, except instead of choosing the next lanes to work368 // on based on their priority, it selects all the lanes that have equal or369 // higher priority than those are given. That way they can be synchronously370 // rendered in a single batch.371 //372 // The main use case is updates scheduled by popstate events, which are373 // flushed synchronously even though they are transitions.374 // Note that we intentionally treat this as a sync flush to include any375 // sync updates in a single pass but also intentionally disables View Transitions376 // inside popstate. Because they can start synchronously before scroll restoration377 // happens.378 const lanesToFlush = SyncUpdateLanes | extraLanesToForceSync;379380 // Early bailout if there's no pending work left.381 const pendingLanes = root.pendingLanes;382 if (pendingLanes === NoLanes) {383 return NoLanes;384 }385386 const suspendedLanes = root.suspendedLanes;387 const pingedLanes = root.pingedLanes;388389 // Remove lanes that are suspended (but not pinged)390 const unblockedLanes = pendingLanes & ~(suspendedLanes & ~pingedLanes);391 const unblockedLanesWithMatchingPriority =392 unblockedLanes & getLanesOfEqualOrHigherPriority(lanesToFlush);393394 // If there are matching hydration lanes, we should do those by themselves.395 // Hydration lanes must never include updates.396 if (unblockedLanesWithMatchingPriority & HydrationLanes) {397 return (398 (unblockedLanesWithMatchingPriority & HydrationLanes) | SyncHydrationLane399 );400 }401402 if (unblockedLanesWithMatchingPriority) {403 // Always include the SyncLane as part of the result, even if there's no404 // pending sync work, to indicate the priority of the entire batch of work405 // is considered Sync.406 return unblockedLanesWithMatchingPriority | SyncLane;407 }408409 return NoLanes;410}411412export function checkIfRootIsPrerendering(413 root: FiberRoot,414 renderLanes: Lanes,415): boolean {416 const pendingLanes = root.pendingLanes;417 const suspendedLanes = root.suspendedLanes;418 const pingedLanes = root.pingedLanes;419 // Remove lanes that are suspended (but not pinged)420 const unblockedLanes = pendingLanes & ~(suspendedLanes & ~pingedLanes);421422 // If there are no unsuspended or pinged lanes, that implies that we're423 // performing a prerender.424 return (unblockedLanes & renderLanes) === 0;425}426427export function getEntangledLanes(root: FiberRoot, renderLanes: Lanes): Lanes {428 let entangledLanes = renderLanes;429430 if ((entangledLanes & InputContinuousLane) !== NoLanes) {431 // When updates are sync by default, we entangle continuous priority updates432 // and default updates, so they render in the same batch. The only reason433 // they use separate lanes is because continuous updates should interrupt434 // transitions, but default updates should not.435 entangledLanes |= entangledLanes & DefaultLane;436 }437438 // Check for entangled lanes and add them to the batch.439 //440 // A lane is said to be entangled with another when it's not allowed to render441 // in a batch that does not also include the other lane. Typically we do this442 // when multiple updates have the same source, and we only want to respond to443 // the most recent event from that source.444 //445 // Note that we apply entanglements *after* checking for partial work above.446 // This means that if a lane is entangled during an interleaved event while447 // it's already rendering, we won't interrupt it. This is intentional, since448 // entanglement is usually "best effort": we'll try our best to render the449 // lanes in the same batch, but it's not worth throwing out partially450 // completed work in order to do it.451 // TODO: Reconsider this. The counter-argument is that the partial work452 // represents an intermediate state, which we don't want to show to the user.453 // And by spending extra time finishing it, we're increasing the amount of454 // time it takes to show the final state, which is what they are actually455 // waiting for.456 //457 // For those exceptions where entanglement is semantically important,458 // we should ensure that there is no partial work at the459 // time we apply the entanglement.460 const allEntangledLanes = root.entangledLanes;461 if (allEntangledLanes !== NoLanes) {462 const entanglements = root.entanglements;463 let lanes = entangledLanes & allEntangledLanes;464 while (lanes > 0) {465 const index = pickArbitraryLaneIndex(lanes);466 const lane = 1 << index;467468 entangledLanes |= entanglements[index];469470 lanes &= ~lane;471 }472 }473474 return entangledLanes;475}476477function computeExpirationTime(lane: Lane, currentTime: number) {478 switch (lane) {479 case SyncHydrationLane:480 case SyncLane:481 case InputContinuousHydrationLane:482 case InputContinuousLane:483 case GestureLane:484 // User interactions should expire slightly more quickly.485 //486 // NOTE: This is set to the corresponding constant as in Scheduler.js.487 // When we made it larger, a product metric in www regressed, suggesting488 // there's a user interaction that's being starved by a series of489 // synchronous updates. If that theory is correct, the proper solution is490 // to fix the starvation. However, this scenario supports the idea that491 // expiration times are an important safeguard when starvation492 // does happen.493 return currentTime + syncLaneExpirationMs;494 case DefaultHydrationLane:495 case DefaultLane:496 case TransitionHydrationLane:497 case TransitionLane1:498 case TransitionLane2:499 case TransitionLane3:500 case TransitionLane4:501 case TransitionLane5:502 case TransitionLane6:503 case TransitionLane7:504 case TransitionLane8:505 case TransitionLane9:506 case TransitionLane10:507 case TransitionLane11:508 case TransitionLane12:509 case TransitionLane13:510 case TransitionLane14:511 return currentTime + transitionLaneExpirationMs;512 case RetryLane1:513 case RetryLane2:514 case RetryLane3:515 case RetryLane4:516 // TODO: Retries should be allowed to expire if they are CPU bound for517 // too long, but when I made this change it caused a spike in browser518 // crashes. There must be some other underlying bug; not super urgent but519 // ideally should figure out why and fix it. Unfortunately we don't have520 // a repro for the crashes, only detected via production metrics.521 return enableRetryLaneExpiration522 ? currentTime + retryLaneExpirationMs523 : NoTimestamp;524 case SelectiveHydrationLane:525 case IdleHydrationLane:526 case IdleLane:527 case OffscreenLane:528 case DeferredLane:529 // Anything idle priority or lower should never expire.530 return NoTimestamp;531 default:532 if (__DEV__) {533 console.error(534 'Should have found matching lanes. This is a bug in React.',535 );536 }537 return NoTimestamp;538 }539}540541export function markStarvedLanesAsExpired(542 root: FiberRoot,543 currentTime: number,544): void {545 // TODO: This gets called every time we yield. We can optimize by storing546 // the earliest expiration time on the root. Then use that to quickly bail out547 // of this function.548549 const pendingLanes = root.pendingLanes;550 const suspendedLanes = root.suspendedLanes;551 const pingedLanes = root.pingedLanes;552 const expirationTimes = root.expirationTimes;553554 // Iterate through the pending lanes and check if we've reached their555 // expiration time. If so, we'll assume the update is being starved and mark556 // it as expired to force it to finish.557 // TODO: We should be able to replace this with upgradePendingLanesToSync558 //559 // We exclude retry lanes because those must always be time sliced, in order560 // to unwrap uncached promises.561 // TODO: Write a test for this562 let lanes = enableRetryLaneExpiration563 ? pendingLanes564 : pendingLanes & ~RetryLanes;565 while (lanes > 0) {566 const index = pickArbitraryLaneIndex(lanes);567 const lane = 1 << index;568569 const expirationTime = expirationTimes[index];570 if (expirationTime === NoTimestamp) {571 // Found a pending lane with no expiration time. If it's not suspended, or572 // if it's pinged, assume it's CPU-bound. Compute a new expiration time573 // using the current time.574 if (575 (lane & suspendedLanes) === NoLanes ||576 (lane & pingedLanes) !== NoLanes577 ) {578 // Assumes timestamps are monotonically increasing.579 expirationTimes[index] = computeExpirationTime(lane, currentTime);580 }581 } else if (expirationTime <= currentTime) {582 // This lane expired583 root.expiredLanes |= lane;584 }585586 lanes &= ~lane;587 }588}589590// This returns the highest priority pending lanes regardless of whether they591// are suspended.592export function getHighestPriorityPendingLanes(root: FiberRoot): Lanes {593 return getHighestPriorityLanes(root.pendingLanes);594}595596export function getLanesToRetrySynchronouslyOnError(597 root: FiberRoot,598 originallyAttemptedLanes: Lanes,599): Lanes {600 if (root.errorRecoveryDisabledLanes & originallyAttemptedLanes) {601 // The error recovery mechanism is disabled until these lanes are cleared.602 return NoLanes;603 }604605 const everythingButOffscreen = root.pendingLanes & ~OffscreenLane;606 if (everythingButOffscreen !== NoLanes) {607 return everythingButOffscreen;608 }609 if (everythingButOffscreen & OffscreenLane) {610 return OffscreenLane;611 }612 return NoLanes;613}614615export function includesSyncLane(lanes: Lanes): boolean {616 return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;617}618619export function includesNonIdleWork(lanes: Lanes): boolean {620 return (lanes & NonIdleLanes) !== NoLanes;621}622export function includesOnlyRetries(lanes: Lanes): boolean {623 return (lanes & RetryLanes) === lanes;624}625export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {626 // TODO: Should hydration lanes be included here? This function is only627 // used in `updateDeferredValueImpl`.628 const UrgentLanes =629 SyncLane | InputContinuousLane | DefaultLane | GestureLane;630 return (lanes & UrgentLanes) === NoLanes;631}632export function includesOnlyTransitions(lanes: Lanes): boolean {633 return (lanes & TransitionLanes) === lanes;634}635636export function includesTransitionLane(lanes: Lanes): boolean {637 return (lanes & TransitionLanes) !== NoLanes;638}639640export function includesRetryLane(lanes: Lanes): boolean {641 return (lanes & RetryLanes) !== NoLanes;642}643644export function includesIdleGroupLanes(lanes: Lanes): boolean {645 return (646 (lanes &647 (SelectiveHydrationLane |648 IdleHydrationLane |649 IdleLane |650 OffscreenLane |651 DeferredLane)) !==652 NoLanes653 );654}655656export function includesOnlyHydrationLanes(lanes: Lanes): boolean {657 return (lanes & HydrationLanes) === lanes;658}659660export function includesOnlyOffscreenLanes(lanes: Lanes): boolean {661 return (lanes & OffscreenLane) === lanes;662}663664export function includesOnlyHydrationOrOffscreenLanes(lanes: Lanes): boolean {665 return (lanes & (HydrationLanes | OffscreenLane)) === lanes;666}667668export function includesOnlyViewTransitionEligibleLanes(lanes: Lanes): boolean {669 return (lanes & (TransitionLanes | RetryLanes | IdleLane)) === lanes;670}671672export function includesOnlySuspenseyCommitEligibleLanes(673 lanes: Lanes,674): boolean {675 return (676 (lanes & (TransitionLanes | RetryLanes | IdleLane | GestureLane)) === lanes677 );678}679680export function includesLoadingIndicatorLanes(lanes: Lanes): boolean {681 return (lanes & (SyncLane | DefaultLane)) !== NoLanes;682}683684export function includesBlockingLane(lanes: Lanes): boolean {685 const SyncDefaultLanes =686 SyncHydrationLane |687 SyncLane |688 InputContinuousHydrationLane |689 InputContinuousLane |690 DefaultHydrationLane |691 DefaultLane |692 GestureLane;693 return (lanes & SyncDefaultLanes) !== NoLanes;694}695696export function includesExpiredLane(root: FiberRoot, lanes: Lanes): boolean {697 // This is a separate check from includesBlockingLane because a lane can698 // expire after a render has already started.699 return (lanes & root.expiredLanes) !== NoLanes;700}701702export function isBlockingLane(lane: Lane): boolean {703 const SyncDefaultLanes =704 SyncHydrationLane |705 SyncLane |706 InputContinuousHydrationLane |707 InputContinuousLane |708 DefaultHydrationLane |709 DefaultLane |710 GestureLane;711 return (lane & SyncDefaultLanes) !== NoLanes;712}713714export function isTransitionLane(lane: Lane): boolean {715 return (lane & TransitionLanes) !== NoLanes;716}717718export function isGestureRender(lanes: Lanes): boolean {719 if (!enableGestureTransition) {720 return false;721 }722 // This should render only the one lane.723 return lanes === GestureLane;724}725726export function claimNextTransitionUpdateLane(): Lane {727 // Cycle through the lanes, assigning each new transition to the next lane.728 // In most cases, this means every transition gets its own lane, until we729 // run out of lanes and cycle back to the beginning.730 const lane = nextTransitionUpdateLane;731 nextTransitionUpdateLane <<= 1;732 if ((nextTransitionUpdateLane & TransitionUpdateLanes) === NoLanes) {733 nextTransitionUpdateLane = TransitionLane1;734 }735 return lane;736}737738export function claimNextTransitionDeferredLane(): Lane {739 const lane = nextTransitionDeferredLane;740 nextTransitionDeferredLane <<= 1;741 if ((nextTransitionDeferredLane & TransitionDeferredLanes) === NoLanes) {742 nextTransitionDeferredLane = TransitionLane11;743 }744 return lane;745}746747export function claimNextRetryLane(): Lane {748 const lane = nextRetryLane;749 nextRetryLane <<= 1;750 if ((nextRetryLane & RetryLanes) === NoLanes) {751 nextRetryLane = RetryLane1;752 }753 return lane;754}755756export function getHighestPriorityLane(lanes: Lanes): Lane {757 return lanes & -lanes;758}759760function getLanesOfEqualOrHigherPriority(lanes: Lane | Lanes): Lanes {761 // Create a mask with all bits to the right or same as the highest bit.762 // So if lanes is 0b100, the result would be 0b111.763 // If lanes is 0b101, the result would be 0b111.764 const lowestPriorityLaneIndex = 31 - clz32(lanes);765 return (1 << (lowestPriorityLaneIndex + 1)) - 1;766}767768export function pickArbitraryLane(lanes: Lanes): Lane {769 // This wrapper function gets inlined. Only exists so to communicate that it770 // doesn't matter which bit is selected; you can pick any bit without771 // affecting the algorithms where its used. Here I'm using772 // getHighestPriorityLane because it requires the fewest operations.773 return getHighestPriorityLane(lanes);774}775776function pickArbitraryLaneIndex(lanes: Lanes) {777 return 31 - clz32(lanes);778}779780function laneToIndex(lane: Lane) {781 return pickArbitraryLaneIndex(lane);782}783784export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {785 return (a & b) !== NoLanes;786}787788export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {789 return (set & subset) === subset;790}791792export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {793 return a | b;794}795796export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {797 return set & ~subset;798}799800export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {801 return a & b;802}803804// Seems redundant, but it changes the type from a single lane (used for805// updates) to a group of lanes (used for flushing work).806export function laneToLanes(lane: Lane): Lanes {807 return lane;808}809810export function higherPriorityLane(a: Lane, b: Lane): Lane {811 // This works because the bit ranges decrease in priority as you go left.812 return a !== NoLane && a < b ? a : b;813}814815export function createLaneMap<T>(initial: T): LaneMap<T> {816 // Intentionally pushing one by one.817 // https://v8.dev/blog/elements-kinds#avoid-creating-holes818 const laneMap = [];819 for (let i = 0; i < TotalLanes; i++) {820 laneMap.push(initial);821 }822 return laneMap;823}824825export function markRootUpdated(root: FiberRoot, updateLane: Lane) {826 root.pendingLanes |= updateLane;827 if (enableDefaultTransitionIndicator) {828 // Mark that this lane might need a loading indicator to be shown.829 root.indicatorLanes |= updateLane & TransitionLanes;830 }831832 // If there are any suspended transitions, it's possible this new update833 // could unblock them. Clear the suspended lanes so that we can try rendering834 // them again.835 //836 // TODO: We really only need to unsuspend only lanes that are in the837 // `subtreeLanes` of the updated fiber, or the update lanes of the return838 // path. This would exclude suspended updates in an unrelated sibling tree,839 // since there's no way for this update to unblock it.840 //841 // We don't do this if the incoming update is idle, because we never process842 // idle updates until after all the regular updates have finished; there's no843 // way it could unblock a transition.844 if (updateLane !== IdleLane) {845 root.suspendedLanes = NoLanes;846 root.pingedLanes = NoLanes;847 root.warmLanes = NoLanes;848 }849}850851export function markRootSuspended(852 root: FiberRoot,853 suspendedLanes: Lanes,854 spawnedLane: Lane,855 didAttemptEntireTree: boolean,856) {857 // TODO: Split this into separate functions for marking the root at the end of858 // a render attempt versus suspending while the root is still in progress.859 root.suspendedLanes |= suspendedLanes;860 root.pingedLanes &= ~suspendedLanes;861862 if (didAttemptEntireTree) {863 // Mark these lanes as warm so we know there's nothing else to work on.864 root.warmLanes |= suspendedLanes;865 } else {866 // Render unwound without attempting all the siblings. Do no mark the lanes867 // as warm. This will cause a prewarm render to be scheduled.868 }869870 // The suspended lanes are no longer CPU-bound. Clear their expiration times.871 const expirationTimes = root.expirationTimes;872 let lanes = suspendedLanes;873 while (lanes > 0) {874 const index = pickArbitraryLaneIndex(lanes);875 const lane = 1 << index;876877 expirationTimes[index] = NoTimestamp;878879 lanes &= ~lane;880 }881882 if (spawnedLane !== NoLane) {883 markSpawnedDeferredLane(root, spawnedLane, suspendedLanes);884 }885}886887export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {888 root.pingedLanes |= root.suspendedLanes & pingedLanes;889 // The data that just resolved could have unblocked additional children, which890 // will also need to be prewarmed if something suspends again.891 root.warmLanes &= ~pingedLanes;892}893894export function markRootFinished(895 root: FiberRoot,896 finishedLanes: Lanes,897 remainingLanes: Lanes,898 spawnedLane: Lane,899 updatedLanes: Lanes,900 suspendedRetryLanes: Lanes,901) {902 const previouslyPendingLanes = root.pendingLanes;903 const noLongerPendingLanes = previouslyPendingLanes & ~remainingLanes;904905 root.pendingLanes = remainingLanes;906907 // Let's try everything again908 root.suspendedLanes = NoLanes;909 root.pingedLanes = NoLanes;910 root.warmLanes = NoLanes;911912 if (enableDefaultTransitionIndicator) {913 root.indicatorLanes &= remainingLanes;914 }915916 root.expiredLanes &= remainingLanes;917918 root.entangledLanes &= remainingLanes;919920 root.errorRecoveryDisabledLanes &= remainingLanes;921 root.shellSuspendCounter = 0;922923 const entanglements = root.entanglements;924 const expirationTimes = root.expirationTimes;925 const hiddenUpdates = root.hiddenUpdates;926927 // Clear the lanes that no longer have pending work928 let lanes = noLongerPendingLanes;929 while (lanes > 0) {930 const index = pickArbitraryLaneIndex(lanes);931 const lane = 1 << index;932933 entanglements[index] = NoLanes;934 expirationTimes[index] = NoTimestamp;935936 const hiddenUpdatesForLane = hiddenUpdates[index];937 if (hiddenUpdatesForLane !== null) {938 hiddenUpdates[index] = null;939 // "Hidden" updates are updates that were made to a hidden component. They940 // have special logic associated with them because they may be entangled941 // with updates that occur outside that tree. But once the outer tree942 // commits, they behave like regular updates.943 for (let i = 0; i < hiddenUpdatesForLane.length; i++) {944 const update = hiddenUpdatesForLane[i];945 // $FlowFixMe[invalid-compare]946 if (update !== null) {947 update.lane &= ~OffscreenLane;948 }949 }950 }951952 lanes &= ~lane;953 }954955 if (spawnedLane !== NoLane) {956 markSpawnedDeferredLane(957 root,958 spawnedLane,959 // This render finished successfully without suspending, so we don't need960 // to entangle the spawned task with the parent task.961 NoLanes,962 );963 }964965 // suspendedRetryLanes represents the retry lanes spawned by new Suspense966 // boundaries during this render that were not later pinged.967 //968 // These lanes were marked as pending on their associated Suspense boundary969 // fiber during the render phase so that we could start rendering them970 // before new data streams in. As soon as the fallback commits, we can try971 // to render them again.972 //973 // But since we know they're still suspended, we can skip straight to the974 // "prerender" mode (i.e. don't skip over siblings after something975 // suspended) instead of the regular mode (i.e. unwind and skip the siblings976 // as soon as something suspends to unblock the rest of the update).977 if (978 suspendedRetryLanes !== NoLanes &&979 // Note that we only do this if there were no updates since we started980 // rendering. This mirrors the logic in markRootUpdated — whenever we981 // receive an update, we reset all the suspended and pinged lanes.982 updatedLanes === NoLanes &&983 !(disableLegacyMode && root.tag === LegacyRoot)984 ) {985 // We also need to avoid marking a retry lane as suspended if it was already986 // pending before this render. We can't say these are now suspended if they987 // weren't included in our attempt.988 const freshlySpawnedRetryLanes =989 suspendedRetryLanes &990 // Remove any retry lane that was already pending before our just-finished991 // attempt, and also wasn't included in that attempt.992 ~(previouslyPendingLanes & ~finishedLanes);993 root.suspendedLanes |= freshlySpawnedRetryLanes;994 }995}996997function markSpawnedDeferredLane(998 root: FiberRoot,999 spawnedLane: Lane,1000 entangledLanes: Lanes,1001) {1002 // This render spawned a deferred task. Mark it as pending.1003 root.pendingLanes |= spawnedLane;1004 root.suspendedLanes &= ~spawnedLane;10051006 // Entangle the spawned lane with the DeferredLane bit so that we know it1007 // was the result of another render. This lets us avoid a useDeferredValue1008 // waterfall — only the first level will defer.1009 // TODO: Now that there is a reserved set of transition lanes that are used1010 // exclusively for deferred work, we should get rid of this special1011 // DeferredLane bit; the same information can be inferred by checking whether1012 // the lane is one of the TransitionDeferredLanes. The only reason this still1013 // exists is because we need to also do the same for OffscreenLane. That1014 // requires additional changes because there are more places around the1015 // codebase that treat OffscreenLane as a magic value; would need to check1016 // for a new OffscreenDeferredLane, too. Will leave this for a follow-up.1017 const spawnedLaneIndex = laneToIndex(spawnedLane);1018 root.entangledLanes |= spawnedLane;1019 root.entanglements[spawnedLaneIndex] |=1020 DeferredLane |1021 // If the parent render task suspended, we must also entangle those lanes1022 // with the spawned task, so that the deferred task includes all the same1023 // updates that the parent task did. We can exclude any lane that is not1024 // used for updates (e.g. Offscreen).1025 (entangledLanes & UpdateLanes);1026}10271028export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {1029 // In addition to entangling each of the given lanes with each other, we also1030 // have to consider _transitive_ entanglements. For each lane that is already1031 // entangled with *any* of the given lanes, that lane is now transitively1032 // entangled with *all* the given lanes.1033 //1034 // Translated: If C is entangled with A, then entangling A with B also1035 // entangles C with B.1036 //1037 // If this is hard to grasp, it might help to intentionally break this1038 // function and look at the tests that fail in ReactTransition-test.js. Try1039 // commenting out one of the conditions below.10401041 const rootEntangledLanes = (root.entangledLanes |= entangledLanes);1042 const entanglements = root.entanglements;1043 let lanes = rootEntangledLanes;1044 while (lanes) {1045 const index = pickArbitraryLaneIndex(lanes);1046 const lane = 1 << index;1047 if (1048 // Is this one of the newly entangled lanes?1049 (lane & entangledLanes) |1050 // Is this lane transitively entangled with the newly entangled lanes?1051 (entanglements[index] & entangledLanes)1052 ) {1053 entanglements[index] |= entangledLanes;1054 }1055 lanes &= ~lane;1056 }1057}10581059export function upgradePendingLanesToSync(1060 root: FiberRoot,1061 lanesToUpgrade: Lanes,1062) {1063 // Same as upgradePendingLaneToSync but accepts multiple lanes, so it's a1064 // bit slower.1065 root.pendingLanes |= SyncLane;1066 root.entangledLanes |= SyncLane;1067 let lanes = lanesToUpgrade;1068 while (lanes) {1069 const index = pickArbitraryLaneIndex(lanes);1070 const lane = 1 << index;1071 root.entanglements[SyncLaneIndex] |= lane;1072 lanes &= ~lane;1073 }1074}10751076export function markHiddenUpdate(1077 root: FiberRoot,1078 update: ConcurrentUpdate,1079 lane: Lane,1080) {1081 const index = laneToIndex(lane);1082 const hiddenUpdates = root.hiddenUpdates;1083 const hiddenUpdatesForLane = hiddenUpdates[index];1084 if (hiddenUpdatesForLane === null) {1085 hiddenUpdates[index] = [update];1086 } else {1087 hiddenUpdatesForLane.push(update);1088 }1089 update.lane = lane | OffscreenLane;1090}10911092export function getBumpedLaneForHydration(1093 root: FiberRoot,1094 renderLanes: Lanes,1095): Lane {1096 const renderLane = getHighestPriorityLane(renderLanes);1097 const bumpedLane =1098 (renderLane & SyncUpdateLanes) !== NoLane1099 ? // Unify sync lanes. We don't do this inside getBumpedLaneForHydrationByLane1100 // because that causes things to flush synchronously when they shouldn't.1101 // TODO: This is not coherent but that's beacuse the unification is not coherent.1102 // We need to get merge these into an actual single lane.1103 SyncHydrationLane1104 : getBumpedLaneForHydrationByLane(renderLane);1105 // Check if the lane we chose is suspended. If so, that indicates that we1106 // already attempted and failed to hydrate at that level. Also check if we're1107 // already rendering that lane, which is rare but could happen.1108 // TODO: This should move into the caller to decide whether giving up is valid.1109 if ((bumpedLane & (root.suspendedLanes | renderLanes)) !== NoLane) {1110 // Give up trying to hydrate and fall back to client render.1111 return NoLane;1112 }1113 return bumpedLane;1114}11151116export function getBumpedLaneForHydrationByLane(lane: Lane): Lane {1117 switch (lane) {1118 case SyncLane:1119 lane = SyncHydrationLane;1120 break;1121 case InputContinuousLane:1122 lane = InputContinuousHydrationLane;1123 break;1124 case DefaultLane:1125 lane = DefaultHydrationLane;1126 break;1127 case TransitionLane1:1128 case TransitionLane2:1129 case TransitionLane3:1130 case TransitionLane4:1131 case TransitionLane5:1132 case TransitionLane6:1133 case TransitionLane7:1134 case TransitionLane8:1135 case TransitionLane9:1136 case TransitionLane10:1137 case TransitionLane11:1138 case TransitionLane12:1139 case TransitionLane13:1140 case TransitionLane14:1141 case RetryLane1:1142 case RetryLane2:1143 case RetryLane3:1144 case RetryLane4:1145 lane = TransitionHydrationLane;1146 break;1147 case IdleLane:1148 lane = IdleHydrationLane;1149 break;1150 default:1151 // Everything else is already either a hydration lane, or shouldn't1152 // be retried at a hydration lane.1153 lane = NoLane;1154 break;1155 }1156 return lane;1157}11581159export function addFiberToLanesMap(1160 root: FiberRoot,1161 fiber: Fiber,1162 lanes: Lanes | Lane,1163) {1164 if (!enableUpdaterTracking) {1165 return;1166 }1167 if (!isDevToolsPresent) {1168 return;1169 }1170 const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;1171 while (lanes > 0) {1172 const index = laneToIndex(lanes);1173 const lane = 1 << index;11741175 const updaters = pendingUpdatersLaneMap[index];1176 updaters.add(fiber);11771178 lanes &= ~lane;1179 }1180}11811182export function movePendingFibersToMemoized(root: FiberRoot, lanes: Lanes) {1183 if (!enableUpdaterTracking) {1184 return;1185 }1186 if (!isDevToolsPresent) {1187 return;1188 }1189 const pendingUpdatersLaneMap = root.pendingUpdatersLaneMap;1190 const memoizedUpdaters = root.memoizedUpdaters;1191 while (lanes > 0) {1192 const index = laneToIndex(lanes);1193 const lane = 1 << index;11941195 const updaters = pendingUpdatersLaneMap[index];1196 if (updaters.size > 0) {1197 updaters.forEach(fiber => {1198 const alternate = fiber.alternate;1199 if (alternate === null || !memoizedUpdaters.has(alternate)) {1200 memoizedUpdaters.add(fiber);1201 }1202 });1203 updaters.clear();1204 }12051206 lanes &= ~lane;1207 }1208}12091210export function addTransitionToLanesMap(1211 root: FiberRoot,1212 transition: Transition,1213 lane: Lane,1214) {1215 if (enableTransitionTracing) {1216 const transitionLanesMap = root.transitionLanes;1217 const index = laneToIndex(lane);1218 let transitions = transitionLanesMap[index];1219 if (transitions === null) {1220 transitions = new Set();1221 }1222 transitions.add(transition);12231224 transitionLanesMap[index] = transitions;1225 }1226}12271228export function getTransitionsForLanes(1229 root: FiberRoot,1230 lanes: Lane | Lanes,1231): Array<Transition> | null {1232 if (!enableTransitionTracing) {1233 return null;1234 }12351236 const transitionsForLanes = [];1237 while (lanes > 0) {1238 const index = laneToIndex(lanes);1239 const lane = 1 << index;1240 const transitions = root.transitionLanes[index];1241 if (transitions !== null) {1242 transitions.forEach(transition => {1243 transitionsForLanes.push(transition);1244 });1245 }12461247 lanes &= ~lane;1248 }12491250 if (transitionsForLanes.length === 0) {1251 return null;1252 }12531254 return transitionsForLanes;1255}12561257export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) {1258 if (!enableTransitionTracing) {1259 return;1260 }12611262 while (lanes > 0) {1263 const index = laneToIndex(lanes);1264 const lane = 1 << index;12651266 const transitions = root.transitionLanes[index];1267 if (transitions !== null) {1268 root.transitionLanes[index] = null;1269 }12701271 lanes &= ~lane;1272 }1273}12741275// Used to name the Performance Track1276export function getGroupNameOfHighestPriorityLane(lanes: Lanes): string {1277 if (1278 lanes &1279 (SyncHydrationLane |1280 SyncLane |1281 InputContinuousHydrationLane |1282 InputContinuousLane |1283 DefaultHydrationLane |1284 DefaultLane)1285 ) {1286 return 'Blocking';1287 }1288 if (lanes & GestureLane) {1289 return 'Gesture';1290 }1291 if (lanes & (TransitionHydrationLane | TransitionLanes)) {1292 return 'Transition';1293 }1294 if (lanes & RetryLanes) {1295 return 'Suspense';1296 }1297 if (1298 lanes &1299 (SelectiveHydrationLane |1300 IdleHydrationLane |1301 IdleLane |1302 OffscreenLane |1303 DeferredLane)1304 ) {1305 return 'Idle';1306 }1307 return 'Other';1308}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.