main
js 5,683 lines 199 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 {REACT_STRICT_MODE_TYPE} from 'shared/ReactSymbols';
11
12 import type {
13 Wakeable,
14 Thenable,
15 GestureOptionsRequired,
16 } from 'shared/ReactTypes';
17 import type {Fiber, FiberRoot} from './ReactInternalTypes';
18 import type {Lanes, Lane} from './ReactFiberLane';
19 import type {ActivityState} from './ReactFiberActivityComponent';
20 import type {SuspenseState} from './ReactFiberSuspenseComponent';
21 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
22 import type {Transition} from 'react/src/ReactStartTransition';
23 import type {
24 PendingTransitionCallbacks,
25 PendingBoundaries,
26 TransitionAbort,
27 } from './ReactFiberTracingMarkerComponent';
28 import type {OffscreenInstance} from './ReactFiberOffscreenComponent';
29 import type {
30 Resource,
31 ViewTransitionInstance,
32 RunningViewTransition,
33 GestureTimeline,
34 SuspendedState,
35 } from './ReactFiberConfig';
36 import type {RootState} from './ReactFiberRoot';
37 import {
38 getViewTransitionName,
39 type ViewTransitionState,
40 } from './ReactFiberViewTransitionComponent';
41 import type {TransitionTypes} from 'react/src/ReactTransitionType';
42
43 import {
44 enableCreateEventHandleAPI,
45 enableProfilerTimer,
46 enableProfilerCommitHooks,
47 enableProfilerNestedUpdatePhase,
48 enableSchedulingProfiler,
49 enableUpdaterTracking,
50 enableTransitionTracing,
51 disableLegacyContext,
52 alwaysThrottleRetries,
53 enableInfiniteRenderLoopDetection,
54 enableInfiniteRenderLoopDetectionForceThrow,
55 disableLegacyMode,
56 enableComponentPerformanceTrack,
57 enableYieldingBeforePassive,
58 enableThrottledScheduling,
59 enableViewTransition,
60 enableGestureTransition,
61 enableDefaultTransitionIndicator,
62 enableParallelTransitions,
63 } from 'shared/ReactFeatureFlags';
64 import {resetOwnerStackLimit} from 'shared/ReactOwnerStackReset';
65 import ReactSharedInternals from 'shared/ReactSharedInternals';
66 import is from 'shared/objectIs';
67
68 import reportGlobalError from 'shared/reportGlobalError';
69
70 import {
71 // Aliased because `act` will override and push to an internal queue
72 scheduleCallback as Scheduler_scheduleCallback,
73 shouldYield,
74 requestPaint,
75 now,
76 NormalPriority as NormalSchedulerPriority,
77 IdlePriority as IdleSchedulerPriority,
78 } from './Scheduler';
79 import {
80 logBlockingStart,
81 logGestureStart,
82 logTransitionStart,
83 logRenderPhase,
84 logInterruptedRenderPhase,
85 logSuspendedRenderPhase,
86 logRecoveredRenderPhase,
87 logErroredRenderPhase,
88 logInconsistentRender,
89 logSuspendedWithDelayPhase,
90 logSuspendedCommitPhase,
91 logSuspendedViewTransitionPhase,
92 logCommitPhase,
93 logPaintYieldPhase,
94 logStartViewTransitionYieldPhase,
95 logAnimatingPhase,
96 logPassiveCommitPhase,
97 logYieldTime,
98 logActionYieldTime,
99 logSuspendedYieldTime,
100 setCurrentTrackFromLanes,
101 markAllLanesInOrder,
102 logApplyGesturePhase,
103 } from './ReactFiberPerformanceTrack';
104
105 import {
106 resetAfterCommit,
107 scheduleTimeout,
108 cancelTimeout,
109 noTimeout,
110 afterActiveInstanceBlur,
111 startSuspendingCommit,
112 suspendOnActiveViewTransition,
113 waitForCommitToBeReady,
114 getSuspendedCommitReason,
115 preloadInstance,
116 preloadResource,
117 supportsHydration,
118 setCurrentUpdatePriority,
119 getCurrentUpdatePriority,
120 resolveUpdatePriority,
121 trackSchedulerEvent,
122 startViewTransition,
123 startGestureTransition,
124 stopViewTransition,
125 addViewTransitionFinishedListener,
126 createViewTransitionInstance,
127 flushHydrationEvents,
128 } from './ReactFiberConfig';
129
130 import {createWorkInProgress, resetWorkInProgress} from './ReactFiber';
131 import {isRootDehydrated} from './ReactFiberShellHydration';
132 import {
133 getIsHydrating,
134 popHydrationStateOnInterruptedWork,
135 } from './ReactFiberHydrationContext';
136 import {
137 NoMode,
138 ProfileMode,
139 ConcurrentMode,
140 StrictLegacyMode,
141 StrictEffectsMode,
142 } from './ReactTypeOfMode';
143 import {
144 HostRoot,
145 ClassComponent,
146 ActivityComponent,
147 SuspenseComponent,
148 SuspenseListComponent,
149 OffscreenComponent,
150 FunctionComponent,
151 ForwardRef,
152 MemoComponent,
153 SimpleMemoComponent,
154 HostComponent,
155 HostHoistable,
156 HostSingleton,
157 } from './ReactWorkTags';
158 import {ConcurrentRoot, LegacyRoot} from './ReactRootTags';
159 import type {Flags} from './ReactFiberFlags';
160 import {
161 NoFlags,
162 Incomplete,
163 StoreConsistency,
164 HostEffectMask,
165 ForceClientRender,
166 BeforeMutationMask,
167 MutationMask,
168 LayoutMask,
169 PassiveMask,
170 PlacementDEV,
171 Visibility,
172 MountPassiveDev,
173 MountLayoutDev,
174 DidDefer,
175 ShouldSuspendCommit,
176 MaySuspendCommit,
177 ScheduleRetry,
178 PassiveTransitionMask,
179 } from './ReactFiberFlags';
180 import {
181 NoLanes,
182 NoLane,
183 SyncLane,
184 claimNextRetryLane,
185 includesSyncLane,
186 isSubsetOfLanes,
187 mergeLanes,
188 removeLanes,
189 pickArbitraryLane,
190 includesNonIdleWork,
191 includesOnlyRetries,
192 includesOnlyTransitions,
193 includesBlockingLane,
194 includesTransitionLane,
195 includesRetryLane,
196 includesIdleGroupLanes,
197 includesExpiredLane,
198 getNextLanes,
199 getEntangledLanes,
200 getLanesToRetrySynchronouslyOnError,
201 upgradePendingLanesToSync,
202 markRootSuspended as _markRootSuspended,
203 markRootUpdated as _markRootUpdated,
204 markRootPinged as _markRootPinged,
205 markRootFinished,
206 addFiberToLanesMap,
207 movePendingFibersToMemoized,
208 addTransitionToLanesMap,
209 getTransitionsForLanes,
210 includesSomeLane,
211 OffscreenLane,
212 SyncUpdateLanes,
213 UpdateLanes,
214 claimNextTransitionDeferredLane,
215 checkIfRootIsPrerendering,
216 includesOnlyViewTransitionEligibleLanes,
217 isGestureRender,
218 GestureLane,
219 SomeTransitionLane,
220 SomeRetryLane,
221 IdleLane,
222 } from './ReactFiberLane';
223 import {
224 DiscreteEventPriority,
225 DefaultEventPriority,
226 lowerEventPriority,
227 lanesToEventPriority,
228 eventPriorityToLane,
229 } from './ReactEventPriorities';
230 import {requestCurrentTransition} from './ReactFiberTransition';
231 import {
232 SelectiveHydrationException,
233 beginWork,
234 replayFunctionComponent,
235 } from './ReactFiberBeginWork';
236 import {completeWork} from './ReactFiberCompleteWork';
237 import {unwindWork, unwindInterruptedWork} from './ReactFiberUnwindWork';
238 import {
239 throwException,
240 createRootErrorUpdate,
241 createClassErrorUpdate,
242 initializeClassErrorUpdate,
243 } from './ReactFiberThrow';
244 import {
245 commitBeforeMutationEffects,
246 shouldFireAfterActiveInstanceBlur,
247 commitAfterMutationEffects,
248 commitLayoutEffects,
249 commitMutationEffects,
250 commitPassiveMountEffects,
251 commitPassiveUnmountEffects,
252 disappearLayoutEffectsForDEVValidation,
253 reconnectPassiveEffects,
254 reappearLayoutEffectsForDEVValidation,
255 disconnectPassiveEffect,
256 invokeLayoutEffectMountInDEV,
257 invokePassiveEffectMountInDEV,
258 invokeLayoutEffectUnmountInDEV,
259 invokePassiveEffectUnmountInDEV,
260 accumulateSuspenseyCommit,
261 } from './ReactFiberCommitWork';
262 import {resetShouldStartViewTransition} from './ReactFiberCommitViewTransitions';
263 import {shouldStartViewTransition} from './ReactFiberCommitViewTransitions';
264 import {
265 insertDestinationClones,
266 applyDepartureTransitions,
267 startGestureAnimations,
268 } from './ReactFiberApplyGesture';
269 import {enqueueUpdate} from './ReactFiberClassUpdateQueue';
270 import {resetContextDependencies} from './ReactFiberNewContext';
271 import {
272 resetHooksAfterThrow,
273 resetHooksOnUnwind,
274 ContextOnlyDispatcher,
275 } from './ReactFiberHooks';
276 import {DefaultAsyncDispatcher} from './ReactFiberAsyncDispatcher';
277 import {
278 createCapturedValueAtFiber,
279 type CapturedValue,
280 } from './ReactCapturedValue';
281 import {
282 enqueueConcurrentRenderForLane,
283 finishQueueingConcurrentUpdates,
284 getConcurrentlyUpdatedLanes,
285 } from './ReactFiberConcurrentUpdates';
286
287 import {
288 blockingClampTime,
289 blockingUpdateTime,
290 blockingUpdateTask,
291 blockingUpdateType,
292 blockingUpdateMethodName,
293 blockingUpdateComponentName,
294 blockingEventTime,
295 blockingEventType,
296 blockingEventRepeatTime,
297 blockingSuspendedTime,
298 gestureClampTime,
299 gestureUpdateTime,
300 gestureUpdateTask,
301 gestureUpdateType,
302 gestureUpdateMethodName,
303 gestureUpdateComponentName,
304 gestureEventTime,
305 gestureEventType,
306 gestureEventRepeatTime,
307 gestureSuspendedTime,
308 transitionClampTime,
309 transitionStartTime,
310 transitionUpdateTime,
311 transitionUpdateTask,
312 transitionUpdateType,
313 transitionUpdateMethodName,
314 transitionUpdateComponentName,
315 transitionEventTime,
316 transitionEventType,
317 transitionEventRepeatTime,
318 transitionSuspendedTime,
319 clearBlockingTimers,
320 clearGestureTimers,
321 clearGestureUpdates,
322 clearTransitionTimers,
323 clampBlockingTimers,
324 clampGestureTimers,
325 clampTransitionTimers,
326 clampRetryTimers,
327 clampIdleTimers,
328 markNestedUpdateScheduled,
329 renderStartTime,
330 commitStartTime,
331 commitEndTime,
332 commitErrors,
333 recordRenderTime,
334 recordCommitTime,
335 recordCommitEndTime,
336 startProfilerTimer,
337 stopProfilerTimerIfRunningAndRecordDuration,
338 stopProfilerTimerIfRunningAndRecordIncompleteDuration,
339 trackSuspendedTime,
340 startYieldTimer,
341 yieldStartTime,
342 yieldReason,
343 startPingTimerByLanes,
344 recordEffectError,
345 resetCommitErrors,
346 PINGED_UPDATE,
347 SPAWNED_UPDATE,
348 startAnimating,
349 stopAnimating,
350 animatingLanes,
351 retryClampTime,
352 idleClampTime,
353 animatingTask,
354 } from './ReactProfilerTimer';
355
356 // DEV stuff
357 import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
358 import ReactStrictModeWarnings from './ReactStrictModeWarnings';
359 import {
360 isRendering as ReactCurrentDebugFiberIsRenderingInDEV,
361 resetCurrentFiber,
362 runWithFiberInDEV,
363 } from './ReactCurrentFiber';
364 import {
365 isDevToolsPresent,
366 markCommitStarted,
367 markCommitStopped,
368 markComponentRenderStopped,
369 markComponentSuspended,
370 markComponentErrored,
371 markLayoutEffectsStarted,
372 markLayoutEffectsStopped,
373 markPassiveEffectsStarted,
374 markPassiveEffectsStopped,
375 markRenderStarted,
376 markRenderYielded,
377 markRenderStopped,
378 onCommitRoot as onCommitRootDevTools,
379 onPostCommitRoot as onPostCommitRootDevTools,
380 setIsStrictModeForDevtools,
381 } from './ReactFiberDevToolsHook';
382 import {onCommitRoot as onCommitRootTestSelector} from './ReactTestSelectors';
383 import {releaseCache} from './ReactFiberCacheComponent';
384 import {
385 isLegacyActEnvironment,
386 isConcurrentActEnvironment,
387 } from './ReactFiberAct';
388 import {processTransitionCallbacks} from './ReactFiberTracingMarkerComponent';
389 import {
390 SuspenseException,
391 SuspenseActionException,
392 SuspenseyCommitException,
393 getSuspendedThenable,
394 isThenableResolved,
395 hasPotentialUseWarnings,
396 clearUseWarnings,
397 } from './ReactFiberThenable';
398 import {schedulePostPaintCallback} from './ReactPostPaintCallback';
399 import {
400 getSuspenseHandler,
401 getShellBoundary,
402 } from './ReactFiberSuspenseContext';
403 import {resetChildReconcilerOnUnwind} from './ReactChildFiber';
404 import {
405 ensureRootIsScheduled,
406 flushSyncWorkOnAllRoots,
407 flushSyncWorkOnLegacyRootsOnly,
408 requestTransitionLane,
409 } from './ReactFiberRootScheduler';
410 import {getMaskedContext, getUnmaskedContext} from './ReactFiberLegacyContext';
411 import {logUncaughtError} from './ReactFiberErrorLogger';
412 import {
413 scheduleGestureCommit,
414 stopCommittedGesture,
415 } from './ReactFiberGestureScheduler';
416 import {claimQueuedTransitionTypes} from './ReactFiberTransitionTypes';
417
418 const PossiblyWeakMap = typeof WeakMap === 'function' ? WeakMap : Map;
419
420 type ExecutionContext = number;
421
422 export const NoContext = /* */ 0b000;
423 const BatchedContext = /* */ 0b001;
424 export const RenderContext = /* */ 0b010;
425 export const CommitContext = /* */ 0b100;
426
427 type RootExitStatus = 0 | 1 | 2 | 3 | 4 | 5 | 6;
428 const RootInProgress = 0;
429 const RootFatalErrored = 1;
430 const RootErrored = 2;
431 const RootSuspended = 3;
432 const RootSuspendedWithDelay = 4;
433 const RootSuspendedAtTheShell = 6;
434 const RootCompleted = 5;
435
436 // Describes where we are in the React execution stack
437 let executionContext: ExecutionContext = NoContext;
438 // The root we're working on
439 let workInProgressRoot: FiberRoot | null = null;
440 // The fiber we're working on
441 let workInProgress: Fiber | null = null;
442 // The lanes we're rendering
443 let workInProgressRootRenderLanes: Lanes = NoLanes;
444
445 export opaque type SuspendedReason = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
446 const NotSuspended: SuspendedReason = 0;
447 const SuspendedOnError: SuspendedReason = 1;
448 const SuspendedOnData: SuspendedReason = 2;
449 const SuspendedOnImmediate: SuspendedReason = 3;
450 const SuspendedOnInstance: SuspendedReason = 4;
451 const SuspendedOnInstanceAndReadyToContinue: SuspendedReason = 5;
452 const SuspendedOnDeprecatedThrowPromise: SuspendedReason = 6;
453 const SuspendedAndReadyToContinue: SuspendedReason = 7;
454 const SuspendedOnHydration: SuspendedReason = 8;
455 const SuspendedOnAction: SuspendedReason = 9;
456
457 // When this is true, the work-in-progress fiber just suspended (or errored) and
458 // we've yet to unwind the stack. In some cases, we may yield to the main thread
459 // after this happens. If the fiber is pinged before we resume, we can retry
460 // immediately instead of unwinding the stack.
461 let workInProgressSuspendedReason: SuspendedReason = NotSuspended;
462 let workInProgressThrownValue: mixed = null;
463
464 // Tracks whether any siblings were skipped during the unwind phase after
465 // something suspends. Used to determine whether to schedule another render
466 // to prewarm the skipped siblings.
467 let workInProgressRootDidSkipSuspendedSiblings: boolean = false;
468 // Whether the work-in-progress render is the result of a prewarm/prerender.
469 // This tells us whether or not we should render the siblings after
470 // something suspends.
471 let workInProgressRootIsPrerendering: boolean = false;
472
473 // Whether a ping listener was attached during this render. This is slightly
474 // different that whether something suspended, because we don't add multiple
475 // listeners to a promise we've already seen (per root and lane).
476 let workInProgressRootDidAttachPingListener: boolean = false;
477
478 // A contextual version of workInProgressRootRenderLanes. It is a superset of
479 // the lanes that we started working on at the root. When we enter a subtree
480 // that is currently hidden, we add the lanes that would have committed if
481 // the hidden tree hadn't been deferred. This is modified by the
482 // HiddenContext module.
483 //
484 // Most things in the work loop should deal with workInProgressRootRenderLanes.
485 // Most things in begin/complete phases should deal with entangledRenderLanes.
486 export let entangledRenderLanes: Lanes = NoLanes;
487
488 // Whether to root completed, errored, suspended, etc.
489 let workInProgressRootExitStatus: RootExitStatus = RootInProgress;
490 // The work left over by components that were visited during this render. Only
491 // includes unprocessed updates, not work in bailed out children.
492 let workInProgressRootSkippedLanes: Lanes = NoLanes;
493 // Lanes that were updated (in an interleaved event) during this render.
494 let workInProgressRootInterleavedUpdatedLanes: Lanes = NoLanes;
495 // Lanes that were updated during the render phase (*not* an interleaved event).
496 let workInProgressRootRenderPhaseUpdatedLanes: Lanes = NoLanes;
497 // Lanes that were pinged (in an interleaved event) during this render.
498 let workInProgressRootPingedLanes: Lanes = NoLanes;
499 // If this render scheduled deferred work, this is the lane of the deferred task.
500 let workInProgressDeferredLane: Lane = NoLane;
501 // Represents the retry lanes that were spawned by this render and have not
502 // been pinged since, implying that they are still suspended.
503 let workInProgressSuspendedRetryLanes: Lanes = NoLanes;
504 // Errors that are thrown during the render phase.
505 let workInProgressRootConcurrentErrors: Array<CapturedValue<mixed>> | null =
506 null;
507 // These are errors that we recovered from without surfacing them to the UI.
508 // We will log them once the tree commits.
509 let workInProgressRootRecoverableErrors: Array<CapturedValue<mixed>> | null =
510 null;
511
512 // Tracks when an update occurs during the render phase.
513 let workInProgressRootDidIncludeRecursiveRenderUpdate: boolean = false;
514 // Thacks when an update occurs during the commit phase. It's a separate
515 // variable from the one for renders because the commit phase may run
516 // concurrently to a render phase.
517 let didIncludeCommitPhaseUpdate: boolean = false;
518 // The most recent time we either committed a fallback, or when a fallback was
519 // filled in with the resolved UI. This lets us throttle the appearance of new
520 // content as it streams in, to minimize jank.
521 // TODO: Think of a better name for this variable?
522 let globalMostRecentFallbackTime: number = 0;
523 // Track the most recent time we started a new Transition. This lets us apply
524 // heuristics like the suspensey image timeout based on how long we've waited
525 // already.
526 let globalMostRecentTransitionTime: number = 0;
527
528 const FALLBACK_THROTTLE_MS: number = 300;
529
530 // The absolute time for when we should start giving up on rendering
531 // more and prefer CPU suspense heuristics instead.
532 let workInProgressRootRenderTargetTime: number = Infinity;
533 // How long a render is supposed to take before we start following CPU
534 // suspense heuristics and opt out of rendering more content.
535 const RENDER_TIMEOUT_MS = 500;
536
537 let workInProgressTransitions: Array<Transition> | null = null;
538 export function getWorkInProgressTransitions(): null | Array<Transition> {
539 return workInProgressTransitions;
540 }
541
542 // The first setState call that eventually caused the current render.
543 let workInProgressUpdateTask: null | ConsoleTask = null;
544
545 let currentPendingTransitionCallbacks: PendingTransitionCallbacks | null = null;
546 let currentEndTime: number | null = null;
547
548 export function addTransitionStartCallbackToPendingTransition(
549 transition: Transition,
550 ) {
551 if (enableTransitionTracing) {
552 if (currentPendingTransitionCallbacks === null) {
553 currentPendingTransitionCallbacks = {
554 transitionStart: [],
555 transitionProgress: null,
556 transitionComplete: null,
557 markerProgress: null,
558 markerIncomplete: null,
559 markerComplete: null,
560 };
561 }
562
563 if (currentPendingTransitionCallbacks.transitionStart === null) {
564 currentPendingTransitionCallbacks.transitionStart =
565 [] as Array<Transition>;
566 }
567
568 currentPendingTransitionCallbacks.transitionStart.push(transition);
569 }
570 }
571
572 export function addMarkerProgressCallbackToPendingTransition(
573 markerName: string,
574 transitions: Set<Transition>,
575 pendingBoundaries: PendingBoundaries,
576 ) {
577 if (enableTransitionTracing) {
578 if (currentPendingTransitionCallbacks === null) {
579 currentPendingTransitionCallbacks = {
580 transitionStart: null,
581 transitionProgress: null,
582 transitionComplete: null,
583 markerProgress: new Map(),
584 markerIncomplete: null,
585 markerComplete: null,
586 } as PendingTransitionCallbacks;
587 }
588
589 if (currentPendingTransitionCallbacks.markerProgress === null) {
590 currentPendingTransitionCallbacks.markerProgress = new Map();
591 }
592
593 currentPendingTransitionCallbacks.markerProgress.set(markerName, {
594 pendingBoundaries,
595 transitions,
596 });
597 }
598 }
599
600 export function addMarkerIncompleteCallbackToPendingTransition(
601 markerName: string,
602 transitions: Set<Transition>,
603 aborts: Array<TransitionAbort>,
604 ) {
605 if (enableTransitionTracing) {
606 if (currentPendingTransitionCallbacks === null) {
607 currentPendingTransitionCallbacks = {
608 transitionStart: null,
609 transitionProgress: null,
610 transitionComplete: null,
611 markerProgress: null,
612 markerIncomplete: new Map(),
613 markerComplete: null,
614 };
615 }
616
617 if (currentPendingTransitionCallbacks.markerIncomplete === null) {
618 currentPendingTransitionCallbacks.markerIncomplete = new Map();
619 }
620
621 currentPendingTransitionCallbacks.markerIncomplete.set(markerName, {
622 transitions,
623 aborts,
624 });
625 }
626 }
627
628 export function addMarkerCompleteCallbackToPendingTransition(
629 markerName: string,
630 transitions: Set<Transition>,
631 ) {
632 if (enableTransitionTracing) {
633 if (currentPendingTransitionCallbacks === null) {
634 currentPendingTransitionCallbacks = {
635 transitionStart: null,
636 transitionProgress: null,
637 transitionComplete: null,
638 markerProgress: null,
639 markerIncomplete: null,
640 markerComplete: new Map(),
641 };
642 }
643
644 if (currentPendingTransitionCallbacks.markerComplete === null) {
645 currentPendingTransitionCallbacks.markerComplete = new Map();
646 }
647
648 currentPendingTransitionCallbacks.markerComplete.set(
649 markerName,
650 transitions,
651 );
652 }
653 }
654
655 export function addTransitionProgressCallbackToPendingTransition(
656 transition: Transition,
657 boundaries: PendingBoundaries,
658 ) {
659 if (enableTransitionTracing) {
660 if (currentPendingTransitionCallbacks === null) {
661 currentPendingTransitionCallbacks = {
662 transitionStart: null,
663 transitionProgress: new Map(),
664 transitionComplete: null,
665 markerProgress: null,
666 markerIncomplete: null,
667 markerComplete: null,
668 };
669 }
670
671 if (currentPendingTransitionCallbacks.transitionProgress === null) {
672 currentPendingTransitionCallbacks.transitionProgress = new Map();
673 }
674
675 currentPendingTransitionCallbacks.transitionProgress.set(
676 transition,
677 boundaries,
678 );
679 }
680 }
681
682 export function addTransitionCompleteCallbackToPendingTransition(
683 transition: Transition,
684 ) {
685 if (enableTransitionTracing) {
686 if (currentPendingTransitionCallbacks === null) {
687 currentPendingTransitionCallbacks = {
688 transitionStart: null,
689 transitionProgress: null,
690 transitionComplete: [],
691 markerProgress: null,
692 markerIncomplete: null,
693 markerComplete: null,
694 };
695 }
696
697 if (currentPendingTransitionCallbacks.transitionComplete === null) {
698 currentPendingTransitionCallbacks.transitionComplete =
699 [] as Array<Transition>;
700 }
701
702 currentPendingTransitionCallbacks.transitionComplete.push(transition);
703 }
704 }
705
706 function resetRenderTimer() {
707 workInProgressRootRenderTargetTime = now() + RENDER_TIMEOUT_MS;
708 }
709
710 export function getRenderTargetTime(): number {
711 return workInProgressRootRenderTargetTime;
712 }
713
714 let legacyErrorBoundariesThatAlreadyFailed: Set<mixed> | null = null;
715
716 type SuspendedCommitReason = null | string;
717
718 type DelayedCommitReason = 0 | 1 | 2 | 3;
719 const IMMEDIATE_COMMIT = 0;
720 const ABORTED_VIEW_TRANSITION_COMMIT = 1;
721 const DELAYED_PASSIVE_COMMIT = 2;
722 const ANIMATION_STARTED_COMMIT = 3;
723
724 const NO_PENDING_EFFECTS = 0;
725 const PENDING_MUTATION_PHASE = 1;
726 const PENDING_LAYOUT_PHASE = 2;
727 const PENDING_AFTER_MUTATION_PHASE = 3;
728 const PENDING_SPAWNED_WORK = 4;
729 const PENDING_PASSIVE_PHASE = 5;
730 const PENDING_GESTURE_MUTATION_PHASE = 6;
731 const PENDING_GESTURE_ANIMATION_PHASE = 7;
732 let pendingEffectsStatus: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 = 0;
733 let pendingEffectsRoot: FiberRoot = null as any;
734 let pendingFinishedWork: Fiber = null as any;
735 let pendingEffectsLanes: Lanes = NoLanes;
736 let pendingEffectsRemainingLanes: Lanes = NoLanes;
737 let pendingEffectsRenderEndTime: number = -0; // Profiling-only
738 let pendingPassiveTransitions: Array<Transition> | null = null;
739 let pendingRecoverableErrors: null | Array<CapturedValue<mixed>> = null;
740 let pendingViewTransition: null | RunningViewTransition = null;
741 let pendingViewTransitionEvents: Array<
742 (types: Array<string>) => void | (() => void),
743 > | null = null;
744 let pendingTransitionTypes: null | TransitionTypes = null;
745 let pendingDidIncludeRenderPhaseUpdate: boolean = false;
746 let pendingSuspendedCommitReason: SuspendedCommitReason = null; // Profiling-only
747 let pendingDelayedCommitReason: DelayedCommitReason = IMMEDIATE_COMMIT; // Profiling-only
748 let pendingSuspendedViewTransitionReason: null | string = null; // Profiling-only
749
750 // Use these to prevent an infinite loop of nested updates
751 const NESTED_UPDATE_LIMIT = 50;
752 let nestedUpdateCount: number = 0;
753 let rootWithNestedUpdates: FiberRoot | null = null;
754 let isFlushingPassiveEffects = false;
755 let didScheduleUpdateDuringPassiveEffects = false;
756
757 const NO_NESTED_UPDATE = 0;
758 const NESTED_UPDATE_SYNC_LANE = 1;
759 const NESTED_UPDATE_PHASE_SPAWN = 2;
760 let nestedUpdateKind: 0 | 1 | 2 = NO_NESTED_UPDATE;
761
762 const NESTED_PASSIVE_UPDATE_LIMIT = 50;
763 let nestedPassiveUpdateCount: number = 0;
764 let rootWithPassiveNestedUpdates: FiberRoot | null = null;
765
766 let isRunningInsertionEffect = false;
767
768 export function getWorkInProgressRoot(): FiberRoot | null {
769 return workInProgressRoot;
770 }
771
772 export function getCommittingRoot(): FiberRoot | null {
773 return pendingEffectsRoot;
774 }
775
776 export function getWorkInProgressRootRenderLanes(): Lanes {
777 return workInProgressRootRenderLanes;
778 }
779
780 export function hasPendingCommitEffects(): boolean {
781 return (
782 pendingEffectsStatus !== NO_PENDING_EFFECTS &&
783 pendingEffectsStatus !== PENDING_PASSIVE_PHASE
784 );
785 }
786
787 export function getRootWithPendingPassiveEffects(): FiberRoot | null {
788 return pendingEffectsStatus === PENDING_PASSIVE_PHASE
789 ? pendingEffectsRoot
790 : null;
791 }
792
793 export function getPendingPassiveEffectsLanes(): Lanes {
794 return pendingEffectsLanes;
795 }
796
797 export function getPendingTransitionTypes(): null | TransitionTypes {
798 return pendingTransitionTypes;
799 }
800
801 export function isWorkLoopSuspendedOnData(): boolean {
802 return (
803 workInProgressSuspendedReason === SuspendedOnData ||
804 workInProgressSuspendedReason === SuspendedOnAction
805 );
806 }
807
808 export function getCurrentTime(): number {
809 return now();
810 }
811
812 export function requestUpdateLane(fiber: Fiber): Lane {
813 // Special cases
814 const mode = fiber.mode;
815 if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
816 return SyncLane as Lane;
817 } else if (
818 (executionContext & RenderContext) !== NoContext &&
819 workInProgressRootRenderLanes !== NoLanes
820 ) {
821 // This is a render phase update. These are not officially supported. The
822 // old behavior is to give this the same "thread" (lanes) as
823 // whatever is currently rendering. So if you call `setState` on a component
824 // that happens later in the same render, it will flush. Ideally, we want to
825 // remove the special case and treat them as if they came from an
826 // interleaved event. Regardless, this pattern is not officially supported.
827 // This behavior is only a fallback. The flag only exists until we can roll
828 // out the setState warning, since existing code might accidentally rely on
829 // the current behavior.
830 return pickArbitraryLane(workInProgressRootRenderLanes);
831 }
832
833 const transition = requestCurrentTransition();
834 if (transition !== null) {
835 if (enableGestureTransition) {
836 if (transition.gesture) {
837 throw new Error(
838 'Cannot setState on regular state inside a startGestureTransition. ' +
839 'Gestures can only update the useOptimistic() hook. There should be no ' +
840 'side-effects associated with starting a Gesture until its Action is ' +
841 'invoked. Move side-effects to the Action instead.',
842 );
843 }
844 }
845 if (__DEV__) {
846 if (!transition._updatedFibers) {
847 transition._updatedFibers = new Set();
848 }
849 transition._updatedFibers.add(fiber);
850 if (
851 hasPotentialUseWarnings() &&
852 resolveUpdatePriority() === DiscreteEventPriority
853 ) {
854 // If we're updating inside a discrete event, then this might be a new user interaction
855 // and not just an automatically resolved loading sequence. Don't warn unless it happens again.
856 clearUseWarnings();
857 }
858 }
859
860 return requestTransitionLane(transition);
861 }
862
863 const priority = resolveUpdatePriority();
864 if (__DEV__ && priority === DiscreteEventPriority) {
865 clearUseWarnings();
866 }
867 return eventPriorityToLane(priority);
868 }
869
870 function requestRetryLane(fiber: Fiber) {
871 // This is a fork of `requestUpdateLane` designed specifically for Suspense
872 // "retries" — a special update that attempts to flip a Suspense boundary
873 // from its placeholder state to its primary/resolved state.
874
875 // Special cases
876 const mode = fiber.mode;
877 if (!disableLegacyMode && (mode & ConcurrentMode) === NoMode) {
878 return SyncLane as Lane;
879 }
880
881 return claimNextRetryLane();
882 }
883
884 export function requestDeferredLane(): Lane {
885 if (workInProgressDeferredLane === NoLane) {
886 // If there are multiple useDeferredValue hooks in the same render, the
887 // tasks that they spawn should all be batched together, so they should all
888 // receive the same lane.
889
890 // Check the priority of the current render to decide the priority of the
891 // deferred task.
892
893 // OffscreenLane is used for prerendering, but we also use OffscreenLane
894 // for incremental hydration. It's given the lowest priority because the
895 // initial HTML is the same as the final UI. But useDeferredValue during
896 // hydration is an exception — we need to upgrade the UI to the final
897 // value. So if we're currently hydrating, we treat it like a transition.
898 const isPrerendering =
899 includesSomeLane(workInProgressRootRenderLanes, OffscreenLane) &&
900 !getIsHydrating();
901 if (isPrerendering) {
902 // There's only one OffscreenLane, so if it contains deferred work, we
903 // should just reschedule using the same lane.
904 workInProgressDeferredLane = OffscreenLane;
905 } else {
906 // Everything else is spawned as a transition.
907 workInProgressDeferredLane = claimNextTransitionDeferredLane();
908 }
909 }
910
911 // Mark the parent Suspense boundary so it knows to spawn the deferred lane.
912 const suspenseHandler = getSuspenseHandler();
913 if (suspenseHandler !== null) {
914 // TODO: As an optimization, we shouldn't entangle the lanes at the root; we
915 // can entangle them using the baseLanes of the Suspense boundary instead.
916 // We only need to do something special if there's no Suspense boundary.
917 suspenseHandler.flags |= DidDefer;
918 }
919
920 return workInProgressDeferredLane;
921 }
922
923 export function scheduleViewTransitionEvent(
924 fiber: Fiber,
925 callback: ?(
926 instance: ViewTransitionInstance,
927 types: Array<string>,
928 ) => void | (() => void),
929 ): void {
930 if (enableViewTransition) {
931 if (callback != null) {
932 const state: ViewTransitionState = fiber.stateNode;
933 let instance = state.ref;
934 if (instance === null) {
935 instance = state.ref = createViewTransitionInstance(
936 getViewTransitionName(fiber.memoizedProps, state),
937 );
938 }
939 if (pendingViewTransitionEvents === null) {
940 pendingViewTransitionEvents = [];
941 }
942 pendingViewTransitionEvents.push(callback.bind(null, instance));
943 }
944 }
945 }
946
947 export function scheduleGestureTransitionEvent(
948 fiber: Fiber,
949 callback: ?(
950 timeline: GestureTimeline,
951 options: GestureOptionsRequired,
952 instance: ViewTransitionInstance,
953 types: Array<string>,
954 ) => void | (() => void),
955 ): void {
956 if (enableGestureTransition) {
957 if (callback != null) {
958 const applyingGesture = pendingEffectsRoot.pendingGestures;
959 if (applyingGesture !== null) {
960 const state: ViewTransitionState = fiber.stateNode;
961 let instance = state.ref;
962 if (instance === null) {
963 instance = state.ref = createViewTransitionInstance(
964 getViewTransitionName(fiber.memoizedProps, state),
965 );
966 }
967 const timeline = applyingGesture.provider;
968 const options = {
969 rangeStart: applyingGesture.rangeStart,
970 rangeEnd: applyingGesture.rangeEnd,
971 };
972 if (pendingViewTransitionEvents === null) {
973 pendingViewTransitionEvents = [];
974 }
975 pendingViewTransitionEvents.push(
976 callback.bind(null, timeline, options, instance),
977 );
978 }
979 }
980 }
981 }
982
983 export function peekDeferredLane(): Lane {
984 return workInProgressDeferredLane;
985 }
986
987 export function scheduleUpdateOnFiber(
988 root: FiberRoot,
989 fiber: Fiber,
990 lane: Lane,
991 ) {
992 if (__DEV__) {
993 if (isRunningInsertionEffect) {
994 console.error('useInsertionEffect must not schedule updates.');
995 }
996 }
997
998 if (__DEV__) {
999 if (isFlushingPassiveEffects) {
1000 didScheduleUpdateDuringPassiveEffects = true;
1001 }
1002 }
1003
1004 // Check if the work loop is currently suspended and waiting for data to
1005 // finish loading.
1006 if (
1007 // Suspended render phase
1008 (root === workInProgressRoot &&
1009 (workInProgressSuspendedReason === SuspendedOnData ||
1010 workInProgressSuspendedReason === SuspendedOnAction)) ||
1011 // Suspended commit phase
1012 root.cancelPendingCommit !== null
1013 ) {
1014 // The incoming update might unblock the current render. Interrupt the
1015 // current attempt and restart from the top.
1016 prepareFreshStack(root, NoLanes);
1017 const didAttemptEntireTree = false;
1018 markRootSuspended(
1019 root,
1020 workInProgressRootRenderLanes,
1021 workInProgressDeferredLane,
1022 didAttemptEntireTree,
1023 );
1024 }
1025
1026 // Mark that the root has a pending update.
1027 markRootUpdated(root, lane);
1028
1029 if (
1030 (executionContext & RenderContext) !== NoContext &&
1031 root === workInProgressRoot
1032 ) {
1033 // This update was dispatched during the render phase. This is a mistake
1034 // if the update originates from user space (with the exception of local
1035 // hook updates, which are handled differently and don't reach this
1036 // function), but there are some internal React features that use this as
1037 // an implementation detail, like selective hydration.
1038 warnAboutRenderPhaseUpdatesInDEV(fiber);
1039
1040 // Track lanes that were updated during the render phase
1041 workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
1042 workInProgressRootRenderPhaseUpdatedLanes,
1043 lane,
1044 );
1045 } else {
1046 // This is a normal update, scheduled from outside the render phase. For
1047 // example, during an input event.
1048 if (enableUpdaterTracking) {
1049 if (isDevToolsPresent) {
1050 addFiberToLanesMap(root, fiber, lane);
1051 }
1052 }
1053
1054 warnIfUpdatesNotWrappedWithActDEV(fiber);
1055
1056 if (enableTransitionTracing) {
1057 const transition = ReactSharedInternals.T;
1058 if (transition !== null && transition.name != null) {
1059 if (transition.startTime === -1) {
1060 transition.startTime = now();
1061 }
1062
1063 addTransitionToLanesMap(root, transition, lane);
1064 }
1065 }
1066
1067 if (root === workInProgressRoot) {
1068 // Received an update to a tree that's in the middle of rendering. Mark
1069 // that there was an interleaved update work on this root.
1070 if ((executionContext & RenderContext) === NoContext) {
1071 workInProgressRootInterleavedUpdatedLanes = mergeLanes(
1072 workInProgressRootInterleavedUpdatedLanes,
1073 lane,
1074 );
1075 }
1076 if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
1077 // The root already suspended with a delay, which means this render
1078 // definitely won't finish. Since we have a new update, let's mark it as
1079 // suspended now, right before marking the incoming update. This has the
1080 // effect of interrupting the current render and switching to the update.
1081 // TODO: Make sure this doesn't override pings that happen while we've
1082 // already started rendering.
1083 const didAttemptEntireTree = false;
1084 markRootSuspended(
1085 root,
1086 workInProgressRootRenderLanes,
1087 workInProgressDeferredLane,
1088 didAttemptEntireTree,
1089 );
1090 }
1091 }
1092
1093 ensureRootIsScheduled(root);
1094 if (
1095 lane === SyncLane &&
1096 executionContext === NoContext &&
1097 !disableLegacyMode &&
1098 (fiber.mode & ConcurrentMode) === NoMode
1099 ) {
1100 if (__DEV__ && ReactSharedInternals.isBatchingLegacy) {
1101 // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
1102 } else {
1103 // Flush the synchronous work now, unless we're already working or inside
1104 // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
1105 // scheduleCallbackForFiber to preserve the ability to schedule a callback
1106 // without immediately flushing it. We only do this for user-initiated
1107 // updates, to preserve historical behavior of legacy mode.
1108 resetRenderTimer();
1109 flushSyncWorkOnLegacyRootsOnly();
1110 }
1111 }
1112 }
1113 }
1114
1115 export function scheduleInitialHydrationOnRoot(root: FiberRoot, lane: Lane) {
1116 // This is a special fork of scheduleUpdateOnFiber that is only used to
1117 // schedule the initial hydration of a root that has just been created. Most
1118 // of the stuff in scheduleUpdateOnFiber can be skipped.
1119 //
1120 // The main reason for this separate path, though, is to distinguish the
1121 // initial children from subsequent updates. In fully client-rendered roots
1122 // (createRoot instead of hydrateRoot), all top-level renders are modeled as
1123 // updates, but hydration roots are special because the initial render must
1124 // match what was rendered on the server.
1125 const current = root.current;
1126 current.lanes = lane;
1127 markRootUpdated(root, lane);
1128 ensureRootIsScheduled(root);
1129 }
1130
1131 export function isUnsafeClassRenderPhaseUpdate(fiber: Fiber): boolean {
1132 // Check if this is a render phase update. Only called by class components,
1133 // which special (deprecated) behavior for UNSAFE_componentWillReceive props.
1134 return (executionContext & RenderContext) !== NoContext;
1135 }
1136
1137 export function performWorkOnRoot(
1138 root: FiberRoot,
1139 lanes: Lanes,
1140 forceSync: boolean,
1141 ): void {
1142 if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
1143 throw new Error('Should not already be working.');
1144 }
1145
1146 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1147 if (workInProgressRootRenderLanes !== NoLanes && workInProgress !== null) {
1148 const yieldedFiber = workInProgress;
1149 // We've returned from yielding to the event loop. Let's log the time it took.
1150 const yieldEndTime = now();
1151 switch (yieldReason) {
1152 case SuspendedOnImmediate:
1153 case SuspendedOnData:
1154 logSuspendedYieldTime(yieldStartTime, yieldEndTime, yieldedFiber);
1155 break;
1156 case SuspendedOnAction:
1157 logActionYieldTime(yieldStartTime, yieldEndTime, yieldedFiber);
1158 break;
1159 default:
1160 logYieldTime(yieldStartTime, yieldEndTime);
1161 }
1162 }
1163 }
1164
1165 // We disable time-slicing in some cases: if the work has been CPU-bound
1166 // for too long ("expired" work, to prevent starvation), or we're in
1167 // sync-updates-by-default mode.
1168 const shouldTimeSlice =
1169 (!forceSync &&
1170 !includesBlockingLane(lanes) &&
1171 !includesExpiredLane(root, lanes)) ||
1172 // If we're prerendering, then we should use the concurrent work loop
1173 // even if the lanes are synchronous, so that prerendering never blocks
1174 // the main thread.
1175 // TODO: We should consider doing this whenever a sync lane is suspended,
1176 // even for regular pings.
1177 checkIfRootIsPrerendering(root, lanes);
1178
1179 let exitStatus: RootExitStatus = shouldTimeSlice
1180 ? renderRootConcurrent(root, lanes)
1181 : renderRootSync(root, lanes, true);
1182
1183 let renderWasConcurrent = shouldTimeSlice;
1184
1185 do {
1186 if (exitStatus === RootInProgress) {
1187 // Render phase is still in progress.
1188 if (workInProgressRootIsPrerendering && !shouldTimeSlice) {
1189 // We're in prerendering mode, but time slicing is not enabled. This
1190 // happens when something suspends during a synchronous update. Exit the
1191 // the work loop. When we resume, we'll use the concurrent work loop so
1192 // that prerendering is non-blocking.
1193 //
1194 // Mark the root as suspended. Usually we do this at the end of the
1195 // render phase, but we do it here so that we resume in
1196 // prerendering mode.
1197 // TODO: Consider always calling markRootSuspended immediately.
1198 // Needs to be *after* we attach a ping listener, though.
1199 const didAttemptEntireTree = false;
1200 markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
1201 }
1202 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1203 // We're about to yield. Let's keep track of how long we yield to the event loop.
1204 // We also stash the suspended reason at the time we yielded since it might have
1205 // changed when we resume such as when it gets pinged.
1206 startYieldTimer(workInProgressSuspendedReason);
1207 }
1208 break;
1209 } else {
1210 let renderEndTime = 0;
1211 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1212 renderEndTime = now();
1213 }
1214
1215 // The render completed.
1216
1217 // Check if this render may have yielded to a concurrent event, and if so,
1218 // confirm that any newly rendered stores are consistent.
1219 // TODO: It's possible that even a concurrent render may never have yielded
1220 // to the main thread, if it was fast enough, or if it expired. We could
1221 // skip the consistency check in that case, too.
1222 const finishedWork: Fiber = root.current.alternate as any;
1223 if (
1224 renderWasConcurrent &&
1225 !isRenderConsistentWithExternalStores(finishedWork)
1226 ) {
1227 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1228 setCurrentTrackFromLanes(lanes);
1229 logInconsistentRender(
1230 renderStartTime,
1231 renderEndTime,
1232 workInProgressUpdateTask,
1233 );
1234 finalizeRender(lanes, renderEndTime);
1235 }
1236 // A store was mutated in an interleaved event. Render again,
1237 // synchronously, to block further mutations.
1238 exitStatus = renderRootSync(root, lanes, false);
1239 // We assume the tree is now consistent because we didn't yield to any
1240 // concurrent events.
1241 renderWasConcurrent = false;
1242 // Need to check the exit status again.
1243 continue;
1244 }
1245
1246 // Check if something threw
1247 if (
1248 (disableLegacyMode || root.tag !== LegacyRoot) &&
1249 exitStatus === RootErrored
1250 ) {
1251 const lanesThatJustErrored = lanes;
1252 const errorRetryLanes = getLanesToRetrySynchronouslyOnError(
1253 root,
1254 lanesThatJustErrored,
1255 );
1256 if (errorRetryLanes !== NoLanes) {
1257 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1258 setCurrentTrackFromLanes(lanes);
1259 logErroredRenderPhase(
1260 renderStartTime,
1261 renderEndTime,
1262 lanes,
1263 workInProgressUpdateTask,
1264 );
1265 finalizeRender(lanes, renderEndTime);
1266 }
1267 lanes = errorRetryLanes;
1268 exitStatus = recoverFromConcurrentError(
1269 root,
1270 lanesThatJustErrored,
1271 errorRetryLanes,
1272 );
1273 renderWasConcurrent = false;
1274 // Need to check the exit status again.
1275 if (exitStatus !== RootErrored) {
1276 // The root did not error this time. Restart the exit algorithm
1277 // from the beginning.
1278 // TODO: Refactor the exit algorithm to be less confusing. Maybe
1279 // more branches + recursion instead of a loop. I think the only
1280 // thing that causes it to be a loop is the RootSuspendedAtTheShell
1281 // check. If that's true, then we don't need a loop/recursion
1282 // at all.
1283 continue;
1284 } else {
1285 // The root errored yet again. Proceed to commit the tree.
1286 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1287 renderEndTime = now();
1288 }
1289 }
1290 }
1291 }
1292 if (exitStatus === RootFatalErrored) {
1293 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1294 setCurrentTrackFromLanes(lanes);
1295 logErroredRenderPhase(
1296 renderStartTime,
1297 renderEndTime,
1298 lanes,
1299 workInProgressUpdateTask,
1300 );
1301 finalizeRender(lanes, renderEndTime);
1302 }
1303 prepareFreshStack(root, NoLanes);
1304 // Since this is a fatal error, we're going to pretend we attempted
1305 // the entire tree, to avoid scheduling a prerender.
1306 const didAttemptEntireTree = true;
1307 markRootSuspended(root, lanes, NoLane, didAttemptEntireTree);
1308 break;
1309 }
1310
1311 // We now have a consistent tree. The next step is either to commit it,
1312 // or, if something suspended, wait to commit it after a timeout.
1313 finishConcurrentRender(
1314 root,
1315 exitStatus,
1316 finishedWork,
1317 lanes,
1318 renderEndTime,
1319 );
1320 }
1321 break;
1322 } while (true);
1323
1324 ensureRootIsScheduled(root);
1325 }
1326
1327 function recoverFromConcurrentError(
1328 root: FiberRoot,
1329 originallyAttemptedLanes: Lanes,
1330 errorRetryLanes: Lanes,
1331 ): RootExitStatus {
1332 // If an error occurred during hydration, discard server response and fall
1333 // back to client side render.
1334
1335 // Before rendering again, save the errors from the previous attempt.
1336 const errorsFromFirstAttempt = workInProgressRootConcurrentErrors;
1337
1338 // $FlowFixMe[constant-condition]
1339 const wasRootDehydrated = supportsHydration && isRootDehydrated(root);
1340 // $FlowFixMe[constant-condition]
1341 if (wasRootDehydrated) {
1342 // The shell failed to hydrate. Set a flag to force a client rendering
1343 // during the next attempt. To do this, we call prepareFreshStack now
1344 // to create the root work-in-progress fiber. This is a bit weird in terms
1345 // of factoring, because it relies on renderRootSync not calling
1346 // prepareFreshStack again in the call below, which happens because the
1347 // root and lanes haven't changed.
1348 //
1349 // TODO: I think what we should do is set ForceClientRender inside
1350 // throwException, like we do for nested Suspense boundaries. The reason
1351 // it's here instead is so we can switch to the synchronous work loop, too.
1352 // Something to consider for a future refactor.
1353 const rootWorkInProgress = prepareFreshStack(root, errorRetryLanes);
1354 rootWorkInProgress.flags |= ForceClientRender;
1355 }
1356
1357 const exitStatus = renderRootSync(root, errorRetryLanes, false);
1358 // A status of RootSuspendedAtTheShell means the retry unwound to the root
1359 // without completing (e.g. something suspended in the shell), so the tree is
1360 // incomplete and must not be treated as recovered — committing it would
1361 // corrupt the current tree. Fall through and return the status as-is so the
1362 // root stays suspended.
1363 if (exitStatus !== RootErrored && exitStatus !== RootSuspendedAtTheShell) {
1364 // Successfully finished rendering on retry
1365
1366 if (workInProgressRootDidAttachPingListener && !wasRootDehydrated) {
1367 // During the synchronous render, we attached additional ping listeners.
1368 // This is highly suggestive of an uncached promise (though it's not the
1369 // only reason this would happen). If it was an uncached promise, then
1370 // it may have masked a downstream error from ocurring without actually
1371 // fixing it. Example:
1372 //
1373 // use(Promise.resolve('uncached'))
1374 // throw new Error('Oops!')
1375 //
1376 // When this happens, there's a conflict between blocking potential
1377 // concurrent data races and unwrapping uncached promise values. We
1378 // have to choose one or the other. Because the data race recovery is
1379 // a last ditch effort, we'll disable it.
1380 root.errorRecoveryDisabledLanes = mergeLanes(
1381 root.errorRecoveryDisabledLanes,
1382 originallyAttemptedLanes,
1383 );
1384
1385 // Mark the current render as suspended and force it to restart. Once
1386 // these lanes finish successfully, we'll re-enable the error recovery
1387 // mechanism for subsequent updates.
1388 workInProgressRootInterleavedUpdatedLanes |= originallyAttemptedLanes;
1389 return RootSuspendedWithDelay;
1390 }
1391
1392 // The errors from the failed first attempt have been recovered. Add
1393 // them to the collection of recoverable errors. We'll log them in the
1394 // commit phase.
1395 const errorsFromSecondAttempt = workInProgressRootRecoverableErrors;
1396 workInProgressRootRecoverableErrors = errorsFromFirstAttempt;
1397 // The errors from the second attempt should be queued after the errors
1398 // from the first attempt, to preserve the causal sequence.
1399 if (errorsFromSecondAttempt !== null) {
1400 queueRecoverableErrors(errorsFromSecondAttempt);
1401 }
1402 } else {
1403 // The UI failed to recover.
1404 }
1405 return exitStatus;
1406 }
1407
1408 export function queueRecoverableErrors(errors: Array<CapturedValue<mixed>>) {
1409 if (workInProgressRootRecoverableErrors === null) {
1410 workInProgressRootRecoverableErrors = errors;
1411 } else {
1412 // $FlowFixMe[method-unbinding]
1413 workInProgressRootRecoverableErrors.push.apply(
1414 workInProgressRootRecoverableErrors,
1415 errors,
1416 );
1417 }
1418 }
1419
1420 function finishConcurrentRender(
1421 root: FiberRoot,
1422 exitStatus: RootExitStatus,
1423 finishedWork: Fiber,
1424 lanes: Lanes,
1425 renderEndTime: number, // Profiling-only
1426 ) {
1427 // TODO: The fact that most of these branches are identical suggests that some
1428 // of the exit statuses are not best modeled as exit statuses and should be
1429 // tracked orthogonally.
1430 switch (exitStatus) {
1431 case RootInProgress:
1432 case RootFatalErrored: {
1433 throw new Error('Root did not complete. This is a bug in React.');
1434 }
1435 case RootSuspendedWithDelay: {
1436 if (!includesOnlyTransitions(lanes) && !includesOnlyRetries(lanes)) {
1437 // Commit the placeholder.
1438 break;
1439 }
1440 }
1441 // Fallthrough
1442 case RootSuspendedAtTheShell: {
1443 // This is a transition, so we should exit without committing a
1444 // placeholder and without scheduling a timeout. Delay indefinitely
1445 // until we receive more data.
1446 if (enableProfilerTimer && enableComponentPerformanceTrack) {
1447 setCurrentTrackFromLanes(lanes);
1448 logSuspendedRenderPhase(
1449 renderStartTime,
1450 renderEndTime,
1451 lanes,
1452 workInProgressUpdateTask,
1453 );
1454 finalizeRender(lanes, renderEndTime);
1455 trackSuspendedTime(lanes, renderEndTime);
1456 }
1457 const didAttemptEntireTree = !workInProgressRootDidSkipSuspendedSiblings;
1458 markRootSuspended(
1459 root,
1460 lanes,
1461 workInProgressDeferredLane,
1462 didAttemptEntireTree,
1463 );
1464 return;
1465 }
1466 case RootErrored: {
1467 // This render errored. Ignore any recoverable errors because we weren't actually
1468 // able to recover. Instead, whatever the final errors were is the ones we log.
1469 // This ensures that we only log the actual client side error if it's just a plain
1470 // error thrown from a component on the server and the client.
1471 workInProgressRootRecoverableErrors = null;
1472 break;
1473 }
1474 case RootSuspended:
1475 case RootCompleted: {
1476 break;
1477 }
1478 default: {
1479 throw new Error('Unknown root exit status.');
1480 }
1481 }
1482
1483 if (shouldForceFlushFallbacksInDEV()) {
1484 // We're inside an `act` scope. Commit immediately.
1485 completeRoot(
1486 root,
1487 finishedWork,
1488 lanes,
1489 workInProgressRootRecoverableErrors,
1490 workInProgressTransitions,
1491 workInProgressRootDidIncludeRecursiveRenderUpdate,
1492 workInProgressDeferredLane,
1493 workInProgressRootInterleavedUpdatedLanes,
1494 workInProgressSuspendedRetryLanes,
1495 workInProgressRootDidSkipSuspendedSiblings,
1496 exitStatus,
1497 null,
1498 null,
1499 renderStartTime,
1500 renderEndTime,
1501 );
1502 } else {
1503 if (
1504 includesOnlyRetries(lanes) &&
1505 (alwaysThrottleRetries || exitStatus === RootSuspended)
1506 ) {
1507 // This render only included retries, no updates. Throttle committing
1508 // retries so that we don't show too many loading states too quickly.
1509 const msUntilTimeout =
1510 globalMostRecentFallbackTime + FALLBACK_THROTTLE_MS - now();
1511
1512 // Don't bother with a very short suspense time.
1513 if (msUntilTimeout > 10) {
1514 const didAttemptEntireTree =
1515 !workInProgressRootDidSkipSuspendedSiblings;
1516 markRootSuspended(
1517 root,
1518 lanes,
1519 workInProgressDeferredLane,
1520 didAttemptEntireTree,
1521 );
1522
1523 const nextLanes = getNextLanes(root, NoLanes, true);
1524 if (nextLanes !== NoLanes) {
1525 // There's additional work we can do on this root. We might as well
1526 // attempt to work on that while we're suspended.
1527 return;
1528 }
1529
1530 // The render is suspended, it hasn't timed out, and there's no
1531 // lower priority work to do. Instead of committing the fallback
1532 // immediately, wait for more data to arrive.
1533 // TODO: Combine retry throttling with Suspensey commits. Right now they
1534 // run one after the other.
1535 pendingEffectsLanes = lanes;
1536 root.timeoutHandle = scheduleTimeout(
1537 completeRootWhenReady.bind(
1538 null,
1539 root,
1540 finishedWork,
1541 workInProgressRootRecoverableErrors,
1542 workInProgressTransitions,
1543 workInProgressRootDidIncludeRecursiveRenderUpdate,
1544 lanes,
1545 workInProgressDeferredLane,
1546 workInProgressRootInterleavedUpdatedLanes,
1547 workInProgressSuspendedRetryLanes,
1548 workInProgressRootDidSkipSuspendedSiblings,
1549 exitStatus,
1550 'Throttled',
1551 renderStartTime,
1552 renderEndTime,
1553 ),
1554 msUntilTimeout,
1555 );
1556 return;
1557 }
1558 }
1559 completeRootWhenReady(
1560 root,
1561 finishedWork,
1562 workInProgressRootRecoverableErrors,
1563 workInProgressTransitions,
1564 workInProgressRootDidIncludeRecursiveRenderUpdate,
1565 lanes,
1566 workInProgressDeferredLane,
1567 workInProgressRootInterleavedUpdatedLanes,
1568 workInProgressSuspendedRetryLanes,
1569 workInProgressRootDidSkipSuspendedSiblings,
1570 exitStatus,
1571 null,
1572 renderStartTime,
1573 renderEndTime,
1574 );
1575 }
1576 }
1577
1578 function completeRootWhenReady(
1579 root: FiberRoot,
1580 finishedWork: Fiber,
1581 recoverableErrors: Array<CapturedValue<mixed>> | null,
1582 transitions: Array<Transition> | null,
1583 didIncludeRenderPhaseUpdate: boolean,
1584 lanes: Lanes,
1585 spawnedLane: Lane,
1586 updatedLanes: Lanes,
1587 suspendedRetryLanes: Lanes,
1588 didSkipSuspendedSiblings: boolean,
1589 exitStatus: RootExitStatus,
1590 suspendedCommitReason: SuspendedCommitReason, // Profiling-only
1591 completedRenderStartTime: number, // Profiling-only
1592 completedRenderEndTime: number, // Profiling-only
1593 ) {
1594 root.timeoutHandle = noTimeout;
1595
1596 // TODO: Combine retry throttling with Suspensey commits. Right now they run
1597 // one after the other.
1598 const BothVisibilityAndMaySuspendCommit = Visibility | MaySuspendCommit;
1599 const subtreeFlags = finishedWork.subtreeFlags;
1600 const isViewTransitionEligible =
1601 enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes); // TODO: Use a subtreeFlag to optimize.
1602 const isGestureTransition = enableGestureTransition && isGestureRender(lanes);
1603 const maySuspendCommit =
1604 subtreeFlags & ShouldSuspendCommit ||
1605 (subtreeFlags & BothVisibilityAndMaySuspendCommit) ===
1606 BothVisibilityAndMaySuspendCommit;
1607 let suspendedState: null | SuspendedState = null;
1608 if (isViewTransitionEligible || maySuspendCommit || isGestureTransition) {
1609 // Before committing, ask the renderer whether the host tree is ready.
1610 // If it's not, we'll wait until it notifies us.
1611 suspendedState = startSuspendingCommit();
1612 // This will walk the completed fiber tree and attach listeners to all
1613 // the suspensey resources. The renderer is responsible for accumulating
1614 // all the load events. This all happens in a single synchronous
1615 // transaction, so it track state in its own module scope.
1616 // This will also track any newly added or appearing ViewTransition
1617 // components for the purposes of forming pairs.
1618 accumulateSuspenseyCommit(finishedWork, lanes, suspendedState);
1619 if (
1620 isViewTransitionEligible ||
1621 (isGestureTransition &&
1622 root.pendingGestures !== null &&
1623 // If this gesture already has a View Transition running then we don't
1624 // have to wait on that one before proceeding. We may hold the commit
1625 // on the gesture committing later on in completeRoot.
1626 root.pendingGestures.running === null)
1627 ) {
1628 // Wait for any pending View Transition (including gestures) to finish.
1629 suspendOnActiveViewTransition(suspendedState, root.containerInfo);
1630 }
1631 // For timeouts we use the previous fallback commit for retries and
1632 // the start time of the transition for transitions. This offset
1633 // represents the time already passed.
1634 const timeoutOffset = includesOnlyRetries(lanes)
1635 ? globalMostRecentFallbackTime - now()
1636 : includesOnlyTransitions(lanes)
1637 ? globalMostRecentTransitionTime - now()
1638 : 0;
1639 // At the end, ask the renderer if it's ready to commit, or if we should
1640 // suspend. If it's not ready, it will return a callback to subscribe to
1641 // a ready event.
1642 const schedulePendingCommit = waitForCommitToBeReady(
1643 suspendedState,
1644 timeoutOffset,
1645 );
1646 if (schedulePendingCommit !== null) {
1647 // NOTE: waitForCommitToBeReady returns a subscribe function so that we
1648 // only allocate a function if the commit isn't ready yet. The other
1649 // pattern would be to always pass a callback to waitForCommitToBeReady.
1650
1651 // Not yet ready to commit. Delay the commit until the renderer notifies
1652 // us that it's ready. This will be canceled if we start work on the
1653 // root again.
1654 pendingEffectsLanes = lanes;
1655 root.cancelPendingCommit = schedulePendingCommit(
1656 completeRoot.bind(
1657 null,
1658 root,
1659 finishedWork,
1660 lanes,
1661 recoverableErrors,
1662 transitions,
1663 didIncludeRenderPhaseUpdate,
1664 spawnedLane,
1665 updatedLanes,
1666 suspendedRetryLanes,
1667 didSkipSuspendedSiblings,
1668 exitStatus,
1669 suspendedState,
1670 enableProfilerTimer
1671 ? getSuspendedCommitReason(suspendedState, root.containerInfo)
1672 : null,
1673 completedRenderStartTime,
1674 completedRenderEndTime,
1675 ),
1676 );
1677 const didAttemptEntireTree = !didSkipSuspendedSiblings;
1678 markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
1679 return;
1680 }
1681 }
1682
1683 // Otherwise, commit immediately.;
1684 completeRoot(
1685 root,
1686 finishedWork,
1687 lanes,
1688 recoverableErrors,
1689 transitions,
1690 didIncludeRenderPhaseUpdate,
1691 spawnedLane,
1692 updatedLanes,
1693 suspendedRetryLanes,
1694 didSkipSuspendedSiblings,
1695 exitStatus,
1696 suspendedState,
1697 suspendedCommitReason,
1698 completedRenderStartTime,
1699 completedRenderEndTime,
1700 );
1701 }
1702
1703 function isRenderConsistentWithExternalStores(finishedWork: Fiber): boolean {
1704 // Search the rendered tree for external store reads, and check whether the
1705 // stores were mutated in a concurrent event. Intentionally using an iterative
1706 // loop instead of recursion so we can exit early.
1707 let node: Fiber = finishedWork;
1708 while (true) {
1709 const tag = node.tag;
1710 if (
1711 (tag === FunctionComponent ||
1712 tag === ForwardRef ||
1713 tag === SimpleMemoComponent) &&
1714 node.flags & StoreConsistency
1715 ) {
1716 const updateQueue: FunctionComponentUpdateQueue | null =
1717 node.updateQueue as any;
1718 if (updateQueue !== null) {
1719 const checks = updateQueue.stores;
1720 if (checks !== null) {
1721 for (let i = 0; i < checks.length; i++) {
1722 const check = checks[i];
1723 const getSnapshot = check.getSnapshot;
1724 const renderedValue = check.value;
1725 try {
1726 if (!is(getSnapshot(), renderedValue)) {
1727 // Found an inconsistent store.
1728 return false;
1729 }
1730 } catch (error) {
1731 // If `getSnapshot` throws, return `false`. This will schedule
1732 // a re-render, and the error will be rethrown during render.
1733 return false;
1734 }
1735 }
1736 }
1737 }
1738 }
1739 const child = node.child;
1740 if (node.subtreeFlags & StoreConsistency && child !== null) {
1741 child.return = node;
1742 node = child;
1743 continue;
1744 }
1745 if (node === finishedWork) {
1746 return true;
1747 }
1748 while (node.sibling === null) {
1749 if (node.return === null || node.return === finishedWork) {
1750 return true;
1751 }
1752 node = node.return;
1753 }
1754 node.sibling.return = node.return;
1755 node = node.sibling;
1756 }
1757 // Flow doesn't know this is unreachable, but eslint does
1758 // eslint-disable-next-line no-unreachable
1759 return true;
1760 }
1761
1762 // The extra indirections around markRootUpdated and markRootSuspended is
1763 // needed to avoid a circular dependency between this module and
1764 // ReactFiberLane. There's probably a better way to split up these modules and
1765 // avoid this problem. Perhaps all the root-marking functions should move into
1766 // the work loop.
1767
1768 function markRootUpdated(root: FiberRoot, updatedLanes: Lanes) {
1769 _markRootUpdated(root, updatedLanes);
1770
1771 if (enableInfiniteRenderLoopDetection) {
1772 // Check for recursive updates
1773 if (executionContext & RenderContext) {
1774 workInProgressRootDidIncludeRecursiveRenderUpdate = true;
1775 } else if (executionContext & CommitContext) {
1776 didIncludeCommitPhaseUpdate = true;
1777 }
1778
1779 throwIfInfiniteUpdateLoopDetected(true);
1780 }
1781 }
1782
1783 function markRootPinged(root: FiberRoot, pingedLanes: Lanes) {
1784 _markRootPinged(root, pingedLanes);
1785
1786 if (enableInfiniteRenderLoopDetection) {
1787 // Check for recursive pings. Pings are conceptually different from updates in
1788 // other contexts but we call it an "update" in this context because
1789 // repeatedly pinging a suspended render can cause a recursive render loop.
1790 // The relevant property is that it can result in a new render attempt
1791 // being scheduled.
1792 if (executionContext & RenderContext) {
1793 workInProgressRootDidIncludeRecursiveRenderUpdate = true;
1794 } else if (executionContext & CommitContext) {
1795 didIncludeCommitPhaseUpdate = true;
1796 }
1797
1798 throwIfInfiniteUpdateLoopDetected(true);
1799 }
1800 }
1801
1802 function markRootSuspended(
1803 root: FiberRoot,
1804 suspendedLanes: Lanes,
1805 spawnedLane: Lane,
1806 didAttemptEntireTree: boolean,
1807 ) {
1808 if (enableParallelTransitions) {
1809 // When suspending, we should always mark the entangled lanes as suspended.
1810 suspendedLanes = getEntangledLanes(root, suspendedLanes);
1811 }
1812
1813 // When suspending, we should always exclude lanes that were pinged or (more
1814 // rarely, since we try to avoid it) updated during the render phase.
1815 suspendedLanes = removeLanes(suspendedLanes, workInProgressRootPingedLanes);
1816 suspendedLanes = removeLanes(
1817 suspendedLanes,
1818 workInProgressRootInterleavedUpdatedLanes,
1819 );
1820 _markRootSuspended(root, suspendedLanes, spawnedLane, didAttemptEntireTree);
1821 }
1822
1823 export function flushRoot(root: FiberRoot, lanes: Lanes) {
1824 if (lanes !== NoLanes) {
1825 upgradePendingLanesToSync(root, lanes);
1826 ensureRootIsScheduled(root);
1827 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1828 resetRenderTimer();
1829 // TODO: For historical reasons this flushes all sync work across all
1830 // roots. It shouldn't really matter either way, but we could change this
1831 // to only flush the given root.
1832 flushSyncWorkOnAllRoots();
1833 }
1834 }
1835 }
1836
1837 export function getExecutionContext(): ExecutionContext {
1838 return executionContext;
1839 }
1840
1841 export function deferredUpdates<A>(fn: () => A): A {
1842 const prevTransition = ReactSharedInternals.T;
1843 const previousPriority = getCurrentUpdatePriority();
1844 try {
1845 setCurrentUpdatePriority(DefaultEventPriority);
1846 ReactSharedInternals.T = null;
1847 return fn();
1848 } finally {
1849 setCurrentUpdatePriority(previousPriority);
1850 ReactSharedInternals.T = prevTransition;
1851 }
1852 }
1853
1854 export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1855 if (disableLegacyMode) {
1856 // batchedUpdates is a no-op now, but there's still some internal react-dom
1857 // code calling it, that we can't remove until we remove legacy mode.
1858 return fn(a);
1859 } else {
1860 const prevExecutionContext = executionContext;
1861 executionContext |= BatchedContext;
1862 try {
1863 return fn(a);
1864 } finally {
1865 executionContext = prevExecutionContext;
1866 // If there were legacy sync updates, flush them at the end of the outer
1867 // most batchedUpdates-like method.
1868 if (
1869 executionContext === NoContext &&
1870 // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
1871 !(__DEV__ && ReactSharedInternals.isBatchingLegacy)
1872 ) {
1873 resetRenderTimer();
1874 flushSyncWorkOnLegacyRootsOnly();
1875 }
1876 }
1877 }
1878 }
1879
1880 export function discreteUpdates<A, B, C, D, R>(
1881 fn: (A, B, C, D) => R,
1882 a: A,
1883 b: B,
1884 c: C,
1885 d: D,
1886 ): R {
1887 const prevTransition = ReactSharedInternals.T;
1888 const previousPriority = getCurrentUpdatePriority();
1889 try {
1890 setCurrentUpdatePriority(DiscreteEventPriority);
1891 ReactSharedInternals.T = null;
1892 return fn(a, b, c, d);
1893 } finally {
1894 setCurrentUpdatePriority(previousPriority);
1895 ReactSharedInternals.T = prevTransition;
1896 if (executionContext === NoContext) {
1897 resetRenderTimer();
1898 }
1899 }
1900 }
1901
1902 // Overload the definition to the two valid signatures.
1903 // Warning, this opts-out of checking the function body.
1904 declare function flushSyncFromReconciler<R>(fn: () => R): R;
1905 declare function flushSyncFromReconciler(void): void;
1906 export function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
1907 // In legacy mode, we flush pending passive effects at the beginning of the
1908 // next event, not at the end of the previous one.
1909 if (
1910 pendingEffectsStatus !== NO_PENDING_EFFECTS &&
1911 !disableLegacyMode &&
1912 pendingEffectsRoot.tag === LegacyRoot &&
1913 (executionContext & (RenderContext | CommitContext)) === NoContext
1914 ) {
1915 flushPendingEffects();
1916 }
1917
1918 const prevExecutionContext = executionContext;
1919 executionContext |= BatchedContext;
1920
1921 const prevTransition = ReactSharedInternals.T;
1922 const previousPriority = getCurrentUpdatePriority();
1923
1924 try {
1925 setCurrentUpdatePriority(DiscreteEventPriority);
1926 ReactSharedInternals.T = null;
1927 if (fn) {
1928 return fn();
1929 } else {
1930 return undefined;
1931 }
1932 } finally {
1933 setCurrentUpdatePriority(previousPriority);
1934 ReactSharedInternals.T = prevTransition;
1935
1936 executionContext = prevExecutionContext;
1937 // Flush the immediate callbacks that were scheduled during this batch.
1938 // Note that this will happen even if batchedUpdates is higher up
1939 // the stack.
1940 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1941 flushSyncWorkOnAllRoots();
1942 }
1943 }
1944 }
1945
1946 // If called outside of a render or commit will flush all sync work on all roots
1947 // Returns whether the the call was during a render or not
1948 export function flushSyncWork(): boolean {
1949 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1950 flushSyncWorkOnAllRoots();
1951 return false;
1952 }
1953 return true;
1954 }
1955
1956 export function isAlreadyRendering(): boolean {
1957 // Used by the renderer to print a warning if certain APIs are called from
1958 // the wrong context, and for profiling warnings.
1959 return (executionContext & (RenderContext | CommitContext)) !== NoContext;
1960 }
1961
1962 export function isInvalidExecutionContextForEventFunction(): boolean {
1963 // Used to throw if certain APIs are called from the wrong context.
1964 return (executionContext & RenderContext) !== NoContext;
1965 }
1966
1967 // This is called by the HiddenContext module when we enter or leave a
1968 // hidden subtree. The stack logic is managed there because that's the only
1969 // place that ever modifies it. Which module it lives in doesn't matter for
1970 // performance because this function will get inlined regardless
1971 export function setEntangledRenderLanes(newEntangledRenderLanes: Lanes) {
1972 entangledRenderLanes = newEntangledRenderLanes;
1973 }
1974
1975 export function getEntangledRenderLanes(): Lanes {
1976 return entangledRenderLanes;
1977 }
1978
1979 function resetWorkInProgressStack() {
1980 if (workInProgress === null) return;
1981 let interruptedWork;
1982 if (workInProgressSuspendedReason === NotSuspended) {
1983 // Normal case. Work-in-progress hasn't started yet. Unwind all
1984 // its parents.
1985 interruptedWork = workInProgress.return;
1986 } else {
1987 // Work-in-progress is in suspended state. Reset the work loop and unwind
1988 // both the suspended fiber and all its parents.
1989 resetSuspendedWorkLoopOnUnwind(workInProgress);
1990 interruptedWork = workInProgress;
1991 }
1992 while (interruptedWork !== null) {
1993 const current = interruptedWork.alternate;
1994 unwindInterruptedWork(
1995 current,
1996 interruptedWork,
1997 workInProgressRootRenderLanes,
1998 );
1999 interruptedWork = interruptedWork.return;
2000 }
2001 workInProgress = null;
2002 }
2003
2004 function finalizeRender(lanes: Lanes, finalizationTime: number): void {
2005 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2006 if (isGestureRender(lanes)) {
2007 clampGestureTimers(finalizationTime);
2008 } else if (includesBlockingLane(lanes)) {
2009 clampBlockingTimers(finalizationTime);
2010 }
2011 if (includesTransitionLane(lanes)) {
2012 clampTransitionTimers(finalizationTime);
2013 }
2014 if (includesRetryLane(lanes)) {
2015 clampRetryTimers(finalizationTime);
2016 }
2017 if (includesIdleGroupLanes(lanes)) {
2018 clampIdleTimers(finalizationTime);
2019 }
2020 }
2021 }
2022
2023 function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
2024 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2025 // The order of tracks within a group are determined by the earliest start time.
2026 // Are tracks should show up in priority order and we should ideally always show
2027 // every track. This is a hack to ensure that we're displaying all tracks in the
2028 // right order. Ideally we could do this only once but because calls that aren't
2029 // recorded aren't considered for ordering purposes, we need to keep adding these
2030 // over and over again in case recording has just started. We can't tell when
2031 // recording starts.
2032 markAllLanesInOrder();
2033
2034 const previousRenderStartTime = renderStartTime;
2035 // Starting a new render. Log the end of any previous renders and the
2036 // blocked time before the render started.
2037 recordRenderTime();
2038 // If this was a restart, e.g. due to an interrupting update, then there's no space
2039 // in the track to log the cause since we'll have rendered all the way up until the
2040 // restart so we need to clamp that.
2041 if (
2042 workInProgressRootRenderLanes !== NoLanes &&
2043 previousRenderStartTime > 0
2044 ) {
2045 setCurrentTrackFromLanes(workInProgressRootRenderLanes);
2046 if (
2047 workInProgressRootExitStatus === RootSuspended ||
2048 workInProgressRootExitStatus === RootSuspendedWithDelay
2049 ) {
2050 // If the root was already suspended when it got interrupted and restarted,
2051 // then this is considered a prewarm and not an interrupted render because
2052 // we couldn't have shown anything anyway so it's not a bad thing that we
2053 // got interrupted.
2054 logSuspendedRenderPhase(
2055 previousRenderStartTime,
2056 renderStartTime,
2057 lanes,
2058 workInProgressUpdateTask,
2059 );
2060 } else {
2061 logInterruptedRenderPhase(
2062 previousRenderStartTime,
2063 renderStartTime,
2064 lanes,
2065 workInProgressUpdateTask,
2066 );
2067 }
2068 finalizeRender(workInProgressRootRenderLanes, renderStartTime);
2069 }
2070 const previousUpdateTask = workInProgressUpdateTask;
2071
2072 workInProgressUpdateTask = null;
2073 if (isGestureRender(lanes)) {
2074 workInProgressUpdateTask = gestureUpdateTask;
2075 const clampedUpdateTime =
2076 gestureUpdateTime >= 0 && gestureUpdateTime < gestureClampTime
2077 ? gestureClampTime
2078 : gestureUpdateTime;
2079 const clampedEventTime =
2080 gestureEventTime >= 0 && gestureEventTime < gestureClampTime
2081 ? gestureClampTime
2082 : gestureEventTime;
2083 const clampedRenderStartTime =
2084 // Clamp the suspended time to the first event/update.
2085 clampedEventTime >= 0
2086 ? clampedEventTime
2087 : clampedUpdateTime >= 0
2088 ? clampedUpdateTime
2089 : renderStartTime;
2090 if (gestureSuspendedTime >= 0) {
2091 setCurrentTrackFromLanes(GestureLane);
2092 logSuspendedWithDelayPhase(
2093 gestureSuspendedTime,
2094 clampedRenderStartTime,
2095 lanes,
2096 workInProgressUpdateTask,
2097 );
2098 } else if (isGestureRender(animatingLanes)) {
2099 // If this lane is still animating, log the time from previous render finishing to now as animating.
2100 setCurrentTrackFromLanes(GestureLane);
2101 logAnimatingPhase(
2102 gestureClampTime,
2103 clampedRenderStartTime,
2104 animatingTask,
2105 );
2106 }
2107 logGestureStart(
2108 clampedUpdateTime,
2109 clampedEventTime,
2110 gestureEventType,
2111 gestureEventRepeatTime > 0,
2112 gestureUpdateType === PINGED_UPDATE,
2113 renderStartTime,
2114 gestureUpdateTask,
2115 gestureUpdateMethodName,
2116 gestureUpdateComponentName,
2117 );
2118 clearGestureTimers();
2119 } else if (includesBlockingLane(lanes)) {
2120 workInProgressUpdateTask = blockingUpdateTask;
2121 const clampedUpdateTime =
2122 blockingUpdateTime >= 0 && blockingUpdateTime < blockingClampTime
2123 ? blockingClampTime
2124 : blockingUpdateTime;
2125 const clampedEventTime =
2126 blockingEventTime >= 0 && blockingEventTime < blockingClampTime
2127 ? blockingClampTime
2128 : blockingEventTime;
2129 const clampedRenderStartTime = // Clamp the suspended time to the first event/update.
2130 clampedEventTime >= 0
2131 ? clampedEventTime
2132 : clampedUpdateTime >= 0
2133 ? clampedUpdateTime
2134 : renderStartTime;
2135 if (blockingSuspendedTime >= 0) {
2136 setCurrentTrackFromLanes(SyncLane);
2137 logSuspendedWithDelayPhase(
2138 blockingSuspendedTime,
2139 clampedRenderStartTime,
2140 lanes,
2141 previousUpdateTask,
2142 );
2143 } else if (
2144 !isGestureRender(animatingLanes) &&
2145 includesBlockingLane(animatingLanes)
2146 ) {
2147 // If this lane is still animating, log the time from previous render finishing to now as animating.
2148 setCurrentTrackFromLanes(SyncLane);
2149 logAnimatingPhase(
2150 blockingClampTime,
2151 clampedRenderStartTime,
2152 animatingTask,
2153 );
2154 }
2155 logBlockingStart(
2156 clampedUpdateTime,
2157 clampedEventTime,
2158 blockingEventType,
2159 blockingEventRepeatTime > 0,
2160 blockingUpdateType === SPAWNED_UPDATE,
2161 blockingUpdateType === PINGED_UPDATE,
2162 renderStartTime,
2163 lanes,
2164 blockingUpdateTask,
2165 blockingUpdateMethodName,
2166 blockingUpdateComponentName,
2167 );
2168 clearBlockingTimers();
2169 }
2170 if (includesTransitionLane(lanes)) {
2171 workInProgressUpdateTask = transitionUpdateTask;
2172 const clampedStartTime =
2173 transitionStartTime >= 0 && transitionStartTime < transitionClampTime
2174 ? transitionClampTime
2175 : transitionStartTime;
2176 const clampedUpdateTime =
2177 transitionUpdateTime >= 0 && transitionUpdateTime < transitionClampTime
2178 ? transitionClampTime
2179 : transitionUpdateTime;
2180 const clampedEventTime =
2181 transitionEventTime >= 0 && transitionEventTime < transitionClampTime
2182 ? transitionClampTime
2183 : transitionEventTime;
2184 const clampedRenderStartTime =
2185 // Clamp the suspended time to the first event/update.
2186 clampedEventTime >= 0
2187 ? clampedEventTime
2188 : clampedUpdateTime >= 0
2189 ? clampedUpdateTime
2190 : renderStartTime;
2191 if (transitionSuspendedTime >= 0) {
2192 setCurrentTrackFromLanes(SomeTransitionLane);
2193 logSuspendedWithDelayPhase(
2194 transitionSuspendedTime,
2195 clampedRenderStartTime,
2196 lanes,
2197 workInProgressUpdateTask,
2198 );
2199 } else if (includesTransitionLane(animatingLanes)) {
2200 // If this lane is still animating, log the time from previous render finishing to now as animating.
2201 setCurrentTrackFromLanes(SomeTransitionLane);
2202 logAnimatingPhase(
2203 transitionClampTime,
2204 clampedRenderStartTime,
2205 animatingTask,
2206 );
2207 }
2208 logTransitionStart(
2209 clampedStartTime,
2210 clampedUpdateTime,
2211 clampedEventTime,
2212 transitionEventType,
2213 transitionEventRepeatTime > 0,
2214 transitionUpdateType === PINGED_UPDATE,
2215 renderStartTime,
2216 transitionUpdateTask,
2217 transitionUpdateMethodName,
2218 transitionUpdateComponentName,
2219 );
2220 clearTransitionTimers();
2221 }
2222 if (includesRetryLane(lanes)) {
2223 if (includesRetryLane(animatingLanes)) {
2224 // If this lane is still animating, log the time from previous render finishing to now as animating.
2225 setCurrentTrackFromLanes(SomeRetryLane);
2226 logAnimatingPhase(retryClampTime, renderStartTime, animatingTask);
2227 }
2228 }
2229 if (includesIdleGroupLanes(lanes)) {
2230 if (includesIdleGroupLanes(animatingLanes)) {
2231 // If this lane is still animating, log the time from previous render finishing to now as animating.
2232 setCurrentTrackFromLanes(IdleLane);
2233 logAnimatingPhase(idleClampTime, renderStartTime, animatingTask);
2234 }
2235 }
2236 }
2237
2238 const timeoutHandle = root.timeoutHandle;
2239 if (timeoutHandle !== noTimeout) {
2240 // The root previous suspended and scheduled a timeout to commit a fallback
2241 // state. Now that we have additional work, cancel the timeout.
2242 root.timeoutHandle = noTimeout;
2243 // $FlowFixMe[incompatible-call] Complains noTimeout is not a TimeoutID, despite the check above
2244 cancelTimeout(timeoutHandle);
2245 }
2246 const cancelPendingCommit = root.cancelPendingCommit;
2247 if (cancelPendingCommit !== null) {
2248 root.cancelPendingCommit = null;
2249 cancelPendingCommit();
2250 }
2251
2252 pendingEffectsLanes = NoLanes;
2253
2254 resetWorkInProgressStack();
2255 workInProgressRoot = root;
2256 const rootWorkInProgress = createWorkInProgress(root.current, null);
2257 workInProgress = rootWorkInProgress;
2258 workInProgressRootRenderLanes = lanes;
2259 workInProgressSuspendedReason = NotSuspended;
2260 workInProgressThrownValue = null;
2261 workInProgressRootDidSkipSuspendedSiblings = false;
2262 workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
2263 workInProgressRootDidAttachPingListener = false;
2264 workInProgressRootExitStatus = RootInProgress;
2265 workInProgressRootSkippedLanes = NoLanes;
2266 workInProgressRootInterleavedUpdatedLanes = NoLanes;
2267 workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
2268 workInProgressRootPingedLanes = NoLanes;
2269 workInProgressDeferredLane = NoLane;
2270 workInProgressSuspendedRetryLanes = NoLanes;
2271 workInProgressRootConcurrentErrors = null;
2272 workInProgressRootRecoverableErrors = null;
2273 workInProgressRootDidIncludeRecursiveRenderUpdate = false;
2274
2275 // Get the lanes that are entangled with whatever we're about to render. We
2276 // track these separately so we can distinguish the priority of the render
2277 // task from the priority of the lanes it is entangled with. For example, a
2278 // transition may not be allowed to finish unless it includes the Sync lane,
2279 // which is currently suspended. We should be able to render the Transition
2280 // and Sync lane in the same batch, but at Transition priority, because the
2281 // Sync lane already suspended.
2282 entangledRenderLanes = getEntangledLanes(root, lanes);
2283
2284 finishQueueingConcurrentUpdates();
2285
2286 if (__DEV__) {
2287 resetOwnerStackLimit();
2288
2289 ReactStrictModeWarnings.discardPendingWarnings();
2290 }
2291
2292 return rootWorkInProgress;
2293 }
2294
2295 function resetSuspendedWorkLoopOnUnwind(fiber: Fiber) {
2296 // Reset module-level state that was set during the render phase.
2297 resetContextDependencies();
2298 resetHooksOnUnwind(fiber);
2299 resetChildReconcilerOnUnwind();
2300 }
2301
2302 function handleThrow(root: FiberRoot, thrownValue: any): void {
2303 // A component threw an exception. Usually this is because it suspended, but
2304 // it also includes regular program errors.
2305 //
2306 // We're either going to unwind the stack to show a Suspense or error
2307 // boundary, or we're going to replay the component again. Like after a
2308 // promise resolves.
2309 //
2310 // Until we decide whether we're going to unwind or replay, we should preserve
2311 // the current state of the work loop without resetting anything.
2312 //
2313 // If we do decide to unwind the stack, module-level variables will be reset
2314 // in resetSuspendedWorkLoopOnUnwind.
2315
2316 // These should be reset immediately because they're only supposed to be set
2317 // when React is executing user code.
2318 resetHooksAfterThrow();
2319 if (__DEV__) {
2320 resetCurrentFiber();
2321 }
2322
2323 if (
2324 thrownValue === SuspenseException ||
2325 thrownValue === SuspenseActionException
2326 ) {
2327 // This is a special type of exception used for Suspense. For historical
2328 // reasons, the rest of the Suspense implementation expects the thrown value
2329 // to be a thenable, because before `use` existed that was the (unstable)
2330 // API for suspending. This implementation detail can change later, once we
2331 // deprecate the old API in favor of `use`.
2332 thrownValue = getSuspendedThenable();
2333 // TODO: Suspending the work loop during the render phase is
2334 // currently not compatible with sibling prerendering. We will add
2335 // this optimization back in a later step.
2336 // Don't suspend work loop, except to check if the data has
2337 // immediately resolved (i.e. in a microtask). Otherwise, trigger the
2338 // nearest Suspense fallback.
2339 workInProgressSuspendedReason = SuspendedOnImmediate;
2340 } else if (thrownValue === SuspenseyCommitException) {
2341 thrownValue = getSuspendedThenable();
2342 workInProgressSuspendedReason = SuspendedOnInstance;
2343 } else if (thrownValue === SelectiveHydrationException) {
2344 // An update flowed into a dehydrated boundary. Before we can apply the
2345 // update, we need to finish hydrating. Interrupt the work-in-progress
2346 // render so we can restart at the hydration lane.
2347 //
2348 // The ideal implementation would be able to switch contexts without
2349 // unwinding the current stack.
2350 //
2351 // We could name this something more general but as of now it's the only
2352 // case where we think this should happen.
2353 workInProgressSuspendedReason = SuspendedOnHydration;
2354 } else {
2355 // This is a regular error.
2356 const isWakeable =
2357 thrownValue !== null &&
2358 typeof thrownValue === 'object' &&
2359 typeof thrownValue.then === 'function';
2360
2361 workInProgressSuspendedReason = isWakeable
2362 ? // A wakeable object was thrown by a legacy Suspense implementation.
2363 // This has slightly different behavior than suspending with `use`.
2364 SuspendedOnDeprecatedThrowPromise
2365 : // This is a regular error. If something earlier in the component already
2366 // suspended, we must clear the thenable state to unblock the work loop.
2367 SuspendedOnError;
2368 }
2369
2370 workInProgressThrownValue = thrownValue;
2371
2372 const erroredWork = workInProgress;
2373 if (erroredWork === null) {
2374 // This is a fatal error
2375 workInProgressRootExitStatus = RootFatalErrored;
2376 logUncaughtError(
2377 root,
2378 createCapturedValueAtFiber(thrownValue, root.current),
2379 );
2380 return;
2381 }
2382
2383 if (enableProfilerTimer && erroredWork.mode & ProfileMode) {
2384 // Record the time spent rendering before an error was thrown. This
2385 // avoids inaccurate Profiler durations in the case of a
2386 // suspended render.
2387 stopProfilerTimerIfRunningAndRecordDuration(erroredWork);
2388 }
2389
2390 if (enableSchedulingProfiler) {
2391 markComponentRenderStopped();
2392 switch (workInProgressSuspendedReason) {
2393 case SuspendedOnError: {
2394 markComponentErrored(
2395 erroredWork,
2396 thrownValue,
2397 workInProgressRootRenderLanes,
2398 );
2399 break;
2400 }
2401 case SuspendedOnData:
2402 case SuspendedOnAction:
2403 case SuspendedOnImmediate:
2404 case SuspendedOnDeprecatedThrowPromise:
2405 case SuspendedAndReadyToContinue: {
2406 const wakeable: Wakeable = thrownValue as any;
2407 markComponentSuspended(
2408 erroredWork,
2409 wakeable,
2410 workInProgressRootRenderLanes,
2411 );
2412 break;
2413 }
2414 case SuspendedOnInstance: {
2415 // This is conceptually like a suspend, but it's not associated with
2416 // a particular wakeable. It's associated with a host resource (e.g.
2417 // a CSS file or an image) that hasn't loaded yet. DevTools doesn't
2418 // handle this currently.
2419 break;
2420 }
2421 case SuspendedOnHydration: {
2422 // This is conceptually like a suspend, but it's not associated with
2423 // a particular wakeable. DevTools doesn't seem to care about this case,
2424 // currently. It's similar to if the component were interrupted, which
2425 // we don't mark with a special function.
2426 break;
2427 }
2428 }
2429 }
2430 }
2431
2432 export function shouldRemainOnPreviousScreen(): boolean {
2433 // This is asking whether it's better to suspend the transition and remain
2434 // on the previous screen, versus showing a fallback as soon as possible. It
2435 // takes into account both the priority of render and also whether showing a
2436 // fallback would produce a desirable user experience.
2437
2438 const handler = getSuspenseHandler();
2439 if (handler === null) {
2440 // There's no Suspense boundary that can provide a fallback. We have no
2441 // choice but to remain on the previous screen.
2442 // NOTE: We do this even for sync updates, for lack of any better option. In
2443 // the future, we may change how we handle this, like by putting the whole
2444 // root into a "detached" mode.
2445 return true;
2446 }
2447
2448 // TODO: Once `use` has fully replaced the `throw promise` pattern, we should
2449 // be able to remove the equivalent check in finishConcurrentRender, and rely
2450 // just on this one.
2451 if (includesOnlyTransitions(workInProgressRootRenderLanes)) {
2452 if (getShellBoundary() === null) {
2453 // We're rendering inside the "shell" of the app. Activating the nearest
2454 // fallback would cause visible content to disappear. It's better to
2455 // suspend the transition and remain on the previous screen.
2456 return true;
2457 } else {
2458 // We're rendering content that wasn't part of the previous screen.
2459 // Rather than block the transition, it's better to show a fallback as
2460 // soon as possible. The appearance of any nested fallbacks will be
2461 // throttled to avoid jank.
2462 return false;
2463 }
2464 }
2465
2466 if (
2467 includesOnlyRetries(workInProgressRootRenderLanes) ||
2468 // In this context, an OffscreenLane counts as a Retry
2469 // TODO: It's become increasingly clear that Retries and Offscreen are
2470 // deeply connected. They probably can be unified further.
2471 includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
2472 ) {
2473 // During a retry, we can suspend rendering if the nearest Suspense boundary
2474 // is the boundary of the "shell", because we're guaranteed not to block
2475 // any new content from appearing.
2476 //
2477 // The reason we must check if this is a retry is because it guarantees
2478 // that suspending the work loop won't block an actual update, because
2479 // retries don't "update" anything; they fill in fallbacks that were left
2480 // behind by a previous transition.
2481 return handler === getShellBoundary();
2482 }
2483
2484 // For all other Lanes besides Transitions and Retries, we should not wait
2485 // for the data to load.
2486 return false;
2487 }
2488
2489 function pushDispatcher(container: any) {
2490 const prevDispatcher = ReactSharedInternals.H;
2491 ReactSharedInternals.H = ContextOnlyDispatcher;
2492 if (prevDispatcher === null) {
2493 // The React isomorphic package does not include a default dispatcher.
2494 // Instead the first renderer will lazily attach one, in order to give
2495 // nicer error messages.
2496 return ContextOnlyDispatcher;
2497 } else {
2498 return prevDispatcher;
2499 }
2500 }
2501
2502 function popDispatcher(prevDispatcher: any) {
2503 ReactSharedInternals.H = prevDispatcher;
2504 }
2505
2506 function pushAsyncDispatcher() {
2507 const prevAsyncDispatcher = ReactSharedInternals.A;
2508 ReactSharedInternals.A = DefaultAsyncDispatcher;
2509 return prevAsyncDispatcher;
2510 }
2511
2512 function popAsyncDispatcher(prevAsyncDispatcher: any) {
2513 ReactSharedInternals.A = prevAsyncDispatcher;
2514 }
2515
2516 export function markRenderDerivedCause(fiber: Fiber): void {
2517 if (enableProfilerTimer && enableComponentPerformanceTrack) {
2518 if (__DEV__) {
2519 if (workInProgressUpdateTask === null) {
2520 // If we don't have a cause associated with this render, it's likely because some
2521 // other render left work behind on this Fiber. The real cause is this Fiber itself.
2522 // We use its debugTask as the cause for this render. This might not be the only
2523 // one when multiple siblings are rendered but they ideally shouldn't be.
2524 workInProgressUpdateTask =
2525 fiber._debugTask == null ? null : fiber._debugTask;
2526 }
2527 }
2528 }
2529 }
2530
2531 export function markTransitionStarted() {
2532 globalMostRecentTransitionTime = now();
2533 }
2534
2535 export function markCommitTimeOfFallback() {
2536 globalMostRecentFallbackTime = now();
2537 }
2538
2539 export function markSkippedUpdateLanes(lane: Lane | Lanes): void {
2540 workInProgressRootSkippedLanes = mergeLanes(
2541 lane,
2542 workInProgressRootSkippedLanes,
2543 );
2544 }
2545
2546 export function renderDidSuspend(): void {
2547 if (workInProgressRootExitStatus === RootInProgress) {
2548 workInProgressRootExitStatus = RootSuspended;
2549 }
2550 }
2551
2552 export function renderDidSuspendDelayIfPossible(): void {
2553 workInProgressRootExitStatus = RootSuspendedWithDelay;
2554
2555 if (
2556 !workInProgressRootDidSkipSuspendedSiblings &&
2557 // Check if the root will be blocked from committing.
2558 // TODO: Consider aligning this better with the rest of the logic. Maybe
2559 // we should only set the exit status to RootSuspendedWithDelay if this
2560 // condition is true? And remove the equivalent checks elsewhere.
2561 (includesOnlyTransitions(workInProgressRootRenderLanes) ||
2562 getSuspenseHandler() === null)
2563 ) {
2564 // This render may not have originally been scheduled as a prerender, but
2565 // something suspended inside the visible part of the tree, which means we
2566 // won't be able to commit a fallback anyway. Let's proceed as if this were
2567 // a prerender so that we can warm up the siblings without scheduling a
2568 // separate pass.
2569 workInProgressRootIsPrerendering = true;
2570 }
2571
2572 // Check if there are updates that we skipped tree that might have unblocked
2573 // this render.
2574 if (
2575 (includesNonIdleWork(workInProgressRootSkippedLanes) ||
2576 includesNonIdleWork(workInProgressRootInterleavedUpdatedLanes)) &&
2577 workInProgressRoot !== null
2578 ) {
2579 // Mark the current render as suspended so that we switch to working on
2580 // the updates that were skipped. Usually we only suspend at the end of
2581 // the render phase.
2582 // TODO: We should probably always mark the root as suspended immediately
2583 // (inside this function), since by suspending at the end of the render
2584 // phase introduces a potential mistake where we suspend lanes that were
2585 // pinged or updated while we were rendering.
2586 // TODO: Consider unwinding immediately, using the
2587 // SuspendedOnHydration mechanism.
2588 const didAttemptEntireTree = false;
2589 markRootSuspended(
2590 workInProgressRoot,
2591 workInProgressRootRenderLanes,
2592 workInProgressDeferredLane,
2593 didAttemptEntireTree,
2594 );
2595 }
2596 }
2597
2598 export function renderDidError() {
2599 if (workInProgressRootExitStatus !== RootSuspendedWithDelay) {
2600 workInProgressRootExitStatus = RootErrored;
2601 }
2602 }
2603
2604 export function queueConcurrentError(error: CapturedValue<mixed>) {
2605 if (workInProgressRootConcurrentErrors === null) {
2606 workInProgressRootConcurrentErrors = [error];
2607 } else {
2608 workInProgressRootConcurrentErrors.push(error);
2609 }
2610 }
2611
2612 // Called during render to determine if anything has suspended.
2613 // Returns false if we're not sure.
2614 export function renderHasNotSuspendedYet(): boolean {
2615 // If something errored or completed, we can't really be sure,
2616 // so those are false.
2617 return workInProgressRootExitStatus === RootInProgress;
2618 }
2619
2620 // TODO: Over time, this function and renderRootConcurrent have become more
2621 // and more similar. Not sure it makes sense to maintain forked paths. Consider
2622 // unifying them again.
2623 function renderRootSync(
2624 root: FiberRoot,
2625 lanes: Lanes,
2626 shouldYieldForPrerendering: boolean,
2627 ): RootExitStatus {
2628 const prevExecutionContext = executionContext;
2629 executionContext |= RenderContext;
2630 const prevDispatcher = pushDispatcher(root.containerInfo);
2631 const prevAsyncDispatcher = pushAsyncDispatcher();
2632
2633 // If the root or lanes have changed, throw out the existing stack
2634 // and prepare a fresh one. Otherwise we'll continue where we left off.
2635 if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
2636 if (enableUpdaterTracking) {
2637 if (isDevToolsPresent) {
2638 const memoizedUpdaters = root.memoizedUpdaters;
2639 if (memoizedUpdaters.size > 0) {
2640 restorePendingUpdaters(root, workInProgressRootRenderLanes);
2641 memoizedUpdaters.clear();
2642 }
2643
2644 // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
2645 // If we bailout on this work, we'll move them back (like above).
2646 // It's important to move them now in case the work spawns more work at the same priority with different updaters.
2647 // That way we can keep the current update and future updates separate.
2648 movePendingFibersToMemoized(root, lanes);
2649 }
2650 }
2651
2652 workInProgressTransitions = getTransitionsForLanes(root, lanes);
2653 prepareFreshStack(root, lanes);
2654 }
2655
2656 if (enableSchedulingProfiler) {
2657 markRenderStarted(lanes);
2658 }
2659
2660 let didSuspendInShell = false;
2661 let exitStatus = workInProgressRootExitStatus;
2662 outer: do {
2663 try {
2664 if (
2665 workInProgressSuspendedReason !== NotSuspended &&
2666 workInProgress !== null
2667 ) {
2668 // The work loop is suspended. During a synchronous render, we don't
2669 // yield to the main thread. Immediately unwind the stack. This will
2670 // trigger either a fallback or an error boundary.
2671 // TODO: For discrete and "default" updates (anything that's not
2672 // flushSync), we want to wait for the microtasks the flush before
2673 // unwinding. Will probably implement this using renderRootConcurrent,
2674 // or merge renderRootSync and renderRootConcurrent into the same
2675 // function and fork the behavior some other way.
2676 const unitOfWork = workInProgress;
2677 const thrownValue = workInProgressThrownValue;
2678 switch (workInProgressSuspendedReason) {
2679 case SuspendedOnHydration: {
2680 // Selective hydration. An update flowed into a dehydrated tree.
2681 // Interrupt the current render so the work loop can switch to the
2682 // hydration lane.
2683 // TODO: I think we might not need to reset the stack here; we can
2684 // just yield and reset the stack when we re-enter the work loop,
2685 // like normal.
2686 resetWorkInProgressStack();
2687 exitStatus = RootSuspendedAtTheShell;
2688 break outer;
2689 }
2690 case SuspendedOnImmediate:
2691 case SuspendedOnData:
2692 case SuspendedOnAction:
2693 case SuspendedOnDeprecatedThrowPromise: {
2694 if (getSuspenseHandler() === null) {
2695 didSuspendInShell = true;
2696 }
2697 const reason = workInProgressSuspendedReason;
2698 workInProgressSuspendedReason = NotSuspended;
2699 workInProgressThrownValue = null;
2700 throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, reason);
2701 if (
2702 shouldYieldForPrerendering &&
2703 workInProgressRootIsPrerendering
2704 ) {
2705 // We've switched into prerendering mode. This implies that we
2706 // suspended outside of a Suspense boundary, which means this
2707 // render will be blocked from committing. Yield to the main
2708 // thread so we can switch to prerendering using the concurrent
2709 // work loop.
2710 exitStatus = RootInProgress;
2711 break outer;
2712 }
2713 break;
2714 }
2715 default: {
2716 // Unwind then continue with the normal work loop.
2717 const reason = workInProgressSuspendedReason;
2718 workInProgressSuspendedReason = NotSuspended;
2719 workInProgressThrownValue = null;
2720 throwAndUnwindWorkLoop(root, unitOfWork, thrownValue, reason);
2721 break;
2722 }
2723 }
2724 }
2725 workLoopSync();
2726 exitStatus = workInProgressRootExitStatus;
2727 break;
2728 } catch (thrownValue) {
2729 handleThrow(root, thrownValue);
2730 }
2731 } while (true);
2732
2733 // Check if something suspended in the shell. We use this to detect an
2734 // infinite ping loop caused by an uncached promise.
2735 //
2736 // Only increment this counter once per synchronous render attempt across the
2737 // whole tree. Even if there are many sibling components that suspend, this
2738 // counter only gets incremented once.
2739 if (didSuspendInShell) {
2740 root.shellSuspendCounter++;
2741 }
2742
2743 resetContextDependencies();
2744
2745 executionContext = prevExecutionContext;
2746 popDispatcher(prevDispatcher);
2747 popAsyncDispatcher(prevAsyncDispatcher);
2748
2749 if (enableSchedulingProfiler) {
2750 markRenderStopped();
2751 }
2752
2753 if (workInProgress !== null) {
2754 // Did not complete the tree. This can happen if something suspended in
2755 // the shell.
2756 } else {
2757 // Normal case. We completed the whole tree.
2758
2759 // Set this to null to indicate there's no in-progress render.
2760 workInProgressRoot = null;
2761 workInProgressRootRenderLanes = NoLanes;
2762
2763 // It's safe to process the queue now that the render phase is complete.
2764 finishQueueingConcurrentUpdates();
2765 }
2766
2767 return exitStatus;
2768 }
2769
2770 // The work loop is an extremely hot path. Tell Closure not to inline it.
2771 /** @noinline */
2772 function workLoopSync() {
2773 // Perform work without checking if we need to yield between fiber.
2774 while (workInProgress !== null) {
2775 performUnitOfWork(workInProgress);
2776 }
2777 }
2778
2779 function renderRootConcurrent(root: FiberRoot, lanes: Lanes): RootExitStatus {
2780 const prevExecutionContext = executionContext;
2781 executionContext |= RenderContext;
2782 const prevDispatcher = pushDispatcher(root.containerInfo);
2783 const prevAsyncDispatcher = pushAsyncDispatcher();
2784
2785 // If the root or lanes have changed, throw out the existing stack
2786 // and prepare a fresh one. Otherwise we'll continue where we left off.
2787 if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
2788 if (enableUpdaterTracking) {
2789 if (isDevToolsPresent) {
2790 const memoizedUpdaters = root.memoizedUpdaters;
2791 if (memoizedUpdaters.size > 0) {
2792 restorePendingUpdaters(root, workInProgressRootRenderLanes);
2793 memoizedUpdaters.clear();
2794 }
2795
2796 // At this point, move Fibers that scheduled the upcoming work from the Map to the Set.
2797 // If we bailout on this work, we'll move them back (like above).
2798 // It's important to move them now in case the work spawns more work at the same priority with different updaters.
2799 // That way we can keep the current update and future updates separate.
2800 movePendingFibersToMemoized(root, lanes);
2801 }
2802 }
2803
2804 workInProgressTransitions = getTransitionsForLanes(root, lanes);
2805
2806 resetRenderTimer();
2807 prepareFreshStack(root, lanes);
2808 } else {
2809 // This is a continuation of an existing work-in-progress.
2810 //
2811 // If we were previously in prerendering mode, check if we received any new
2812 // data during an interleaved event.
2813 workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
2814 }
2815
2816 if (enableSchedulingProfiler) {
2817 markRenderStarted(lanes);
2818 }
2819
2820 outer: do {
2821 try {
2822 if (
2823 workInProgressSuspendedReason !== NotSuspended &&
2824 workInProgress !== null
2825 ) {
2826 // The work loop is suspended. We need to either unwind the stack or
2827 // replay the suspended component.
2828 const unitOfWork = workInProgress;
2829 const thrownValue = workInProgressThrownValue;
2830 resumeOrUnwind: switch (workInProgressSuspendedReason) {
2831 case SuspendedOnError: {
2832 // Unwind then continue with the normal work loop.
2833 workInProgressSuspendedReason = NotSuspended;
2834 workInProgressThrownValue = null;
2835 throwAndUnwindWorkLoop(
2836 root,
2837 unitOfWork,
2838 thrownValue,
2839 SuspendedOnError,
2840 );
2841 break;
2842 }
2843 case SuspendedOnData:
2844 case SuspendedOnAction: {
2845 const thenable: Thenable<mixed> = thrownValue as any;
2846 if (isThenableResolved(thenable)) {
2847 // The data resolved. Try rendering the component again.
2848 workInProgressSuspendedReason = NotSuspended;
2849 workInProgressThrownValue = null;
2850 replaySuspendedUnitOfWork(unitOfWork);
2851 break;
2852 }
2853 // The work loop is suspended on data. We should wait for it to
2854 // resolve before continuing to render.
2855 // TODO: Handle the case where the promise resolves synchronously.
2856 // Usually this is handled when we instrument the promise to add a
2857 // `status` field, but if the promise already has a status, we won't
2858 // have added a listener until right here.
2859 const onResolution = () => {
2860 // Check if the root is still suspended on this promise.
2861 if (
2862 (workInProgressSuspendedReason === SuspendedOnData ||
2863 workInProgressSuspendedReason === SuspendedOnAction) &&
2864 workInProgressRoot === root
2865 ) {
2866 // Mark the root as ready to continue rendering.
2867 workInProgressSuspendedReason = SuspendedAndReadyToContinue;
2868 }
2869 // Ensure the root is scheduled. We should do this even if we're
2870 // currently working on a different root, so that we resume
2871 // rendering later.
2872 ensureRootIsScheduled(root);
2873 };
2874 thenable.then(onResolution, onResolution);
2875 break outer;
2876 }
2877 case SuspendedOnImmediate: {
2878 // If this fiber just suspended, it's possible the data is already
2879 // cached. Yield to the main thread to give it a chance to ping. If
2880 // it does, we can retry immediately without unwinding the stack.
2881 workInProgressSuspendedReason = SuspendedAndReadyToContinue;
2882 break outer;
2883 }
2884 case SuspendedOnInstance: {
2885 workInProgressSuspendedReason =
2886 SuspendedOnInstanceAndReadyToContinue;
2887 break outer;
2888 }
2889 case SuspendedAndReadyToContinue: {
2890 const thenable: Thenable<mixed> = thrownValue as any;
2891 if (isThenableResolved(thenable)) {
2892 // The data resolved. Try rendering the component again.
2893 workInProgressSuspendedReason = NotSuspended;
2894 workInProgressThrownValue = null;
2895 replaySuspendedUnitOfWork(unitOfWork);
2896 } else {
2897 // Otherwise, unwind then continue with the normal work loop.
2898 workInProgressSuspendedReason = NotSuspended;
2899 workInProgressThrownValue = null;
2900 throwAndUnwindWorkLoop(
2901 root,
2902 unitOfWork,
2903 thrownValue,
2904 SuspendedAndReadyToContinue,
2905 );
2906 }
2907 break;
2908 }
2909 case SuspendedOnInstanceAndReadyToContinue: {
2910 let resource: null | Resource = null;
2911 switch (workInProgress.tag) {
2912 case HostHoistable: {
2913 resource = workInProgress.memoizedState;
2914 }
2915 // intentional fallthrough
2916 case HostComponent:
2917 case HostSingleton: {
2918 // Before unwinding the stack, check one more time if the
2919 // instance is ready. It may have loaded when React yielded to
2920 // the main thread.
2921
2922 // Assigning this to a constant so Flow knows the binding won't
2923 // be mutated by `preloadInstance`.
2924 const hostFiber = workInProgress;
2925 const type = hostFiber.type;
2926 const props = hostFiber.pendingProps;
2927 const isReady = resource
2928 ? preloadResource(resource)
2929 : preloadInstance(hostFiber.stateNode, type, props);
2930 if (isReady) {
2931 // The data resolved. Resume the work loop as if nothing
2932 // suspended. Unlike when a user component suspends, we don't
2933 // have to replay anything because the host fiber
2934 // already completed.
2935 workInProgressSuspendedReason = NotSuspended;
2936 workInProgressThrownValue = null;
2937 const sibling = hostFiber.sibling;
2938 if (sibling !== null) {
2939 workInProgress = sibling;
2940 } else {
2941 const returnFiber = hostFiber.return;
2942 if (returnFiber !== null) {
2943 workInProgress = returnFiber;
2944 completeUnitOfWork(returnFiber);
2945 } else {
2946 workInProgress = null;
2947 }
2948 }
2949 break resumeOrUnwind;
2950 }
2951 break;
2952 }
2953 default: {
2954 // This will fail gracefully but it's not correct, so log a
2955 // warning in dev.
2956 if (__DEV__) {
2957 console.error(
2958 'Unexpected type of fiber triggered a suspensey commit. ' +
2959 'This is a bug in React.',
2960 );
2961 }
2962 break;
2963 }
2964 }
2965 // Otherwise, unwind then continue with the normal work loop.
2966 workInProgressSuspendedReason = NotSuspended;
2967 workInProgressThrownValue = null;
2968 throwAndUnwindWorkLoop(
2969 root,
2970 unitOfWork,
2971 thrownValue,
2972 SuspendedOnInstanceAndReadyToContinue,
2973 );
2974 break;
2975 }
2976 case SuspendedOnDeprecatedThrowPromise: {
2977 // Suspended by an old implementation that uses the `throw promise`
2978 // pattern. The newer replaying behavior can cause subtle issues
2979 // like infinite ping loops. So we maintain the old behavior and
2980 // always unwind.
2981 workInProgressSuspendedReason = NotSuspended;
2982 workInProgressThrownValue = null;
2983 throwAndUnwindWorkLoop(
2984 root,
2985 unitOfWork,
2986 thrownValue,
2987 SuspendedOnDeprecatedThrowPromise,
2988 );
2989 break;
2990 }
2991 case SuspendedOnHydration: {
2992 // Selective hydration. An update flowed into a dehydrated tree.
2993 // Interrupt the current render so the work loop can switch to the
2994 // hydration lane.
2995 resetWorkInProgressStack();
2996 workInProgressRootExitStatus = RootSuspendedAtTheShell;
2997 break outer;
2998 }
2999 default: {
3000 throw new Error(
3001 'Unexpected SuspendedReason. This is a bug in React.',
3002 );
3003 }
3004 }
3005 }
3006
3007 if (__DEV__ && ReactSharedInternals.actQueue !== null) {
3008 // `act` special case: If we're inside an `act` scope, don't consult
3009 // `shouldYield`. Always keep working until the render is complete.
3010 // This is not just an optimization: in a unit test environment, we
3011 // can't trust the result of `shouldYield`, because the host I/O is
3012 // likely mocked.
3013 workLoopSync();
3014 } else if (enableThrottledScheduling) {
3015 workLoopConcurrent(includesNonIdleWork(lanes));
3016 } else {
3017 workLoopConcurrentByScheduler();
3018 }
3019 break;
3020 } catch (thrownValue) {
3021 handleThrow(root, thrownValue);
3022 }
3023 } while (true);
3024 resetContextDependencies();
3025
3026 popDispatcher(prevDispatcher);
3027 popAsyncDispatcher(prevAsyncDispatcher);
3028 executionContext = prevExecutionContext;
3029
3030 // Check if the tree has completed.
3031 if (workInProgress !== null) {
3032 // Still work remaining.
3033 if (enableSchedulingProfiler) {
3034 markRenderYielded();
3035 }
3036 return RootInProgress;
3037 } else {
3038 // Completed the tree.
3039 if (enableSchedulingProfiler) {
3040 markRenderStopped();
3041 }
3042
3043 // Set this to null to indicate there's no in-progress render.
3044 workInProgressRoot = null;
3045 workInProgressRootRenderLanes = NoLanes;
3046
3047 // It's safe to process the queue now that the render phase is complete.
3048 finishQueueingConcurrentUpdates();
3049
3050 // Return the final exit status.
3051 return workInProgressRootExitStatus;
3052 }
3053 }
3054
3055 /** @noinline */
3056 function workLoopConcurrent(nonIdle: boolean) {
3057 // We yield every other "frame" when rendering Transition or Retries. Those are blocking
3058 // revealing new content. The purpose of this yield is not to avoid the overhead of yielding,
3059 // which is very low, but rather to intentionally block any frequently occurring other main
3060 // thread work like animations from starving our work. In other words, the purpose of this
3061 // is to reduce the framerate of animations to 30 frames per second.
3062 // For Idle work we yield every 5ms to keep animations going smooth.
3063 if (workInProgress !== null) {
3064 const yieldAfter = now() + (nonIdle ? 25 : 5);
3065 do {
3066 // $FlowFixMe[incompatible-type] flow doesn't know that now() is side-effect free
3067 performUnitOfWork(workInProgress);
3068 } while (workInProgress !== null && now() < yieldAfter);
3069 }
3070 }
3071
3072 /** @noinline */
3073 function workLoopConcurrentByScheduler() {
3074 // Perform work until Scheduler asks us to yield
3075 while (workInProgress !== null && !shouldYield()) {
3076 // $FlowFixMe[incompatible-type] flow doesn't know that shouldYield() is side-effect free
3077 performUnitOfWork(workInProgress);
3078 }
3079 }
3080
3081 function performUnitOfWork(unitOfWork: Fiber): void {
3082 // The current, flushed, state of this fiber is the alternate. Ideally
3083 // nothing should rely on this, but relying on it here means that we don't
3084 // need an additional field on the work in progress.
3085 const current = unitOfWork.alternate;
3086
3087 let next;
3088 if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
3089 startProfilerTimer(unitOfWork);
3090 if (__DEV__) {
3091 next = runWithFiberInDEV(
3092 unitOfWork,
3093 beginWork,
3094 current,
3095 unitOfWork,
3096 entangledRenderLanes,
3097 );
3098 } else {
3099 next = beginWork(current, unitOfWork, entangledRenderLanes);
3100 }
3101 stopProfilerTimerIfRunningAndRecordDuration(unitOfWork);
3102 } else {
3103 if (__DEV__) {
3104 next = runWithFiberInDEV(
3105 unitOfWork,
3106 beginWork,
3107 current,
3108 unitOfWork,
3109 entangledRenderLanes,
3110 );
3111 } else {
3112 next = beginWork(current, unitOfWork, entangledRenderLanes);
3113 }
3114 }
3115
3116 unitOfWork.memoizedProps = unitOfWork.pendingProps;
3117 if (next === null) {
3118 // If this doesn't spawn new work, complete the current work.
3119 completeUnitOfWork(unitOfWork);
3120 } else {
3121 workInProgress = next;
3122 }
3123 }
3124
3125 function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
3126 // This is a fork of performUnitOfWork specifcally for replaying a fiber that
3127 // just suspended.
3128 let next;
3129 if (__DEV__) {
3130 next = runWithFiberInDEV(unitOfWork, replayBeginWork, unitOfWork);
3131 } else {
3132 next = replayBeginWork(unitOfWork);
3133 }
3134
3135 unitOfWork.memoizedProps = unitOfWork.pendingProps;
3136 if (next === null) {
3137 // If this doesn't spawn new work, complete the current work.
3138 completeUnitOfWork(unitOfWork);
3139 } else {
3140 workInProgress = next;
3141 }
3142 }
3143
3144 function replayBeginWork(unitOfWork: Fiber): null | Fiber {
3145 // This is a fork of beginWork specifcally for replaying a fiber that
3146 // just suspended.
3147
3148 const current = unitOfWork.alternate;
3149
3150 let next;
3151 const isProfilingMode =
3152 enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode;
3153 if (isProfilingMode) {
3154 startProfilerTimer(unitOfWork);
3155 }
3156 switch (unitOfWork.tag) {
3157 case SimpleMemoComponent:
3158 case FunctionComponent: {
3159 // Resolve `defaultProps`. This logic is copied from `beginWork`.
3160 // TODO: Consider moving this switch statement into that module. Also,
3161 // could maybe use this as an opportunity to say `use` doesn't work with
3162 // `defaultProps` :)
3163 const Component = unitOfWork.type;
3164 let context: any;
3165 if (!disableLegacyContext) {
3166 const unmaskedContext = getUnmaskedContext(unitOfWork, Component, true);
3167 context = getMaskedContext(unitOfWork, unmaskedContext);
3168 }
3169 next = replayFunctionComponent(
3170 current,
3171 unitOfWork,
3172 unitOfWork.pendingProps,
3173 Component,
3174 context,
3175 workInProgressRootRenderLanes,
3176 );
3177 break;
3178 }
3179 case ForwardRef: {
3180 // Resolve `defaultProps`. This logic is copied from `beginWork`.
3181 // TODO: Consider moving this switch statement into that module. Also,
3182 // could maybe use this as an opportunity to say `use` doesn't work with
3183 // `defaultProps` :)
3184 const Component = unitOfWork.type.render;
3185 next = replayFunctionComponent(
3186 current,
3187 unitOfWork,
3188 unitOfWork.pendingProps,
3189 Component,
3190 unitOfWork.ref,
3191 workInProgressRootRenderLanes,
3192 );
3193 break;
3194 }
3195 case HostComponent: {
3196 // Some host components are stateful (that's how we implement form
3197 // actions) but we don't bother to reuse the memoized state because it's
3198 // not worth the extra code. The main reason to reuse the previous hooks
3199 // is to reuse uncached promises, but we happen to know that the only
3200 // promises that a host component might suspend on are definitely cached
3201 // because they are controlled by us. So don't bother.
3202 resetHooksOnUnwind(unitOfWork);
3203 // We are about to retry this host component and need to ensure the hydration
3204 // state is appropriate for hydrating this unit. Other fiber types hydrate differently
3205 // and aren't reliant on the cursor positioning so this function is only for HostComponent
3206 popHydrationStateOnInterruptedWork(unitOfWork);
3207 // Fallthrough to the next branch.
3208 }
3209 default: {
3210 // Other types besides function components are reset completely before
3211 // being replayed. Currently this only happens when a Usable type is
3212 // reconciled — the reconciler will suspend.
3213 //
3214 // We reset the fiber back to its original state; however, this isn't
3215 // a full "unwind" because we're going to reuse the promises that were
3216 // reconciled previously. So it's intentional that we don't call
3217 // resetSuspendedWorkLoopOnUnwind here.
3218 unwindInterruptedWork(current, unitOfWork, workInProgressRootRenderLanes);
3219 unitOfWork = workInProgress = resetWorkInProgress(
3220 unitOfWork,
3221 entangledRenderLanes,
3222 );
3223 next = beginWork(current, unitOfWork, entangledRenderLanes);
3224 break;
3225 }
3226 }
3227 if (isProfilingMode) {
3228 stopProfilerTimerIfRunningAndRecordDuration(unitOfWork);
3229 }
3230
3231 return next;
3232 }
3233
3234 function throwAndUnwindWorkLoop(
3235 root: FiberRoot,
3236 unitOfWork: Fiber,
3237 thrownValue: mixed,
3238 suspendedReason: SuspendedReason,
3239 ) {
3240 // This is a fork of performUnitOfWork specifcally for unwinding a fiber
3241 // that threw an exception.
3242 //
3243 // Return to the normal work loop. This will unwind the stack, and potentially
3244 // result in showing a fallback.
3245 resetSuspendedWorkLoopOnUnwind(unitOfWork);
3246
3247 const returnFiber = unitOfWork.return;
3248 try {
3249 // Find and mark the nearest Suspense or error boundary that can handle
3250 // this "exception".
3251 const didFatal = throwException(
3252 root,
3253 returnFiber,
3254 unitOfWork,
3255 thrownValue,
3256 workInProgressRootRenderLanes,
3257 );
3258 if (didFatal) {
3259 panicOnRootError(root, thrownValue);
3260 return;
3261 }
3262 } catch (error) {
3263 // We had trouble processing the error. An example of this happening is
3264 // when accessing the `componentDidCatch` property of an error boundary
3265 // throws an error. A weird edge case. There's a regression test for this.
3266 // To prevent an infinite loop, bubble the error up to the next parent.
3267 if (returnFiber !== null) {
3268 workInProgress = returnFiber;
3269 throw error;
3270 } else {
3271 panicOnRootError(root, thrownValue);
3272 return;
3273 }
3274 }
3275
3276 if (unitOfWork.flags & Incomplete) {
3277 // Unwind the stack until we reach the nearest boundary.
3278 let skipSiblings;
3279
3280 if (
3281 // The current algorithm for both hydration and error handling assumes
3282 // that the tree is rendered sequentially. So we always skip the siblings.
3283 getIsHydrating() ||
3284 suspendedReason === SuspendedOnError
3285 ) {
3286 skipSiblings = true;
3287 // We intentionally don't set workInProgressRootDidSkipSuspendedSiblings,
3288 // because we don't want to trigger another prerender attempt.
3289 } else if (
3290 // Check whether this is a prerender
3291 !workInProgressRootIsPrerendering &&
3292 // Offscreen rendering is also a form of speculative rendering
3293 !includesSomeLane(workInProgressRootRenderLanes, OffscreenLane)
3294 ) {
3295 // This is not a prerender. Skip the siblings during this render. A
3296 // separate prerender will be scheduled for later.
3297 skipSiblings = true;
3298 workInProgressRootDidSkipSuspendedSiblings = true;
3299
3300 // Because we're skipping the siblings, schedule an immediate retry of
3301 // this boundary.
3302 //
3303 // The reason we do this is because a prerender is only scheduled when
3304 // the root is blocked from committing, i.e. RootSuspendedWithDelay.
3305 // When the root is not blocked, as in the case when we render a
3306 // fallback, the original lane is considered to be finished, and
3307 // therefore no longer in need of being prerendered. However, there's
3308 // still a pending retry that will happen once the data streams in.
3309 // We should start rendering that even before the data streams in so we
3310 // can prerender the siblings.
3311 if (
3312 suspendedReason === SuspendedOnData ||
3313 suspendedReason === SuspendedOnAction ||
3314 suspendedReason === SuspendedOnImmediate ||
3315 suspendedReason === SuspendedOnDeprecatedThrowPromise
3316 ) {
3317 const boundary = getSuspenseHandler();
3318 if (boundary !== null && boundary.tag === SuspenseComponent) {
3319 boundary.flags |= ScheduleRetry;
3320 }
3321 }
3322 } else {
3323 // This is a prerender. Don't skip the siblings.
3324 skipSiblings = false;
3325 }
3326
3327 unwindUnitOfWork(unitOfWork, skipSiblings);
3328 } else {
3329 // Although the fiber suspended, we're intentionally going to commit it in
3330 // an inconsistent state. We can do this safely in cases where we know the
3331 // inconsistent tree will be hidden.
3332 //
3333 // This currently only applies to Legacy Suspense implementation, but we may
3334 // port a version of this to concurrent roots, too, when performing a
3335 // synchronous render. Because that will allow us to mutate the tree as we
3336 // go instead of buffering mutations until the end. Though it's unclear if
3337 // this particular path is how that would be implemented.
3338 completeUnitOfWork(unitOfWork);
3339 }
3340 }
3341
3342 export function markSpawnedRetryLane(lane: Lane): void {
3343 // Keep track of the retry lanes that were spawned by a fallback during the
3344 // current render and were not later pinged. This will represent the lanes
3345 // that are known to still be suspended.
3346 workInProgressSuspendedRetryLanes = mergeLanes(
3347 workInProgressSuspendedRetryLanes,
3348 lane,
3349 );
3350 }
3351
3352 function panicOnRootError(root: FiberRoot, error: mixed) {
3353 // There's no ancestor that can handle this exception. This should never
3354 // happen because the root is supposed to capture all errors that weren't
3355 // caught by an error boundary. This is a fatal error, or panic condition,
3356 // because we've run out of ways to recover.
3357 workInProgressRootExitStatus = RootFatalErrored;
3358 logUncaughtError(root, createCapturedValueAtFiber(error, root.current));
3359 // Set `workInProgress` to null. This represents advancing to the next
3360 // sibling, or the parent if there are no siblings. But since the root
3361 // has no siblings nor a parent, we set it to null. Usually this is
3362 // handled by `completeUnitOfWork` or `unwindWork`, but since we're
3363 // intentionally not calling those, we need set it here.
3364 // TODO: Consider calling `unwindWork` to pop the contexts.
3365 workInProgress = null;
3366 }
3367
3368 function completeUnitOfWork(unitOfWork: Fiber): void {
3369 // Attempt to complete the current unit of work, then move to the next
3370 // sibling. If there are no more siblings, return to the parent fiber.
3371 let completedWork: Fiber = unitOfWork;
3372 do {
3373 if ((completedWork.flags & Incomplete) !== NoFlags) {
3374 // This fiber did not complete, because one of its children did not
3375 // complete. Switch to unwinding the stack instead of completing it.
3376 //
3377 // The reason "unwind" and "complete" is interleaved is because when
3378 // something suspends, we continue rendering the siblings even though
3379 // they will be replaced by a fallback.
3380 const skipSiblings = workInProgressRootDidSkipSuspendedSiblings;
3381 unwindUnitOfWork(completedWork, skipSiblings);
3382 return;
3383 }
3384
3385 // The current, flushed, state of this fiber is the alternate. Ideally
3386 // nothing should rely on this, but relying on it here means that we don't
3387 // need an additional field on the work in progress.
3388 const current = completedWork.alternate;
3389 const returnFiber = completedWork.return;
3390
3391 let next;
3392 startProfilerTimer(completedWork);
3393 if (__DEV__) {
3394 next = runWithFiberInDEV(
3395 completedWork,
3396 completeWork,
3397 current,
3398 completedWork,
3399 entangledRenderLanes,
3400 );
3401 } else {
3402 next = completeWork(current, completedWork, entangledRenderLanes);
3403 }
3404 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {
3405 // Update render duration assuming we didn't error.
3406 stopProfilerTimerIfRunningAndRecordIncompleteDuration(completedWork);
3407 }
3408 if (next !== null) {
3409 // Completing this fiber spawned new work. Work on that next.
3410 workInProgress = next;
3411 return;
3412 }
3413
3414 const siblingFiber = completedWork.sibling;
3415 if (siblingFiber !== null) {
3416 // If there is more work to do in this returnFiber, do that next.
3417 workInProgress = siblingFiber;
3418 return;
3419 }
3420 // Otherwise, return to the parent
3421 // $FlowFixMe[incompatible-type] we bail out when we get a null
3422 completedWork = returnFiber;
3423 // Update the next thing we're working on in case something throws.
3424 workInProgress = completedWork;
3425 } while (completedWork !== null);
3426
3427 // We've reached the root.
3428 if (workInProgressRootExitStatus === RootInProgress) {
3429 workInProgressRootExitStatus = RootCompleted;
3430 }
3431 }
3432
3433 function unwindUnitOfWork(unitOfWork: Fiber, skipSiblings: boolean): void {
3434 let incompleteWork: Fiber = unitOfWork;
3435 do {
3436 // The current, flushed, state of this fiber is the alternate. Ideally
3437 // nothing should rely on this, but relying on it here means that we don't
3438 // need an additional field on the work in progress.
3439 const current = incompleteWork.alternate;
3440
3441 // This fiber did not complete because something threw. Pop values off
3442 // the stack without entering the complete phase. If this is a boundary,
3443 // capture values if possible.
3444 const next = unwindWork(current, incompleteWork, entangledRenderLanes);
3445
3446 // Because this fiber did not complete, don't reset its lanes.
3447
3448 if (next !== null) {
3449 // Found a boundary that can handle this exception. Re-renter the
3450 // begin phase. This branch will return us to the normal work loop.
3451 //
3452 // Since we're restarting, remove anything that is not a host effect
3453 // from the effect tag.
3454 next.flags &= HostEffectMask;
3455 workInProgress = next;
3456 return;
3457 }
3458
3459 // Keep unwinding until we reach either a boundary or the root.
3460
3461 if (enableProfilerTimer && (incompleteWork.mode & ProfileMode) !== NoMode) {
3462 // Record the render duration for the fiber that errored.
3463 stopProfilerTimerIfRunningAndRecordIncompleteDuration(incompleteWork);
3464
3465 // Include the time spent working on failed children before continuing.
3466 let actualDuration = incompleteWork.actualDuration;
3467 let child = incompleteWork.child;
3468 while (child !== null) {
3469 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
3470 actualDuration += child.actualDuration;
3471 child = child.sibling;
3472 }
3473 incompleteWork.actualDuration = actualDuration;
3474 }
3475
3476 // TODO: Once we stop prerendering siblings, instead of resetting the parent
3477 // of the node being unwound, we should be able to reset node itself as we
3478 // unwind the stack. Saves an additional null check.
3479 const returnFiber = incompleteWork.return;
3480 if (returnFiber !== null) {
3481 // Mark the parent fiber as incomplete and clear its subtree flags.
3482 // TODO: Once we stop prerendering siblings, we may be able to get rid of
3483 // the Incomplete flag because unwinding to the nearest boundary will
3484 // happen synchronously.
3485 returnFiber.flags |= Incomplete;
3486 returnFiber.subtreeFlags = NoFlags;
3487 returnFiber.deletions = null;
3488 }
3489
3490 if (!skipSiblings) {
3491 const siblingFiber = incompleteWork.sibling;
3492 if (siblingFiber !== null) {
3493 // This branch will return us to the normal work loop.
3494 workInProgress = siblingFiber;
3495 return;
3496 }
3497 }
3498
3499 // Otherwise, return to the parent
3500 // $FlowFixMe[incompatible-type] we bail out when we get a null
3501 incompleteWork = returnFiber;
3502 // Update the next thing we're working on in case something throws.
3503 workInProgress = incompleteWork;
3504 } while (incompleteWork !== null);
3505
3506 // We've unwound all the way to the root.
3507 workInProgressRootExitStatus = RootSuspendedAtTheShell;
3508 workInProgress = null;
3509 }
3510
3511 function completeRoot(
3512 root: FiberRoot,
3513 finishedWork: null | Fiber,
3514 lanes: Lanes,
3515 recoverableErrors: null | Array<CapturedValue<mixed>>,
3516 transitions: Array<Transition> | null,
3517 didIncludeRenderPhaseUpdate: boolean,
3518 spawnedLane: Lane,
3519 updatedLanes: Lanes,
3520 suspendedRetryLanes: Lanes,
3521 didSkipSuspendedSiblings: boolean,
3522 exitStatus: RootExitStatus,
3523 suspendedState: null | SuspendedState,
3524 suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3525 completedRenderStartTime: number, // Profiling-only
3526 completedRenderEndTime: number, // Profiling-only
3527 ): void {
3528 root.cancelPendingCommit = null;
3529
3530 do {
3531 // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which
3532 // means `flushPassiveEffects` will sometimes result in additional
3533 // passive effects. So we need to keep flushing in a loop until there are
3534 // no more pending effects.
3535 // TODO: Might be better if `flushPassiveEffects` did not automatically
3536 // flush synchronous work at the end, to avoid factoring hazards like this.
3537 flushPendingEffects();
3538 } while (pendingEffectsStatus !== NO_PENDING_EFFECTS);
3539 flushRenderPhaseStrictModeWarningsInDEV();
3540
3541 if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
3542 throw new Error('Should not already be working.');
3543 }
3544
3545 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3546 // Log the previous render phase once we commit. I.e. we weren't interrupted.
3547 setCurrentTrackFromLanes(lanes);
3548 if (isGestureRender(lanes)) {
3549 // Clamp the render start time in case if something else on this lane was committed
3550 // (such as this same tree before).
3551 if (completedRenderStartTime < gestureClampTime) {
3552 completedRenderStartTime = gestureClampTime;
3553 }
3554 if (completedRenderEndTime < gestureClampTime) {
3555 completedRenderEndTime = gestureClampTime;
3556 }
3557 }
3558 if (exitStatus === RootErrored) {
3559 logErroredRenderPhase(
3560 completedRenderStartTime,
3561 completedRenderEndTime,
3562 lanes,
3563 workInProgressUpdateTask,
3564 );
3565 } else if (recoverableErrors !== null) {
3566 const hydrationFailed =
3567 finishedWork !== null &&
3568 finishedWork.alternate !== null &&
3569 (finishedWork.alternate.memoizedState as RootState).isDehydrated &&
3570 (finishedWork.flags & ForceClientRender) !== NoFlags;
3571 logRecoveredRenderPhase(
3572 completedRenderStartTime,
3573 completedRenderEndTime,
3574 lanes,
3575 recoverableErrors,
3576 hydrationFailed,
3577 workInProgressUpdateTask,
3578 );
3579 } else {
3580 logRenderPhase(
3581 completedRenderStartTime,
3582 completedRenderEndTime,
3583 lanes,
3584 workInProgressUpdateTask,
3585 );
3586 }
3587 }
3588
3589 if (enableSchedulingProfiler) {
3590 markCommitStarted(lanes);
3591 }
3592
3593 if (finishedWork === null) {
3594 if (enableSchedulingProfiler) {
3595 markCommitStopped();
3596 }
3597 if (enableGestureTransition) {
3598 // Stop any gestures that were committed.
3599 if (isGestureRender(lanes)) {
3600 stopCommittedGesture(root);
3601 }
3602 }
3603 return;
3604 } else {
3605 if (__DEV__) {
3606 if (lanes === NoLanes) {
3607 console.error(
3608 'finishedLanes should not be empty during a commit. This is a ' +
3609 'bug in React.',
3610 );
3611 }
3612 }
3613 }
3614
3615 if (finishedWork === root.current) {
3616 throw new Error(
3617 'Cannot commit the same tree as before. This error is likely caused by ' +
3618 'a bug in React. Please file an issue.',
3619 );
3620 }
3621
3622 if (root === workInProgressRoot) {
3623 // We can reset these now that they are finished.
3624 workInProgressRoot = null;
3625 workInProgress = null;
3626 workInProgressRootRenderLanes = NoLanes;
3627 } else {
3628 // This indicates that the last root we worked on is not the same one that
3629 // we're committing now. This most commonly happens when a suspended root
3630 // times out.
3631 }
3632
3633 // workInProgressX might be overwritten, so we want
3634 // to store it in pendingPassiveX until they get processed
3635 // We need to pass this through as an argument to completeRoot
3636 // because workInProgressX might have changed between
3637 // the previous render and commit if we throttle the commit
3638 // with setTimeout
3639 pendingFinishedWork = finishedWork;
3640 pendingEffectsRoot = root;
3641 pendingEffectsLanes = lanes;
3642 pendingPassiveTransitions = transitions;
3643 pendingRecoverableErrors = recoverableErrors;
3644 pendingDidIncludeRenderPhaseUpdate = didIncludeRenderPhaseUpdate;
3645 if (enableProfilerTimer) {
3646 pendingEffectsRenderEndTime = completedRenderEndTime;
3647 pendingSuspendedCommitReason = suspendedCommitReason;
3648 pendingDelayedCommitReason = IMMEDIATE_COMMIT;
3649 pendingSuspendedViewTransitionReason = null;
3650 }
3651
3652 if (enableGestureTransition && isGestureRender(lanes)) {
3653 const committingGesture = root.pendingGestures;
3654 if (committingGesture !== null && !committingGesture.committing) {
3655 // This gesture is not ready to commit yet. We'll mark it as suspended and
3656 // start a gesture transition which isn't really a side-effect. Then later
3657 // we might come back around to actually committing the root.
3658 const didAttemptEntireTree = !didSkipSuspendedSiblings;
3659 markRootSuspended(root, lanes, spawnedLane, didAttemptEntireTree);
3660 if (committingGesture.running === null) {
3661 applyGestureOnRoot(
3662 root,
3663 finishedWork,
3664 recoverableErrors,
3665 suspendedState,
3666 enableProfilerTimer
3667 ? suspendedCommitReason === null
3668 ? completedRenderEndTime
3669 : commitStartTime
3670 : 0,
3671 );
3672 } else {
3673 // If we already have a gesture running, we don't update it in place
3674 // even if we have a new tree. Instead we wait until we can commit.
3675 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3676 // Clamp at the render time since we're not going to finish the rest
3677 // of this commit or apply yet.
3678 finalizeRender(lanes, completedRenderEndTime);
3679 }
3680 // We are no longer committing.
3681 pendingEffectsRoot = null as any; // Clear for GC purposes.
3682 pendingFinishedWork = null as any; // Clear for GC purposes.
3683 pendingEffectsLanes = NoLanes;
3684 }
3685 // Schedule the root to be committed when the gesture completes.
3686 root.cancelPendingCommit = scheduleGestureCommit(
3687 committingGesture,
3688 completeRoot.bind(
3689 null,
3690 root,
3691 finishedWork,
3692 lanes,
3693 recoverableErrors,
3694 transitions,
3695 didIncludeRenderPhaseUpdate,
3696 spawnedLane,
3697 updatedLanes,
3698 suspendedRetryLanes,
3699 didSkipSuspendedSiblings,
3700 exitStatus,
3701 suspendedState,
3702 'Waiting for the Gesture to finish' /* suspendedCommitReason */,
3703 completedRenderStartTime,
3704 completedRenderEndTime,
3705 ),
3706 );
3707 return;
3708 }
3709 }
3710
3711 // If we're not starting a gesture we now actually commit the root.
3712 commitRoot(
3713 root,
3714 finishedWork,
3715 lanes,
3716 spawnedLane,
3717 updatedLanes,
3718 suspendedRetryLanes,
3719 suspendedState,
3720 suspendedCommitReason,
3721 completedRenderEndTime,
3722 );
3723 }
3724
3725 function commitRoot(
3726 root: FiberRoot,
3727 finishedWork: Fiber,
3728 lanes: Lanes,
3729 spawnedLane: Lane,
3730 updatedLanes: Lanes,
3731 suspendedRetryLanes: Lanes,
3732 suspendedState: null | SuspendedState,
3733 suspendedCommitReason: SuspendedCommitReason, // Profiling-only
3734 completedRenderEndTime: number, // Profiling-only
3735 ) {
3736 // Check which lanes no longer have any work scheduled on them, and mark
3737 // those as finished.
3738 let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
3739
3740 pendingEffectsRemainingLanes = remainingLanes;
3741
3742 // Make sure to account for lanes that were updated by a concurrent event
3743 // during the render phase; don't mark them as finished.
3744 const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
3745 remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
3746
3747 if (enableGestureTransition && root.pendingGestures === null) {
3748 // Gestures don't clear their lanes while the gesture is still active but it
3749 // might not be scheduled to do any more renders and so we shouldn't schedule
3750 // any more gesture lane work until a new gesture is scheduled.
3751 if (enableProfilerTimer && (remainingLanes & GestureLane) !== NoLanes) {
3752 // We need to clear any updates scheduled so that we can treat future updates
3753 // as the cause of the render.
3754 clearGestureUpdates();
3755 }
3756 remainingLanes &= ~GestureLane;
3757 }
3758
3759 markRootFinished(
3760 root,
3761 lanes,
3762 remainingLanes,
3763 spawnedLane,
3764 updatedLanes,
3765 suspendedRetryLanes,
3766 );
3767
3768 // Reset this before firing side effects so we can detect recursive updates.
3769 didIncludeCommitPhaseUpdate = false;
3770
3771 // If there are pending passive effects, schedule a callback to process them.
3772 // Do this as early as possible, so it is queued before anything else that
3773 // might get scheduled in the commit phase. (See #16714.)
3774 // TODO: Delete all other places that schedule the passive effect callback
3775 // They're redundant.
3776 let passiveSubtreeMask;
3777 if (enableViewTransition) {
3778 pendingViewTransitionEvents = null;
3779 if (includesOnlyViewTransitionEligibleLanes(lanes)) {
3780 // Claim any pending Transition Types for this commit.
3781 pendingTransitionTypes = claimQueuedTransitionTypes(root);
3782 passiveSubtreeMask = PassiveTransitionMask;
3783 } else {
3784 pendingTransitionTypes = null;
3785 passiveSubtreeMask = PassiveMask;
3786 }
3787 } else {
3788 passiveSubtreeMask = PassiveMask;
3789 }
3790 if (
3791 // If this subtree rendered with profiling this commit, we need to visit it to log it.
3792 (enableProfilerTimer &&
3793 enableComponentPerformanceTrack &&
3794 finishedWork.actualDuration !== 0) ||
3795 (finishedWork.subtreeFlags & passiveSubtreeMask) !== NoFlags ||
3796 (finishedWork.flags & passiveSubtreeMask) !== NoFlags
3797 ) {
3798 if (enableYieldingBeforePassive) {
3799 // We don't schedule a separate task for flushing passive effects.
3800 // Instead, we just rely on ensureRootIsScheduled below to schedule
3801 // a callback for us to flush the passive effects.
3802 } else {
3803 // So we can clear these now to allow a new callback to be scheduled.
3804 root.callbackNode = null;
3805 root.callbackPriority = NoLane;
3806 scheduleCallback(NormalSchedulerPriority, () => {
3807 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3808 // Track the currently executing event if there is one so we can ignore this
3809 // event when logging events.
3810 trackSchedulerEvent();
3811 }
3812 if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) {
3813 pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT;
3814 }
3815 flushPassiveEffects();
3816 // This render triggered passive effects: release the root cache pool
3817 // *after* passive effects fire to avoid freeing a cache pool that may
3818 // be referenced by a node in the tree (HostRoot, Cache boundary etc)
3819 return null;
3820 });
3821 }
3822 } else {
3823 // If we don't have passive effects, we're not going to need to perform more work
3824 // so we can clear the callback now.
3825 root.callbackNode = null;
3826 root.callbackPriority = NoLane;
3827 }
3828
3829 if (enableProfilerTimer) {
3830 // Mark the current commit time to be shared by all Profilers in this
3831 // batch. This enables them to be grouped later.
3832 resetCommitErrors();
3833 recordCommitTime();
3834 if (enableComponentPerformanceTrack) {
3835 if (suspendedCommitReason !== null) {
3836 logSuspendedCommitPhase(
3837 completedRenderEndTime,
3838 commitStartTime,
3839 suspendedCommitReason,
3840 workInProgressUpdateTask,
3841 );
3842 }
3843 }
3844 }
3845
3846 resetShouldStartViewTransition();
3847
3848 // The commit phase is broken into several sub-phases. We do a separate pass
3849 // of the effect list for each phase: all mutation effects come before all
3850 // layout effects, and so on.
3851
3852 // Check if there are any effects in the whole tree.
3853 // TODO: This is left over from the effect list implementation, where we had
3854 // to check for the existence of `firstEffect` to satisfy Flow. I think the
3855 // only other reason this optimization exists is because it affects profiling.
3856 // Reconsider whether this is necessary.
3857 const subtreeHasBeforeMutationEffects =
3858 (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask)) !==
3859 NoFlags;
3860 const rootHasBeforeMutationEffect =
3861 (finishedWork.flags & (BeforeMutationMask | MutationMask)) !== NoFlags;
3862
3863 if (subtreeHasBeforeMutationEffects || rootHasBeforeMutationEffect) {
3864 const prevTransition = ReactSharedInternals.T;
3865 ReactSharedInternals.T = null;
3866 const previousPriority = getCurrentUpdatePriority();
3867 setCurrentUpdatePriority(DiscreteEventPriority);
3868 const prevExecutionContext = executionContext;
3869 executionContext |= CommitContext;
3870 try {
3871 // The first phase a "before mutation" phase. We use this phase to read the
3872 // state of the host tree right before we mutate it. This is where
3873 // getSnapshotBeforeUpdate is called.
3874 commitBeforeMutationEffects(root, finishedWork, lanes);
3875 } finally {
3876 // Reset the priority to the previous non-sync value.
3877 executionContext = prevExecutionContext;
3878 setCurrentUpdatePriority(previousPriority);
3879 ReactSharedInternals.T = prevTransition;
3880 }
3881 }
3882
3883 if (enableGestureTransition) {
3884 // Stop any gestures that were committed.
3885 if (isGestureRender(lanes)) {
3886 stopCommittedGesture(root);
3887 // Note that shouldStartViewTransition should always be false here because
3888 // committing a gesture never starts a new View Transition itself since it's
3889 // not a View Transition eligible lane. Only follow up Transition commits can
3890 // cause animate.
3891 }
3892 }
3893
3894 pendingEffectsStatus = PENDING_MUTATION_PHASE;
3895 if (enableViewTransition && shouldStartViewTransition) {
3896 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3897 startAnimating(lanes);
3898 }
3899 pendingViewTransition = startViewTransition(
3900 suspendedState,
3901 root.containerInfo,
3902 pendingTransitionTypes,
3903 flushMutationEffects,
3904 flushLayoutEffects,
3905 flushAfterMutationEffects,
3906 flushSpawnedWork,
3907 flushPassiveEffects,
3908 reportViewTransitionError,
3909 enableProfilerTimer ? suspendedViewTransition : (null as any),
3910 enableProfilerTimer
3911 ? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
3912 finishedViewTransition.bind(null, lanes)
3913 : (null as any),
3914 );
3915 } else {
3916 // Flush synchronously.
3917 flushMutationEffects();
3918 flushLayoutEffects();
3919 // Skip flushAfterMutationEffects
3920 flushSpawnedWork();
3921 }
3922 }
3923
3924 function reportViewTransitionError(error: mixed) {
3925 // Report errors that happens while preparing a View Transition.
3926 if (pendingEffectsStatus === NO_PENDING_EFFECTS) {
3927 return;
3928 }
3929 const root = pendingEffectsRoot;
3930 const onRecoverableError = root.onRecoverableError;
3931 onRecoverableError(error, makeErrorInfo(null));
3932 }
3933
3934 function suspendedViewTransition(reason: string): void {
3935 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3936 // We'll split the commit into two phases, because we're suspended in the middle.
3937 recordCommitEndTime();
3938 logCommitPhase(
3939 pendingSuspendedCommitReason === null
3940 ? pendingEffectsRenderEndTime
3941 : commitStartTime,
3942 commitEndTime,
3943 commitErrors,
3944 pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
3945 workInProgressUpdateTask, // TODO: Use a ViewTransition Task and this is not safe to read in this phase.
3946 );
3947 pendingSuspendedViewTransitionReason = reason;
3948 pendingSuspendedCommitReason = reason;
3949 }
3950 }
3951
3952 function finishedViewTransition(lanes: Lanes): void {
3953 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3954 if ((animatingLanes & lanes) === NoLanes) {
3955 // Was already stopped by some other action or maybe other root.
3956 return;
3957 }
3958 const task = animatingTask;
3959 stopAnimating(lanes);
3960 // If an affected track isn't in the middle of rendering or committing, log from the previous
3961 // finished render until the end of the animation.
3962 if (
3963 isGestureRender(lanes) &&
3964 !isGestureRender(workInProgressRootRenderLanes) &&
3965 !isGestureRender(pendingEffectsLanes)
3966 ) {
3967 setCurrentTrackFromLanes(GestureLane);
3968 logAnimatingPhase(gestureClampTime, now(), task);
3969 }
3970 if (
3971 includesTransitionLane(lanes) &&
3972 !includesTransitionLane(workInProgressRootRenderLanes) &&
3973 !includesTransitionLane(pendingEffectsLanes)
3974 ) {
3975 setCurrentTrackFromLanes(SomeTransitionLane);
3976 logAnimatingPhase(transitionClampTime, now(), task);
3977 }
3978 if (
3979 includesRetryLane(lanes) &&
3980 !includesRetryLane(workInProgressRootRenderLanes) &&
3981 !includesRetryLane(pendingEffectsLanes)
3982 ) {
3983 setCurrentTrackFromLanes(SomeRetryLane);
3984 logAnimatingPhase(retryClampTime, now(), task);
3985 }
3986 if (
3987 includesIdleGroupLanes(lanes) &&
3988 !includesIdleGroupLanes(workInProgressRootRenderLanes) &&
3989 !includesIdleGroupLanes(pendingEffectsLanes)
3990 ) {
3991 setCurrentTrackFromLanes(IdleLane);
3992 logAnimatingPhase(idleClampTime, now(), task);
3993 }
3994 }
3995 }
3996
3997 function flushAfterMutationEffects(): void {
3998 if (pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE) {
3999 return;
4000 }
4001 pendingEffectsStatus = NO_PENDING_EFFECTS;
4002 const root = pendingEffectsRoot;
4003 const finishedWork = pendingFinishedWork;
4004 const lanes = pendingEffectsLanes;
4005 commitAfterMutationEffects(root, finishedWork, lanes);
4006 pendingEffectsStatus = PENDING_SPAWNED_WORK;
4007 }
4008
4009 function flushMutationEffects(): void {
4010 if (pendingEffectsStatus !== PENDING_MUTATION_PHASE) {
4011 return;
4012 }
4013 pendingEffectsStatus = NO_PENDING_EFFECTS;
4014
4015 const root = pendingEffectsRoot;
4016 const finishedWork = pendingFinishedWork;
4017 const lanes = pendingEffectsLanes;
4018 const subtreeMutationHasEffects =
4019 (finishedWork.subtreeFlags & MutationMask) !== NoFlags;
4020 const rootMutationHasEffect = (finishedWork.flags & MutationMask) !== NoFlags;
4021
4022 if (subtreeMutationHasEffects || rootMutationHasEffect) {
4023 const prevTransition = ReactSharedInternals.T;
4024 ReactSharedInternals.T = null;
4025 const previousPriority = getCurrentUpdatePriority();
4026 setCurrentUpdatePriority(DiscreteEventPriority);
4027 const prevExecutionContext = executionContext;
4028 executionContext |= CommitContext;
4029 try {
4030 // The next phase is the mutation phase, where we mutate the host tree.
4031 commitMutationEffects(root, finishedWork, lanes);
4032
4033 if (enableCreateEventHandleAPI) {
4034 if (shouldFireAfterActiveInstanceBlur) {
4035 afterActiveInstanceBlur();
4036 }
4037 }
4038 resetAfterCommit(root.containerInfo);
4039 } finally {
4040 // Reset the priority to the previous non-sync value.
4041 executionContext = prevExecutionContext;
4042 setCurrentUpdatePriority(previousPriority);
4043 ReactSharedInternals.T = prevTransition;
4044 }
4045 }
4046
4047 // The work-in-progress tree is now the current tree. This must come after
4048 // the mutation phase, so that the previous tree is still current during
4049 // componentWillUnmount, but before the layout phase, so that the finished
4050 // work is current during componentDidMount/Update.
4051 root.current = finishedWork;
4052 pendingEffectsStatus = PENDING_LAYOUT_PHASE;
4053 }
4054
4055 function flushLayoutEffects(): void {
4056 if (pendingEffectsStatus !== PENDING_LAYOUT_PHASE) {
4057 return;
4058 }
4059 pendingEffectsStatus = NO_PENDING_EFFECTS;
4060
4061 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4062 const suspendedViewTransitionReason = pendingSuspendedViewTransitionReason;
4063 if (suspendedViewTransitionReason !== null) {
4064 // We suspended in the middle of the commit for the view transition.
4065 // We'll start a new commit track now.
4066 recordCommitTime();
4067 logSuspendedViewTransitionPhase(
4068 commitEndTime, // The start is the end of the first commit part.
4069 commitStartTime, // The end is the start of the second commit part.
4070 suspendedViewTransitionReason,
4071 animatingTask,
4072 );
4073 }
4074 }
4075
4076 const root = pendingEffectsRoot;
4077 const finishedWork = pendingFinishedWork;
4078 const lanes = pendingEffectsLanes;
4079
4080 if (enableDefaultTransitionIndicator) {
4081 const cleanUpIndicator = root.pendingIndicator;
4082 if (cleanUpIndicator !== null && root.indicatorLanes === NoLanes) {
4083 // We have now committed all Transitions that needed the default indicator
4084 // so we can now run the clean up function. We do this in the layout phase
4085 // so it has the same semantics as if you did it with a useLayoutEffect or
4086 // if it was reset automatically with useOptimistic.
4087 const prevTransition = ReactSharedInternals.T;
4088 ReactSharedInternals.T = null;
4089 const previousPriority = getCurrentUpdatePriority();
4090 setCurrentUpdatePriority(DiscreteEventPriority);
4091 const prevExecutionContext = executionContext;
4092 executionContext |= CommitContext;
4093 root.pendingIndicator = null;
4094 try {
4095 cleanUpIndicator();
4096 } catch (x) {
4097 reportGlobalError(x);
4098 } finally {
4099 // Reset the priority to the previous non-sync value.
4100 executionContext = prevExecutionContext;
4101 setCurrentUpdatePriority(previousPriority);
4102 ReactSharedInternals.T = prevTransition;
4103 }
4104 }
4105 }
4106
4107 const subtreeHasLayoutEffects =
4108 (finishedWork.subtreeFlags & LayoutMask) !== NoFlags;
4109 const rootHasLayoutEffect = (finishedWork.flags & LayoutMask) !== NoFlags;
4110
4111 if (subtreeHasLayoutEffects || rootHasLayoutEffect) {
4112 const prevTransition = ReactSharedInternals.T;
4113 ReactSharedInternals.T = null;
4114 const previousPriority = getCurrentUpdatePriority();
4115 setCurrentUpdatePriority(DiscreteEventPriority);
4116 const prevExecutionContext = executionContext;
4117 executionContext |= CommitContext;
4118 try {
4119 // The next phase is the layout phase, where we call effects that read
4120 // the host tree after it's been mutated. The idiomatic use case for this is
4121 // layout, but class component lifecycles also fire here for legacy reasons.
4122 if (enableSchedulingProfiler) {
4123 markLayoutEffectsStarted(lanes);
4124 }
4125 commitLayoutEffects(finishedWork, root, lanes);
4126 if (enableSchedulingProfiler) {
4127 markLayoutEffectsStopped();
4128 }
4129 } finally {
4130 // Reset the priority to the previous non-sync value.
4131 executionContext = prevExecutionContext;
4132 setCurrentUpdatePriority(previousPriority);
4133 ReactSharedInternals.T = prevTransition;
4134 }
4135 }
4136
4137 const completedRenderEndTime = pendingEffectsRenderEndTime;
4138 const suspendedCommitReason = pendingSuspendedCommitReason;
4139
4140 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4141 recordCommitEndTime();
4142 logCommitPhase(
4143 suspendedCommitReason === null ? completedRenderEndTime : commitStartTime,
4144 commitEndTime,
4145 commitErrors,
4146 pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
4147 workInProgressUpdateTask,
4148 );
4149 }
4150
4151 pendingEffectsStatus = PENDING_AFTER_MUTATION_PHASE;
4152 }
4153
4154 function flushSpawnedWork(): void {
4155 if (
4156 pendingEffectsStatus !== PENDING_SPAWNED_WORK &&
4157 // If a startViewTransition times out, we might flush this earlier than
4158 // after mutation phase. In that case, we just skip the after mutation phase.
4159 pendingEffectsStatus !== PENDING_AFTER_MUTATION_PHASE
4160 ) {
4161 return;
4162 }
4163 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4164 // If we didn't skip the after mutation phase, when is means we started an animation.
4165 const startedAnimation = pendingEffectsStatus === PENDING_SPAWNED_WORK;
4166 if (startedAnimation) {
4167 const startViewTransitionStartTime = commitEndTime;
4168 // Update the new commitEndTime to when we started the animation.
4169 recordCommitEndTime();
4170 logStartViewTransitionYieldPhase(
4171 startViewTransitionStartTime,
4172 commitEndTime,
4173 pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
4174 animatingTask,
4175 );
4176 if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) {
4177 pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT;
4178 }
4179 }
4180 }
4181
4182 pendingEffectsStatus = NO_PENDING_EFFECTS;
4183
4184 const committedViewTransition = pendingViewTransition;
4185 pendingViewTransition = null; // The view transition has now fully started.
4186
4187 // Tell Scheduler to yield at the end of the frame, so the browser has an
4188 // opportunity to paint.
4189 requestPaint();
4190
4191 const root = pendingEffectsRoot;
4192 const finishedWork = pendingFinishedWork;
4193 const lanes = pendingEffectsLanes;
4194 const recoverableErrors = pendingRecoverableErrors;
4195 const didIncludeRenderPhaseUpdate = pendingDidIncludeRenderPhaseUpdate;
4196
4197 const passiveSubtreeMask =
4198 enableViewTransition && includesOnlyViewTransitionEligibleLanes(lanes)
4199 ? PassiveTransitionMask
4200 : PassiveMask;
4201 const rootDidHavePassiveEffects = // If this subtree rendered with profiling this commit, we need to visit it to log it.
4202 (enableProfilerTimer &&
4203 enableComponentPerformanceTrack &&
4204 finishedWork.actualDuration !== 0) ||
4205 (finishedWork.subtreeFlags & passiveSubtreeMask) !== NoFlags ||
4206 (finishedWork.flags & passiveSubtreeMask) !== NoFlags;
4207
4208 if (rootDidHavePassiveEffects) {
4209 pendingEffectsStatus = PENDING_PASSIVE_PHASE;
4210 } else {
4211 pendingEffectsStatus = NO_PENDING_EFFECTS;
4212 pendingEffectsRoot = null as any; // Clear for GC purposes.
4213 pendingFinishedWork = null as any; // Clear for GC purposes.
4214 // There were no passive effects, so we can immediately release the cache
4215 // pool for this render.
4216 releaseRootPooledCache(root, root.pendingLanes);
4217 if (__DEV__) {
4218 nestedPassiveUpdateCount = 0;
4219 rootWithPassiveNestedUpdates = null;
4220 }
4221 }
4222
4223 // Read this again, since an effect might have updated it
4224 let remainingLanes = root.pendingLanes;
4225
4226 // Check if there's remaining work on this root
4227 // TODO: This is part of the `componentDidCatch` implementation. Its purpose
4228 // is to detect whether something might have called setState inside
4229 // `componentDidCatch`. The mechanism is known to be flawed because `setState`
4230 // inside `componentDidCatch` is itself flawed — that's why we recommend
4231 // `getDerivedStateFromError` instead. However, it could be improved by
4232 // checking if remainingLanes includes Sync work, instead of whether there's
4233 // any work remaining at all (which would also include stuff like Suspense
4234 // retries or transitions). It's been like this for a while, though, so fixing
4235 // it probably isn't that urgent.
4236 if (remainingLanes === NoLanes) {
4237 // If there's no remaining work, we can clear the set of already failed
4238 // error boundaries.
4239 legacyErrorBoundariesThatAlreadyFailed = null;
4240 }
4241
4242 if (__DEV__) {
4243 if (!rootDidHavePassiveEffects) {
4244 commitDoubleInvokeEffectsInDEV(root, false);
4245 }
4246 }
4247
4248 const renderPriority = lanesToEventPriority(lanes);
4249 onCommitRootDevTools(finishedWork.stateNode, renderPriority);
4250
4251 if (enableUpdaterTracking) {
4252 if (isDevToolsPresent) {
4253 root.memoizedUpdaters.clear();
4254 }
4255 }
4256
4257 if (__DEV__) {
4258 onCommitRootTestSelector();
4259 }
4260
4261 if (recoverableErrors !== null) {
4262 const prevTransition = ReactSharedInternals.T;
4263 const previousUpdateLanePriority = getCurrentUpdatePriority();
4264 setCurrentUpdatePriority(DiscreteEventPriority);
4265 ReactSharedInternals.T = null;
4266 try {
4267 // There were errors during this render, but recovered from them without
4268 // needing to surface it to the UI. We log them here.
4269 const onRecoverableError = root.onRecoverableError;
4270 for (let i = 0; i < recoverableErrors.length; i++) {
4271 const recoverableError = recoverableErrors[i];
4272 const errorInfo = makeErrorInfo(recoverableError.stack);
4273 if (__DEV__) {
4274 runWithFiberInDEV(
4275 recoverableError.source,
4276 onRecoverableError,
4277 recoverableError.value,
4278 errorInfo,
4279 );
4280 } else {
4281 onRecoverableError(recoverableError.value, errorInfo);
4282 }
4283 }
4284 } finally {
4285 ReactSharedInternals.T = prevTransition;
4286 setCurrentUpdatePriority(previousUpdateLanePriority);
4287 }
4288 }
4289
4290 if (enableViewTransition) {
4291 // We should now be after the startViewTransition's .ready call which is late enough
4292 // to start animating any pseudo-elements. We do this before flushing any passive
4293 // effects or spawned sync work since this is still part of the previous commit.
4294 // Even though conceptually it's like its own task between layout effets and passive.
4295 const pendingEvents = pendingViewTransitionEvents;
4296 let pendingTypes = pendingTransitionTypes;
4297 pendingTransitionTypes = null;
4298 if (pendingEvents !== null) {
4299 pendingViewTransitionEvents = null;
4300 if (pendingTypes === null) {
4301 // Normalize the type. This is lazily created only for events.
4302 pendingTypes = [];
4303 }
4304 if (committedViewTransition !== null) {
4305 for (let i = 0; i < pendingEvents.length; i++) {
4306 const viewTransitionEvent = pendingEvents[i];
4307 const cleanup = viewTransitionEvent(pendingTypes);
4308 if (cleanup !== undefined) {
4309 addViewTransitionFinishedListener(committedViewTransition, cleanup);
4310 }
4311 }
4312 }
4313 }
4314 }
4315
4316 // If the passive effects are the result of a discrete render, flush them
4317 // synchronously at the end of the current task so that the result is
4318 // immediately observable. Otherwise, we assume that they are not
4319 // order-dependent and do not need to be observed by external systems, so we
4320 // can wait until after paint.
4321 // TODO: We can optimize this by not scheduling the callback earlier. Since we
4322 // currently schedule the callback in multiple places, will wait until those
4323 // are consolidated.
4324 if (
4325 includesSyncLane(pendingEffectsLanes) &&
4326 (disableLegacyMode || root.tag !== LegacyRoot)
4327 ) {
4328 flushPendingEffects();
4329 }
4330
4331 // Always call this before exiting `completeRoot`, to ensure that any
4332 // additional work on this root is scheduled.
4333 ensureRootIsScheduled(root);
4334
4335 // Read this again, since a passive effect might have updated it
4336 remainingLanes = root.pendingLanes;
4337
4338 // Check if this render scheduled a cascading synchronous update. This is a
4339 // heurstic to detect infinite update loops. We are intentionally excluding
4340 // hydration lanes in this check, because render triggered by selective
4341 // hydration is conceptually not an update.
4342 if (
4343 // Was the finished render the result of an update (not hydration)?
4344 includesSomeLane(lanes, UpdateLanes) &&
4345 // Did it schedule a sync update?
4346 includesSomeLane(remainingLanes, SyncUpdateLanes)
4347 ) {
4348 if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
4349 markNestedUpdateScheduled();
4350 }
4351
4352 // Count the number of times the root synchronously re-renders without
4353 // finishing. If there are too many, it indicates an infinite update loop.
4354 if (root === rootWithNestedUpdates) {
4355 nestedUpdateCount++;
4356 } else {
4357 nestedUpdateCount = 0;
4358 rootWithNestedUpdates = root;
4359 }
4360 nestedUpdateKind = NESTED_UPDATE_SYNC_LANE;
4361 } else if (
4362 // Check if there was a recursive update spawned by this render, in either
4363 // the render phase or the commit phase. We track these explicitly because
4364 // we can't infer from the remaining lanes alone.
4365 enableInfiniteRenderLoopDetection &&
4366 (didIncludeRenderPhaseUpdate || didIncludeCommitPhaseUpdate)
4367 ) {
4368 if (enableProfilerTimer && enableProfilerNestedUpdatePhase) {
4369 markNestedUpdateScheduled();
4370 }
4371
4372 // Count the number of times the root synchronously re-renders without
4373 // finishing. If there are too many, it indicates an infinite update loop.
4374 if (root === rootWithNestedUpdates) {
4375 nestedUpdateCount++;
4376 } else {
4377 nestedUpdateCount = 0;
4378 rootWithNestedUpdates = root;
4379 }
4380 nestedUpdateKind = NESTED_UPDATE_PHASE_SPAWN;
4381 } else {
4382 nestedUpdateCount = 0;
4383 rootWithNestedUpdates = null;
4384 nestedUpdateKind = NO_NESTED_UPDATE;
4385 }
4386
4387 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4388 if (!rootDidHavePassiveEffects) {
4389 finalizeRender(lanes, commitEndTime);
4390 }
4391 }
4392
4393 // Eagerly flush any event replaying that we unblocked within this commit.
4394 // This ensures that those are observed before we render any new changes.
4395 // $FlowFixMe[constant-condition]
4396 if (supportsHydration) {
4397 flushHydrationEvents();
4398 }
4399
4400 // If layout work was scheduled, flush it now.
4401 flushSyncWorkOnAllRoots();
4402
4403 if (enableSchedulingProfiler) {
4404 markCommitStopped();
4405 }
4406
4407 if (enableTransitionTracing) {
4408 // We process transitions during passive effects. However, passive effects can be
4409 // processed synchronously during the commit phase as well as asynchronously after
4410 // paint. At the end of the commit phase, we schedule a callback that will be called
4411 // after the next paint. If the transitions have already been processed (passive
4412 // effect phase happened synchronously), we will schedule a callback to process
4413 // the transitions. However, if we don't have any pending transition callbacks, this
4414 // means that the transitions have yet to be processed (passive effects processed after paint)
4415 // so we will store the end time of paint so that we can process the transitions
4416 // and then call the callback via the correct end time.
4417 const prevRootTransitionCallbacks = root.transitionCallbacks;
4418 if (prevRootTransitionCallbacks !== null) {
4419 schedulePostPaintCallback(endTime => {
4420 const prevPendingTransitionCallbacks =
4421 currentPendingTransitionCallbacks;
4422 if (prevPendingTransitionCallbacks !== null) {
4423 currentPendingTransitionCallbacks = null;
4424 scheduleCallback(IdleSchedulerPriority, () => {
4425 processTransitionCallbacks(
4426 prevPendingTransitionCallbacks,
4427 endTime,
4428 prevRootTransitionCallbacks,
4429 );
4430 });
4431 } else {
4432 currentEndTime = endTime;
4433 }
4434 });
4435 }
4436 }
4437 }
4438
4439 function applyGestureOnRoot(
4440 root: FiberRoot,
4441 finishedWork: Fiber,
4442 recoverableErrors: null | Array<CapturedValue<mixed>>,
4443 suspendedState: null | SuspendedState,
4444 renderEndTime: number, // Profiling-only
4445 ): void {
4446 // We assume that the gesture we just rendered was the first one in the queue.
4447 const finishedGesture = root.pendingGestures;
4448 if (finishedGesture === null) {
4449 // We must have already cancelled this gesture before we had a chance to
4450 // render it. Let's schedule work on the next set of lanes.
4451 ensureRootIsScheduled(root);
4452 return;
4453 }
4454
4455 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4456 startAnimating(pendingEffectsLanes);
4457 }
4458
4459 pendingViewTransitionEvents = null;
4460
4461 const prevTransition = ReactSharedInternals.T;
4462 ReactSharedInternals.T = null;
4463 const previousPriority = getCurrentUpdatePriority();
4464 setCurrentUpdatePriority(DiscreteEventPriority);
4465 const prevExecutionContext = executionContext;
4466 executionContext |= CommitContext;
4467 try {
4468 insertDestinationClones(root, finishedWork);
4469 } finally {
4470 // Reset the priority to the previous non-sync value.
4471 executionContext = prevExecutionContext;
4472 setCurrentUpdatePriority(previousPriority);
4473 ReactSharedInternals.T = prevTransition;
4474 }
4475 pendingTransitionTypes = finishedGesture.types;
4476 pendingEffectsStatus = PENDING_GESTURE_MUTATION_PHASE;
4477
4478 pendingViewTransition = finishedGesture.running = startGestureTransition(
4479 suspendedState,
4480 root.containerInfo,
4481 finishedGesture.provider,
4482 finishedGesture.rangeStart,
4483 finishedGesture.rangeEnd,
4484 pendingTransitionTypes,
4485 flushGestureMutations,
4486 flushGestureAnimations,
4487 reportViewTransitionError,
4488 enableProfilerTimer
4489 ? // This callback fires after "pendingEffects" so we need to snapshot the arguments.
4490 finishedViewTransition.bind(null, pendingEffectsLanes)
4491 : (null as any),
4492 );
4493 }
4494
4495 function flushGestureMutations(): void {
4496 if (!enableGestureTransition) {
4497 return;
4498 }
4499 if (pendingEffectsStatus !== PENDING_GESTURE_MUTATION_PHASE) {
4500 return;
4501 }
4502 pendingEffectsStatus = NO_PENDING_EFFECTS;
4503 const root = pendingEffectsRoot;
4504 const finishedWork = pendingFinishedWork;
4505
4506 const prevTransition = ReactSharedInternals.T;
4507 ReactSharedInternals.T = null;
4508 const previousPriority = getCurrentUpdatePriority();
4509 setCurrentUpdatePriority(DiscreteEventPriority);
4510 const prevExecutionContext = executionContext;
4511 executionContext |= CommitContext;
4512 try {
4513 applyDepartureTransitions(root, finishedWork);
4514 } finally {
4515 // Reset the priority to the previous non-sync value.
4516 executionContext = prevExecutionContext;
4517 setCurrentUpdatePriority(previousPriority);
4518 ReactSharedInternals.T = prevTransition;
4519 }
4520
4521 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4522 recordCommitEndTime();
4523 logApplyGesturePhase(
4524 pendingEffectsRenderEndTime,
4525 commitEndTime,
4526 animatingTask,
4527 );
4528 }
4529
4530 pendingEffectsStatus = PENDING_GESTURE_ANIMATION_PHASE;
4531 }
4532
4533 function flushGestureAnimations(): void {
4534 if (!enableGestureTransition) {
4535 return;
4536 }
4537 // If we get canceled before we start we might not have applied
4538 // mutations yet. We need to apply them first.
4539 flushGestureMutations();
4540 if (pendingEffectsStatus !== PENDING_GESTURE_ANIMATION_PHASE) {
4541 return;
4542 }
4543
4544 const lanes = pendingEffectsLanes;
4545
4546 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4547 const startViewTransitionStartTime = commitEndTime;
4548 // Update the new commitEndTime to when we started the animation.
4549 recordCommitEndTime();
4550 logStartViewTransitionYieldPhase(
4551 startViewTransitionStartTime,
4552 commitEndTime,
4553 pendingDelayedCommitReason === ABORTED_VIEW_TRANSITION_COMMIT,
4554 animatingTask,
4555 );
4556 if (pendingDelayedCommitReason !== ABORTED_VIEW_TRANSITION_COMMIT) {
4557 pendingDelayedCommitReason = ANIMATION_STARTED_COMMIT;
4558 }
4559 }
4560
4561 pendingEffectsStatus = NO_PENDING_EFFECTS;
4562 const root = pendingEffectsRoot;
4563 const finishedWork = pendingFinishedWork;
4564 pendingEffectsRoot = null as any; // Clear for GC purposes.
4565 pendingFinishedWork = null as any; // Clear for GC purposes.
4566 pendingEffectsLanes = NoLanes;
4567
4568 pendingViewTransition = null; // The view transition has now fully started.
4569
4570 const prevTransition = ReactSharedInternals.T;
4571 ReactSharedInternals.T = null;
4572 const previousPriority = getCurrentUpdatePriority();
4573 setCurrentUpdatePriority(DiscreteEventPriority);
4574 const prevExecutionContext = executionContext;
4575 executionContext |= CommitContext;
4576 try {
4577 startGestureAnimations(root, finishedWork);
4578 } finally {
4579 // Reset the priority to the previous non-sync value.
4580 executionContext = prevExecutionContext;
4581 setCurrentUpdatePriority(previousPriority);
4582 ReactSharedInternals.T = prevTransition;
4583 }
4584
4585 if (enableViewTransition) {
4586 // We should now be after the startGestureTransition's .ready call which is late enough
4587 // to start animating any pseudo-elements. We have also already applied any adjustments
4588 // we do to the built-in animations which can now be read by the refs.
4589 const pendingEvents = pendingViewTransitionEvents;
4590 let pendingTypes = pendingTransitionTypes;
4591 pendingTransitionTypes = null;
4592 if (pendingEvents !== null) {
4593 pendingViewTransitionEvents = null;
4594 if (pendingTypes === null) {
4595 // Normalize the type. This is lazily created only for events.
4596 pendingTypes = [];
4597 }
4598 const appliedGesture = root.pendingGestures;
4599 if (appliedGesture !== null) {
4600 const runningTransition = appliedGesture.running;
4601 if (runningTransition !== null) {
4602 for (let i = 0; i < pendingEvents.length; i++) {
4603 const viewTransitionEvent = pendingEvents[i];
4604 const cleanup = viewTransitionEvent(pendingTypes);
4605 if (cleanup !== undefined) {
4606 addViewTransitionFinishedListener(runningTransition, cleanup);
4607 }
4608 }
4609 }
4610 }
4611 }
4612 }
4613
4614 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4615 finalizeRender(lanes, commitEndTime);
4616 }
4617
4618 // Now that we've rendered this lane. Start working on the next lane.
4619 ensureRootIsScheduled(root);
4620 }
4621
4622 function makeErrorInfo(componentStack: ?string) {
4623 const errorInfo = {
4624 componentStack,
4625 };
4626 if (__DEV__) {
4627 Object.defineProperty(errorInfo as any, 'digest', {
4628 get() {
4629 console.error(
4630 'You are accessing "digest" from the errorInfo object passed to onRecoverableError.' +
4631 ' This property is no longer provided as part of errorInfo but can be accessed as a property' +
4632 ' of the Error instance itself.',
4633 );
4634 },
4635 });
4636 }
4637 return errorInfo;
4638 }
4639
4640 function releaseRootPooledCache(root: FiberRoot, remainingLanes: Lanes) {
4641 const pooledCacheLanes = (root.pooledCacheLanes &= remainingLanes);
4642 if (pooledCacheLanes === NoLanes) {
4643 // None of the remaining work relies on the cache pool. Clear it so
4644 // subsequent requests get a new cache
4645 const pooledCache = root.pooledCache;
4646 if (pooledCache != null) {
4647 root.pooledCache = null;
4648 releaseCache(pooledCache);
4649 }
4650 }
4651 }
4652
4653 let didWarnAboutInterruptedViewTransitions = false;
4654
4655 export function flushPendingEffectsDelayed(): boolean {
4656 if (pendingDelayedCommitReason === IMMEDIATE_COMMIT) {
4657 pendingDelayedCommitReason = DELAYED_PASSIVE_COMMIT;
4658 }
4659 return flushPendingEffects();
4660 }
4661
4662 export function flushPendingEffects(): boolean {
4663 // Returns whether passive effects were flushed.
4664 if (enableViewTransition && pendingViewTransition !== null) {
4665 // If we forced a flush before the View Transition full started then we skip it.
4666 // This ensures that we're not running a partial animation.
4667 stopViewTransition(pendingViewTransition);
4668 if (__DEV__) {
4669 if (!didWarnAboutInterruptedViewTransitions) {
4670 didWarnAboutInterruptedViewTransitions = true;
4671 console.warn(
4672 'A flushSync update cancelled a View Transition because it was called ' +
4673 'while the View Transition was still preparing. To preserve the synchronous ' +
4674 'semantics, React had to skip the View Transition. If you can, try to avoid ' +
4675 "flushSync() in a scenario that's likely to interfere.",
4676 );
4677 }
4678 }
4679 pendingViewTransition = null;
4680 pendingDelayedCommitReason = ABORTED_VIEW_TRANSITION_COMMIT;
4681 }
4682 flushGestureMutations();
4683 flushGestureAnimations();
4684 flushMutationEffects();
4685 flushLayoutEffects();
4686 // Skip flushAfterMutation if we're forcing this early.
4687 flushSpawnedWork();
4688 return flushPassiveEffects();
4689 }
4690
4691 function flushPassiveEffects(): boolean {
4692 if (pendingEffectsStatus !== PENDING_PASSIVE_PHASE) {
4693 return false;
4694 }
4695 // TODO: Merge flushPassiveEffectsImpl into this function. I believe they were only separate
4696 // in the first place because we used to wrap it with
4697 // `Scheduler.runWithPriority`, which accepts a function. But now we track the
4698 // priority within React itself, so we can mutate the variable directly.
4699 // Cache the root since pendingEffectsRoot is cleared in
4700 // flushPassiveEffectsImpl
4701 const root = pendingEffectsRoot;
4702 // Cache and clear the remaining lanes flag; it must be reset since this
4703 // method can be called from various places, not always from completeRoot
4704 // where the remaining lanes are known
4705 const remainingLanes = pendingEffectsRemainingLanes;
4706 pendingEffectsRemainingLanes = NoLanes;
4707
4708 const renderPriority = lanesToEventPriority(pendingEffectsLanes);
4709 const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
4710 const prevTransition = ReactSharedInternals.T;
4711 const previousPriority = getCurrentUpdatePriority();
4712
4713 try {
4714 setCurrentUpdatePriority(priority);
4715 ReactSharedInternals.T = null;
4716 return flushPassiveEffectsImpl();
4717 } finally {
4718 setCurrentUpdatePriority(previousPriority);
4719 ReactSharedInternals.T = prevTransition;
4720
4721 // Once passive effects have run for the tree - giving components a
4722 // chance to retain cache instances they use - release the pooled
4723 // cache at the root (if there is one)
4724 releaseRootPooledCache(root, remainingLanes);
4725 }
4726 }
4727
4728 function flushPassiveEffectsImpl() {
4729 // Cache and clear the transitions flag
4730 const transitions = pendingPassiveTransitions;
4731 pendingPassiveTransitions = null;
4732
4733 const root = pendingEffectsRoot;
4734 const lanes = pendingEffectsLanes;
4735 pendingEffectsStatus = NO_PENDING_EFFECTS;
4736 pendingEffectsRoot = null as any; // Clear for GC purposes.
4737 pendingFinishedWork = null as any; // Clear for GC purposes.
4738 // TODO: This is sometimes out of sync with pendingEffectsRoot.
4739 // Figure out why and fix it. It's not causing any known issues (probably
4740 // because it's only used for profiling), but it's a refactor hazard.
4741 pendingEffectsLanes = NoLanes;
4742
4743 if (enableYieldingBeforePassive) {
4744 // We've finished our work for this render pass.
4745 root.callbackNode = null;
4746 root.callbackPriority = NoLane;
4747 }
4748
4749 if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
4750 throw new Error('Cannot flush passive effects while already rendering.');
4751 }
4752
4753 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4754 // We're about to log a lot of profiling for this commit.
4755 // We set this once so we don't have to recompute it for every log.
4756 setCurrentTrackFromLanes(lanes);
4757 }
4758
4759 if (__DEV__) {
4760 isFlushingPassiveEffects = true;
4761 didScheduleUpdateDuringPassiveEffects = false;
4762 }
4763
4764 let passiveEffectStartTime = 0;
4765 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4766 resetCommitErrors();
4767 passiveEffectStartTime = now();
4768 if (pendingDelayedCommitReason === ANIMATION_STARTED_COMMIT) {
4769 // The animation was started, so we've been animating since that happened.
4770 logAnimatingPhase(commitEndTime, passiveEffectStartTime, animatingTask);
4771 } else {
4772 logPaintYieldPhase(
4773 commitEndTime,
4774 passiveEffectStartTime,
4775 pendingDelayedCommitReason === DELAYED_PASSIVE_COMMIT,
4776 workInProgressUpdateTask,
4777 );
4778 }
4779 }
4780
4781 if (enableSchedulingProfiler) {
4782 markPassiveEffectsStarted(lanes);
4783 }
4784
4785 const prevExecutionContext = executionContext;
4786 executionContext |= CommitContext;
4787
4788 commitPassiveUnmountEffects(root.current);
4789 commitPassiveMountEffects(
4790 root,
4791 root.current,
4792 lanes,
4793 transitions,
4794 pendingEffectsRenderEndTime,
4795 );
4796
4797 if (enableSchedulingProfiler) {
4798 markPassiveEffectsStopped();
4799 }
4800
4801 if (__DEV__) {
4802 commitDoubleInvokeEffectsInDEV(root, true);
4803 }
4804
4805 executionContext = prevExecutionContext;
4806
4807 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4808 const passiveEffectsEndTime = now();
4809 logPassiveCommitPhase(
4810 passiveEffectStartTime,
4811 passiveEffectsEndTime,
4812 commitErrors,
4813 workInProgressUpdateTask,
4814 );
4815 finalizeRender(lanes, passiveEffectsEndTime);
4816 }
4817
4818 flushSyncWorkOnAllRoots();
4819
4820 if (enableTransitionTracing) {
4821 const prevPendingTransitionCallbacks = currentPendingTransitionCallbacks;
4822 const prevRootTransitionCallbacks = root.transitionCallbacks;
4823 const prevEndTime = currentEndTime;
4824 if (
4825 prevPendingTransitionCallbacks !== null &&
4826 prevRootTransitionCallbacks !== null &&
4827 prevEndTime !== null
4828 ) {
4829 currentPendingTransitionCallbacks = null;
4830 currentEndTime = null;
4831 scheduleCallback(IdleSchedulerPriority, () => {
4832 processTransitionCallbacks(
4833 prevPendingTransitionCallbacks,
4834 prevEndTime,
4835 prevRootTransitionCallbacks,
4836 );
4837 });
4838 }
4839 }
4840
4841 if (__DEV__) {
4842 // If additional passive effects were scheduled, increment a counter. If this
4843 // exceeds the limit, we'll fire a warning.
4844 if (didScheduleUpdateDuringPassiveEffects) {
4845 if (root === rootWithPassiveNestedUpdates) {
4846 nestedPassiveUpdateCount++;
4847 } else {
4848 nestedPassiveUpdateCount = 0;
4849 rootWithPassiveNestedUpdates = root;
4850 }
4851 } else {
4852 nestedPassiveUpdateCount = 0;
4853 }
4854 isFlushingPassiveEffects = false;
4855 didScheduleUpdateDuringPassiveEffects = false;
4856 }
4857
4858 if (enableYieldingBeforePassive) {
4859 // Next, we reschedule any remaining work in a new task since it's a new
4860 // sequence of work. We wait until the end to do this in case the passive
4861 // effect schedules higher priority work than we had remaining. That way
4862 // we don't schedule an early callback that gets cancelled anyway.
4863 ensureRootIsScheduled(root);
4864 }
4865
4866 // TODO: Move to commitPassiveMountEffects
4867 onPostCommitRootDevTools(root);
4868 if (enableProfilerTimer && enableProfilerCommitHooks) {
4869 const stateNode = root.current.stateNode;
4870 stateNode.effectDuration = 0;
4871 stateNode.passiveEffectDuration = 0;
4872 }
4873
4874 return true;
4875 }
4876
4877 export function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {
4878 return (
4879 legacyErrorBoundariesThatAlreadyFailed !== null &&
4880 legacyErrorBoundariesThatAlreadyFailed.has(instance)
4881 );
4882 }
4883
4884 export function markLegacyErrorBoundaryAsFailed(instance: mixed) {
4885 if (legacyErrorBoundariesThatAlreadyFailed === null) {
4886 legacyErrorBoundariesThatAlreadyFailed = new Set([instance]);
4887 } else {
4888 legacyErrorBoundariesThatAlreadyFailed.add(instance);
4889 }
4890 }
4891
4892 function captureCommitPhaseErrorOnRoot(
4893 rootFiber: Fiber,
4894 sourceFiber: Fiber,
4895 error: mixed,
4896 ) {
4897 const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
4898 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4899 recordEffectError(errorInfo);
4900 }
4901 const update = createRootErrorUpdate(
4902 rootFiber.stateNode,
4903 errorInfo,
4904 SyncLane as Lane,
4905 );
4906 const root = enqueueUpdate(rootFiber, update, SyncLane as Lane);
4907 if (root !== null) {
4908 markRootUpdated(root, SyncLane);
4909 ensureRootIsScheduled(root);
4910 }
4911 }
4912
4913 export function captureCommitPhaseError(
4914 sourceFiber: Fiber,
4915 nearestMountedAncestor: Fiber | null,
4916 error: mixed,
4917 ) {
4918 if (__DEV__) {
4919 setIsRunningInsertionEffect(false);
4920 }
4921 if (sourceFiber.tag === HostRoot) {
4922 // Error was thrown at the root. There is no parent, so the root
4923 // itself should capture it.
4924 captureCommitPhaseErrorOnRoot(sourceFiber, sourceFiber, error);
4925 return;
4926 }
4927
4928 let fiber = nearestMountedAncestor;
4929 while (fiber !== null) {
4930 if (fiber.tag === HostRoot) {
4931 captureCommitPhaseErrorOnRoot(fiber, sourceFiber, error);
4932 return;
4933 } else if (fiber.tag === ClassComponent) {
4934 const ctor = fiber.type;
4935 const instance = fiber.stateNode;
4936 if (
4937 typeof ctor.getDerivedStateFromError === 'function' ||
4938 (typeof instance.componentDidCatch === 'function' &&
4939 !isAlreadyFailedLegacyErrorBoundary(instance))
4940 ) {
4941 const errorInfo = createCapturedValueAtFiber(error, sourceFiber);
4942 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4943 recordEffectError(errorInfo);
4944 }
4945 const update = createClassErrorUpdate(SyncLane as Lane);
4946 const root = enqueueUpdate(fiber, update, SyncLane as Lane);
4947 if (root !== null) {
4948 initializeClassErrorUpdate(update, root, fiber, errorInfo);
4949 markRootUpdated(root, SyncLane);
4950 ensureRootIsScheduled(root);
4951 }
4952 return;
4953 }
4954 }
4955 fiber = fiber.return;
4956 }
4957
4958 if (__DEV__) {
4959 console.error(
4960 'Internal React error: Attempted to capture a commit phase error ' +
4961 'inside a detached tree. This indicates a bug in React. Potential ' +
4962 'causes include deleting the same fiber more than once, committing an ' +
4963 'already-finished tree, or an inconsistent return pointer.\n\n' +
4964 'Error message:\n\n%s',
4965 error,
4966 );
4967 }
4968 }
4969
4970 export function attachPingListener(
4971 root: FiberRoot,
4972 wakeable: Wakeable,
4973 lanes: Lanes,
4974 ) {
4975 // Attach a ping listener
4976 //
4977 // The data might resolve before we have a chance to commit the fallback. Or,
4978 // in the case of a refresh, we'll never commit a fallback. So we need to
4979 // attach a listener now. When it resolves ("pings"), we can decide whether to
4980 // try rendering the tree again.
4981 //
4982 // Only attach a listener if one does not already exist for the lanes
4983 // we're currently rendering (which acts like a "thread ID" here).
4984 //
4985 // We only need to do this in concurrent mode. Legacy Suspense always
4986 // commits fallbacks synchronously, so there are no pings.
4987 let pingCache = root.pingCache;
4988 let threadIDs;
4989 if (pingCache === null) {
4990 pingCache = root.pingCache = new PossiblyWeakMap();
4991 threadIDs = new Set<mixed>();
4992 pingCache.set(wakeable, threadIDs);
4993 } else {
4994 threadIDs = pingCache.get(wakeable);
4995 if (threadIDs === undefined) {
4996 threadIDs = new Set();
4997 pingCache.set(wakeable, threadIDs);
4998 }
4999 }
5000 if (!threadIDs.has(lanes)) {
Showing first 5,000 of 5,683 lines. View raw