main
js 1,308 lines 43.4 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber, FiberRoot} from './ReactInternalTypes';
11 import type {Transition} from 'react/src/ReactStartTransition';
12 import type {ConcurrentUpdate} from './ReactFiberConcurrentUpdates';
13
14 // TODO: Ideally these types would be opaque but that doesn't work well with
15 // our reconciler fork infra, since these leak into non-reconciler packages.
16
17 export type Lanes = number;
18 export type Lane = number;
19 export type LaneMap<T> = Array<T>;
20
21 import {
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';
34 import {isDevToolsPresent} from './ReactFiberDevToolsHook';
35 import {clz32} from './clz32';
36 import {LegacyRoot} from './ReactRootTags';
37
38 // 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.
40
41 export const TotalLanes = 31;
42
43 export const NoLanes: Lanes = /* */ 0b0000000000000000000000000000000;
44 export const NoLane: Lane = /* */ 0b0000000000000000000000000000000;
45
46 export const SyncHydrationLane: Lane = /* */ 0b0000000000000000000000000000001;
47 export const SyncLane: Lane = /* */ 0b0000000000000000000000000000010;
48 export const SyncLaneIndex: number = 1;
49
50 export const InputContinuousHydrationLane: Lane = /* */ 0b0000000000000000000000000000100;
51 export const InputContinuousLane: Lane = /* */ 0b0000000000000000000000000001000;
52
53 export const DefaultHydrationLane: Lane = /* */ 0b0000000000000000000000000010000;
54 export const DefaultLane: Lane = /* */ 0b0000000000000000000000000100000;
55
56 export const SyncUpdateLanes: Lane =
57 SyncLane | InputContinuousLane | DefaultLane;
58
59 export const GestureLane: Lane = /* */ 0b0000000000000000000000001000000;
60
61 const TransitionHydrationLane: Lane = /* */ 0b0000000000000000000000010000000;
62 const TransitionLanes: Lanes = /* */ 0b0000000001111111111111100000000;
63 const TransitionLane1: Lane = /* */ 0b0000000000000000000000100000000;
64 const TransitionLane2: Lane = /* */ 0b0000000000000000000001000000000;
65 const TransitionLane3: Lane = /* */ 0b0000000000000000000010000000000;
66 const TransitionLane4: Lane = /* */ 0b0000000000000000000100000000000;
67 const TransitionLane5: Lane = /* */ 0b0000000000000000001000000000000;
68 const TransitionLane6: Lane = /* */ 0b0000000000000000010000000000000;
69 const TransitionLane7: Lane = /* */ 0b0000000000000000100000000000000;
70 const TransitionLane8: Lane = /* */ 0b0000000000000001000000000000000;
71 const TransitionLane9: Lane = /* */ 0b0000000000000010000000000000000;
72 const TransitionLane10: Lane = /* */ 0b0000000000000100000000000000000;
73 const TransitionLane11: Lane = /* */ 0b0000000000001000000000000000000;
74 const TransitionLane12: Lane = /* */ 0b0000000000010000000000000000000;
75 const TransitionLane13: Lane = /* */ 0b0000000000100000000000000000000;
76 const TransitionLane14: Lane = /* */ 0b0000000001000000000000000000000;
77
78 export const SomeTransitionLane: Lane = TransitionLane1;
79
80 const TransitionUpdateLanes =
81 TransitionLane1 |
82 TransitionLane2 |
83 TransitionLane3 |
84 TransitionLane4 |
85 TransitionLane5 |
86 TransitionLane6 |
87 TransitionLane7 |
88 TransitionLane8 |
89 TransitionLane9 |
90 TransitionLane10;
91 const TransitionDeferredLanes =
92 TransitionLane11 | TransitionLane12 | TransitionLane13 | TransitionLane14;
93
94 const RetryLanes: Lanes = /* */ 0b0000011110000000000000000000000;
95 const RetryLane1: Lane = /* */ 0b0000000010000000000000000000000;
96 const RetryLane2: Lane = /* */ 0b0000000100000000000000000000000;
97 const RetryLane3: Lane = /* */ 0b0000001000000000000000000000000;
98 const RetryLane4: Lane = /* */ 0b0000010000000000000000000000000;
99
100 export const SomeRetryLane: Lane = RetryLane1;
101
102 export const SelectiveHydrationLane: Lane = /* */ 0b0000100000000000000000000000000;
103
104 const NonIdleLanes: Lanes = /* */ 0b0000111111111111111111111111111;
105
106 export const IdleHydrationLane: Lane = /* */ 0b0001000000000000000000000000000;
107 export const IdleLane: Lane = /* */ 0b0010000000000000000000000000000;
108
109 export const OffscreenLane: Lane = /* */ 0b0100000000000000000000000000000;
110 export const DeferredLane: Lane = /* */ 0b1000000000000000000000000000000;
111
112 // Any lane that might schedule an update. This is used to detect infinite
113 // update loops, so it doesn't include hydration lanes or retries.
114 export const UpdateLanes: Lanes =
115 SyncLane | InputContinuousLane | DefaultLane | TransitionUpdateLanes;
116
117 export const HydrationLanes =
118 SyncHydrationLane |
119 InputContinuousHydrationLane |
120 DefaultHydrationLane |
121 TransitionHydrationLane |
122 SelectiveHydrationLane |
123 IdleHydrationLane;
124
125 // This function is used for the experimental timeline (react-devtools-timeline)
126 // It should be kept in sync with the Lanes values above.
127 export 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 }
173
174 export const NoTimestamp = -1;
175
176 let nextTransitionUpdateLane: Lane = TransitionLane1;
177 let nextTransitionDeferredLane: Lane = TransitionLane11;
178 let nextRetryLane: Lane = RetryLane1;
179
180 function 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 entangled
236 // 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 }
248
249 export 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 }
259
260 let nextLanes: Lanes = NoLanes;
261
262 const suspendedLanes = root.suspendedLanes;
263 const pingedLanes = root.pingedLanes;
264 const warmLanes = root.warmLanes;
265
266 // finishedLanes represents a completed tree that is ready to commit.
267 //
268 // It's not worth doing discarding the completed tree in favor of performing
269 // speculative work. So always check this before deciding to warm up
270 // the siblings.
271 //
272 // Note that this is not set in a "suspend indefinitely" scenario, like when
273 // suspending outside of a Suspense boundary, or in the shell during a
274 // transition — only in cases where we are very likely to commit the tree in
275 // a brief amount of time (i.e. below the "Just Noticeable Difference"
276 // threshold).
277 //
278
279 // 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 around
305 // speculative rendering has evolved since this was implemented. Consider
306 // removing until we've thought about this again.
307
308 // 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 }
327
328 if (nextLanes === NoLanes) {
329 // This should only be reachable if we're suspended
330 // TODO: Consider warning in this path if a fallback timer is not scheduled.
331 return NoLanes;
332 }
333
334 // If we're already in the middle of a render, switching lanes will interrupt
335 // it and we'll lose our progress. We should only do this if the new lanes are
336 // higher priority.
337 if (
338 wipLanes !== NoLanes &&
339 wipLanes !== nextLanes &&
340 // If we already suspended with a delay, then interrupting is fine. Don't
341 // bother waiting until the root is complete.
342 (wipLanes & suspendedLanes) === NoLanes
343 ) {
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 wip
348 // 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. The
351 // only difference between default updates and transition updates is that
352 // 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 }
359
360 return nextLanes;
361 }
362
363 export function getNextLanesToFlushSync(
364 root: FiberRoot,
365 extraLanesToForceSync: Lane | Lanes,
366 ): Lanes {
367 // Similar to getNextLanes, except instead of choosing the next lanes to work
368 // on based on their priority, it selects all the lanes that have equal or
369 // higher priority than those are given. That way they can be synchronously
370 // rendered in a single batch.
371 //
372 // The main use case is updates scheduled by popstate events, which are
373 // flushed synchronously even though they are transitions.
374 // Note that we intentionally treat this as a sync flush to include any
375 // sync updates in a single pass but also intentionally disables View Transitions
376 // inside popstate. Because they can start synchronously before scroll restoration
377 // happens.
378 const lanesToFlush = SyncUpdateLanes | extraLanesToForceSync;
379
380 // Early bailout if there's no pending work left.
381 const pendingLanes = root.pendingLanes;
382 if (pendingLanes === NoLanes) {
383 return NoLanes;
384 }
385
386 const suspendedLanes = root.suspendedLanes;
387 const pingedLanes = root.pingedLanes;
388
389 // Remove lanes that are suspended (but not pinged)
390 const unblockedLanes = pendingLanes & ~(suspendedLanes & ~pingedLanes);
391 const unblockedLanesWithMatchingPriority =
392 unblockedLanes & getLanesOfEqualOrHigherPriority(lanesToFlush);
393
394 // 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) | SyncHydrationLane
399 );
400 }
401
402 if (unblockedLanesWithMatchingPriority) {
403 // Always include the SyncLane as part of the result, even if there's no
404 // pending sync work, to indicate the priority of the entire batch of work
405 // is considered Sync.
406 return unblockedLanesWithMatchingPriority | SyncLane;
407 }
408
409 return NoLanes;
410 }
411
412 export 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);
421
422 // If there are no unsuspended or pinged lanes, that implies that we're
423 // performing a prerender.
424 return (unblockedLanes & renderLanes) === 0;
425 }
426
427 export function getEntangledLanes(root: FiberRoot, renderLanes: Lanes): Lanes {
428 let entangledLanes = renderLanes;
429
430 if ((entangledLanes & InputContinuousLane) !== NoLanes) {
431 // When updates are sync by default, we entangle continuous priority updates
432 // and default updates, so they render in the same batch. The only reason
433 // they use separate lanes is because continuous updates should interrupt
434 // transitions, but default updates should not.
435 entangledLanes |= entangledLanes & DefaultLane;
436 }
437
438 // 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 render
441 // in a batch that does not also include the other lane. Typically we do this
442 // when multiple updates have the same source, and we only want to respond to
443 // 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 while
447 // it's already rendering, we won't interrupt it. This is intentional, since
448 // entanglement is usually "best effort": we'll try our best to render the
449 // lanes in the same batch, but it's not worth throwing out partially
450 // completed work in order to do it.
451 // TODO: Reconsider this. The counter-argument is that the partial work
452 // 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 of
454 // time it takes to show the final state, which is what they are actually
455 // waiting for.
456 //
457 // For those exceptions where entanglement is semantically important,
458 // we should ensure that there is no partial work at the
459 // 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;
467
468 entangledLanes |= entanglements[index];
469
470 lanes &= ~lane;
471 }
472 }
473
474 return entangledLanes;
475 }
476
477 function 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, suggesting
488 // there's a user interaction that's being starved by a series of
489 // synchronous updates. If that theory is correct, the proper solution is
490 // to fix the starvation. However, this scenario supports the idea that
491 // expiration times are an important safeguard when starvation
492 // 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 for
517 // too long, but when I made this change it caused a spike in browser
518 // crashes. There must be some other underlying bug; not super urgent but
519 // ideally should figure out why and fix it. Unfortunately we don't have
520 // a repro for the crashes, only detected via production metrics.
521 return enableRetryLaneExpiration
522 ? currentTime + retryLaneExpirationMs
523 : 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 }
540
541 export function markStarvedLanesAsExpired(
542 root: FiberRoot,
543 currentTime: number,
544 ): void {
545 // TODO: This gets called every time we yield. We can optimize by storing
546 // the earliest expiration time on the root. Then use that to quickly bail out
547 // of this function.
548
549 const pendingLanes = root.pendingLanes;
550 const suspendedLanes = root.suspendedLanes;
551 const pingedLanes = root.pingedLanes;
552 const expirationTimes = root.expirationTimes;
553
554 // Iterate through the pending lanes and check if we've reached their
555 // expiration time. If so, we'll assume the update is being starved and mark
556 // it as expired to force it to finish.
557 // TODO: We should be able to replace this with upgradePendingLanesToSync
558 //
559 // We exclude retry lanes because those must always be time sliced, in order
560 // to unwrap uncached promises.
561 // TODO: Write a test for this
562 let lanes = enableRetryLaneExpiration
563 ? pendingLanes
564 : pendingLanes & ~RetryLanes;
565 while (lanes > 0) {
566 const index = pickArbitraryLaneIndex(lanes);
567 const lane = 1 << index;
568
569 const expirationTime = expirationTimes[index];
570 if (expirationTime === NoTimestamp) {
571 // Found a pending lane with no expiration time. If it's not suspended, or
572 // if it's pinged, assume it's CPU-bound. Compute a new expiration time
573 // using the current time.
574 if (
575 (lane & suspendedLanes) === NoLanes ||
576 (lane & pingedLanes) !== NoLanes
577 ) {
578 // Assumes timestamps are monotonically increasing.
579 expirationTimes[index] = computeExpirationTime(lane, currentTime);
580 }
581 } else if (expirationTime <= currentTime) {
582 // This lane expired
583 root.expiredLanes |= lane;
584 }
585
586 lanes &= ~lane;
587 }
588 }
589
590 // This returns the highest priority pending lanes regardless of whether they
591 // are suspended.
592 export function getHighestPriorityPendingLanes(root: FiberRoot): Lanes {
593 return getHighestPriorityLanes(root.pendingLanes);
594 }
595
596 export 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 }
604
605 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 }
614
615 export function includesSyncLane(lanes: Lanes): boolean {
616 return (lanes & (SyncLane | SyncHydrationLane)) !== NoLanes;
617 }
618
619 export function includesNonIdleWork(lanes: Lanes): boolean {
620 return (lanes & NonIdleLanes) !== NoLanes;
621 }
622 export function includesOnlyRetries(lanes: Lanes): boolean {
623 return (lanes & RetryLanes) === lanes;
624 }
625 export function includesOnlyNonUrgentLanes(lanes: Lanes): boolean {
626 // TODO: Should hydration lanes be included here? This function is only
627 // used in `updateDeferredValueImpl`.
628 const UrgentLanes =
629 SyncLane | InputContinuousLane | DefaultLane | GestureLane;
630 return (lanes & UrgentLanes) === NoLanes;
631 }
632 export function includesOnlyTransitions(lanes: Lanes): boolean {
633 return (lanes & TransitionLanes) === lanes;
634 }
635
636 export function includesTransitionLane(lanes: Lanes): boolean {
637 return (lanes & TransitionLanes) !== NoLanes;
638 }
639
640 export function includesRetryLane(lanes: Lanes): boolean {
641 return (lanes & RetryLanes) !== NoLanes;
642 }
643
644 export function includesIdleGroupLanes(lanes: Lanes): boolean {
645 return (
646 (lanes &
647 (SelectiveHydrationLane |
648 IdleHydrationLane |
649 IdleLane |
650 OffscreenLane |
651 DeferredLane)) !==
652 NoLanes
653 );
654 }
655
656 export function includesOnlyHydrationLanes(lanes: Lanes): boolean {
657 return (lanes & HydrationLanes) === lanes;
658 }
659
660 export function includesOnlyOffscreenLanes(lanes: Lanes): boolean {
661 return (lanes & OffscreenLane) === lanes;
662 }
663
664 export function includesOnlyHydrationOrOffscreenLanes(lanes: Lanes): boolean {
665 return (lanes & (HydrationLanes | OffscreenLane)) === lanes;
666 }
667
668 export function includesOnlyViewTransitionEligibleLanes(lanes: Lanes): boolean {
669 return (lanes & (TransitionLanes | RetryLanes | IdleLane)) === lanes;
670 }
671
672 export function includesOnlySuspenseyCommitEligibleLanes(
673 lanes: Lanes,
674 ): boolean {
675 return (
676 (lanes & (TransitionLanes | RetryLanes | IdleLane | GestureLane)) === lanes
677 );
678 }
679
680 export function includesLoadingIndicatorLanes(lanes: Lanes): boolean {
681 return (lanes & (SyncLane | DefaultLane)) !== NoLanes;
682 }
683
684 export 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 }
695
696 export function includesExpiredLane(root: FiberRoot, lanes: Lanes): boolean {
697 // This is a separate check from includesBlockingLane because a lane can
698 // expire after a render has already started.
699 return (lanes & root.expiredLanes) !== NoLanes;
700 }
701
702 export 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 }
713
714 export function isTransitionLane(lane: Lane): boolean {
715 return (lane & TransitionLanes) !== NoLanes;
716 }
717
718 export 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 }
725
726 export 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 we
729 // 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 }
737
738 export function claimNextTransitionDeferredLane(): Lane {
739 const lane = nextTransitionDeferredLane;
740 nextTransitionDeferredLane <<= 1;
741 if ((nextTransitionDeferredLane & TransitionDeferredLanes) === NoLanes) {
742 nextTransitionDeferredLane = TransitionLane11;
743 }
744 return lane;
745 }
746
747 export function claimNextRetryLane(): Lane {
748 const lane = nextRetryLane;
749 nextRetryLane <<= 1;
750 if ((nextRetryLane & RetryLanes) === NoLanes) {
751 nextRetryLane = RetryLane1;
752 }
753 return lane;
754 }
755
756 export function getHighestPriorityLane(lanes: Lanes): Lane {
757 return lanes & -lanes;
758 }
759
760 function 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 }
767
768 export function pickArbitraryLane(lanes: Lanes): Lane {
769 // This wrapper function gets inlined. Only exists so to communicate that it
770 // doesn't matter which bit is selected; you can pick any bit without
771 // affecting the algorithms where its used. Here I'm using
772 // getHighestPriorityLane because it requires the fewest operations.
773 return getHighestPriorityLane(lanes);
774 }
775
776 function pickArbitraryLaneIndex(lanes: Lanes) {
777 return 31 - clz32(lanes);
778 }
779
780 function laneToIndex(lane: Lane) {
781 return pickArbitraryLaneIndex(lane);
782 }
783
784 export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane): boolean {
785 return (a & b) !== NoLanes;
786 }
787
788 export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane): boolean {
789 return (set & subset) === subset;
790 }
791
792 export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
793 return a | b;
794 }
795
796 export function removeLanes(set: Lanes, subset: Lanes | Lane): Lanes {
797 return set & ~subset;
798 }
799
800 export function intersectLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
801 return a & b;
802 }
803
804 // Seems redundant, but it changes the type from a single lane (used for
805 // updates) to a group of lanes (used for flushing work).
806 export function laneToLanes(lane: Lane): Lanes {
807 return lane;
808 }
809
810 export 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 }
814
815 export function createLaneMap<T>(initial: T): LaneMap<T> {
816 // Intentionally pushing one by one.
817 // https://v8.dev/blog/elements-kinds#avoid-creating-holes
818 const laneMap = [];
819 for (let i = 0; i < TotalLanes; i++) {
820 laneMap.push(initial);
821 }
822 return laneMap;
823 }
824
825 export 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 }
831
832 // If there are any suspended transitions, it's possible this new update
833 // could unblock them. Clear the suspended lanes so that we can try rendering
834 // them again.
835 //
836 // TODO: We really only need to unsuspend only lanes that are in the
837 // `subtreeLanes` of the updated fiber, or the update lanes of the return
838 // 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 process
842 // idle updates until after all the regular updates have finished; there's no
843 // way it could unblock a transition.
844 if (updateLane !== IdleLane) {
845 root.suspendedLanes = NoLanes;
846 root.pingedLanes = NoLanes;
847 root.warmLanes = NoLanes;
848 }
849 }
850
851 export 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 of
858 // a render attempt versus suspending while the root is still in progress.
859 root.suspendedLanes |= suspendedLanes;
860 root.pingedLanes &= ~suspendedLanes;
861
862 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 lanes
867 // as warm. This will cause a prewarm render to be scheduled.
868 }
869
870 // 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;
876
877 expirationTimes[index] = NoTimestamp;
878
879 lanes &= ~lane;
880 }
881
882 if (spawnedLane !== NoLane) {
883 markSpawnedDeferredLane(root, spawnedLane, suspendedLanes);
884 }
885 }
886
887 export function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
888 root.pingedLanes |= root.suspendedLanes & pingedLanes;
889 // The data that just resolved could have unblocked additional children, which
890 // will also need to be prewarmed if something suspends again.
891 root.warmLanes &= ~pingedLanes;
892 }
893
894 export 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;
904
905 root.pendingLanes = remainingLanes;
906
907 // Let's try everything again
908 root.suspendedLanes = NoLanes;
909 root.pingedLanes = NoLanes;
910 root.warmLanes = NoLanes;
911
912 if (enableDefaultTransitionIndicator) {
913 root.indicatorLanes &= remainingLanes;
914 }
915
916 root.expiredLanes &= remainingLanes;
917
918 root.entangledLanes &= remainingLanes;
919
920 root.errorRecoveryDisabledLanes &= remainingLanes;
921 root.shellSuspendCounter = 0;
922
923 const entanglements = root.entanglements;
924 const expirationTimes = root.expirationTimes;
925 const hiddenUpdates = root.hiddenUpdates;
926
927 // Clear the lanes that no longer have pending work
928 let lanes = noLongerPendingLanes;
929 while (lanes > 0) {
930 const index = pickArbitraryLaneIndex(lanes);
931 const lane = 1 << index;
932
933 entanglements[index] = NoLanes;
934 expirationTimes[index] = NoTimestamp;
935
936 const hiddenUpdatesForLane = hiddenUpdates[index];
937 if (hiddenUpdatesForLane !== null) {
938 hiddenUpdates[index] = null;
939 // "Hidden" updates are updates that were made to a hidden component. They
940 // have special logic associated with them because they may be entangled
941 // with updates that occur outside that tree. But once the outer tree
942 // 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 }
951
952 lanes &= ~lane;
953 }
954
955 if (spawnedLane !== NoLane) {
956 markSpawnedDeferredLane(
957 root,
958 spawnedLane,
959 // This render finished successfully without suspending, so we don't need
960 // to entangle the spawned task with the parent task.
961 NoLanes,
962 );
963 }
964
965 // suspendedRetryLanes represents the retry lanes spawned by new Suspense
966 // boundaries during this render that were not later pinged.
967 //
968 // These lanes were marked as pending on their associated Suspense boundary
969 // fiber during the render phase so that we could start rendering them
970 // before new data streams in. As soon as the fallback commits, we can try
971 // to render them again.
972 //
973 // But since we know they're still suspended, we can skip straight to the
974 // "prerender" mode (i.e. don't skip over siblings after something
975 // suspended) instead of the regular mode (i.e. unwind and skip the siblings
976 // 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 started
980 // rendering. This mirrors the logic in markRootUpdated — whenever we
981 // 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 already
986 // pending before this render. We can't say these are now suspended if they
987 // weren't included in our attempt.
988 const freshlySpawnedRetryLanes =
989 suspendedRetryLanes &
990 // Remove any retry lane that was already pending before our just-finished
991 // attempt, and also wasn't included in that attempt.
992 ~(previouslyPendingLanes & ~finishedLanes);
993 root.suspendedLanes |= freshlySpawnedRetryLanes;
994 }
995 }
996
997 function 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;
1005
1006 // Entangle the spawned lane with the DeferredLane bit so that we know it
1007 // was the result of another render. This lets us avoid a useDeferredValue
1008 // waterfall — only the first level will defer.
1009 // TODO: Now that there is a reserved set of transition lanes that are used
1010 // exclusively for deferred work, we should get rid of this special
1011 // DeferredLane bit; the same information can be inferred by checking whether
1012 // the lane is one of the TransitionDeferredLanes. The only reason this still
1013 // exists is because we need to also do the same for OffscreenLane. That
1014 // requires additional changes because there are more places around the
1015 // codebase that treat OffscreenLane as a magic value; would need to check
1016 // 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 lanes
1022 // with the spawned task, so that the deferred task includes all the same
1023 // updates that the parent task did. We can exclude any lane that is not
1024 // used for updates (e.g. Offscreen).
1025 (entangledLanes & UpdateLanes);
1026 }
1027
1028 export function markRootEntangled(root: FiberRoot, entangledLanes: Lanes) {
1029 // In addition to entangling each of the given lanes with each other, we also
1030 // have to consider _transitive_ entanglements. For each lane that is already
1031 // entangled with *any* of the given lanes, that lane is now transitively
1032 // entangled with *all* the given lanes.
1033 //
1034 // Translated: If C is entangled with A, then entangling A with B also
1035 // entangles C with B.
1036 //
1037 // If this is hard to grasp, it might help to intentionally break this
1038 // function and look at the tests that fail in ReactTransition-test.js. Try
1039 // commenting out one of the conditions below.
1040
1041 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 }
1058
1059 export function upgradePendingLanesToSync(
1060 root: FiberRoot,
1061 lanesToUpgrade: Lanes,
1062 ) {
1063 // Same as upgradePendingLaneToSync but accepts multiple lanes, so it's a
1064 // 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 }
1075
1076 export 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 }
1091
1092 export function getBumpedLaneForHydration(
1093 root: FiberRoot,
1094 renderLanes: Lanes,
1095 ): Lane {
1096 const renderLane = getHighestPriorityLane(renderLanes);
1097 const bumpedLane =
1098 (renderLane & SyncUpdateLanes) !== NoLane
1099 ? // Unify sync lanes. We don't do this inside getBumpedLaneForHydrationByLane
1100 // 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 SyncHydrationLane
1104 : getBumpedLaneForHydrationByLane(renderLane);
1105 // Check if the lane we chose is suspended. If so, that indicates that we
1106 // already attempted and failed to hydrate at that level. Also check if we're
1107 // 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 }
1115
1116 export 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't
1152 // be retried at a hydration lane.
1153 lane = NoLane;
1154 break;
1155 }
1156 return lane;
1157 }
1158
1159 export 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;
1174
1175 const updaters = pendingUpdatersLaneMap[index];
1176 updaters.add(fiber);
1177
1178 lanes &= ~lane;
1179 }
1180 }
1181
1182 export 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;
1194
1195 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 }
1205
1206 lanes &= ~lane;
1207 }
1208 }
1209
1210 export 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);
1223
1224 transitionLanesMap[index] = transitions;
1225 }
1226 }
1227
1228 export function getTransitionsForLanes(
1229 root: FiberRoot,
1230 lanes: Lane | Lanes,
1231 ): Array<Transition> | null {
1232 if (!enableTransitionTracing) {
1233 return null;
1234 }
1235
1236 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 }
1246
1247 lanes &= ~lane;
1248 }
1249
1250 if (transitionsForLanes.length === 0) {
1251 return null;
1252 }
1253
1254 return transitionsForLanes;
1255 }
1256
1257 export function clearTransitionsForLanes(root: FiberRoot, lanes: Lane | Lanes) {
1258 if (!enableTransitionTracing) {
1259 return;
1260 }
1261
1262 while (lanes > 0) {
1263 const index = laneToIndex(lanes);
1264 const lane = 1 << index;
1265
1266 const transitions = root.transitionLanes[index];
1267 if (transitions !== null) {
1268 root.transitionLanes[index] = null;
1269 }
1270
1271 lanes &= ~lane;
1272 }
1273 }
1274
1275 // Used to name the Performance Track
1276 export 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 }