main
js 5,618 lines 187 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {
11 Instance,
12 TextInstance,
13 ActivityInstance,
14 SuspenseInstance,
15 Container,
16 HoistableRoot,
17 FormInstance,
18 Props,
19 SuspendedState,
20 } from './ReactFiberConfig';
21 import type {Fiber, FiberRoot} from './ReactInternalTypes';
22 import type {Lanes} from './ReactFiberLane';
23 import {
24 includesLoadingIndicatorLanes,
25 includesOnlySuspenseyCommitEligibleLanes,
26 includesOnlyViewTransitionEligibleLanes,
27 } from './ReactFiberLane';
28 import type {ActivityState} from './ReactFiberActivityComponent';
29 import type {SuspenseState, RetryQueue} from './ReactFiberSuspenseComponent';
30 import type {UpdateQueue} from './ReactFiberClassUpdateQueue';
31 import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';
32 import type {Wakeable, ViewTransitionProps} from 'shared/ReactTypes';
33 import type {
34 OffscreenState,
35 OffscreenInstance,
36 OffscreenQueue,
37 } from './ReactFiberOffscreenComponent';
38 import type {Cache} from './ReactFiberCacheComponent';
39 import type {RootState} from './ReactFiberRoot';
40 import type {Transition} from 'react/src/ReactStartTransition';
41 import type {
42 TracingMarkerInstance,
43 TransitionAbort,
44 } from './ReactFiberTracingMarkerComponent';
45 import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';
46
47 import {
48 alwaysThrottleRetries,
49 enableCreateEventHandleAPI,
50 enableEffectEventMutationPhase,
51 enableProfilerTimer,
52 enableProfilerCommitHooks,
53 enableSuspenseCallback,
54 enableScopeAPI,
55 enableUpdaterTracking,
56 enableTransitionTracing,
57 enableLegacyHidden,
58 disableLegacyMode,
59 enableComponentPerformanceTrack,
60 enableViewTransition,
61 enableFragmentRefs,
62 enableDefaultTransitionIndicator,
63 enableFragmentRefsTextNodes,
64 } from 'shared/ReactFeatureFlags';
65 import {
66 FunctionComponent,
67 ForwardRef,
68 ClassComponent,
69 HostRoot,
70 HostComponent,
71 HostHoistable,
72 HostSingleton,
73 HostText,
74 HostPortal,
75 Profiler,
76 ActivityComponent,
77 SuspenseComponent,
78 DehydratedFragment,
79 IncompleteClassComponent,
80 MemoComponent,
81 SimpleMemoComponent,
82 SuspenseListComponent,
83 ScopeComponent,
84 OffscreenComponent,
85 LegacyHiddenComponent,
86 CacheComponent,
87 TracingMarkerComponent,
88 ViewTransitionComponent,
89 Fragment,
90 } from './ReactWorkTags';
91 import {
92 NoFlags,
93 ContentReset,
94 Placement,
95 ChildDeletion,
96 Snapshot,
97 Update,
98 Hydrate,
99 Callback,
100 Ref,
101 Hydrating,
102 Passive,
103 BeforeMutationMask,
104 BeforeAndAfterMutationTransitionMask,
105 MutationMask,
106 LayoutMask,
107 PassiveMask,
108 PassiveTransitionMask,
109 Visibility,
110 ShouldSuspendCommit,
111 MaySuspendCommit,
112 FormReset,
113 Cloned,
114 PerformedWork,
115 ForceClientRender,
116 DidCapture,
117 AffectedParentLayout,
118 ViewTransitionNamedStatic,
119 PortalStatic,
120 } from './ReactFiberFlags';
121 import {
122 commitStartTime,
123 pushNestedEffectDurations,
124 popNestedEffectDurations,
125 bubbleNestedEffectDurations,
126 resetComponentEffectTimers,
127 pushComponentEffectStart,
128 popComponentEffectStart,
129 pushComponentEffectDuration,
130 popComponentEffectDuration,
131 pushComponentEffectErrors,
132 popComponentEffectErrors,
133 pushComponentEffectDidSpawnUpdate,
134 popComponentEffectDidSpawnUpdate,
135 componentEffectStartTime,
136 componentEffectEndTime,
137 componentEffectDuration,
138 componentEffectErrors,
139 componentEffectSpawnedUpdate,
140 } from './ReactProfilerTimer';
141 import {
142 logComponentRender,
143 logComponentErrored,
144 logComponentEffect,
145 logComponentMount,
146 logComponentUnmount,
147 logComponentReappeared,
148 logComponentDisappeared,
149 pushDeepEquality,
150 popDeepEquality,
151 } from './ReactFiberPerformanceTrack';
152 import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';
153 import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';
154 import {
155 supportsMutation,
156 supportsPersistence,
157 supportsHydration,
158 supportsResources,
159 supportsSingletons,
160 clearSuspenseBoundary,
161 clearSuspenseBoundaryFromContainer,
162 createContainerChildSet,
163 clearContainer,
164 prepareScopeUpdate,
165 prepareForCommit,
166 beforeActiveInstanceBlur,
167 detachDeletedInstance,
168 getHoistableRoot,
169 acquireResource,
170 releaseResource,
171 hydrateHoistable,
172 createHoistableInstance,
173 mountHoistable,
174 unmountHoistable,
175 prepareToCommitHoistables,
176 maySuspendCommitInSyncRender,
177 suspendInstance,
178 suspendResource,
179 resetFormInstance,
180 registerSuspenseInstanceRetry,
181 cancelViewTransitionName,
182 cancelRootViewTransitionName,
183 restoreRootViewTransitionName,
184 isSingletonScope,
185 updateFragmentInstanceFiber,
186 } from './ReactFiberConfig';
187 import {
188 captureCommitPhaseError,
189 resolveRetryWakeable,
190 markCommitTimeOfFallback,
191 restorePendingUpdaters,
192 addTransitionStartCallbackToPendingTransition,
193 addTransitionProgressCallbackToPendingTransition,
194 addTransitionCompleteCallbackToPendingTransition,
195 addMarkerProgressCallbackToPendingTransition,
196 addMarkerIncompleteCallbackToPendingTransition,
197 addMarkerCompleteCallbackToPendingTransition,
198 retryDehydratedSuspenseBoundary,
199 scheduleViewTransitionEvent,
200 } from './ReactFiberWorkLoop';
201 import {
202 HasEffect as HookHasEffect,
203 Layout as HookLayout,
204 Insertion as HookInsertion,
205 Passive as HookPassive,
206 } from './ReactHookEffectTags';
207 import {doesFiberContain} from './ReactFiberTreeReflection';
208 import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';
209 import {releaseCache, retainCache} from './ReactFiberCacheComponent';
210 import {clearTransitionsForLanes} from './ReactFiberLane';
211 import {
212 OffscreenVisible,
213 OffscreenPassiveEffectsConnected,
214 } from './ReactFiberOffscreenComponent';
215 import {
216 TransitionRoot,
217 TransitionTracingMarker,
218 } from './ReactFiberTracingMarkerComponent';
219 import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';
220 import {
221 commitHookLayoutEffects,
222 commitHookLayoutUnmountEffects,
223 commitHookEffectListMount,
224 commitHookEffectListUnmount,
225 commitHookPassiveMountEffects,
226 commitHookPassiveUnmountEffects,
227 commitClassLayoutLifecycles,
228 commitClassDidMount,
229 commitClassCallbacks,
230 commitClassHiddenCallbacks,
231 commitClassSnapshot,
232 safelyCallComponentWillUnmount,
233 safelyAttachRef,
234 safelyDetachRef,
235 commitProfilerUpdate,
236 commitProfilerPostCommit,
237 commitRootCallbacks,
238 } from './ReactFiberCommitEffects';
239 import {
240 commitHostMount,
241 commitHostHydratedInstance,
242 commitHostUpdate,
243 commitHostTextUpdate,
244 commitHostResetTextContent,
245 commitShowHideSuspenseBoundary,
246 commitShowHideHostInstance,
247 commitShowHideHostTextInstance,
248 commitHostPlacement,
249 commitHostRootContainerChildren,
250 commitHostPortalContainerChildren,
251 commitHostHydratedContainer,
252 commitHostHydratedActivity,
253 commitHostHydratedSuspense,
254 commitHostRemoveChildFromContainer,
255 commitHostRemoveChild,
256 commitHostSingletonAcquisition,
257 commitHostSingletonRelease,
258 } from './ReactFiberCommitHostEffects';
259 import {
260 commitFragmentInstanceDeletionEffects,
261 commitFragmentInstanceInsertionEffects,
262 } from './ReactFiberFragmentInstance';
263 import {
264 trackEnterViewTransitions,
265 commitEnterViewTransitions,
266 commitExitViewTransitions,
267 commitBeforeUpdateViewTransition,
268 commitNestedViewTransitions,
269 restoreEnterOrExitViewTransitions,
270 restoreUpdateViewTransition,
271 restoreNestedViewTransitions,
272 measureUpdateViewTransition,
273 measureNestedViewTransitions,
274 resetAppearingViewTransitions,
275 trackAppearingViewTransition,
276 viewTransitionCancelableChildren,
277 pushViewTransitionCancelableScope,
278 popViewTransitionCancelableScope,
279 } from './ReactFiberCommitViewTransitions';
280 import {
281 viewTransitionMutationContext,
282 pushRootMutationContext,
283 pushMutationContext,
284 popMutationContext,
285 rootMutationContext,
286 } from './ReactFiberMutationTracking';
287 import {
288 trackNamedViewTransition,
289 untrackNamedViewTransition,
290 } from './ReactFiberDuplicateViewTransitions';
291 import {markIndicatorHandled} from './ReactFiberRootScheduler';
292 import type {Flags} from './ReactFiberFlags';
293
294 type LayoutEffectTraversalFlags = number;
295
296 const NoLayoutEffectTraversalFlags = /* */ 0b00;
297 const IncludeWorkInProgressEffects = /* */ 0b01;
298 const IncludeHostSingletons = /* */ 0b10;
299
300 // Used during the commit phase to track the state of the Offscreen component stack.
301 // Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.
302 let offscreenSubtreeIsHidden: boolean = false;
303 let offscreenSubtreeWasHidden: boolean = false;
304 // Track whether there's a hidden offscreen above with no HostComponent between. If so,
305 // it overrides the hiddenness of the HostComponent below.
306 let offscreenDirectParentIsHidden: boolean = false;
307
308 // Used to track if a form needs to be reset at the end of the mutation phase.
309 let needsFormReset = false;
310
311 const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;
312
313 let nextEffect: Fiber | null = null;
314
315 // Used for Profiling builds to track updaters.
316 let inProgressLanes: Lanes | null = null;
317 let inProgressRoot: FiberRoot | null = null;
318
319 let focusedInstanceHandle: null | Fiber = null;
320 export let shouldFireAfterActiveInstanceBlur: boolean = false;
321
322 // Used during the commit phase to track whether a parent ViewTransition component
323 // might have been affected by any mutations / relayouts below.
324 let viewTransitionContextChanged: boolean = false;
325 let inUpdateViewTransition: boolean = false;
326 let rootViewTransitionAffected: boolean = false;
327 let rootViewTransitionNameCanceled: boolean = false;
328
329 function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {
330 if (finishedWork.tag === ActivityComponent) {
331 const prevState: ActivityState | null = current.memoizedState;
332 const nextState: ActivityState | null = finishedWork.memoizedState;
333 return prevState !== null && nextState === null;
334 } else if (finishedWork.tag === SuspenseComponent) {
335 const prevState: SuspenseState | null = current.memoizedState;
336 const nextState: SuspenseState | null = finishedWork.memoizedState;
337 return (
338 prevState !== null &&
339 prevState.dehydrated !== null &&
340 (nextState === null || nextState.dehydrated === null)
341 );
342 } else if (finishedWork.tag === HostRoot) {
343 return (
344 (current.memoizedState as RootState).isDehydrated &&
345 (finishedWork.flags & ForceClientRender) === NoFlags
346 );
347 } else {
348 return false;
349 }
350 }
351
352 export function commitBeforeMutationEffects(
353 root: FiberRoot,
354 firstChild: Fiber,
355 committedLanes: Lanes,
356 ): void {
357 focusedInstanceHandle = prepareForCommit(root.containerInfo);
358 shouldFireAfterActiveInstanceBlur = false;
359
360 const isViewTransitionEligible =
361 enableViewTransition &&
362 includesOnlyViewTransitionEligibleLanes(committedLanes);
363
364 nextEffect = firstChild;
365 commitBeforeMutationEffects_begin(isViewTransitionEligible);
366
367 // We no longer need to track the active instance fiber
368 focusedInstanceHandle = null;
369 // We've found any matched pairs and can now reset.
370 resetAppearingViewTransitions();
371 }
372
373 function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {
374 // If this commit is eligible for a View Transition we look into all mutated subtrees.
375 // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.
376 const subtreeMask = isViewTransitionEligible
377 ? BeforeAndAfterMutationTransitionMask
378 : BeforeMutationMask;
379 while (nextEffect !== null) {
380 const fiber = nextEffect;
381
382 // This phase is only used for beforeActiveInstanceBlur.
383 // Let's skip the whole loop if it's off.
384 if (enableCreateEventHandleAPI || isViewTransitionEligible) {
385 // TODO: Should wrap this in flags check, too, as optimization
386 const deletions = fiber.deletions;
387 if (deletions !== null) {
388 for (let i = 0; i < deletions.length; i++) {
389 const deletion = deletions[i];
390 commitBeforeMutationEffectsDeletion(
391 deletion,
392 isViewTransitionEligible,
393 );
394 }
395 }
396 }
397
398 if (
399 enableViewTransition &&
400 fiber.alternate === null &&
401 (fiber.flags & Placement) !== NoFlags
402 ) {
403 // Skip before mutation effects of the children because we don't want
404 // to trigger updates of any nested view transitions and we shouldn't
405 // have any other before mutation effects since snapshot effects are
406 // only applied to updates. TODO: Model this using only flags.
407 if (isViewTransitionEligible) {
408 trackEnterViewTransitions(fiber);
409 }
410 commitBeforeMutationEffects_complete(isViewTransitionEligible);
411 continue;
412 }
413
414 // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.
415 if (enableViewTransition && fiber.tag === OffscreenComponent) {
416 const isModernRoot =
417 disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;
418 if (isModernRoot) {
419 const current = fiber.alternate;
420 const isHidden = fiber.memoizedState !== null;
421 if (isHidden) {
422 if (
423 current !== null &&
424 current.memoizedState === null &&
425 isViewTransitionEligible
426 ) {
427 // Was previously mounted as visible but is now hidden.
428 commitExitViewTransitions(current);
429 }
430 // Skip before mutation effects of the children because they're hidden.
431 commitBeforeMutationEffects_complete(isViewTransitionEligible);
432 continue;
433 } else if (current !== null && current.memoizedState !== null) {
434 // Was previously mounted as hidden but is now visible.
435 // Skip before mutation effects of the children because we don't want
436 // to trigger updates of any nested view transitions and we shouldn't
437 // have any other before mutation effects since snapshot effects are
438 // only applied to updates. TODO: Model this using only flags.
439 if (isViewTransitionEligible) {
440 trackEnterViewTransitions(fiber);
441 }
442 commitBeforeMutationEffects_complete(isViewTransitionEligible);
443 continue;
444 }
445 }
446 }
447
448 const child = fiber.child;
449 if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {
450 child.return = fiber;
451 nextEffect = child;
452 } else {
453 if (isViewTransitionEligible) {
454 // We are inside an updated subtree. Any mutations that affected the
455 // parent HostInstance's layout or set of children (such as reorders)
456 // might have also affected the positioning or size of the inner
457 // ViewTransitions. Therefore we need to find them inside.
458 commitNestedViewTransitions(fiber);
459 }
460 commitBeforeMutationEffects_complete(isViewTransitionEligible);
461 }
462 }
463 }
464
465 function commitBeforeMutationEffects_complete(
466 isViewTransitionEligible: boolean,
467 ) {
468 while (nextEffect !== null) {
469 const fiber = nextEffect;
470 commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);
471
472 const sibling = fiber.sibling;
473 if (sibling !== null) {
474 sibling.return = fiber.return;
475 nextEffect = sibling;
476 return;
477 }
478
479 nextEffect = fiber.return;
480 }
481 }
482
483 function commitBeforeMutationEffectsOnFiber(
484 finishedWork: Fiber,
485 isViewTransitionEligible: boolean,
486 ) {
487 const current = finishedWork.alternate;
488 const flags = finishedWork.flags;
489
490 if (enableCreateEventHandleAPI) {
491 if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
492 // Check to see if the focused element was inside of a hidden (Suspense) subtree.
493 // TODO: Move this out of the hot path using a dedicated effect tag.
494 // TODO: This should consider Offscreen in general and not just SuspenseComponent.
495 if (
496 finishedWork.tag === SuspenseComponent &&
497 isSuspenseBoundaryBeingHidden(current, finishedWork) &&
498 // $FlowFixMe[incompatible-type] found when upgrading Flow
499 doesFiberContain(finishedWork, focusedInstanceHandle)
500 ) {
501 shouldFireAfterActiveInstanceBlur = true;
502 beforeActiveInstanceBlur(finishedWork);
503 }
504 }
505 }
506
507 switch (finishedWork.tag) {
508 case FunctionComponent:
509 case ForwardRef:
510 case SimpleMemoComponent: {
511 if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {
512 const updateQueue: FunctionComponentUpdateQueue | null =
513 finishedWork.updateQueue as any;
514 const eventPayloads = updateQueue !== null ? updateQueue.events : null;
515 if (eventPayloads !== null) {
516 for (let ii = 0; ii < eventPayloads.length; ii++) {
517 const {ref, nextImpl} = eventPayloads[ii];
518 ref.impl = nextImpl;
519 }
520 }
521 }
522 break;
523 }
524 case ClassComponent: {
525 if ((flags & Snapshot) !== NoFlags) {
526 if (current !== null) {
527 commitClassSnapshot(finishedWork, current);
528 }
529 }
530 break;
531 }
532 case HostRoot: {
533 if ((flags & Snapshot) !== NoFlags) {
534 // $FlowFixMe[constant-condition]
535 if (supportsMutation) {
536 const root = finishedWork.stateNode;
537 clearContainer(root.containerInfo);
538 }
539 }
540 break;
541 }
542 case HostComponent:
543 case HostHoistable:
544 case HostSingleton:
545 case HostText:
546 case HostPortal:
547 case IncompleteClassComponent:
548 // Nothing to do for these component types
549 break;
550 case ViewTransitionComponent:
551 if (enableViewTransition) {
552 if (isViewTransitionEligible) {
553 if (current === null) {
554 // This is a new mount. We should have handled this as part of the
555 // Placement effect or it is deeper inside a entering transition.
556 } else {
557 // Something may have mutated within this subtree. This might need to cause
558 // a cross-fade of this parent. We first assign old names to the
559 // previous tree in the before mutation phase in case we need to.
560 // TODO: This walks the tree that we might continue walking anyway.
561 // We should just stash the parent ViewTransitionComponent and continue
562 // walking the tree until we find HostComponent but to do that we need
563 // to use a stack which requires refactoring this phase.
564 commitBeforeUpdateViewTransition(current, finishedWork);
565 }
566 }
567 break;
568 }
569 // Fallthrough
570 default: {
571 if ((flags & Snapshot) !== NoFlags) {
572 throw new Error(
573 'This unit of work tag should not have side-effects. This error is ' +
574 'likely caused by a bug in React. Please file an issue.',
575 );
576 }
577 }
578 }
579 }
580
581 function commitBeforeMutationEffectsDeletion(
582 deletion: Fiber,
583 isViewTransitionEligible: boolean,
584 ) {
585 if (enableCreateEventHandleAPI) {
586 // TODO (effects) It would be nice to avoid calling doesFiberContain()
587 // Maybe we can repurpose one of the subtreeFlags positions for this instead?
588 // Use it to store which part of the tree the focused instance is in?
589 // This assumes we can safely determine that instance during the "render" phase.
590 if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {
591 shouldFireAfterActiveInstanceBlur = true;
592 beforeActiveInstanceBlur(deletion);
593 }
594 }
595 if (isViewTransitionEligible) {
596 commitExitViewTransitions(deletion);
597 }
598 }
599
600 function commitLayoutEffectOnFiber(
601 finishedRoot: FiberRoot,
602 current: Fiber | null,
603 finishedWork: Fiber,
604 committedLanes: Lanes,
605 ): void {
606 const prevEffectStart = pushComponentEffectStart();
607 const prevEffectDuration = pushComponentEffectDuration();
608 const prevEffectErrors = pushComponentEffectErrors();
609 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
610 // When updating this function, also update reappearLayoutEffects, which does
611 // most of the same things when an offscreen tree goes from hidden -> visible.
612 const flags = finishedWork.flags;
613 switch (finishedWork.tag) {
614 case FunctionComponent:
615 case ForwardRef:
616 case SimpleMemoComponent: {
617 recursivelyTraverseLayoutEffects(
618 finishedRoot,
619 finishedWork,
620 committedLanes,
621 );
622 if (flags & Update) {
623 commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);
624 }
625 break;
626 }
627 case ClassComponent: {
628 recursivelyTraverseLayoutEffects(
629 finishedRoot,
630 finishedWork,
631 committedLanes,
632 );
633 if (flags & Update) {
634 commitClassLayoutLifecycles(finishedWork, current);
635 }
636
637 if (flags & Callback) {
638 commitClassCallbacks(finishedWork);
639 }
640
641 if (flags & Ref) {
642 safelyAttachRef(finishedWork, finishedWork.return);
643 }
644 break;
645 }
646 case HostRoot: {
647 const prevProfilerEffectDuration = pushNestedEffectDurations();
648 recursivelyTraverseLayoutEffects(
649 finishedRoot,
650 finishedWork,
651 committedLanes,
652 );
653 if (flags & Callback) {
654 commitRootCallbacks(finishedWork);
655 }
656 if (enableProfilerTimer && enableProfilerCommitHooks) {
657 finishedRoot.effectDuration += popNestedEffectDurations(
658 prevProfilerEffectDuration,
659 );
660 }
661 break;
662 }
663 case HostSingleton: {
664 // $FlowFixMe[constant-condition]
665 if (supportsSingletons) {
666 // We acquire the singleton instance first so it has appropriate
667 // styles before other layout effects run. This isn't perfect because
668 // an early sibling of the singleton may have an effect that can
669 // observe the singleton before it is acquired.
670 // @TODO move this to the mutation phase. The reason it isn't there yet
671 // is it seemingly requires an extra traversal because we need to move the
672 // disappear effect into a phase before the appear phase
673 if (current === null && flags & Update) {
674 // Unlike in the reappear path we only acquire on new mount
675 commitHostSingletonAcquisition(finishedWork);
676 }
677 // We fall through to the HostComponent case below.
678 }
679 // Fallthrough
680 }
681 case HostHoistable:
682 case HostComponent: {
683 recursivelyTraverseLayoutEffects(
684 finishedRoot,
685 finishedWork,
686 committedLanes,
687 );
688
689 // Renderers may schedule work to be done after host components are mounted
690 // (eg DOM renderer may schedule auto-focus for inputs and form controls).
691 // These effects should only be committed when components are first mounted,
692 // aka when there is no current/alternate.
693 if (current === null) {
694 if (flags & Update) {
695 commitHostMount(finishedWork);
696 } else if (flags & Hydrate) {
697 commitHostHydratedInstance(finishedWork);
698 }
699 }
700
701 if (flags & Ref) {
702 safelyAttachRef(finishedWork, finishedWork.return);
703 }
704 break;
705 }
706 case Profiler: {
707 // TODO: Should this fire inside an offscreen tree? Or should it wait to
708 // fire when the tree becomes visible again.
709 if (flags & Update) {
710 const prevProfilerEffectDuration = pushNestedEffectDurations();
711
712 recursivelyTraverseLayoutEffects(
713 finishedRoot,
714 finishedWork,
715 committedLanes,
716 );
717
718 const profilerInstance = finishedWork.stateNode;
719
720 if (enableProfilerTimer && enableProfilerCommitHooks) {
721 // Propagate layout effect durations to the next nearest Profiler ancestor.
722 // Do not reset these values until the next render so DevTools has a chance to read them first.
723 profilerInstance.effectDuration += bubbleNestedEffectDurations(
724 prevProfilerEffectDuration,
725 );
726 }
727
728 commitProfilerUpdate(
729 finishedWork,
730 current,
731 commitStartTime,
732 profilerInstance.effectDuration,
733 );
734 } else {
735 recursivelyTraverseLayoutEffects(
736 finishedRoot,
737 finishedWork,
738 committedLanes,
739 );
740 }
741 break;
742 }
743 case ActivityComponent: {
744 recursivelyTraverseLayoutEffects(
745 finishedRoot,
746 finishedWork,
747 committedLanes,
748 );
749 if (flags & Update) {
750 commitActivityHydrationCallbacks(finishedRoot, finishedWork);
751 }
752 break;
753 }
754 case SuspenseComponent: {
755 recursivelyTraverseLayoutEffects(
756 finishedRoot,
757 finishedWork,
758 committedLanes,
759 );
760 if (flags & Update) {
761 commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
762 }
763 if (flags & Callback) {
764 // This Boundary is in fallback and has a dehydrated Suspense instance.
765 // We could in theory assume the dehydrated state but we recheck it for
766 // certainty.
767 const finishedState: SuspenseState | null = finishedWork.memoizedState;
768 if (finishedState !== null) {
769 const dehydrated = finishedState.dehydrated;
770 if (dehydrated !== null) {
771 // Register a callback to retry this boundary once the server has sent the result.
772 const retry = retryDehydratedSuspenseBoundary.bind(
773 null,
774 finishedWork,
775 );
776 registerSuspenseInstanceRetry(dehydrated, retry);
777 }
778 }
779 }
780 break;
781 }
782 case OffscreenComponent: {
783 const isModernRoot =
784 disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;
785 if (isModernRoot) {
786 const isHidden = finishedWork.memoizedState !== null;
787 const newOffscreenSubtreeIsHidden =
788 isHidden || offscreenSubtreeIsHidden;
789 if (newOffscreenSubtreeIsHidden) {
790 // The Offscreen tree is hidden. Skip over its layout effects.
791 } else {
792 // The Offscreen tree is visible.
793
794 const wasHidden = current !== null && current.memoizedState !== null;
795 const newOffscreenSubtreeWasHidden =
796 wasHidden || offscreenSubtreeWasHidden;
797 const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
798 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
799 offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
800 offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
801
802 if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {
803 // This is the root of a reappearing boundary. As we continue
804 // traversing the layout effects, we must also re-mount layout
805 // effects that were unmounted when the Offscreen subtree was
806 // hidden. So this is a superset of the normal commitLayoutEffects.
807 let layoutEffectTraversalFlags: LayoutEffectTraversalFlags;
808 // $FlowFixMe[constant-condition]
809 if (supportsSingletons) {
810 layoutEffectTraversalFlags = IncludeHostSingletons;
811 } else {
812 layoutEffectTraversalFlags = NoLayoutEffectTraversalFlags;
813 }
814 if ((finishedWork.subtreeFlags & LayoutMask) !== NoFlags) {
815 layoutEffectTraversalFlags |= IncludeWorkInProgressEffects;
816 }
817 recursivelyTraverseReappearLayoutEffects(
818 finishedRoot,
819 finishedWork,
820 layoutEffectTraversalFlags,
821 );
822 if (
823 enableProfilerTimer &&
824 enableProfilerCommitHooks &&
825 enableComponentPerformanceTrack &&
826 (finishedWork.mode & ProfileMode) !== NoMode &&
827 componentEffectStartTime >= 0 &&
828 componentEffectEndTime >= 0 &&
829 componentEffectEndTime - componentEffectStartTime > 0.05
830 ) {
831 logComponentReappeared(
832 finishedWork,
833 componentEffectStartTime,
834 componentEffectEndTime,
835 );
836 }
837 } else {
838 recursivelyTraverseLayoutEffects(
839 finishedRoot,
840 finishedWork,
841 committedLanes,
842 );
843 }
844 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
845 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
846 }
847 } else {
848 recursivelyTraverseLayoutEffects(
849 finishedRoot,
850 finishedWork,
851 committedLanes,
852 );
853 }
854 break;
855 }
856 case ViewTransitionComponent: {
857 if (enableViewTransition) {
858 if (__DEV__) {
859 if (flags & ViewTransitionNamedStatic) {
860 trackNamedViewTransition(finishedWork);
861 }
862 }
863 recursivelyTraverseLayoutEffects(
864 finishedRoot,
865 finishedWork,
866 committedLanes,
867 );
868 if (flags & Ref) {
869 safelyAttachRef(finishedWork, finishedWork.return);
870 }
871 break;
872 }
873 break;
874 }
875 case Fragment:
876 if (enableFragmentRefs) {
877 if (flags & Ref) {
878 safelyAttachRef(finishedWork, finishedWork.return);
879 }
880 }
881 // Fallthrough
882 default: {
883 recursivelyTraverseLayoutEffects(
884 finishedRoot,
885 finishedWork,
886 committedLanes,
887 );
888 break;
889 }
890 }
891
892 if (
893 enableProfilerTimer &&
894 enableProfilerCommitHooks &&
895 enableComponentPerformanceTrack &&
896 (finishedWork.mode & ProfileMode) !== NoMode &&
897 componentEffectStartTime >= 0 &&
898 componentEffectEndTime >= 0
899 ) {
900 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
901 logComponentEffect(
902 finishedWork,
903 componentEffectStartTime,
904 componentEffectEndTime,
905 componentEffectDuration,
906 componentEffectErrors,
907 );
908 }
909 if (
910 // Insertion
911 finishedWork.alternate === null &&
912 finishedWork.return !== null &&
913 finishedWork.return.alternate !== null &&
914 componentEffectEndTime - componentEffectStartTime > 0.05
915 ) {
916 const isHydration = isHydratingParent(
917 finishedWork.return.alternate,
918 finishedWork.return,
919 );
920 if (!isHydration) {
921 logComponentMount(
922 finishedWork,
923 componentEffectStartTime,
924 componentEffectEndTime,
925 );
926 }
927 }
928 }
929
930 popComponentEffectStart(prevEffectStart);
931 popComponentEffectDuration(prevEffectDuration);
932 popComponentEffectErrors(prevEffectErrors);
933 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
934 }
935
936 function abortRootTransitions(
937 root: FiberRoot,
938 abort: TransitionAbort,
939 deletedTransitions: Set<Transition>,
940 deletedOffscreenInstance: OffscreenInstance | null,
941 isInDeletedTree: boolean,
942 ) {
943 if (enableTransitionTracing) {
944 const rootTransitions = root.incompleteTransitions;
945 deletedTransitions.forEach(transition => {
946 if (rootTransitions.has(transition)) {
947 const transitionInstance: TracingMarkerInstance = rootTransitions.get(
948 transition,
949 ) as any;
950 if (transitionInstance.aborts === null) {
951 transitionInstance.aborts = [];
952 }
953 transitionInstance.aborts.push(abort);
954
955 if (deletedOffscreenInstance !== null) {
956 if (
957 transitionInstance.pendingBoundaries !== null &&
958 transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)
959 ) {
960 // $FlowFixMe[incompatible-use] found when upgrading Flow
961 transitionInstance.pendingBoundaries.delete(
962 deletedOffscreenInstance,
963 );
964 }
965 }
966 }
967 });
968 }
969 }
970
971 function abortTracingMarkerTransitions(
972 abortedFiber: Fiber,
973 abort: TransitionAbort,
974 deletedTransitions: Set<Transition>,
975 deletedOffscreenInstance: OffscreenInstance | null,
976 isInDeletedTree: boolean,
977 ) {
978 if (enableTransitionTracing) {
979 const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;
980 const markerTransitions = markerInstance.transitions;
981 const pendingBoundaries = markerInstance.pendingBoundaries;
982 if (markerTransitions !== null) {
983 // TODO: Refactor this code. Is there a way to move this code to
984 // the deletions phase instead of calculating it here while making sure
985 // complete is called appropriately?
986 deletedTransitions.forEach(transition => {
987 // If one of the transitions on the tracing marker is a transition
988 // that was in an aborted subtree, we will abort that tracing marker
989 if (
990 // $FlowFixMe[invalid-compare]
991 abortedFiber !== null &&
992 markerTransitions.has(transition) &&
993 (markerInstance.aborts === null ||
994 !markerInstance.aborts.includes(abort))
995 ) {
996 if (markerInstance.transitions !== null) {
997 if (markerInstance.aborts === null) {
998 markerInstance.aborts = [abort];
999 addMarkerIncompleteCallbackToPendingTransition(
1000 abortedFiber.memoizedProps.name,
1001 markerInstance.transitions,
1002 markerInstance.aborts,
1003 );
1004 } else {
1005 markerInstance.aborts.push(abort);
1006 }
1007
1008 // We only want to call onTransitionProgress when the marker hasn't been
1009 // deleted
1010 if (
1011 deletedOffscreenInstance !== null &&
1012 !isInDeletedTree &&
1013 pendingBoundaries !== null &&
1014 pendingBoundaries.has(deletedOffscreenInstance)
1015 ) {
1016 pendingBoundaries.delete(deletedOffscreenInstance);
1017
1018 addMarkerProgressCallbackToPendingTransition(
1019 abortedFiber.memoizedProps.name,
1020 deletedTransitions,
1021 pendingBoundaries,
1022 );
1023 }
1024 }
1025 }
1026 });
1027 }
1028 }
1029 }
1030
1031 function abortParentMarkerTransitionsForDeletedFiber(
1032 abortedFiber: Fiber,
1033 abort: TransitionAbort,
1034 deletedTransitions: Set<Transition>,
1035 deletedOffscreenInstance: OffscreenInstance | null,
1036 isInDeletedTree: boolean,
1037 ) {
1038 if (enableTransitionTracing) {
1039 // Find all pending markers that are waiting on child suspense boundaries in the
1040 // aborted subtree and cancels them
1041 let fiber: null | Fiber = abortedFiber;
1042 while (fiber !== null) {
1043 switch (fiber.tag) {
1044 case TracingMarkerComponent:
1045 abortTracingMarkerTransitions(
1046 fiber,
1047 abort,
1048 deletedTransitions,
1049 deletedOffscreenInstance,
1050 isInDeletedTree,
1051 );
1052 break;
1053 case HostRoot:
1054 const root = fiber.stateNode;
1055 abortRootTransitions(
1056 root,
1057 abort,
1058 deletedTransitions,
1059 deletedOffscreenInstance,
1060 isInDeletedTree,
1061 );
1062
1063 break;
1064 default:
1065 break;
1066 }
1067
1068 fiber = fiber.return;
1069 }
1070 }
1071 }
1072
1073 function commitTransitionProgress(offscreenFiber: Fiber) {
1074 if (enableTransitionTracing) {
1075 // This function adds suspense boundaries to the root
1076 // or tracing marker's pendingBoundaries map.
1077 // When a suspense boundary goes from a resolved to a fallback
1078 // state we add the boundary to the map, and when it goes from
1079 // a fallback to a resolved state, we remove the boundary from
1080 // the map.
1081
1082 // We use stateNode on the Offscreen component as a stable object
1083 // that doesnt change from render to render. This way we can
1084 // distinguish between different Offscreen instances (vs. the same
1085 // Offscreen instance with different fibers)
1086 const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;
1087
1088 let prevState: SuspenseState | null = null;
1089 const previousFiber = offscreenFiber.alternate;
1090 if (previousFiber !== null && previousFiber.memoizedState !== null) {
1091 prevState = previousFiber.memoizedState;
1092 }
1093 const nextState: SuspenseState | null = offscreenFiber.memoizedState;
1094
1095 const wasHidden = prevState !== null;
1096 const isHidden = nextState !== null;
1097
1098 const pendingMarkers = offscreenInstance._pendingMarkers;
1099 // If there is a name on the suspense boundary, store that in
1100 // the pending boundaries.
1101 let name = null;
1102 const parent = offscreenFiber.return;
1103 if (
1104 parent !== null &&
1105 parent.tag === SuspenseComponent &&
1106 parent.memoizedProps.name
1107 ) {
1108 name = parent.memoizedProps.name;
1109 }
1110
1111 if (!wasHidden && isHidden) {
1112 // The suspense boundaries was just hidden. Add the boundary
1113 // to the pending boundary set if it's there
1114 if (pendingMarkers !== null) {
1115 pendingMarkers.forEach(markerInstance => {
1116 const pendingBoundaries = markerInstance.pendingBoundaries;
1117 const transitions = markerInstance.transitions;
1118 const markerName = markerInstance.name;
1119 if (
1120 pendingBoundaries !== null &&
1121 !pendingBoundaries.has(offscreenInstance)
1122 ) {
1123 pendingBoundaries.set(offscreenInstance, {
1124 name,
1125 });
1126 if (transitions !== null) {
1127 if (
1128 markerInstance.tag === TransitionTracingMarker &&
1129 markerName !== null
1130 ) {
1131 addMarkerProgressCallbackToPendingTransition(
1132 markerName,
1133 transitions,
1134 pendingBoundaries,
1135 );
1136 } else if (markerInstance.tag === TransitionRoot) {
1137 transitions.forEach(transition => {
1138 addTransitionProgressCallbackToPendingTransition(
1139 transition,
1140 pendingBoundaries,
1141 );
1142 });
1143 }
1144 }
1145 }
1146 });
1147 }
1148 } else if (wasHidden && !isHidden) {
1149 // The suspense boundary went from hidden to visible. Remove
1150 // the boundary from the pending suspense boundaries set
1151 // if it's there
1152 if (pendingMarkers !== null) {
1153 pendingMarkers.forEach(markerInstance => {
1154 const pendingBoundaries = markerInstance.pendingBoundaries;
1155 const transitions = markerInstance.transitions;
1156 const markerName = markerInstance.name;
1157 if (
1158 pendingBoundaries !== null &&
1159 pendingBoundaries.has(offscreenInstance)
1160 ) {
1161 pendingBoundaries.delete(offscreenInstance);
1162 if (transitions !== null) {
1163 if (
1164 markerInstance.tag === TransitionTracingMarker &&
1165 markerName !== null
1166 ) {
1167 addMarkerProgressCallbackToPendingTransition(
1168 markerName,
1169 transitions,
1170 pendingBoundaries,
1171 );
1172
1173 // If there are no more unresolved suspense boundaries, the interaction
1174 // is considered finished
1175 if (pendingBoundaries.size === 0) {
1176 if (markerInstance.aborts === null) {
1177 addMarkerCompleteCallbackToPendingTransition(
1178 markerName,
1179 transitions,
1180 );
1181 }
1182 markerInstance.transitions = null;
1183 markerInstance.pendingBoundaries = null;
1184 markerInstance.aborts = null;
1185 }
1186 } else if (markerInstance.tag === TransitionRoot) {
1187 transitions.forEach(transition => {
1188 addTransitionProgressCallbackToPendingTransition(
1189 transition,
1190 pendingBoundaries,
1191 );
1192 });
1193 }
1194 }
1195 }
1196 });
1197 }
1198 }
1199 }
1200 }
1201
1202 function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {
1203 // $FlowFixMe[constant-condition]
1204 if (!supportsMutation) {
1205 return;
1206 }
1207 // Finds the nearest host component children and updates their visibility
1208 // to either hidden or visible.
1209 let child = parentFiber.child;
1210 while (child !== null) {
1211 hideOrUnhideAllChildrenOnFiber(child, isHidden);
1212 child = child.sibling;
1213 }
1214 }
1215
1216 function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {
1217 // $FlowFixMe[constant-condition]
1218 if (!supportsMutation) {
1219 return;
1220 }
1221 switch (fiber.tag) {
1222 case HostComponent:
1223 case HostHoistable: {
1224 // Found the nearest host component. Hide it.
1225 commitShowHideHostInstance(fiber, isHidden);
1226 // Typically, only the nearest host nodes need to be hidden, since that
1227 // has the effect of also hiding everything inside of them.
1228 //
1229 // However, there's a special case for portals, because portals do not
1230 // exist in the regular host tree hierarchy; we can't assume that just
1231 // because a portal's HostComponent parent in the React tree will also be
1232 // a parent in the actual host tree.
1233 //
1234 // So, if any portals exist within the tree, regardless of how deeply
1235 // nested they are, we need to repeat this algorithm for its children.
1236 hideOrUnhideNearestPortals(fiber, isHidden);
1237 return;
1238 }
1239 case HostText: {
1240 commitShowHideHostTextInstance(fiber, isHidden);
1241 return;
1242 }
1243 case DehydratedFragment: {
1244 commitShowHideSuspenseBoundary(fiber, isHidden);
1245 return;
1246 }
1247 case OffscreenComponent:
1248 case LegacyHiddenComponent: {
1249 const offscreenState: OffscreenState | null = fiber.memoizedState;
1250 if (offscreenState !== null) {
1251 // Found a nested Offscreen component that is hidden.
1252 // Don't search any deeper. This tree should remain hidden.
1253 } else {
1254 hideOrUnhideAllChildren(fiber, isHidden);
1255 }
1256 return;
1257 }
1258 default: {
1259 hideOrUnhideAllChildren(fiber, isHidden);
1260 return;
1261 }
1262 }
1263 }
1264
1265 function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {
1266 // $FlowFixMe[constant-condition]
1267 if (!supportsMutation) {
1268 return;
1269 }
1270 if (parentFiber.subtreeFlags & PortalStatic) {
1271 let child = parentFiber.child;
1272 while (child !== null) {
1273 hideOrUnhideNearestPortalsOnFiber(child, isHidden);
1274 child = child.sibling;
1275 }
1276 }
1277 }
1278
1279 function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {
1280 // $FlowFixMe[constant-condition]
1281 if (!supportsMutation) {
1282 return;
1283 }
1284 switch (fiber.tag) {
1285 case HostPortal: {
1286 // Found a portal. Switch back to the normal hide/unhide algorithm to
1287 // toggle the visibility of its children.
1288 hideOrUnhideAllChildrenOnFiber(fiber, isHidden);
1289 return;
1290 }
1291 case OffscreenComponent: {
1292 const offscreenState: OffscreenState | null = fiber.memoizedState;
1293 if (offscreenState !== null) {
1294 // Found a nested Offscreen component that is hidden. Don't search any
1295 // deeper. This tree should remain hidden.
1296 } else {
1297 hideOrUnhideNearestPortals(fiber, isHidden);
1298 }
1299 return;
1300 }
1301 default: {
1302 hideOrUnhideNearestPortals(fiber, isHidden);
1303 return;
1304 }
1305 }
1306 }
1307
1308 function detachFiberMutation(fiber: Fiber) {
1309 // Cut off the return pointer to disconnect it from the tree.
1310 // This enables us to detect and warn against state updates on an unmounted component.
1311 // It also prevents events from bubbling from within disconnected components.
1312 //
1313 // Ideally, we should also clear the child pointer of the parent alternate to let this
1314 // get GC:ed but we don't know which for sure which parent is the current
1315 // one so we'll settle for GC:ing the subtree of this child.
1316 // This child itself will be GC:ed when the parent updates the next time.
1317 //
1318 // Note that we can't clear child or sibling pointers yet.
1319 // They're needed for passive effects and for findDOMNode.
1320 // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).
1321 //
1322 // Don't reset the alternate yet, either. We need that so we can detach the
1323 // alternate's fields in the passive phase. Clearing the return pointer is
1324 // sufficient for findDOMNode semantics.
1325 const alternate = fiber.alternate;
1326 if (alternate !== null) {
1327 alternate.return = null;
1328 }
1329 fiber.return = null;
1330 }
1331
1332 function detachFiberAfterEffects(fiber: Fiber) {
1333 const alternate = fiber.alternate;
1334 if (alternate !== null) {
1335 fiber.alternate = null;
1336 detachFiberAfterEffects(alternate);
1337 }
1338
1339 // Clear cyclical Fiber fields. This level alone is designed to roughly
1340 // approximate the planned Fiber refactor. In that world, `setState` will be
1341 // bound to a special "instance" object instead of a Fiber. The Instance
1342 // object will not have any of these fields. It will only be connected to
1343 // the fiber tree via a single link at the root. So if this level alone is
1344 // sufficient to fix memory issues, that bodes well for our plans.
1345 fiber.child = null;
1346 fiber.deletions = null;
1347 fiber.sibling = null;
1348
1349 // The `stateNode` is cyclical because on host nodes it points to the host
1350 // tree, which has its own pointers to children, parents, and siblings.
1351 // The other host nodes also point back to fibers, so we should detach that
1352 // one, too.
1353 if (fiber.tag === HostComponent) {
1354 const hostInstance: Instance = fiber.stateNode;
1355 // $FlowFixMe[invalid-compare]
1356 if (hostInstance !== null) {
1357 detachDeletedInstance(hostInstance);
1358 }
1359 }
1360 fiber.stateNode = null;
1361
1362 if (__DEV__) {
1363 fiber._debugOwner = null;
1364 }
1365
1366 // Theoretically, nothing in here should be necessary, because we already
1367 // disconnected the fiber from the tree. So even if something leaks this
1368 // particular fiber, it won't leak anything else.
1369 fiber.return = null;
1370 fiber.dependencies = null;
1371 fiber.memoizedProps = null;
1372 fiber.memoizedState = null;
1373 fiber.pendingProps = null;
1374 fiber.stateNode = null;
1375 // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.
1376 fiber.updateQueue = null;
1377 }
1378
1379 // These are tracked on the stack as we recursively traverse a
1380 // deleted subtree.
1381 // TODO: Update these during the whole mutation phase, not just during
1382 // a deletion.
1383 let hostParent: Instance | Container | null = null;
1384 let hostParentIsContainer: boolean = false;
1385
1386 function commitDeletionEffects(
1387 root: FiberRoot,
1388 returnFiber: Fiber,
1389 deletedFiber: Fiber,
1390 ) {
1391 const prevEffectStart = pushComponentEffectStart();
1392
1393 // $FlowFixMe[constant-condition]
1394 if (supportsMutation) {
1395 // We only have the top Fiber that was deleted but we need to recurse down its
1396 // children to find all the terminal nodes.
1397
1398 // Recursively delete all host nodes from the parent, detach refs, clean
1399 // up mounted layout effects, and call componentWillUnmount.
1400
1401 // We only need to remove the topmost host child in each branch. But then we
1402 // still need to keep traversing to unmount effects, refs, and cWU. TODO: We
1403 // could split this into two separate traversals functions, where the second
1404 // one doesn't include any removeChild logic. This is maybe the same
1405 // function as "disappearLayoutEffects" (or whatever that turns into after
1406 // the layout phase is refactored to use recursion).
1407
1408 // Before starting, find the nearest host parent on the stack so we know
1409 // which instance/container to remove the children from.
1410 // TODO: Instead of searching up the fiber return path on every deletion, we
1411 // can track the nearest host component on the JS stack as we traverse the
1412 // tree during the commit phase. This would make insertions faster, too.
1413 let parent: null | Fiber = returnFiber;
1414 findParent: while (parent !== null) {
1415 switch (parent.tag) {
1416 case HostSingleton: {
1417 // $FlowFixMe[constant-condition]
1418 if (supportsSingletons) {
1419 if (isSingletonScope(parent.type)) {
1420 hostParent = parent.stateNode;
1421 hostParentIsContainer = false;
1422 break findParent;
1423 }
1424 break;
1425 }
1426 // Expected fallthrough when supportsSingletons is false
1427 }
1428 case HostComponent: {
1429 hostParent = parent.stateNode;
1430 hostParentIsContainer = false;
1431 break findParent;
1432 }
1433 case HostRoot:
1434 case HostPortal: {
1435 hostParent = parent.stateNode.containerInfo;
1436 hostParentIsContainer = true;
1437 break findParent;
1438 }
1439 }
1440 parent = parent.return;
1441 }
1442 if (hostParent === null) {
1443 throw new Error(
1444 'Expected to find a host parent. This error is likely caused by ' +
1445 'a bug in React. Please file an issue.',
1446 );
1447 }
1448
1449 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
1450 hostParent = null;
1451 hostParentIsContainer = false;
1452 } else {
1453 // Detach refs and call componentWillUnmount() on the whole subtree.
1454 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);
1455 }
1456
1457 if (
1458 enableProfilerTimer &&
1459 enableProfilerCommitHooks &&
1460 enableComponentPerformanceTrack &&
1461 (deletedFiber.mode & ProfileMode) !== NoMode &&
1462 componentEffectStartTime >= 0 &&
1463 componentEffectEndTime >= 0 &&
1464 componentEffectEndTime - componentEffectStartTime > 0.05
1465 ) {
1466 logComponentUnmount(
1467 deletedFiber,
1468 componentEffectStartTime,
1469 componentEffectEndTime,
1470 );
1471 }
1472 popComponentEffectStart(prevEffectStart);
1473
1474 detachFiberMutation(deletedFiber);
1475 }
1476
1477 function recursivelyTraverseDeletionEffects(
1478 finishedRoot: FiberRoot,
1479 nearestMountedAncestor: Fiber,
1480 parent: Fiber,
1481 ) {
1482 // TODO: Use a static flag to skip trees that don't have unmount effects
1483 let child = parent.child;
1484 while (child !== null) {
1485 commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);
1486 child = child.sibling;
1487 }
1488 }
1489
1490 function commitDeletionEffectsOnFiber(
1491 finishedRoot: FiberRoot,
1492 nearestMountedAncestor: Fiber,
1493 deletedFiber: Fiber,
1494 ) {
1495 // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.
1496 onCommitUnmount(deletedFiber);
1497
1498 const prevEffectStart = pushComponentEffectStart();
1499 const prevEffectDuration = pushComponentEffectDuration();
1500 const prevEffectErrors = pushComponentEffectErrors();
1501 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
1502
1503 // The cases in this outer switch modify the stack before they traverse
1504 // into their subtree. There are simpler cases in the inner switch
1505 // that don't modify the stack.
1506 switch (deletedFiber.tag) {
1507 case HostHoistable: {
1508 // $FlowFixMe[constant-condition]
1509 if (supportsResources) {
1510 if (!offscreenSubtreeWasHidden) {
1511 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1512 }
1513 recursivelyTraverseDeletionEffects(
1514 finishedRoot,
1515 nearestMountedAncestor,
1516 deletedFiber,
1517 );
1518 if (deletedFiber.memoizedState) {
1519 releaseResource(deletedFiber.memoizedState);
1520 } else if (deletedFiber.stateNode) {
1521 // A Hoistable Instance lives in document.head only when its enclosing
1522 // Activity is visible. If the Activity is hidden (or has been hidden
1523 // since mount), the instance was either never inserted or was
1524 // detached by the disappear traversal. Skip in those cases.
1525 if (!offscreenSubtreeWasHidden) {
1526 unmountHoistable(deletedFiber.stateNode);
1527 }
1528 }
1529 break;
1530 }
1531 // Fall through
1532 }
1533 case HostSingleton: {
1534 // $FlowFixMe[constant-condition]
1535 if (supportsSingletons) {
1536 if (!offscreenSubtreeWasHidden) {
1537 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1538 }
1539 if (enableFragmentRefs) {
1540 commitFragmentInstanceDeletionEffects(deletedFiber);
1541 }
1542
1543 const prevHostParent = hostParent;
1544 const prevHostParentIsContainer = hostParentIsContainer;
1545 if (isSingletonScope(deletedFiber.type)) {
1546 hostParent = deletedFiber.stateNode;
1547 hostParentIsContainer = false;
1548 }
1549 recursivelyTraverseDeletionEffects(
1550 finishedRoot,
1551 nearestMountedAncestor,
1552 deletedFiber,
1553 );
1554
1555 // Normally this is called in passive unmount effect phase however with
1556 // HostSingleton we warn if you acquire one that is already associated to
1557 // a different fiber. To increase our chances of avoiding this, specifically
1558 // if you keyed a HostSingleton so there will be a delete followed by a Placement
1559 // we treat detach eagerly here
1560 commitHostSingletonRelease(deletedFiber);
1561
1562 hostParent = prevHostParent;
1563 hostParentIsContainer = prevHostParentIsContainer;
1564
1565 break;
1566 }
1567 // Fall through
1568 }
1569 case HostComponent: {
1570 if (!offscreenSubtreeWasHidden) {
1571 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1572 }
1573 if (enableFragmentRefs) {
1574 commitFragmentInstanceDeletionEffects(deletedFiber);
1575 }
1576 // Intentional fallthrough to next branch
1577 }
1578 case HostText: {
1579 if (
1580 enableFragmentRefs &&
1581 enableFragmentRefsTextNodes &&
1582 // HostComponent falls through into this case.
1583 deletedFiber.tag === HostText
1584 ) {
1585 commitFragmentInstanceDeletionEffects(deletedFiber);
1586 }
1587 // We only need to remove the nearest host child. Set the host parent
1588 // to `null` on the stack to indicate that nested children don't
1589 // need to be removed.
1590 // $FlowFixMe[constant-condition]
1591 if (supportsMutation) {
1592 const prevHostParent = hostParent;
1593 const prevHostParentIsContainer = hostParentIsContainer;
1594 hostParent = null;
1595 recursivelyTraverseDeletionEffects(
1596 finishedRoot,
1597 nearestMountedAncestor,
1598 deletedFiber,
1599 );
1600 hostParent = prevHostParent;
1601 hostParentIsContainer = prevHostParentIsContainer;
1602
1603 if (hostParent !== null) {
1604 // Now that all the child effects have unmounted, we can remove the
1605 // node from the tree.
1606 if (hostParentIsContainer) {
1607 commitHostRemoveChildFromContainer(
1608 deletedFiber,
1609 nearestMountedAncestor,
1610 hostParent as any as Container,
1611 deletedFiber.stateNode as Instance | TextInstance,
1612 );
1613 } else {
1614 commitHostRemoveChild(
1615 deletedFiber,
1616 nearestMountedAncestor,
1617 hostParent as any as Instance,
1618 deletedFiber.stateNode as Instance | TextInstance,
1619 );
1620 }
1621 }
1622 } else {
1623 recursivelyTraverseDeletionEffects(
1624 finishedRoot,
1625 nearestMountedAncestor,
1626 deletedFiber,
1627 );
1628 }
1629 break;
1630 }
1631 case DehydratedFragment: {
1632 if (enableSuspenseCallback) {
1633 const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1634 if (hydrationCallbacks !== null) {
1635 try {
1636 const onDeleted = hydrationCallbacks.onDeleted;
1637 if (onDeleted) {
1638 onDeleted(
1639 deletedFiber.stateNode as SuspenseInstance | ActivityInstance,
1640 );
1641 }
1642 } catch (error) {
1643 captureCommitPhaseError(
1644 deletedFiber,
1645 nearestMountedAncestor,
1646 error,
1647 );
1648 }
1649 }
1650 }
1651
1652 // Dehydrated fragments don't have any children
1653
1654 // Delete the dehydrated suspense boundary and all of its content.
1655 // $FlowFixMe[constant-condition]
1656 if (supportsMutation) {
1657 if (hostParent !== null) {
1658 if (hostParentIsContainer) {
1659 clearSuspenseBoundaryFromContainer(
1660 hostParent as any as Container,
1661 deletedFiber.stateNode as SuspenseInstance,
1662 );
1663 } else {
1664 clearSuspenseBoundary(
1665 hostParent as any as Instance,
1666 deletedFiber.stateNode as SuspenseInstance,
1667 );
1668 }
1669 }
1670 }
1671 break;
1672 }
1673 case HostPortal: {
1674 // $FlowFixMe[constant-condition]
1675 if (supportsMutation) {
1676 // When we go into a portal, it becomes the parent to remove from.
1677 const prevHostParent = hostParent;
1678 const prevHostParentIsContainer = hostParentIsContainer;
1679 hostParent = deletedFiber.stateNode.containerInfo;
1680 hostParentIsContainer = true;
1681 recursivelyTraverseDeletionEffects(
1682 finishedRoot,
1683 nearestMountedAncestor,
1684 deletedFiber,
1685 );
1686 hostParent = prevHostParent;
1687 hostParentIsContainer = prevHostParentIsContainer;
1688 } else {
1689 // $FlowFixMe[constant-condition]
1690 if (supportsPersistence) {
1691 commitHostPortalContainerChildren(
1692 deletedFiber.stateNode,
1693 deletedFiber,
1694 createContainerChildSet(),
1695 );
1696 }
1697
1698 recursivelyTraverseDeletionEffects(
1699 finishedRoot,
1700 nearestMountedAncestor,
1701 deletedFiber,
1702 );
1703 }
1704 break;
1705 }
1706 case FunctionComponent:
1707 case ForwardRef:
1708 case MemoComponent:
1709 case SimpleMemoComponent: {
1710 // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
1711 commitHookEffectListUnmount(
1712 HookInsertion,
1713 deletedFiber,
1714 nearestMountedAncestor,
1715 );
1716 if (!offscreenSubtreeWasHidden) {
1717 commitHookLayoutUnmountEffects(
1718 deletedFiber,
1719 nearestMountedAncestor,
1720 HookLayout,
1721 );
1722 }
1723 recursivelyTraverseDeletionEffects(
1724 finishedRoot,
1725 nearestMountedAncestor,
1726 deletedFiber,
1727 );
1728 break;
1729 }
1730 case ClassComponent: {
1731 if (!offscreenSubtreeWasHidden) {
1732 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1733 const instance = deletedFiber.stateNode;
1734 if (typeof instance.componentWillUnmount === 'function') {
1735 safelyCallComponentWillUnmount(
1736 deletedFiber,
1737 nearestMountedAncestor,
1738 instance,
1739 );
1740 }
1741 }
1742 recursivelyTraverseDeletionEffects(
1743 finishedRoot,
1744 nearestMountedAncestor,
1745 deletedFiber,
1746 );
1747 break;
1748 }
1749 case ScopeComponent: {
1750 if (enableScopeAPI) {
1751 if (!offscreenSubtreeWasHidden) {
1752 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1753 }
1754 }
1755 recursivelyTraverseDeletionEffects(
1756 finishedRoot,
1757 nearestMountedAncestor,
1758 deletedFiber,
1759 );
1760 break;
1761 }
1762 case OffscreenComponent: {
1763 if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {
1764 // If this offscreen component is hidden, we already unmounted it. Before
1765 // deleting the children, track that it's already unmounted so that we
1766 // don't attempt to unmount the effects again.
1767 // TODO: If the tree is hidden, in most cases we should be able to skip
1768 // over the nested children entirely. An exception is we haven't yet found
1769 // the topmost host node to delete, which we already track on the stack.
1770 // But the other case is portals, which need to be detached no matter how
1771 // deeply they are nested. We should use a subtree flag to track whether a
1772 // subtree includes a nested portal.
1773 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
1774 offscreenSubtreeWasHidden =
1775 prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;
1776
1777 recursivelyTraverseDeletionEffects(
1778 finishedRoot,
1779 nearestMountedAncestor,
1780 deletedFiber,
1781 );
1782 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
1783 } else {
1784 recursivelyTraverseDeletionEffects(
1785 finishedRoot,
1786 nearestMountedAncestor,
1787 deletedFiber,
1788 );
1789 }
1790 break;
1791 }
1792 case ViewTransitionComponent: {
1793 if (enableViewTransition) {
1794 if (__DEV__) {
1795 if (deletedFiber.flags & ViewTransitionNamedStatic) {
1796 untrackNamedViewTransition(deletedFiber);
1797 }
1798 }
1799 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1800 recursivelyTraverseDeletionEffects(
1801 finishedRoot,
1802 nearestMountedAncestor,
1803 deletedFiber,
1804 );
1805 break;
1806 }
1807 // Fallthrough
1808 }
1809 case Fragment: {
1810 if (enableFragmentRefs) {
1811 if (!offscreenSubtreeWasHidden) {
1812 safelyDetachRef(deletedFiber, nearestMountedAncestor);
1813 }
1814 recursivelyTraverseDeletionEffects(
1815 finishedRoot,
1816 nearestMountedAncestor,
1817 deletedFiber,
1818 );
1819 break;
1820 }
1821 // Fallthrough
1822 }
1823 default: {
1824 recursivelyTraverseDeletionEffects(
1825 finishedRoot,
1826 nearestMountedAncestor,
1827 deletedFiber,
1828 );
1829 break;
1830 }
1831 }
1832
1833 if (
1834 enableProfilerTimer &&
1835 enableProfilerCommitHooks &&
1836 enableComponentPerformanceTrack &&
1837 (deletedFiber.mode & ProfileMode) !== NoMode &&
1838 componentEffectStartTime >= 0 &&
1839 componentEffectEndTime >= 0 &&
1840 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
1841 ) {
1842 logComponentEffect(
1843 deletedFiber,
1844 componentEffectStartTime,
1845 componentEffectEndTime,
1846 componentEffectDuration,
1847 componentEffectErrors,
1848 );
1849 }
1850
1851 popComponentEffectStart(prevEffectStart);
1852 popComponentEffectDuration(prevEffectDuration);
1853 popComponentEffectErrors(prevEffectErrors);
1854 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
1855 }
1856
1857 function commitSuspenseCallback(finishedWork: Fiber) {
1858 // TODO: Delete this feature. It's not properly covered by DEV features.
1859 const newState: SuspenseState | null = finishedWork.memoizedState;
1860 if (enableSuspenseCallback && newState !== null) {
1861 const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;
1862 if (typeof suspenseCallback === 'function') {
1863 const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
1864 if (retryQueue !== null) {
1865 suspenseCallback(new Set(retryQueue));
1866 }
1867 } else if (__DEV__) {
1868 if (suspenseCallback !== undefined) {
1869 console.error('Unexpected type for suspenseCallback.');
1870 }
1871 }
1872 }
1873 }
1874
1875 function commitActivityHydrationCallbacks(
1876 finishedRoot: FiberRoot,
1877 finishedWork: Fiber,
1878 ) {
1879 // $FlowFixMe[constant-condition]
1880 if (!supportsHydration) {
1881 return;
1882 }
1883 const newState: ActivityState | null = finishedWork.memoizedState;
1884 if (newState === null) {
1885 const current = finishedWork.alternate;
1886 if (current !== null) {
1887 const prevState: ActivityState | null = current.memoizedState;
1888 if (prevState !== null) {
1889 const activityInstance = prevState.dehydrated;
1890 commitHostHydratedActivity(activityInstance, finishedWork);
1891 if (enableSuspenseCallback) {
1892 try {
1893 // TODO: Delete this feature. It's not properly covered by DEV features.
1894 const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1895 if (hydrationCallbacks !== null) {
1896 const onHydrated = hydrationCallbacks.onHydrated;
1897 if (onHydrated) {
1898 onHydrated(activityInstance);
1899 }
1900 }
1901 } catch (error) {
1902 captureCommitPhaseError(finishedWork, finishedWork.return, error);
1903 }
1904 }
1905 }
1906 }
1907 }
1908 }
1909
1910 function commitSuspenseHydrationCallbacks(
1911 finishedRoot: FiberRoot,
1912 finishedWork: Fiber,
1913 ) {
1914 // $FlowFixMe[constant-condition]
1915 if (!supportsHydration) {
1916 return;
1917 }
1918 const newState: SuspenseState | null = finishedWork.memoizedState;
1919 if (newState === null) {
1920 const current = finishedWork.alternate;
1921 if (current !== null) {
1922 const prevState: SuspenseState | null = current.memoizedState;
1923 if (prevState !== null) {
1924 const suspenseInstance = prevState.dehydrated;
1925 if (suspenseInstance !== null) {
1926 commitHostHydratedSuspense(suspenseInstance, finishedWork);
1927 if (enableSuspenseCallback) {
1928 try {
1929 // TODO: Delete this feature. It's not properly covered by DEV features.
1930 const hydrationCallbacks = finishedRoot.hydrationCallbacks;
1931 if (hydrationCallbacks !== null) {
1932 const onHydrated = hydrationCallbacks.onHydrated;
1933 if (onHydrated) {
1934 onHydrated(suspenseInstance);
1935 }
1936 }
1937 } catch (error) {
1938 captureCommitPhaseError(finishedWork, finishedWork.return, error);
1939 }
1940 }
1941 }
1942 }
1943 }
1944 }
1945 }
1946
1947 function getRetryCache(finishedWork: Fiber) {
1948 // TODO: Unify the interface for the retry cache so we don't have to switch
1949 // on the tag like this.
1950 switch (finishedWork.tag) {
1951 case ActivityComponent:
1952 case SuspenseComponent:
1953 case SuspenseListComponent: {
1954 let retryCache = finishedWork.stateNode;
1955 if (retryCache === null) {
1956 retryCache = finishedWork.stateNode = new PossiblyWeakSet();
1957 }
1958 return retryCache;
1959 }
1960 case OffscreenComponent: {
1961 const instance: OffscreenInstance = finishedWork.stateNode;
1962 let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =
1963 instance._retryCache;
1964 if (retryCache === null) {
1965 retryCache = instance._retryCache = new PossiblyWeakSet();
1966 }
1967 return retryCache;
1968 }
1969 default: {
1970 throw new Error(
1971 `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +
1972 'bug in React.',
1973 );
1974 }
1975 }
1976 }
1977
1978 function attachSuspenseRetryListeners(
1979 finishedWork: Fiber,
1980 wakeables: RetryQueue,
1981 ) {
1982 // If this boundary just timed out, then it will have a set of wakeables.
1983 // For each wakeable, attach a listener so that when it resolves, React
1984 // attempts to re-render the boundary in the primary (pre-timeout) state.
1985 const retryCache = getRetryCache(finishedWork);
1986 wakeables.forEach(wakeable => {
1987 // Memoize using the boundary fiber to prevent redundant listeners.
1988 if (!retryCache.has(wakeable)) {
1989 retryCache.add(wakeable);
1990
1991 if (enableUpdaterTracking) {
1992 if (isDevToolsPresent) {
1993 if (inProgressLanes !== null && inProgressRoot !== null) {
1994 // If we have pending work still, associate the original updaters with it.
1995 restorePendingUpdaters(inProgressRoot, inProgressLanes);
1996 } else {
1997 throw Error(
1998 'Expected finished root and lanes to be set. This is a bug in React.',
1999 );
2000 }
2001 }
2002 }
2003
2004 const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);
2005 wakeable.then(retry, retry);
2006 }
2007 });
2008 }
2009
2010 // This function detects when a Suspense boundary goes from visible to hidden.
2011 // It returns false if the boundary is already hidden.
2012 // TODO: Use an effect tag.
2013 function isSuspenseBoundaryBeingHidden(
2014 current: Fiber | null,
2015 finishedWork: Fiber,
2016 ): boolean {
2017 if (current !== null) {
2018 const oldState: SuspenseState | null = current.memoizedState;
2019 if (oldState === null || oldState.dehydrated !== null) {
2020 const newState: SuspenseState | null = finishedWork.memoizedState;
2021 return newState !== null && newState.dehydrated === null;
2022 }
2023 }
2024 return false;
2025 }
2026
2027 export function commitMutationEffects(
2028 root: FiberRoot,
2029 finishedWork: Fiber,
2030 committedLanes: Lanes,
2031 ) {
2032 inProgressLanes = committedLanes;
2033 inProgressRoot = root;
2034
2035 rootViewTransitionAffected = false;
2036 inUpdateViewTransition = false;
2037
2038 resetComponentEffectTimers();
2039
2040 commitMutationEffectsOnFiber(finishedWork, root, committedLanes);
2041
2042 inProgressLanes = null;
2043 inProgressRoot = null;
2044 }
2045
2046 function recursivelyTraverseMutationEffects(
2047 root: FiberRoot,
2048 parentFiber: Fiber,
2049 lanes: Lanes,
2050 ) {
2051 // Deletions effects can be scheduled on any fiber type. They need to happen
2052 // before the children effects have fired.
2053 const deletions = parentFiber.deletions;
2054 if (deletions !== null) {
2055 for (let i = 0; i < deletions.length; i++) {
2056 const childToDelete = deletions[i];
2057 commitDeletionEffects(root, parentFiber, childToDelete);
2058 }
2059 }
2060
2061 if (parentFiber.subtreeFlags & (MutationMask | Cloned)) {
2062 let child = parentFiber.child;
2063 while (child !== null) {
2064 commitMutationEffectsOnFiber(child, root, lanes);
2065 child = child.sibling;
2066 }
2067 }
2068 }
2069
2070 let currentHoistableRoot: HoistableRoot | null = null;
2071
2072 function commitMutationEffectsOnFiber(
2073 finishedWork: Fiber,
2074 root: FiberRoot,
2075 lanes: Lanes,
2076 ) {
2077 const prevEffectStart = pushComponentEffectStart();
2078 const prevEffectDuration = pushComponentEffectDuration();
2079 const prevEffectErrors = pushComponentEffectErrors();
2080 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
2081 const current = finishedWork.alternate;
2082 const flags = finishedWork.flags;
2083
2084 // The effect flag should be checked *after* we refine the type of fiber,
2085 // because the fiber tag is more specific. An exception is any flag related
2086 // to reconciliation, because those can be set on all fiber types.
2087 switch (finishedWork.tag) {
2088 case FunctionComponent:
2089 case ForwardRef:
2090 case MemoComponent:
2091 case SimpleMemoComponent: {
2092 // Mutate event effect callbacks on the way down, before mutation effects.
2093 // This ensures that parent event effects are mutated before child effects.
2094 // This isn't a supported use case, so we can re-consider it,
2095 // but this was the behavior we originally shipped.
2096 if (enableEffectEventMutationPhase) {
2097 if (flags & Update) {
2098 const updateQueue: FunctionComponentUpdateQueue | null =
2099 finishedWork.updateQueue as any;
2100 const eventPayloads =
2101 updateQueue !== null ? updateQueue.events : null;
2102 if (eventPayloads !== null) {
2103 for (let ii = 0; ii < eventPayloads.length; ii++) {
2104 const {ref, nextImpl} = eventPayloads[ii];
2105 ref.impl = nextImpl;
2106 }
2107 }
2108 }
2109 }
2110 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2111 commitReconciliationEffects(finishedWork, lanes);
2112
2113 if (flags & Update) {
2114 commitHookEffectListUnmount(
2115 HookInsertion | HookHasEffect,
2116 finishedWork,
2117 finishedWork.return,
2118 );
2119 // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.
2120 commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
2121 commitHookLayoutUnmountEffects(
2122 finishedWork,
2123 finishedWork.return,
2124 HookLayout | HookHasEffect,
2125 );
2126 }
2127 break;
2128 }
2129 case ClassComponent: {
2130 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2131 commitReconciliationEffects(finishedWork, lanes);
2132
2133 if (flags & Ref) {
2134 if (!offscreenSubtreeWasHidden && current !== null) {
2135 safelyDetachRef(current, current.return);
2136 }
2137 }
2138
2139 if (flags & Callback && offscreenSubtreeIsHidden) {
2140 const updateQueue: UpdateQueue<mixed> | null =
2141 finishedWork.updateQueue as any;
2142 if (updateQueue !== null) {
2143 deferHiddenCallbacks(updateQueue);
2144 }
2145 }
2146 break;
2147 }
2148 case HostHoistable: {
2149 // $FlowFixMe[constant-condition]
2150 if (supportsResources) {
2151 // We cast because we always set the root at the React root and so it cannot be
2152 // null while we are processing mutation effects
2153 const hoistableRoot: HoistableRoot = currentHoistableRoot as any;
2154 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2155 commitReconciliationEffects(finishedWork, lanes);
2156
2157 if (flags & Ref) {
2158 if (!offscreenSubtreeWasHidden && current !== null) {
2159 safelyDetachRef(current, current.return);
2160 }
2161 }
2162
2163 if (flags & Update) {
2164 const currentResource =
2165 current !== null ? current.memoizedState : null;
2166 const newResource = finishedWork.memoizedState;
2167 if (current === null) {
2168 // We are mounting a new HostHoistable Fiber. We fork the mount
2169 // behavior based on whether this instance is a Hoistable Instance
2170 // or a Hoistable Resource
2171 if (newResource === null) {
2172 if (finishedWork.stateNode === null) {
2173 // Initial mount. The instance has not been created yet, which
2174 // happens during hydration (createHoistableInstance is normally
2175 // called in beginWork's updateHostHoistable, but is skipped
2176 // when hydrating).
2177 if (offscreenSubtreeIsHidden) {
2178 // We're inside a hidden Activity boundary. Create the
2179 // instance off-document so we don't leak metadata into
2180 // the head. It will be mounted by the reappear path when
2181 // the Activity becomes visible.
2182 finishedWork.stateNode = createHoistableInstance(
2183 finishedWork.type,
2184 finishedWork.memoizedProps,
2185 root.containerInfo,
2186 finishedWork,
2187 );
2188 } else {
2189 finishedWork.stateNode = hydrateHoistable(
2190 hoistableRoot,
2191 finishedWork.type,
2192 finishedWork.memoizedProps,
2193 finishedWork,
2194 );
2195 }
2196 } else if (!offscreenSubtreeIsHidden) {
2197 // The instance was created in beginWork. Only mount it into
2198 // the document if we're not inside a hidden Activity boundary.
2199 mountHoistable(
2200 hoistableRoot,
2201 finishedWork.type,
2202 finishedWork.stateNode,
2203 );
2204 }
2205 } else {
2206 finishedWork.stateNode = acquireResource(
2207 hoistableRoot,
2208 newResource,
2209 finishedWork.memoizedProps,
2210 );
2211 }
2212 } else if (currentResource !== newResource) {
2213 // We are moving to or from Hoistable Resource, or between different Hoistable Resources
2214 if (currentResource === null) {
2215 // Transitioning from Instance to Resource. Only unmount when the
2216 // Instance is currently mounted in the document; hidden Activity
2217 // boundaries keep instances off-document or detach them before
2218 // this update is processed.
2219 const instance = current.stateNode;
2220 if (instance !== null && !offscreenSubtreeWasHidden) {
2221 unmountHoistable(instance);
2222 }
2223 } else {
2224 releaseResource(currentResource);
2225 }
2226 if (newResource === null) {
2227 // Transitioning to an Instance. Only mount if visible; hidden
2228 // Activity boundaries will mount via the reappear path.
2229 if (!offscreenSubtreeIsHidden) {
2230 mountHoistable(
2231 hoistableRoot,
2232 finishedWork.type,
2233 finishedWork.stateNode,
2234 );
2235 }
2236 } else {
2237 acquireResource(
2238 hoistableRoot,
2239 newResource,
2240 finishedWork.memoizedProps,
2241 );
2242 }
2243 } else if (newResource === null && finishedWork.stateNode !== null) {
2244 commitHostUpdate(
2245 finishedWork,
2246 finishedWork.memoizedProps,
2247 current.memoizedProps,
2248 );
2249 }
2250 }
2251 break;
2252 }
2253 // Fall through
2254 }
2255 case HostSingleton: {
2256 // $FlowFixMe[constant-condition]
2257 if (supportsSingletons) {
2258 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2259 commitReconciliationEffects(finishedWork, lanes);
2260 if (flags & Ref) {
2261 if (!offscreenSubtreeWasHidden && current !== null) {
2262 safelyDetachRef(current, current.return);
2263 }
2264 }
2265 if (current !== null && flags & Update) {
2266 const newProps = finishedWork.memoizedProps;
2267 const oldProps = current.memoizedProps;
2268 commitHostUpdate(finishedWork, newProps, oldProps);
2269 }
2270 break;
2271 }
2272 // Fall through
2273 }
2274 case HostComponent: {
2275 // We've hit a host component, so it's no longer a direct parent.
2276 const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
2277 offscreenDirectParentIsHidden = false;
2278
2279 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2280
2281 offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden;
2282
2283 commitReconciliationEffects(finishedWork, lanes);
2284
2285 if (flags & Ref) {
2286 if (!offscreenSubtreeWasHidden && current !== null) {
2287 safelyDetachRef(current, current.return);
2288 }
2289 }
2290 // $FlowFixMe[constant-condition]
2291 if (supportsMutation) {
2292 // TODO: ContentReset gets cleared by the children during the commit
2293 // phase. This is a refactor hazard because it means we must read
2294 // flags the flags after `commitReconciliationEffects` has already run;
2295 // the order matters. We should refactor so that ContentReset does not
2296 // rely on mutating the flag during commit. Like by setting a flag
2297 // during the render phase instead.
2298 if (finishedWork.flags & ContentReset) {
2299 commitHostResetTextContent(finishedWork);
2300 }
2301
2302 if (flags & Update) {
2303 const instance: Instance = finishedWork.stateNode;
2304 if (instance != null) {
2305 // Commit the work prepared earlier.
2306 // For hydration we reuse the update path but we treat the oldProps
2307 // as the newProps. The updatePayload will contain the real change in
2308 // this case.
2309 const newProps = finishedWork.memoizedProps;
2310 const oldProps =
2311 current !== null ? current.memoizedProps : newProps;
2312 commitHostUpdate(finishedWork, newProps, oldProps);
2313 }
2314 }
2315
2316 if (flags & FormReset) {
2317 needsFormReset = true;
2318 if (__DEV__) {
2319 if (finishedWork.type !== 'form') {
2320 // Paranoid coding. In case we accidentally start using the
2321 // FormReset bit for something else.
2322 console.error(
2323 'Unexpected host component type. Expected a form. This is a ' +
2324 'bug in React.',
2325 );
2326 }
2327 }
2328 }
2329 } else {
2330 // $FlowFixMe[constant-condition]
2331 if (supportsPersistence) {
2332 if (finishedWork.alternate !== null) {
2333 // `finishedWork.alternate.stateNode` is pointing to a stale shadow
2334 // node at this point, retaining it and its subtree. To reclaim
2335 // memory, point `alternate.stateNode` to new shadow node. This
2336 // prevents shadow node from staying in memory longer than it
2337 // needs to. The correct behaviour of this is checked by test in
2338 // React Native: ShadowNodeReferenceCounter-itest.js#L150
2339 finishedWork.alternate.stateNode = finishedWork.stateNode;
2340 }
2341 }
2342 }
2343 break;
2344 }
2345 case HostText: {
2346 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2347 commitReconciliationEffects(finishedWork, lanes);
2348
2349 if (flags & Update) {
2350 // $FlowFixMe[constant-condition]
2351 if (supportsMutation) {
2352 if (finishedWork.stateNode === null) {
2353 throw new Error(
2354 'This should have a text node initialized. This error is likely ' +
2355 'caused by a bug in React. Please file an issue.',
2356 );
2357 }
2358
2359 const newText: string = finishedWork.memoizedProps;
2360 // For hydration we reuse the update path but we treat the oldProps
2361 // as the newProps. The updatePayload will contain the real change in
2362 // this case.
2363 const oldText: string =
2364 current !== null ? current.memoizedProps : newText;
2365
2366 commitHostTextUpdate(finishedWork, newText, oldText);
2367 }
2368 }
2369 break;
2370 }
2371 case HostRoot: {
2372 const prevProfilerEffectDuration = pushNestedEffectDurations();
2373
2374 pushRootMutationContext();
2375 // $FlowFixMe[constant-condition]
2376 if (supportsResources) {
2377 prepareToCommitHoistables();
2378
2379 const previousHoistableRoot = currentHoistableRoot;
2380 currentHoistableRoot = getHoistableRoot(root.containerInfo);
2381
2382 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2383 currentHoistableRoot = previousHoistableRoot;
2384
2385 commitReconciliationEffects(finishedWork, lanes);
2386 } else {
2387 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2388 commitReconciliationEffects(finishedWork, lanes);
2389 }
2390
2391 if (flags & Update) {
2392 // $FlowFixMe[constant-condition]
2393 if (supportsMutation && supportsHydration) {
2394 if (current !== null) {
2395 const prevRootState: RootState = current.memoizedState;
2396 if (prevRootState.isDehydrated) {
2397 commitHostHydratedContainer(root, finishedWork);
2398 }
2399 }
2400 }
2401 // $FlowFixMe[constant-condition]
2402 if (supportsPersistence) {
2403 commitHostRootContainerChildren(root, finishedWork);
2404 }
2405 }
2406
2407 if (needsFormReset) {
2408 // A form component requested to be reset during this commit. We do this
2409 // after all mutations in the rest of the tree so that `defaultValue`
2410 // will already be updated. This way you can update `defaultValue` using
2411 // data sent by the server as a result of the form submission.
2412 //
2413 // Theoretically we could check finishedWork.subtreeFlags & FormReset,
2414 // but the FormReset bit is overloaded with other flags used by other
2415 // fiber types. So this extra variable lets us skip traversing the tree
2416 // except when a form was actually submitted.
2417 needsFormReset = false;
2418 recursivelyResetForms(finishedWork);
2419 }
2420
2421 if (enableProfilerTimer && enableProfilerCommitHooks) {
2422 root.effectDuration += popNestedEffectDurations(
2423 prevProfilerEffectDuration,
2424 );
2425 }
2426
2427 popMutationContext(false);
2428
2429 if (
2430 enableDefaultTransitionIndicator &&
2431 rootMutationContext &&
2432 includesLoadingIndicatorLanes(lanes)
2433 ) {
2434 // This root had a mutation. Mark this root as having rendered a manual
2435 // loading state.
2436 markIndicatorHandled(root);
2437 }
2438
2439 break;
2440 }
2441 case HostPortal: {
2442 // For the purposes of visibility toggling, the direct children of a
2443 // portal are considered "children" of the nearest hidden
2444 // OffscreenComponent, regardless of whether there are any host components
2445 // in between them. This is because portals are not part of the regular
2446 // host tree hierarchy; we can't assume that just because a portal's
2447 // HostComponent parent in the React tree will also be a parent in the
2448 // actual host tree. So we must hide all of them.
2449 const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
2450 offscreenDirectParentIsHidden = offscreenSubtreeIsHidden;
2451 const prevMutationContext = pushMutationContext();
2452 // $FlowFixMe[constant-condition]
2453 if (supportsResources) {
2454 const previousHoistableRoot = currentHoistableRoot;
2455 currentHoistableRoot = getHoistableRoot(
2456 finishedWork.stateNode.containerInfo,
2457 );
2458 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2459 commitReconciliationEffects(finishedWork, lanes);
2460 currentHoistableRoot = previousHoistableRoot;
2461 } else {
2462 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2463 commitReconciliationEffects(finishedWork, lanes);
2464 }
2465 if (viewTransitionMutationContext && inUpdateViewTransition) {
2466 // A Portal doesn't necessarily exist within the context of this subtree.
2467 // Ideally we would track which React ViewTransition component nests the container
2468 // but that's costly. Instead, we treat each Portal as if it's a new React root.
2469 // Therefore any leaked mutation means that the root should animate.
2470 rootViewTransitionAffected = true;
2471 }
2472 popMutationContext(prevMutationContext);
2473 offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden;
2474
2475 if (flags & Update) {
2476 // $FlowFixMe[constant-condition]
2477 if (supportsPersistence) {
2478 commitHostPortalContainerChildren(
2479 finishedWork.stateNode,
2480 finishedWork,
2481 finishedWork.stateNode.pendingChildren,
2482 );
2483 }
2484 }
2485 break;
2486 }
2487 case Profiler: {
2488 const prevProfilerEffectDuration = pushNestedEffectDurations();
2489
2490 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2491 commitReconciliationEffects(finishedWork, lanes);
2492
2493 if (enableProfilerTimer && enableProfilerCommitHooks) {
2494 const profilerInstance = finishedWork.stateNode;
2495 // Propagate layout effect durations to the next nearest Profiler ancestor.
2496 // Do not reset these values until the next render so DevTools has a chance to read them first.
2497 profilerInstance.effectDuration += bubbleNestedEffectDurations(
2498 prevProfilerEffectDuration,
2499 );
2500 }
2501 break;
2502 }
2503 case ActivityComponent: {
2504 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2505 commitReconciliationEffects(finishedWork, lanes);
2506 if (flags & Update) {
2507 const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
2508 if (retryQueue !== null) {
2509 finishedWork.updateQueue = null;
2510 attachSuspenseRetryListeners(finishedWork, retryQueue);
2511 }
2512 }
2513 break;
2514 }
2515 case SuspenseComponent: {
2516 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2517 commitReconciliationEffects(finishedWork, lanes);
2518
2519 // TODO: We should mark a flag on the Suspense fiber itself, rather than
2520 // relying on the Offscreen fiber having a flag also being marked. The
2521 // reason is that this offscreen fiber might not be part of the work-in-
2522 // progress tree! It could have been reused from a previous render. This
2523 // doesn't lead to incorrect behavior because we don't rely on the flag
2524 // check alone; we also compare the states explicitly below. But for
2525 // modeling purposes, we _should_ be able to rely on the flag check alone.
2526 // So this is a bit fragile.
2527 //
2528 // Also, all this logic could/should move to the passive phase so it
2529 // doesn't block paint.
2530 const offscreenFiber: Fiber = finishedWork.child as any;
2531 if (offscreenFiber.flags & Visibility) {
2532 // Throttle the appearance and disappearance of Suspense fallbacks.
2533 const isShowingFallback =
2534 (finishedWork.memoizedState as SuspenseState | null) !== null;
2535 const wasShowingFallback =
2536 current !== null &&
2537 (current.memoizedState as SuspenseState | null) !== null;
2538
2539 if (alwaysThrottleRetries) {
2540 if (isShowingFallback !== wasShowingFallback) {
2541 // A fallback is either appearing or disappearing.
2542 markCommitTimeOfFallback();
2543 }
2544 } else {
2545 if (isShowingFallback && !wasShowingFallback) {
2546 // Old behavior. Only mark when a fallback appears, not when
2547 // it disappears.
2548 markCommitTimeOfFallback();
2549 }
2550 }
2551 }
2552
2553 if (flags & Update) {
2554 try {
2555 commitSuspenseCallback(finishedWork);
2556 } catch (error) {
2557 captureCommitPhaseError(finishedWork, finishedWork.return, error);
2558 }
2559 const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;
2560 if (retryQueue !== null) {
2561 finishedWork.updateQueue = null;
2562 attachSuspenseRetryListeners(finishedWork, retryQueue);
2563 }
2564 }
2565 break;
2566 }
2567 case OffscreenComponent: {
2568 const newState: OffscreenState | null = finishedWork.memoizedState;
2569 const isHidden = newState !== null;
2570 const wasHidden = current !== null && current.memoizedState !== null;
2571
2572 if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
2573 // Before committing the children, track on the stack whether this
2574 // offscreen subtree was already hidden, so that we don't unmount the
2575 // effects again.
2576 const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
2577 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
2578 const prevOffscreenDirectParentIsHidden = offscreenDirectParentIsHidden;
2579 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden || isHidden;
2580 offscreenDirectParentIsHidden =
2581 prevOffscreenDirectParentIsHidden || isHidden;
2582 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden || wasHidden;
2583 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2584 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
2585 offscreenDirectParentIsHidden = prevOffscreenDirectParentIsHidden;
2586 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
2587
2588 if (
2589 // If this was the root of the reappear.
2590 wasHidden &&
2591 !isHidden &&
2592 !prevOffscreenSubtreeIsHidden &&
2593 !prevOffscreenSubtreeWasHidden &&
2594 enableProfilerTimer &&
2595 enableProfilerCommitHooks &&
2596 enableComponentPerformanceTrack &&
2597 (finishedWork.mode & ProfileMode) !== NoMode &&
2598 componentEffectStartTime >= 0 &&
2599 componentEffectEndTime >= 0 &&
2600 componentEffectEndTime - componentEffectStartTime > 0.05
2601 ) {
2602 logComponentReappeared(
2603 finishedWork,
2604 componentEffectStartTime,
2605 componentEffectEndTime,
2606 );
2607 }
2608 } else {
2609 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2610 }
2611
2612 commitReconciliationEffects(finishedWork, lanes);
2613
2614 if (flags & Visibility) {
2615 const offscreenInstance: OffscreenInstance = finishedWork.stateNode;
2616
2617 // Track the current state on the Offscreen instance so we can
2618 // read it during an event
2619 if (isHidden) {
2620 offscreenInstance._visibility &= ~OffscreenVisible;
2621 } else {
2622 offscreenInstance._visibility |= OffscreenVisible;
2623 }
2624
2625 const isUpdate = current !== null;
2626 if (isHidden) {
2627 // Only trigger disappear layout effects if:
2628 // - This is an update, not first mount.
2629 // - This Offscreen was not hidden before.
2630 // - Ancestor Offscreen was not hidden in previous commit or in this commit
2631 if (
2632 isUpdate &&
2633 !wasHidden &&
2634 !offscreenSubtreeIsHidden &&
2635 !offscreenSubtreeWasHidden
2636 ) {
2637 if (
2638 disableLegacyMode ||
2639 (finishedWork.mode & ConcurrentMode) !== NoMode
2640 ) {
2641 // Disappear the layout effects of all the children
2642 let layoutEffectTraversalFlags: LayoutEffectTraversalFlags;
2643 // $FlowFixMe[constant-condition]
2644 if (supportsSingletons) {
2645 layoutEffectTraversalFlags = IncludeHostSingletons;
2646 } else {
2647 layoutEffectTraversalFlags = NoLayoutEffectTraversalFlags;
2648 }
2649 const newOffscreenSubtreeIsHidden =
2650 // $FlowFixMe[constant-condition]
2651 isHidden || offscreenSubtreeIsHidden;
2652 const newOffscreenSubtreeWasHidden =
2653 // $FlowFixMe[constant-condition]
2654 wasHidden || offscreenSubtreeWasHidden;
2655 const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;
2656 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;
2657 offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;
2658 offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;
2659 recursivelyTraverseDisappearLayoutEffects(
2660 finishedWork,
2661 layoutEffectTraversalFlags,
2662 );
2663
2664 if (
2665 enableProfilerTimer &&
2666 enableProfilerCommitHooks &&
2667 enableComponentPerformanceTrack &&
2668 (finishedWork.mode & ProfileMode) !== NoMode &&
2669 componentEffectStartTime >= 0 &&
2670 componentEffectEndTime >= 0 &&
2671 componentEffectEndTime - componentEffectStartTime > 0.05
2672 ) {
2673 logComponentDisappeared(
2674 finishedWork,
2675 componentEffectStartTime,
2676 componentEffectEndTime,
2677 );
2678 }
2679 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;
2680 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;
2681 }
2682 }
2683 }
2684
2685 // $FlowFixMe[constant-condition]
2686 if (supportsMutation) {
2687 // If it's trying to unhide but the parent is still hidden, then we should not unhide.
2688 if (isHidden || !offscreenDirectParentIsHidden) {
2689 hideOrUnhideAllChildren(finishedWork, isHidden);
2690 }
2691 }
2692 }
2693
2694 // TODO: Move to passive phase
2695 if (flags & Update) {
2696 const offscreenQueue: OffscreenQueue | null =
2697 finishedWork.updateQueue as any;
2698 if (offscreenQueue !== null) {
2699 const retryQueue = offscreenQueue.retryQueue;
2700 if (retryQueue !== null) {
2701 offscreenQueue.retryQueue = null;
2702 attachSuspenseRetryListeners(finishedWork, retryQueue);
2703 }
2704 }
2705 }
2706 break;
2707 }
2708 case SuspenseListComponent: {
2709 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2710 commitReconciliationEffects(finishedWork, lanes);
2711
2712 if (flags & Update) {
2713 const retryQueue: Set<Wakeable> | null =
2714 finishedWork.updateQueue as any;
2715 if (retryQueue !== null) {
2716 finishedWork.updateQueue = null;
2717 attachSuspenseRetryListeners(finishedWork, retryQueue);
2718 }
2719 }
2720 break;
2721 }
2722 case ViewTransitionComponent: {
2723 if (enableViewTransition) {
2724 if (flags & Ref) {
2725 if (!offscreenSubtreeWasHidden && current !== null) {
2726 safelyDetachRef(current, current.return);
2727 }
2728 }
2729 const prevMutationContext = pushMutationContext();
2730 const prevUpdate = inUpdateViewTransition;
2731 const isViewTransitionEligible =
2732 // $FlowFixMe[constant-condition]
2733 enableViewTransition &&
2734 includesOnlyViewTransitionEligibleLanes(lanes);
2735 const props = finishedWork.memoizedProps;
2736 inUpdateViewTransition =
2737 isViewTransitionEligible &&
2738 getViewTransitionClassName(props.default, props.update) !== 'none';
2739 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2740 commitReconciliationEffects(finishedWork, lanes);
2741 if (isViewTransitionEligible) {
2742 if (current === null) {
2743 // This is a new mount. We should have handled this as part of the
2744 // Placement effect or it is deeper inside a entering transition.
2745 } else if (viewTransitionMutationContext) {
2746 // Something mutated in this tree so we need to animate this regardless
2747 // what the measurements say. We use the Update flag to track this.
2748 // If diffing was done in the render phase, like we used, this could have
2749 // been done in the render already.
2750 finishedWork.flags |= Update;
2751 }
2752 }
2753 inUpdateViewTransition = prevUpdate;
2754 popMutationContext(prevMutationContext);
2755 break;
2756 }
2757 break;
2758 }
2759 case ScopeComponent: {
2760 if (enableScopeAPI) {
2761 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2762 commitReconciliationEffects(finishedWork, lanes);
2763
2764 // TODO: This is a temporary solution that allowed us to transition away
2765 // from React Flare on www.
2766 if (flags & Ref) {
2767 if (!offscreenSubtreeWasHidden && current !== null) {
2768 safelyDetachRef(finishedWork, finishedWork.return);
2769 }
2770 if (!offscreenSubtreeIsHidden) {
2771 safelyAttachRef(finishedWork, finishedWork.return);
2772 }
2773 }
2774 if (flags & Update) {
2775 const scopeInstance = finishedWork.stateNode;
2776 prepareScopeUpdate(scopeInstance, finishedWork);
2777 }
2778 }
2779 break;
2780 }
2781 case Fragment:
2782 if (enableFragmentRefs) {
2783 if (flags & Ref) {
2784 if (!offscreenSubtreeWasHidden && current !== null) {
2785 safelyDetachRef(current, current.return);
2786 }
2787 }
2788 if (current && current.stateNode !== null) {
2789 updateFragmentInstanceFiber(finishedWork, current.stateNode);
2790 }
2791 }
2792 // Fallthrough
2793 default: {
2794 recursivelyTraverseMutationEffects(root, finishedWork, lanes);
2795 commitReconciliationEffects(finishedWork, lanes);
2796
2797 break;
2798 }
2799 }
2800
2801 if (
2802 enableProfilerTimer &&
2803 enableProfilerCommitHooks &&
2804 enableComponentPerformanceTrack &&
2805 (finishedWork.mode & ProfileMode) !== NoMode &&
2806 componentEffectStartTime >= 0 &&
2807 componentEffectEndTime >= 0
2808 ) {
2809 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
2810 logComponentEffect(
2811 finishedWork,
2812 componentEffectStartTime,
2813 componentEffectEndTime,
2814 componentEffectDuration,
2815 componentEffectErrors,
2816 );
2817 }
2818 if (
2819 // Insertion
2820 finishedWork.alternate === null &&
2821 finishedWork.return !== null &&
2822 finishedWork.return.alternate !== null &&
2823 componentEffectEndTime - componentEffectStartTime > 0.05
2824 ) {
2825 const isHydration = isHydratingParent(
2826 finishedWork.return.alternate,
2827 finishedWork.return,
2828 );
2829 if (!isHydration) {
2830 logComponentMount(
2831 finishedWork,
2832 componentEffectStartTime,
2833 componentEffectEndTime,
2834 );
2835 }
2836 }
2837 }
2838
2839 popComponentEffectStart(prevEffectStart);
2840 popComponentEffectDuration(prevEffectDuration);
2841 popComponentEffectErrors(prevEffectErrors);
2842 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
2843 }
2844
2845 function commitReconciliationEffects(
2846 finishedWork: Fiber,
2847 committedLanes: Lanes,
2848 ) {
2849 // Placement effects (insertions, reorders) can be scheduled on any fiber
2850 // type. They needs to happen after the children effects have fired, but
2851 // before the effects on this fiber have fired.
2852 const flags = finishedWork.flags;
2853 if (flags & Placement) {
2854 commitHostPlacement(finishedWork);
2855 // Clear the "placement" from effect tag so that we know that this is
2856 // inserted, before any life-cycles like componentDidMount gets called.
2857 // TODO: findDOMNode doesn't rely on this any more but isMounted does
2858 // and isMounted is deprecated anyway so we should be able to kill this.
2859 finishedWork.flags &= ~Placement;
2860 }
2861 if (flags & Hydrating) {
2862 finishedWork.flags &= ~Hydrating;
2863 }
2864 }
2865
2866 function recursivelyResetForms(parentFiber: Fiber) {
2867 if (parentFiber.subtreeFlags & FormReset) {
2868 let child = parentFiber.child;
2869 while (child !== null) {
2870 resetFormOnFiber(child);
2871 child = child.sibling;
2872 }
2873 }
2874 }
2875
2876 function resetFormOnFiber(fiber: Fiber) {
2877 recursivelyResetForms(fiber);
2878 if (fiber.tag === HostComponent && fiber.flags & FormReset) {
2879 const formInstance: FormInstance = fiber.stateNode;
2880 resetFormInstance(formInstance);
2881 }
2882 }
2883
2884 export function commitAfterMutationEffects(
2885 root: FiberRoot,
2886 finishedWork: Fiber,
2887 committedLanes: Lanes,
2888 ): void {
2889 if (!enableViewTransition) {
2890 // This phase is only used for view transitions.
2891 return;
2892 }
2893 commitAfterMutationEffectsOnFiber(finishedWork, root, committedLanes);
2894 }
2895
2896 function recursivelyTraverseAfterMutationEffects(
2897 root: FiberRoot,
2898 parentFiber: Fiber,
2899 lanes: Lanes,
2900 ) {
2901 // We need to visit the same nodes that we visited in the before mutation phase.
2902 if (parentFiber.subtreeFlags & BeforeAndAfterMutationTransitionMask) {
2903 let child = parentFiber.child;
2904 while (child !== null) {
2905 commitAfterMutationEffectsOnFiber(child, root, lanes);
2906 child = child.sibling;
2907 }
2908 } else {
2909 // Nothing has changed in this subtree, but the parent may have still affected
2910 // its size and position. We need to measure this and if not, restore it to
2911 // not animate.
2912 measureNestedViewTransitions(parentFiber, false);
2913 }
2914 }
2915
2916 function commitAfterMutationEffectsOnFiber(
2917 finishedWork: Fiber,
2918 root: FiberRoot,
2919 lanes: Lanes,
2920 ) {
2921 const current = finishedWork.alternate;
2922 if (current === null) {
2923 // This is a newly inserted subtree. We can't use Placement flags to detect
2924 // this since they get removed in the mutation phase. Usually it's not enough
2925 // to just check current because that can also happen deeper in the same tree.
2926 // However, since we don't need to visit newly inserted subtrees in AfterMutation
2927 // we can just bail after we're done with the first one.
2928 // The first ViewTransition inside a newly mounted tree runs an enter transition
2929 // but other nested ones don't unless they have a named pair.
2930 commitEnterViewTransitions(finishedWork, false);
2931 return;
2932 }
2933
2934 switch (finishedWork.tag) {
2935 case HostRoot: {
2936 viewTransitionContextChanged = false;
2937 rootViewTransitionNameCanceled = false;
2938 pushViewTransitionCancelableScope();
2939 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2940 if (!viewTransitionContextChanged && !rootViewTransitionAffected) {
2941 // If we didn't leak any resizing out to the root, we don't have to transition
2942 // the root itself. This means that we can now safely cancel any cancellations
2943 // that bubbled all the way up.
2944 const cancelableChildren = viewTransitionCancelableChildren;
2945 if (cancelableChildren !== null) {
2946 for (let i = 0; i < cancelableChildren.length; i += 3) {
2947 cancelViewTransitionName(
2948 cancelableChildren[i] as any as Instance,
2949 cancelableChildren[i + 1] as any as string,
2950 cancelableChildren[i + 2] as any as Props,
2951 );
2952 }
2953 }
2954 // We also cancel the root itself.
2955 cancelRootViewTransitionName(root.containerInfo);
2956 rootViewTransitionNameCanceled = true;
2957 }
2958 popViewTransitionCancelableScope(null);
2959 break;
2960 }
2961 case HostComponent: {
2962 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2963 break;
2964 }
2965 case HostPortal: {
2966 const prevContextChanged = viewTransitionContextChanged;
2967 viewTransitionContextChanged = false;
2968 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2969 if (viewTransitionContextChanged) {
2970 // A Portal doesn't necessarily exist within the context of this subtree.
2971 // Ideally we would track which React ViewTransition component nests the container
2972 // but that's costly. Instead, we treat each Portal as if it's a new React root.
2973 // Therefore any leaked resize of a child could affect the root so the root should animate.
2974 // We only do this if the Portal is inside a ViewTransition and it is not disabled
2975 // with update="none". Otherwise the Portal is considered not animating.
2976 rootViewTransitionAffected = true;
2977 }
2978 viewTransitionContextChanged = prevContextChanged;
2979 break;
2980 }
2981 case OffscreenComponent: {
2982 const isModernRoot =
2983 disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;
2984 if (isModernRoot) {
2985 const isHidden = finishedWork.memoizedState !== null;
2986 if (isHidden) {
2987 // The Offscreen tree is hidden. Skip over its after mutation effects.
2988 } else {
2989 // The Offscreen tree is visible.
2990 const wasHidden = current.memoizedState !== null;
2991 if (wasHidden) {
2992 commitEnterViewTransitions(finishedWork, false);
2993 // If it was previous hidden then the children are treated as enter
2994 // not updates so we don't need to visit these children.
2995 } else {
2996 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
2997 }
2998 }
2999 } else {
3000 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
3001 }
3002 break;
3003 }
3004 case ViewTransitionComponent: {
3005 const prevContextChanged = viewTransitionContextChanged;
3006 const prevCancelableChildren = pushViewTransitionCancelableScope();
3007 viewTransitionContextChanged = false;
3008 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
3009
3010 if (viewTransitionContextChanged) {
3011 finishedWork.flags |= Update;
3012 }
3013
3014 const inViewport = measureUpdateViewTransition(
3015 current,
3016 finishedWork,
3017 false,
3018 );
3019
3020 if ((finishedWork.flags & Update) === NoFlags || !inViewport) {
3021 // If this boundary didn't update, then we may be able to cancel its children.
3022 // We bubble them up to the parent set to be determined later if we can cancel.
3023 // Similarly, if old and new state was outside the viewport, we can skip it
3024 // even if it did update.
3025 if (prevCancelableChildren === null) {
3026 // Bubbling up this whole set to the parent.
3027 } else {
3028 // Merge with parent set.
3029 // $FlowFixMe[method-unbinding]
3030 prevCancelableChildren.push.apply(
3031 prevCancelableChildren,
3032 viewTransitionCancelableChildren,
3033 );
3034 popViewTransitionCancelableScope(prevCancelableChildren);
3035 }
3036 // TODO: If this doesn't end up canceled, because a parent animates,
3037 // then we should probably issue an event since this instance is part of it.
3038 } else {
3039 const props: ViewTransitionProps = finishedWork.memoizedProps;
3040 scheduleViewTransitionEvent(finishedWork, props.onUpdate);
3041
3042 // If this boundary did update, we cannot cancel its children so those are dropped.
3043 popViewTransitionCancelableScope(prevCancelableChildren);
3044 }
3045
3046 if ((finishedWork.flags & AffectedParentLayout) !== NoFlags) {
3047 // This boundary changed size in a way that may have caused its parent to
3048 // relayout. We need to bubble this information up to the parent.
3049 viewTransitionContextChanged = true;
3050 } else {
3051 // Otherwise, we restore it to whatever the parent had found so far.
3052 viewTransitionContextChanged = prevContextChanged;
3053 }
3054 break;
3055 }
3056 default: {
3057 recursivelyTraverseAfterMutationEffects(root, finishedWork, lanes);
3058 break;
3059 }
3060 }
3061 }
3062
3063 export function commitLayoutEffects(
3064 finishedWork: Fiber,
3065 root: FiberRoot,
3066 committedLanes: Lanes,
3067 ): void {
3068 inProgressLanes = committedLanes;
3069 inProgressRoot = root;
3070
3071 resetComponentEffectTimers();
3072
3073 const current = finishedWork.alternate;
3074 commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
3075
3076 inProgressLanes = null;
3077 inProgressRoot = null;
3078 }
3079
3080 function recursivelyTraverseLayoutEffects(
3081 root: FiberRoot,
3082 parentFiber: Fiber,
3083 lanes: Lanes,
3084 ) {
3085 if (parentFiber.subtreeFlags & LayoutMask) {
3086 let child = parentFiber.child;
3087 while (child !== null) {
3088 const current = child.alternate;
3089 commitLayoutEffectOnFiber(root, current, child, lanes);
3090 child = child.sibling;
3091 }
3092 }
3093 }
3094
3095 export function disappearLayoutEffectsForDEVValidation(finishedWork: Fiber) {
3096 if (__DEV__) {
3097 disappearLayoutEffects(finishedWork, NoLayoutEffectTraversalFlags);
3098 }
3099 }
3100
3101 function disappearLayoutEffects(
3102 finishedWork: Fiber,
3103 layoutEffectTraversalFlags: LayoutEffectTraversalFlags,
3104 ) {
3105 const prevEffectStart = pushComponentEffectStart();
3106 const prevEffectDuration = pushComponentEffectDuration();
3107 const prevEffectErrors = pushComponentEffectErrors();
3108 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
3109 switch (finishedWork.tag) {
3110 case FunctionComponent:
3111 case ForwardRef:
3112 case MemoComponent:
3113 case SimpleMemoComponent: {
3114 // TODO (Offscreen) Check: flags & LayoutStatic
3115 commitHookLayoutUnmountEffects(
3116 finishedWork,
3117 finishedWork.return,
3118 HookLayout,
3119 );
3120 recursivelyTraverseDisappearLayoutEffects(
3121 finishedWork,
3122 layoutEffectTraversalFlags,
3123 );
3124 break;
3125 }
3126 case ClassComponent: {
3127 // TODO (Offscreen) Check: flags & RefStatic
3128 safelyDetachRef(finishedWork, finishedWork.return);
3129
3130 const instance = finishedWork.stateNode;
3131 if (typeof instance.componentWillUnmount === 'function') {
3132 safelyCallComponentWillUnmount(
3133 finishedWork,
3134 finishedWork.return,
3135 instance,
3136 );
3137 }
3138
3139 recursivelyTraverseDisappearLayoutEffects(
3140 finishedWork,
3141 layoutEffectTraversalFlags,
3142 );
3143 break;
3144 }
3145 case HostSingleton: {
3146 // $FlowFixMe[constant-condition]
3147 if (supportsSingletons) {
3148 const includeHostSingletons =
3149 (layoutEffectTraversalFlags & IncludeHostSingletons) !==
3150 NoLayoutEffectTraversalFlags;
3151 if (includeHostSingletons) {
3152 // TODO (Offscreen) Check: flags & RefStatic
3153 commitHostSingletonRelease(finishedWork);
3154 }
3155 }
3156 // Expected fallthrough to HostComponent
3157 }
3158 case HostComponent: {
3159 // TODO (Offscreen) Check: flags & RefStatic
3160 safelyDetachRef(finishedWork, finishedWork.return);
3161
3162 if (
3163 enableFragmentRefs &&
3164 // HostHoistable shares this case via fallthrough but must not be
3165 // attributed to fragment instances. HostText has its own case below.
3166 (finishedWork.tag === HostComponent ||
3167 finishedWork.tag === HostSingleton)
3168 ) {
3169 commitFragmentInstanceDeletionEffects(finishedWork);
3170 }
3171
3172 recursivelyTraverseDisappearLayoutEffects(
3173 finishedWork,
3174 layoutEffectTraversalFlags,
3175 );
3176 break;
3177 }
3178 case HostText: {
3179 if (enableFragmentRefs && enableFragmentRefsTextNodes) {
3180 commitFragmentInstanceDeletionEffects(finishedWork);
3181 }
3182 break;
3183 }
3184 case HostHoistable: {
3185 // TODO (Offscreen) Check: flags & RefStatic
3186 safelyDetachRef(finishedWork, finishedWork.return);
3187
3188 // $FlowFixMe[constant-condition]
3189 if (supportsResources) {
3190 // We only act on Hoistable Instances (memoizedState === null).
3191 // Resources (memoizedState !== null) are ref-counted and intentionally
3192 // remain in the document across Activity visibility transitions;
3193 // they are released only on actual deletion.
3194 const instance = finishedWork.stateNode;
3195 if (
3196 finishedWork.memoizedState === null &&
3197 instance !== null &&
3198 !offscreenSubtreeWasHidden
3199 ) {
3200 unmountHoistable(instance);
3201 }
3202 }
3203
3204 recursivelyTraverseDisappearLayoutEffects(
3205 finishedWork,
3206 layoutEffectTraversalFlags,
3207 );
3208 break;
3209 }
3210 case OffscreenComponent: {
3211 const isHidden = finishedWork.memoizedState !== null;
3212 if (isHidden) {
3213 // Nested Offscreen tree is already hidden. Don't disappear
3214 // its effects.
3215 } else {
3216 recursivelyTraverseDisappearLayoutEffects(
3217 finishedWork,
3218 layoutEffectTraversalFlags,
3219 );
3220 }
3221 break;
3222 }
3223 case ViewTransitionComponent: {
3224 if (enableViewTransition) {
3225 if (__DEV__) {
3226 if (finishedWork.flags & ViewTransitionNamedStatic) {
3227 untrackNamedViewTransition(finishedWork);
3228 }
3229 }
3230 safelyDetachRef(finishedWork, finishedWork.return);
3231 }
3232 recursivelyTraverseDisappearLayoutEffects(
3233 finishedWork,
3234 layoutEffectTraversalFlags,
3235 );
3236 break;
3237 }
3238 case Fragment: {
3239 if (enableFragmentRefs) {
3240 safelyDetachRef(finishedWork, finishedWork.return);
3241 }
3242 // Fallthrough
3243 }
3244 default: {
3245 recursivelyTraverseDisappearLayoutEffects(
3246 finishedWork,
3247 layoutEffectTraversalFlags,
3248 );
3249 break;
3250 }
3251 }
3252
3253 if (
3254 enableProfilerTimer &&
3255 enableProfilerCommitHooks &&
3256 enableComponentPerformanceTrack &&
3257 (finishedWork.mode & ProfileMode) !== NoMode &&
3258 componentEffectStartTime >= 0 &&
3259 componentEffectEndTime >= 0 &&
3260 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
3261 ) {
3262 logComponentEffect(
3263 finishedWork,
3264 componentEffectStartTime,
3265 componentEffectEndTime,
3266 componentEffectDuration,
3267 componentEffectErrors,
3268 );
3269 }
3270
3271 popComponentEffectStart(prevEffectStart);
3272 popComponentEffectDuration(prevEffectDuration);
3273 popComponentEffectErrors(prevEffectErrors);
3274 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
3275 }
3276
3277 function recursivelyTraverseDisappearLayoutEffects(
3278 parentFiber: Fiber,
3279 layoutEffectTraversalFlags: LayoutEffectTraversalFlags,
3280 ) {
3281 // TODO (Offscreen) Check: subtreeflags & (RefStatic | LayoutStatic)
3282 let child = parentFiber.child;
3283 while (child !== null) {
3284 disappearLayoutEffects(child, layoutEffectTraversalFlags);
3285 child = child.sibling;
3286 }
3287 }
3288
3289 export function reappearLayoutEffectsForDEVValidation(
3290 finishedRoot: FiberRoot,
3291 current: Fiber | null,
3292 finishedWork: Fiber,
3293 ) {
3294 if (__DEV__) {
3295 reappearLayoutEffects(
3296 finishedRoot,
3297 current,
3298 finishedWork,
3299 NoLayoutEffectTraversalFlags,
3300 );
3301 }
3302 }
3303
3304 function reappearLayoutEffects(
3305 finishedRoot: FiberRoot,
3306 current: Fiber | null,
3307 finishedWork: Fiber,
3308 // This function visits both newly finished work and nodes that were re-used
3309 // from a previously committed tree. We cannot check non-static flags if the
3310 // node was reused.
3311 layoutEffectTraversalFlags: LayoutEffectTraversalFlags,
3312 ) {
3313 const prevEffectStart = pushComponentEffectStart();
3314 const prevEffectDuration = pushComponentEffectDuration();
3315 const prevEffectErrors = pushComponentEffectErrors();
3316 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
3317 // Turn on layout effects in a tree that previously disappeared.
3318 const flags = finishedWork.flags;
3319 const includeWorkInProgressEffects =
3320 (layoutEffectTraversalFlags & IncludeWorkInProgressEffects) !==
3321 NoLayoutEffectTraversalFlags;
3322 switch (finishedWork.tag) {
3323 case FunctionComponent:
3324 case ForwardRef:
3325 case SimpleMemoComponent: {
3326 recursivelyTraverseReappearLayoutEffects(
3327 finishedRoot,
3328 finishedWork,
3329 layoutEffectTraversalFlags,
3330 );
3331 // TODO: Check flags & LayoutStatic
3332 commitHookLayoutEffects(finishedWork, HookLayout);
3333 break;
3334 }
3335 case ClassComponent: {
3336 recursivelyTraverseReappearLayoutEffects(
3337 finishedRoot,
3338 finishedWork,
3339 layoutEffectTraversalFlags,
3340 );
3341
3342 commitClassDidMount(finishedWork);
3343
3344 commitClassHiddenCallbacks(finishedWork);
3345
3346 // If this is newly finished work, check for setState callbacks
3347 if (includeWorkInProgressEffects && flags & Callback) {
3348 commitClassCallbacks(finishedWork);
3349 }
3350
3351 // TODO: Check flags & RefStatic
3352 safelyAttachRef(finishedWork, finishedWork.return);
3353 break;
3354 }
3355 // Unlike commitLayoutEffectsOnFiber, we don't need to handle HostRoot
3356 // because this function only visits nodes that are inside an
3357 // Offscreen fiber.
3358 // case HostRoot: {
3359 // ...
3360 // }
3361 case HostSingleton: {
3362 // $FlowFixMe[constant-condition]
3363 if (supportsSingletons) {
3364 const includeHostSingletons =
3365 (layoutEffectTraversalFlags & IncludeHostSingletons) !==
3366 NoLayoutEffectTraversalFlags;
3367 if (includeHostSingletons) {
3368 // We acquire the singleton instance first so it has appropriate
3369 // styles before other layout effects run. This isn't perfect because
3370 // an early sibling of the singleton may have an effect that can
3371 // observe the singleton before it is acquired.
3372 // @TODO move this to the mutation phase. The reason it isn't there yet
3373 // is it seemingly requires an extra traversal because we need to move the
3374 // disappear effect into a phase before the appear phase
3375 commitHostSingletonAcquisition(finishedWork);
3376 // We fall through to the HostComponent case below.
3377 }
3378 }
3379 // Fallthrough
3380 }
3381 case HostComponent: {
3382 if (
3383 enableFragmentRefs &&
3384 (finishedWork.tag === HostComponent ||
3385 finishedWork.tag === HostSingleton)
3386 ) {
3387 commitFragmentInstanceInsertionEffects(finishedWork);
3388 }
3389 recursivelyTraverseReappearLayoutEffects(
3390 finishedRoot,
3391 finishedWork,
3392 layoutEffectTraversalFlags,
3393 );
3394
3395 // Renderers may schedule work to be done after host components are mounted
3396 // (eg DOM renderer may schedule auto-focus for inputs and form controls).
3397 // These effects should only be committed when components are first mounted,
3398 // aka when there is no current/alternate.
3399 if (includeWorkInProgressEffects && current === null && flags & Update) {
3400 commitHostMount(finishedWork);
3401 }
3402
3403 // TODO: Check flags & Ref
3404 safelyAttachRef(finishedWork, finishedWork.return);
3405 break;
3406 }
3407 case HostText: {
3408 if (enableFragmentRefs && enableFragmentRefsTextNodes) {
3409 commitFragmentInstanceInsertionEffects(finishedWork);
3410 }
3411 break;
3412 }
3413 case HostHoistable: {
3414 // $FlowFixMe[constant-condition]
3415 if (supportsResources) {
3416 // The reappear traversal runs whenever an Activity transitions from
3417 // hidden to visible. We piggy-back on it (rather than adding a
3418 // separate recursive traversal) to insert hoistable metadata such as
3419 // <title> and <meta> into the document.
3420 //
3421 // We only act on Hoistable Instances (memoizedState === null).
3422 // Resources stay mounted across Activity visibility transitions.
3423 //
3424 // The parentNode guard makes this idempotent and safe under StrictMode
3425 // dev double-invoke: if the instance is already attached we skip.
3426 //
3427 // Note: this runs in the layout phase. A useLayoutEffect on an earlier
3428 // sibling can therefore observe document.title before the hoistable
3429 // is re-attached. Moving this to the mutation phase would require an
3430 // additional unconditional traversal of the Activity subtree (the
3431 // mutation traversal is gated by subtreeFlags and would skip an
3432 // unchanged hoistable). This is the same tradeoff as for HostSingleton.
3433 const instance = finishedWork.stateNode;
3434 if (
3435 finishedWork.memoizedState === null &&
3436 instance !== null &&
3437 !offscreenSubtreeIsHidden
3438 ) {
3439 // currentHoistableRoot is only maintained during the mutation phase.
3440 // Derive the hoistable root from the instance's owner document so
3441 // this works in the layout phase too. Hoistable Instances are
3442 // hoisted to document.head, which always lives in ownerDocument.
3443 mountHoistable(
3444 getHoistableRoot(instance.ownerDocument),
3445 finishedWork.type,
3446 instance,
3447 );
3448 }
3449 }
3450 recursivelyTraverseReappearLayoutEffects(
3451 finishedRoot,
3452 finishedWork,
3453 layoutEffectTraversalFlags,
3454 );
3455
3456 if (includeWorkInProgressEffects && current === null && flags & Update) {
3457 commitHostMount(finishedWork);
3458 }
3459
3460 // TODO: Check flags & Ref
3461 safelyAttachRef(finishedWork, finishedWork.return);
3462 break;
3463 }
3464 case Profiler: {
3465 // TODO: Figure out how Profiler updates should work with Offscreen
3466 if (includeWorkInProgressEffects && flags & Update) {
3467 const prevProfilerEffectDuration = pushNestedEffectDurations();
3468
3469 recursivelyTraverseReappearLayoutEffects(
3470 finishedRoot,
3471 finishedWork,
3472 layoutEffectTraversalFlags,
3473 );
3474
3475 const profilerInstance = finishedWork.stateNode;
3476
3477 if (enableProfilerTimer && enableProfilerCommitHooks) {
3478 // Propagate layout effect durations to the next nearest Profiler ancestor.
3479 // Do not reset these values until the next render so DevTools has a chance to read them first.
3480 profilerInstance.effectDuration += bubbleNestedEffectDurations(
3481 prevProfilerEffectDuration,
3482 );
3483 }
3484
3485 commitProfilerUpdate(
3486 finishedWork,
3487 current,
3488 commitStartTime,
3489 profilerInstance.effectDuration,
3490 );
3491 } else {
3492 recursivelyTraverseReappearLayoutEffects(
3493 finishedRoot,
3494 finishedWork,
3495 layoutEffectTraversalFlags,
3496 );
3497 }
3498 break;
3499 }
3500 case ActivityComponent: {
3501 recursivelyTraverseReappearLayoutEffects(
3502 finishedRoot,
3503 finishedWork,
3504 layoutEffectTraversalFlags,
3505 );
3506
3507 if (includeWorkInProgressEffects && flags & Update) {
3508 // TODO: Delete this feature.
3509 commitActivityHydrationCallbacks(finishedRoot, finishedWork);
3510 }
3511 break;
3512 }
3513 case SuspenseComponent: {
3514 recursivelyTraverseReappearLayoutEffects(
3515 finishedRoot,
3516 finishedWork,
3517 layoutEffectTraversalFlags,
3518 );
3519
3520 if (includeWorkInProgressEffects && flags & Update) {
3521 // TODO: Delete this feature.
3522 commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);
3523 }
3524 break;
3525 }
3526 case OffscreenComponent: {
3527 const offscreenState: OffscreenState = finishedWork.memoizedState;
3528 // $FlowFixMe[invalid-compare]
3529 const isHidden = offscreenState !== null;
3530 if (isHidden) {
3531 // Nested Offscreen tree is still hidden. Don't re-appear its effects.
3532 } else {
3533 recursivelyTraverseReappearLayoutEffects(
3534 finishedRoot,
3535 finishedWork,
3536 layoutEffectTraversalFlags,
3537 );
3538 }
3539 // TODO: Check flags & Ref
3540 safelyAttachRef(finishedWork, finishedWork.return);
3541 break;
3542 }
3543 case ViewTransitionComponent: {
3544 if (enableViewTransition) {
3545 recursivelyTraverseReappearLayoutEffects(
3546 finishedRoot,
3547 finishedWork,
3548 layoutEffectTraversalFlags,
3549 );
3550 if (__DEV__) {
3551 if (flags & ViewTransitionNamedStatic) {
3552 trackNamedViewTransition(finishedWork);
3553 }
3554 }
3555 safelyAttachRef(finishedWork, finishedWork.return);
3556 break;
3557 }
3558 break;
3559 }
3560 case Fragment: {
3561 if (enableFragmentRefs) {
3562 safelyAttachRef(finishedWork, finishedWork.return);
3563 }
3564 // Fallthrough
3565 }
3566 default: {
3567 recursivelyTraverseReappearLayoutEffects(
3568 finishedRoot,
3569 finishedWork,
3570 layoutEffectTraversalFlags,
3571 );
3572 break;
3573 }
3574 }
3575
3576 if (
3577 enableProfilerTimer &&
3578 enableProfilerCommitHooks &&
3579 enableComponentPerformanceTrack &&
3580 (finishedWork.mode & ProfileMode) !== NoMode &&
3581 componentEffectStartTime >= 0 &&
3582 componentEffectEndTime >= 0 &&
3583 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
3584 ) {
3585 logComponentEffect(
3586 finishedWork,
3587 componentEffectStartTime,
3588 componentEffectEndTime,
3589 componentEffectDuration,
3590 componentEffectErrors,
3591 );
3592 }
3593
3594 popComponentEffectStart(prevEffectStart);
3595 popComponentEffectDuration(prevEffectDuration);
3596 popComponentEffectErrors(prevEffectErrors);
3597 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
3598 }
3599
3600 function recursivelyTraverseReappearLayoutEffects(
3601 finishedRoot: FiberRoot,
3602 parentFiber: Fiber,
3603 layoutEffectTraversalFlags: LayoutEffectTraversalFlags,
3604 ) {
3605 // This function visits both newly finished work and nodes that were re-used
3606 // from a previously committed tree. We cannot check non-static flags if the
3607 // node was reused.
3608 const childLayoutEffectTraversalFlags =
3609 (parentFiber.subtreeFlags & LayoutMask) !== NoFlags
3610 ? layoutEffectTraversalFlags
3611 : layoutEffectTraversalFlags & ~IncludeWorkInProgressEffects;
3612
3613 // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
3614 let child = parentFiber.child;
3615 while (child !== null) {
3616 const current = child.alternate;
3617 reappearLayoutEffects(
3618 finishedRoot,
3619 current,
3620 child,
3621 childLayoutEffectTraversalFlags,
3622 );
3623 child = child.sibling;
3624 }
3625 }
3626
3627 function commitOffscreenPassiveMountEffects(
3628 current: Fiber | null,
3629 finishedWork: Fiber,
3630 instance: OffscreenInstance,
3631 ) {
3632 let previousCache: Cache | null = null;
3633 if (
3634 current !== null &&
3635 current.memoizedState !== null &&
3636 current.memoizedState.cachePool !== null
3637 ) {
3638 previousCache = current.memoizedState.cachePool.pool;
3639 }
3640 let nextCache: Cache | null = null;
3641 if (
3642 finishedWork.memoizedState !== null &&
3643 finishedWork.memoizedState.cachePool !== null
3644 ) {
3645 nextCache = finishedWork.memoizedState.cachePool.pool;
3646 }
3647 // Retain/release the cache used for pending (suspended) nodes.
3648 // Note that this is only reached in the non-suspended/visible case:
3649 // when the content is suspended/hidden, the retain/release occurs
3650 // via the parent Suspense component (see case above).
3651 if (nextCache !== previousCache) {
3652 if (nextCache != null) {
3653 retainCache(nextCache);
3654 }
3655 if (previousCache != null) {
3656 releaseCache(previousCache);
3657 }
3658 }
3659
3660 if (enableTransitionTracing) {
3661 // TODO: Pre-rendering should not be counted as part of a transition. We
3662 // may add separate logs for pre-rendering, but it's not part of the
3663 // primary metrics.
3664 const offscreenState: OffscreenState = finishedWork.memoizedState;
3665 const queue: OffscreenQueue | null = finishedWork.updateQueue as any;
3666
3667 // $FlowFixMe[invalid-compare]
3668 const isHidden = offscreenState !== null;
3669 if (queue !== null) {
3670 if (isHidden) {
3671 const transitions = queue.transitions;
3672 if (transitions !== null) {
3673 transitions.forEach(transition => {
3674 // Add all the transitions saved in the update queue during
3675 // the render phase (ie the transitions associated with this boundary)
3676 // into the transitions set.
3677 if (instance._transitions === null) {
3678 instance._transitions = new Set();
3679 }
3680 instance._transitions.add(transition);
3681 });
3682 }
3683
3684 const markerInstances = queue.markerInstances;
3685 if (markerInstances !== null) {
3686 markerInstances.forEach(markerInstance => {
3687 const markerTransitions = markerInstance.transitions;
3688 // There should only be a few tracing marker transitions because
3689 // they should be only associated with the transition that
3690 // caused them
3691 if (markerTransitions !== null) {
3692 markerTransitions.forEach(transition => {
3693 if (instance._transitions === null) {
3694 instance._transitions = new Set();
3695 } else if (instance._transitions.has(transition)) {
3696 if (markerInstance.pendingBoundaries === null) {
3697 markerInstance.pendingBoundaries = new Map();
3698 }
3699 if (instance._pendingMarkers === null) {
3700 instance._pendingMarkers = new Set();
3701 }
3702
3703 instance._pendingMarkers.add(markerInstance);
3704 }
3705 });
3706 }
3707 });
3708 }
3709 }
3710
3711 finishedWork.updateQueue = null;
3712 }
3713
3714 commitTransitionProgress(finishedWork);
3715
3716 // TODO: Refactor this into an if/else branch
3717 if (!isHidden) {
3718 instance._transitions = null;
3719 instance._pendingMarkers = null;
3720 }
3721 }
3722 }
3723
3724 function commitCachePassiveMountEffect(
3725 current: Fiber | null,
3726 finishedWork: Fiber,
3727 ) {
3728 let previousCache: Cache | null = null;
3729 if (finishedWork.alternate !== null) {
3730 previousCache = finishedWork.alternate.memoizedState.cache;
3731 }
3732 const nextCache = finishedWork.memoizedState.cache;
3733 // Retain/release the cache. In theory the cache component
3734 // could be "borrowing" a cache instance owned by some parent,
3735 // in which case we could avoid retaining/releasing. But it
3736 // is non-trivial to determine when that is the case, so we
3737 // always retain/release.
3738 if (nextCache !== previousCache) {
3739 retainCache(nextCache);
3740 if (previousCache != null) {
3741 releaseCache(previousCache);
3742 }
3743 }
3744 }
3745
3746 function commitTracingMarkerPassiveMountEffect(finishedWork: Fiber) {
3747 // Get the transitions that were initiatized during the render
3748 // and add a start transition callback for each of them
3749 // We will only call this on initial mount of the tracing marker
3750 // only if there are no suspense children
3751 const instance = finishedWork.stateNode;
3752 if (instance.transitions !== null && instance.pendingBoundaries === null) {
3753 addMarkerCompleteCallbackToPendingTransition(
3754 finishedWork.memoizedProps.name,
3755 instance.transitions,
3756 );
3757 instance.transitions = null;
3758 instance.pendingBoundaries = null;
3759 instance.aborts = null;
3760 instance.name = null;
3761 }
3762 }
3763
3764 export function commitPassiveMountEffects(
3765 root: FiberRoot,
3766 finishedWork: Fiber,
3767 committedLanes: Lanes,
3768 committedTransitions: Array<Transition> | null,
3769 renderEndTime: number, // Profiling-only
3770 ): void {
3771 resetComponentEffectTimers();
3772
3773 commitPassiveMountOnFiber(
3774 root,
3775 finishedWork,
3776 committedLanes,
3777 committedTransitions,
3778 enableProfilerTimer && enableComponentPerformanceTrack ? renderEndTime : 0,
3779 );
3780 }
3781
3782 function recursivelyTraversePassiveMountEffects(
3783 root: FiberRoot,
3784 parentFiber: Fiber,
3785 committedLanes: Lanes,
3786 committedTransitions: Array<Transition> | null,
3787 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
3788 ) {
3789 const isViewTransitionEligible =
3790 enableViewTransition &&
3791 includesOnlyViewTransitionEligibleLanes(committedLanes);
3792 // TODO: We could optimize this by marking these with the Passive subtree flag in the render phase.
3793 const subtreeMask = isViewTransitionEligible
3794 ? PassiveTransitionMask
3795 : PassiveMask;
3796 if (
3797 parentFiber.subtreeFlags & subtreeMask ||
3798 // If this subtree rendered with profiling this commit, we need to visit it to log it.
3799 (enableProfilerTimer &&
3800 enableComponentPerformanceTrack &&
3801 parentFiber.actualDuration !== 0 &&
3802 (parentFiber.alternate === null ||
3803 parentFiber.alternate.child !== parentFiber.child))
3804 ) {
3805 let child = parentFiber.child;
3806 while (child !== null) {
3807 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3808 const nextSibling = child.sibling;
3809 commitPassiveMountOnFiber(
3810 root,
3811 child,
3812 committedLanes,
3813 committedTransitions,
3814 nextSibling !== null
3815 ? (nextSibling.actualStartTime as any as number)
3816 : endTime,
3817 );
3818 child = nextSibling;
3819 } else {
3820 commitPassiveMountOnFiber(
3821 root,
3822 child,
3823 committedLanes,
3824 committedTransitions,
3825 0,
3826 );
3827 child = child.sibling;
3828 }
3829 }
3830 } else if (isViewTransitionEligible) {
3831 // We are inside an updated subtree. Any mutations that affected the
3832 // parent HostInstance's layout or set of children (such as reorders)
3833 // might have also affected the positioning or size of the inner
3834 // ViewTransitions. Therefore we need to restore those too.
3835 restoreNestedViewTransitions(parentFiber);
3836 }
3837 }
3838
3839 let inHydratedSubtree = false;
3840
3841 function commitPassiveMountOnFiber(
3842 finishedRoot: FiberRoot,
3843 finishedWork: Fiber,
3844 committedLanes: Lanes,
3845 committedTransitions: Array<Transition> | null,
3846 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
3847 ): void {
3848 const prevEffectStart = pushComponentEffectStart();
3849 const prevEffectDuration = pushComponentEffectDuration();
3850 const prevEffectErrors = pushComponentEffectErrors();
3851 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
3852 const prevDeepEquality = pushDeepEquality();
3853
3854 const isViewTransitionEligible = enableViewTransition
3855 ? includesOnlyViewTransitionEligibleLanes(committedLanes)
3856 : false;
3857
3858 if (
3859 isViewTransitionEligible &&
3860 finishedWork.alternate === null &&
3861 // We can't use the Placement flag here because it gets reset earlier. Instead,
3862 // we check if this is the root of the insertion by checking if the parent
3863 // was previous existing.
3864 finishedWork.return !== null &&
3865 finishedWork.return.alternate !== null
3866 ) {
3867 // This was a new mount. This means we could've triggered an enter animation on
3868 // the content. Restore the view transitions if there were any assigned in the
3869 // snapshot phase.
3870 restoreEnterOrExitViewTransitions(finishedWork);
3871 }
3872
3873 // When updating this function, also update reconnectPassiveEffects, which does
3874 // most of the same things when an offscreen tree goes from hidden -> visible,
3875 // or when toggling effects inside a hidden tree.
3876 const flags = finishedWork.flags;
3877 switch (finishedWork.tag) {
3878 case FunctionComponent:
3879 case ForwardRef:
3880 case SimpleMemoComponent: {
3881 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
3882 // render time. We do this after the fact in the passive effect to avoid the overhead of this
3883 // getting in the way of the render characteristics and avoid the overhead of unwinding
3884 // uncommitted renders.
3885 if (
3886 enableProfilerTimer &&
3887 enableComponentPerformanceTrack &&
3888 (finishedWork.mode & ProfileMode) !== NoMode &&
3889 (finishedWork.actualStartTime as any as number) > 0 &&
3890 (finishedWork.flags & PerformedWork) !== NoFlags
3891 ) {
3892 logComponentRender(
3893 finishedWork,
3894 finishedWork.actualStartTime as any as number,
3895 endTime,
3896 inHydratedSubtree,
3897 committedLanes,
3898 );
3899 }
3900
3901 recursivelyTraversePassiveMountEffects(
3902 finishedRoot,
3903 finishedWork,
3904 committedLanes,
3905 committedTransitions,
3906 endTime,
3907 );
3908 if (flags & Passive) {
3909 commitHookPassiveMountEffects(
3910 finishedWork,
3911 HookPassive | HookHasEffect,
3912 );
3913 }
3914 break;
3915 }
3916 case ClassComponent: {
3917 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
3918 // render time. We do this after the fact in the passive effect to avoid the overhead of this
3919 // getting in the way of the render characteristics and avoid the overhead of unwinding
3920 // uncommitted renders.
3921 if (
3922 enableProfilerTimer &&
3923 enableComponentPerformanceTrack &&
3924 (finishedWork.mode & ProfileMode) !== NoMode &&
3925 (finishedWork.actualStartTime as any as number) > 0
3926 ) {
3927 if ((finishedWork.flags & DidCapture) !== NoFlags) {
3928 logComponentErrored(
3929 finishedWork,
3930 finishedWork.actualStartTime as any as number,
3931 endTime,
3932 // TODO: The captured values are all hidden inside the updater/callback closures so
3933 // we can't get to the errors but they're there so we should be able to log them.
3934 [],
3935 );
3936 } else if ((finishedWork.flags & PerformedWork) !== NoFlags) {
3937 logComponentRender(
3938 finishedWork,
3939 finishedWork.actualStartTime as any as number,
3940 endTime,
3941 inHydratedSubtree,
3942 committedLanes,
3943 );
3944 }
3945 }
3946
3947 recursivelyTraversePassiveMountEffects(
3948 finishedRoot,
3949 finishedWork,
3950 committedLanes,
3951 committedTransitions,
3952 endTime,
3953 );
3954 break;
3955 }
3956 case HostRoot: {
3957 const prevProfilerEffectDuration = pushNestedEffectDurations();
3958
3959 const wasInHydratedSubtree = inHydratedSubtree;
3960 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3961 // Detect if this was a hydration commit by look at if the previous state was
3962 // dehydrated and this wasn't a forced client render.
3963 inHydratedSubtree =
3964 finishedWork.alternate !== null &&
3965 (finishedWork.alternate.memoizedState as RootState).isDehydrated &&
3966 (finishedWork.flags & ForceClientRender) === NoFlags;
3967 }
3968
3969 recursivelyTraversePassiveMountEffects(
3970 finishedRoot,
3971 finishedWork,
3972 committedLanes,
3973 committedTransitions,
3974 endTime,
3975 );
3976
3977 if (enableProfilerTimer && enableComponentPerformanceTrack) {
3978 inHydratedSubtree = wasInHydratedSubtree;
3979 }
3980
3981 if (isViewTransitionEligible) {
3982 // $FlowFixMe[constant-condition]
3983 if (supportsMutation && rootViewTransitionNameCanceled) {
3984 restoreRootViewTransitionName(finishedRoot.containerInfo);
3985 }
3986 }
3987
3988 if (flags & Passive) {
3989 let previousCache: Cache | null = null;
3990 if (finishedWork.alternate !== null) {
3991 previousCache = finishedWork.alternate.memoizedState.cache;
3992 }
3993 const nextCache = finishedWork.memoizedState.cache;
3994 // Retain/release the root cache.
3995 // Note that on initial mount, previousCache and nextCache will be the same
3996 // and this retain won't occur. To counter this, we instead retain the HostRoot's
3997 // initial cache when creating the root itself (see createFiberRoot() in
3998 // ReactFiberRoot.js). Subsequent updates that change the cache are reflected
3999 // here, such that previous/next caches are retained correctly.
4000 if (nextCache !== previousCache) {
4001 retainCache(nextCache);
4002 if (previousCache != null) {
4003 releaseCache(previousCache);
4004 }
4005 }
4006
4007 if (enableTransitionTracing) {
4008 // Get the transitions that were initiatized during the render
4009 // and add a start transition callback for each of them
4010 const root: FiberRoot = finishedWork.stateNode;
4011 const incompleteTransitions = root.incompleteTransitions;
4012 // Initial render
4013 if (committedTransitions !== null) {
4014 committedTransitions.forEach(transition => {
4015 addTransitionStartCallbackToPendingTransition(transition);
4016 });
4017
4018 clearTransitionsForLanes(finishedRoot, committedLanes);
4019 }
4020
4021 incompleteTransitions.forEach((markerInstance, transition) => {
4022 const pendingBoundaries = markerInstance.pendingBoundaries;
4023 if (pendingBoundaries === null || pendingBoundaries.size === 0) {
4024 if (markerInstance.aborts === null) {
4025 addTransitionCompleteCallbackToPendingTransition(transition);
4026 }
4027 incompleteTransitions.delete(transition);
4028 }
4029 });
4030
4031 clearTransitionsForLanes(finishedRoot, committedLanes);
4032 }
4033 }
4034 if (enableProfilerTimer && enableProfilerCommitHooks) {
4035 finishedRoot.passiveEffectDuration += popNestedEffectDurations(
4036 prevProfilerEffectDuration,
4037 );
4038 }
4039 break;
4040 }
4041 case Profiler: {
4042 // Only Profilers with work in their subtree will have a Passive effect scheduled.
4043 if (flags & Passive) {
4044 const prevProfilerEffectDuration = pushNestedEffectDurations();
4045
4046 recursivelyTraversePassiveMountEffects(
4047 finishedRoot,
4048 finishedWork,
4049 committedLanes,
4050 committedTransitions,
4051 endTime,
4052 );
4053
4054 const profilerInstance = finishedWork.stateNode;
4055
4056 if (enableProfilerTimer && enableProfilerCommitHooks) {
4057 // Bubble times to the next nearest ancestor Profiler.
4058 // After we process that Profiler, we'll bubble further up.
4059 profilerInstance.passiveEffectDuration += bubbleNestedEffectDurations(
4060 prevProfilerEffectDuration,
4061 );
4062 }
4063
4064 commitProfilerPostCommit(
4065 finishedWork,
4066 finishedWork.alternate,
4067 // This value will still reflect the previous commit phase.
4068 // It does not get reset until the start of the next commit phase.
4069 commitStartTime,
4070 profilerInstance.passiveEffectDuration,
4071 );
4072 } else {
4073 recursivelyTraversePassiveMountEffects(
4074 finishedRoot,
4075 finishedWork,
4076 committedLanes,
4077 committedTransitions,
4078 endTime,
4079 );
4080 }
4081 break;
4082 }
4083 case ActivityComponent: {
4084 const wasInHydratedSubtree = inHydratedSubtree;
4085 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4086 const prevState: ActivityState | null =
4087 finishedWork.alternate !== null
4088 ? finishedWork.alternate.memoizedState
4089 : null;
4090 const nextState: ActivityState | null = finishedWork.memoizedState;
4091 if (prevState !== null && nextState === null) {
4092 // This was dehydrated but is no longer dehydrated. We may have now either hydrated it
4093 // or client rendered it.
4094 const deletions = finishedWork.deletions;
4095 if (
4096 deletions !== null &&
4097 deletions.length > 0 &&
4098 deletions[0].tag === DehydratedFragment
4099 ) {
4100 // This was an abandoned hydration that deleted the dehydrated fragment. That means we
4101 // are not hydrating this Suspense boundary.
4102 inHydratedSubtree = false;
4103 const hydrationErrors = prevState.hydrationErrors;
4104 // If there were no hydration errors, that suggests that this was an intentional client
4105 // rendered boundary.
4106 if (hydrationErrors !== null) {
4107 const startTime: number = finishedWork.actualStartTime as any;
4108 logComponentErrored(
4109 finishedWork,
4110 startTime,
4111 endTime,
4112 hydrationErrors,
4113 );
4114 }
4115 } else {
4116 // If any children committed they were hydrated.
4117 inHydratedSubtree = true;
4118 }
4119 } else {
4120 inHydratedSubtree = false;
4121 }
4122 }
4123
4124 recursivelyTraversePassiveMountEffects(
4125 finishedRoot,
4126 finishedWork,
4127 committedLanes,
4128 committedTransitions,
4129 endTime,
4130 );
4131
4132 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4133 inHydratedSubtree = wasInHydratedSubtree;
4134 }
4135 break;
4136 }
4137 case SuspenseComponent: {
4138 const wasInHydratedSubtree = inHydratedSubtree;
4139 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4140 const prevState: SuspenseState | null =
4141 finishedWork.alternate !== null
4142 ? finishedWork.alternate.memoizedState
4143 : null;
4144 const nextState: SuspenseState | null = finishedWork.memoizedState;
4145 if (
4146 prevState !== null &&
4147 prevState.dehydrated !== null &&
4148 (nextState === null || nextState.dehydrated === null)
4149 ) {
4150 // This was dehydrated but is no longer dehydrated. We may have now either hydrated it
4151 // or client rendered it.
4152 const deletions = finishedWork.deletions;
4153 if (
4154 deletions !== null &&
4155 deletions.length > 0 &&
4156 deletions[0].tag === DehydratedFragment
4157 ) {
4158 // This was an abandoned hydration that deleted the dehydrated fragment. That means we
4159 // are not hydrating this Suspense boundary.
4160 inHydratedSubtree = false;
4161 const hydrationErrors = prevState.hydrationErrors;
4162 // If there were no hydration errors, that suggests that this was an intentional client
4163 // rendered boundary.
4164 if (hydrationErrors !== null) {
4165 const startTime: number = finishedWork.actualStartTime as any;
4166 logComponentErrored(
4167 finishedWork,
4168 startTime,
4169 endTime,
4170 hydrationErrors,
4171 );
4172 }
4173 } else {
4174 // If any children committed they were hydrated.
4175 inHydratedSubtree = true;
4176 }
4177 } else {
4178 inHydratedSubtree = false;
4179 }
4180 }
4181
4182 recursivelyTraversePassiveMountEffects(
4183 finishedRoot,
4184 finishedWork,
4185 committedLanes,
4186 committedTransitions,
4187 endTime,
4188 );
4189
4190 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4191 inHydratedSubtree = wasInHydratedSubtree;
4192 }
4193 break;
4194 }
4195 case LegacyHiddenComponent: {
4196 if (enableLegacyHidden) {
4197 recursivelyTraversePassiveMountEffects(
4198 finishedRoot,
4199 finishedWork,
4200 committedLanes,
4201 committedTransitions,
4202 endTime,
4203 );
4204
4205 if (flags & Passive) {
4206 const current = finishedWork.alternate;
4207 const instance: OffscreenInstance = finishedWork.stateNode;
4208 commitOffscreenPassiveMountEffects(current, finishedWork, instance);
4209 }
4210 }
4211 break;
4212 }
4213 case OffscreenComponent: {
4214 // TODO: Pass `current` as argument to this function
4215 const instance: OffscreenInstance = finishedWork.stateNode;
4216 const current = finishedWork.alternate;
4217 const nextState: OffscreenState | null = finishedWork.memoizedState;
4218
4219 const isHidden = nextState !== null;
4220
4221 if (isHidden) {
4222 if (
4223 isViewTransitionEligible &&
4224 current !== null &&
4225 current.memoizedState === null
4226 ) {
4227 // Content is now hidden but wasn't before. This means we could've
4228 // triggered an exit animation on the content. Restore the view
4229 // transitions if there were any assigned in the snapshot phase.
4230 restoreEnterOrExitViewTransitions(current);
4231 }
4232 if (instance._visibility & OffscreenPassiveEffectsConnected) {
4233 // The effects are currently connected. Update them.
4234 recursivelyTraversePassiveMountEffects(
4235 finishedRoot,
4236 finishedWork,
4237 committedLanes,
4238 committedTransitions,
4239 endTime,
4240 );
4241 } else {
4242 if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
4243 // The effects are currently disconnected. Since the tree is hidden,
4244 // don't connect them. This also applies to the initial render.
4245 // "Atomic" effects are ones that need to fire on every commit,
4246 // even during pre-rendering. An example is updating the reference
4247 // count on cache instances.
4248 recursivelyTraverseAtomicPassiveEffects(
4249 finishedRoot,
4250 finishedWork,
4251 committedLanes,
4252 committedTransitions,
4253 endTime,
4254 );
4255 } else {
4256 // Legacy Mode: Fire the effects even if the tree is hidden.
4257 instance._visibility |= OffscreenPassiveEffectsConnected;
4258 recursivelyTraversePassiveMountEffects(
4259 finishedRoot,
4260 finishedWork,
4261 committedLanes,
4262 committedTransitions,
4263 endTime,
4264 );
4265 }
4266 }
4267 } else {
4268 // Tree is visible
4269 if (
4270 isViewTransitionEligible &&
4271 current !== null &&
4272 current.memoizedState !== null
4273 ) {
4274 // Content is now visible but wasn't before. This means we could've
4275 // triggered an enter animation on the content. Restore the view
4276 // transitions if there were any assigned in the snapshot phase.
4277 restoreEnterOrExitViewTransitions(finishedWork);
4278 }
4279 if (instance._visibility & OffscreenPassiveEffectsConnected) {
4280 // The effects are currently connected. Update them.
4281 recursivelyTraversePassiveMountEffects(
4282 finishedRoot,
4283 finishedWork,
4284 committedLanes,
4285 committedTransitions,
4286 endTime,
4287 );
4288 } else {
4289 // The effects are currently disconnected. Reconnect them, while also
4290 // firing effects inside newly mounted trees. This also applies to
4291 // the initial render.
4292 instance._visibility |= OffscreenPassiveEffectsConnected;
4293
4294 const includeWorkInProgressEffects =
4295 (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
4296 (enableProfilerTimer &&
4297 enableComponentPerformanceTrack &&
4298 finishedWork.actualDuration !== 0 &&
4299 (finishedWork.alternate === null ||
4300 finishedWork.alternate.child !== finishedWork.child));
4301 recursivelyTraverseReconnectPassiveEffects(
4302 finishedRoot,
4303 finishedWork,
4304 committedLanes,
4305 committedTransitions,
4306 includeWorkInProgressEffects,
4307 endTime,
4308 );
4309
4310 if (
4311 enableProfilerTimer &&
4312 enableProfilerCommitHooks &&
4313 enableComponentPerformanceTrack &&
4314 (finishedWork.mode & ProfileMode) !== NoMode &&
4315 !inHydratedSubtree
4316 ) {
4317 // Log the reappear in the render phase.
4318 const startTime = finishedWork.actualStartTime as any as number;
4319 if (startTime >= 0 && endTime - startTime > 0.05) {
4320 logComponentReappeared(finishedWork, startTime, endTime);
4321 }
4322 if (
4323 componentEffectStartTime >= 0 &&
4324 componentEffectEndTime >= 0 &&
4325 componentEffectEndTime - componentEffectStartTime > 0.05
4326 ) {
4327 logComponentReappeared(
4328 finishedWork,
4329 componentEffectStartTime,
4330 componentEffectEndTime,
4331 );
4332 }
4333 }
4334 }
4335 }
4336
4337 if (flags & Passive) {
4338 commitOffscreenPassiveMountEffects(current, finishedWork, instance);
4339 }
4340 break;
4341 }
4342 case CacheComponent: {
4343 recursivelyTraversePassiveMountEffects(
4344 finishedRoot,
4345 finishedWork,
4346 committedLanes,
4347 committedTransitions,
4348 endTime,
4349 );
4350 if (flags & Passive) {
4351 // TODO: Pass `current` as argument to this function
4352 const current = finishedWork.alternate;
4353 commitCachePassiveMountEffect(current, finishedWork);
4354 }
4355 break;
4356 }
4357 case ViewTransitionComponent: {
4358 if (enableViewTransition) {
4359 if (isViewTransitionEligible) {
4360 const current = finishedWork.alternate;
4361 if (current === null) {
4362 // This is a new mount. We should have handled this as part of the
4363 // Placement effect or it is deeper inside a entering transition.
4364 } else {
4365 // Something mutated within this subtree. This might have caused
4366 // something to cross-fade if we didn't already cancel it.
4367 // If not, restore it.
4368 restoreUpdateViewTransition(current, finishedWork);
4369 }
4370 }
4371 recursivelyTraversePassiveMountEffects(
4372 finishedRoot,
4373 finishedWork,
4374 committedLanes,
4375 committedTransitions,
4376 endTime,
4377 );
4378 break;
4379 }
4380 // Fallthrough
4381 }
4382 case TracingMarkerComponent: {
4383 if (enableTransitionTracing) {
4384 recursivelyTraversePassiveMountEffects(
4385 finishedRoot,
4386 finishedWork,
4387 committedLanes,
4388 committedTransitions,
4389 endTime,
4390 );
4391 if (flags & Passive) {
4392 commitTracingMarkerPassiveMountEffect(finishedWork);
4393 }
4394 break;
4395 }
4396 // Intentional fallthrough to next branch
4397 }
4398 default: {
4399 recursivelyTraversePassiveMountEffects(
4400 finishedRoot,
4401 finishedWork,
4402 committedLanes,
4403 committedTransitions,
4404 endTime,
4405 );
4406 break;
4407 }
4408 }
4409
4410 if (
4411 enableProfilerTimer &&
4412 enableProfilerCommitHooks &&
4413 enableComponentPerformanceTrack &&
4414 (finishedWork.mode & ProfileMode) !== NoMode
4415 ) {
4416 const isMount =
4417 !inHydratedSubtree &&
4418 finishedWork.alternate === null &&
4419 finishedWork.return !== null &&
4420 finishedWork.return.alternate !== null;
4421 if (isMount) {
4422 // Log the mount in the render phase.
4423 const startTime = finishedWork.actualStartTime as any as number;
4424 if (startTime >= 0 && endTime - startTime > 0.05) {
4425 logComponentMount(finishedWork, startTime, endTime);
4426 }
4427 }
4428 if (componentEffectStartTime >= 0 && componentEffectEndTime >= 0) {
4429 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {
4430 logComponentEffect(
4431 finishedWork,
4432 componentEffectStartTime,
4433 componentEffectEndTime,
4434 componentEffectDuration,
4435 componentEffectErrors,
4436 );
4437 }
4438 if (isMount && componentEffectEndTime - componentEffectStartTime > 0.05) {
4439 logComponentMount(
4440 finishedWork,
4441 componentEffectStartTime,
4442 componentEffectEndTime,
4443 );
4444 }
4445 }
4446 }
4447
4448 popComponentEffectStart(prevEffectStart);
4449 popComponentEffectDuration(prevEffectDuration);
4450 popComponentEffectErrors(prevEffectErrors);
4451 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4452 popDeepEquality(prevDeepEquality);
4453 }
4454
4455 function recursivelyTraverseReconnectPassiveEffects(
4456 finishedRoot: FiberRoot,
4457 parentFiber: Fiber,
4458 committedLanes: Lanes,
4459 committedTransitions: Array<Transition> | null,
4460 includeWorkInProgressEffects: boolean,
4461 endTime: number,
4462 ) {
4463 // This function visits both newly finished work and nodes that were re-used
4464 // from a previously committed tree. We cannot check non-static flags if the
4465 // node was reused.
4466 const childShouldIncludeWorkInProgressEffects =
4467 includeWorkInProgressEffects &&
4468 ((parentFiber.subtreeFlags & PassiveMask) !== NoFlags ||
4469 (enableProfilerTimer &&
4470 enableComponentPerformanceTrack &&
4471 parentFiber.actualDuration !== 0 &&
4472 (parentFiber.alternate === null ||
4473 parentFiber.alternate.child !== parentFiber.child)));
4474
4475 // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
4476 let child = parentFiber.child;
4477 while (child !== null) {
4478 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4479 const nextSibling = child.sibling;
4480 reconnectPassiveEffects(
4481 finishedRoot,
4482 child,
4483 committedLanes,
4484 committedTransitions,
4485 childShouldIncludeWorkInProgressEffects,
4486 nextSibling !== null
4487 ? (nextSibling.actualStartTime as any as number)
4488 : endTime,
4489 );
4490 child = nextSibling;
4491 } else {
4492 reconnectPassiveEffects(
4493 finishedRoot,
4494 child,
4495 committedLanes,
4496 committedTransitions,
4497 childShouldIncludeWorkInProgressEffects,
4498 endTime,
4499 );
4500 child = child.sibling;
4501 }
4502 }
4503 }
4504
4505 export function reconnectPassiveEffects(
4506 finishedRoot: FiberRoot,
4507 finishedWork: Fiber,
4508 committedLanes: Lanes,
4509 committedTransitions: Array<Transition> | null,
4510 // This function visits both newly finished work and nodes that were re-used
4511 // from a previously committed tree. We cannot check non-static flags if the
4512 // node was reused.
4513 includeWorkInProgressEffects: boolean,
4514 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
4515 ) {
4516 const prevEffectStart = pushComponentEffectStart();
4517 const prevEffectDuration = pushComponentEffectDuration();
4518 const prevEffectErrors = pushComponentEffectErrors();
4519 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();
4520 const prevDeepEquality = pushDeepEquality();
4521
4522 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
4523 // render time. We do this after the fact in the passive effect to avoid the overhead of this
4524 // getting in the way of the render characteristics and avoid the overhead of unwinding
4525 // uncommitted renders.
4526 if (
4527 enableProfilerTimer &&
4528 enableComponentPerformanceTrack &&
4529 includeWorkInProgressEffects &&
4530 (finishedWork.mode & ProfileMode) !== NoMode &&
4531 (finishedWork.actualStartTime as any as number) > 0 &&
4532 (finishedWork.flags & PerformedWork) !== NoFlags
4533 ) {
4534 logComponentRender(
4535 finishedWork,
4536 finishedWork.actualStartTime as any as number,
4537 endTime,
4538 inHydratedSubtree,
4539 committedLanes,
4540 );
4541 }
4542
4543 const flags = finishedWork.flags;
4544 switch (finishedWork.tag) {
4545 case FunctionComponent:
4546 case ForwardRef:
4547 case SimpleMemoComponent: {
4548 recursivelyTraverseReconnectPassiveEffects(
4549 finishedRoot,
4550 finishedWork,
4551 committedLanes,
4552 committedTransitions,
4553 includeWorkInProgressEffects,
4554 endTime,
4555 );
4556 // TODO: Check for PassiveStatic flag
4557 commitHookPassiveMountEffects(finishedWork, HookPassive);
4558 break;
4559 }
4560 // Unlike commitPassiveMountOnFiber, we don't need to handle HostRoot
4561 // because this function only visits nodes that are inside an
4562 // Offscreen fiber.
4563 // case HostRoot: {
4564 // ...
4565 // }
4566 case LegacyHiddenComponent: {
4567 if (enableLegacyHidden) {
4568 recursivelyTraverseReconnectPassiveEffects(
4569 finishedRoot,
4570 finishedWork,
4571 committedLanes,
4572 committedTransitions,
4573 includeWorkInProgressEffects,
4574 endTime,
4575 );
4576
4577 if (includeWorkInProgressEffects && flags & Passive) {
4578 // TODO: Pass `current` as argument to this function
4579 const current: Fiber | null = finishedWork.alternate;
4580 const instance: OffscreenInstance = finishedWork.stateNode;
4581 commitOffscreenPassiveMountEffects(current, finishedWork, instance);
4582 }
4583 }
4584 break;
4585 }
4586 case OffscreenComponent: {
4587 const instance: OffscreenInstance = finishedWork.stateNode;
4588 const nextState: OffscreenState | null = finishedWork.memoizedState;
4589
4590 const isHidden = nextState !== null;
4591
4592 if (isHidden) {
4593 if (instance._visibility & OffscreenPassiveEffectsConnected) {
4594 // The effects are currently connected. Update them.
4595 recursivelyTraverseReconnectPassiveEffects(
4596 finishedRoot,
4597 finishedWork,
4598 committedLanes,
4599 committedTransitions,
4600 includeWorkInProgressEffects,
4601 endTime,
4602 );
4603 } else {
4604 if (disableLegacyMode || finishedWork.mode & ConcurrentMode) {
4605 // The effects are currently disconnected. Since the tree is hidden,
4606 // don't connect them. This also applies to the initial render.
4607 // "Atomic" effects are ones that need to fire on every commit,
4608 // even during pre-rendering. An example is updating the reference
4609 // count on cache instances.
4610 recursivelyTraverseAtomicPassiveEffects(
4611 finishedRoot,
4612 finishedWork,
4613 committedLanes,
4614 committedTransitions,
4615 endTime,
4616 );
4617 } else {
4618 // Legacy Mode: Fire the effects even if the tree is hidden.
4619 instance._visibility |= OffscreenPassiveEffectsConnected;
4620 recursivelyTraverseReconnectPassiveEffects(
4621 finishedRoot,
4622 finishedWork,
4623 committedLanes,
4624 committedTransitions,
4625 includeWorkInProgressEffects,
4626 endTime,
4627 );
4628 }
4629 }
4630 } else {
4631 // Tree is visible
4632
4633 // Since we're already inside a reconnecting tree, it doesn't matter
4634 // whether the effects are currently connected. In either case, we'll
4635 // continue traversing the tree and firing all the effects.
4636 //
4637 // We do need to set the "connected" flag on the instance, though.
4638 instance._visibility |= OffscreenPassiveEffectsConnected;
4639
4640 recursivelyTraverseReconnectPassiveEffects(
4641 finishedRoot,
4642 finishedWork,
4643 committedLanes,
4644 committedTransitions,
4645 includeWorkInProgressEffects,
4646 endTime,
4647 );
4648 }
4649
4650 if (includeWorkInProgressEffects && flags & Passive) {
4651 // TODO: Pass `current` as argument to this function
4652 const current: Fiber | null = finishedWork.alternate;
4653 commitOffscreenPassiveMountEffects(current, finishedWork, instance);
4654 }
4655 break;
4656 }
4657 case CacheComponent: {
4658 recursivelyTraverseReconnectPassiveEffects(
4659 finishedRoot,
4660 finishedWork,
4661 committedLanes,
4662 committedTransitions,
4663 includeWorkInProgressEffects,
4664 endTime,
4665 );
4666 if (includeWorkInProgressEffects && flags & Passive) {
4667 // TODO: Pass `current` as argument to this function
4668 const current = finishedWork.alternate;
4669 commitCachePassiveMountEffect(current, finishedWork);
4670 }
4671 break;
4672 }
4673 case TracingMarkerComponent: {
4674 if (enableTransitionTracing) {
4675 recursivelyTraverseReconnectPassiveEffects(
4676 finishedRoot,
4677 finishedWork,
4678 committedLanes,
4679 committedTransitions,
4680 includeWorkInProgressEffects,
4681 endTime,
4682 );
4683 if (includeWorkInProgressEffects && flags & Passive) {
4684 commitTracingMarkerPassiveMountEffect(finishedWork);
4685 }
4686 break;
4687 }
4688 // Intentional fallthrough to next branch
4689 }
4690 default: {
4691 recursivelyTraverseReconnectPassiveEffects(
4692 finishedRoot,
4693 finishedWork,
4694 committedLanes,
4695 committedTransitions,
4696 includeWorkInProgressEffects,
4697 endTime,
4698 );
4699 break;
4700 }
4701 }
4702
4703 if (
4704 enableProfilerTimer &&
4705 enableProfilerCommitHooks &&
4706 enableComponentPerformanceTrack &&
4707 (finishedWork.mode & ProfileMode) !== NoMode &&
4708 componentEffectStartTime >= 0 &&
4709 componentEffectEndTime >= 0 &&
4710 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)
4711 ) {
4712 logComponentEffect(
4713 finishedWork,
4714 componentEffectStartTime,
4715 componentEffectEndTime,
4716 componentEffectDuration,
4717 componentEffectErrors,
4718 );
4719 }
4720
4721 popComponentEffectStart(prevEffectStart);
4722 popComponentEffectDuration(prevEffectDuration);
4723 popComponentEffectErrors(prevEffectErrors);
4724 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);
4725 popDeepEquality(prevDeepEquality);
4726 }
4727
4728 function recursivelyTraverseAtomicPassiveEffects(
4729 finishedRoot: FiberRoot,
4730 parentFiber: Fiber,
4731 committedLanes: Lanes,
4732 committedTransitions: Array<Transition> | null,
4733 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
4734 ) {
4735 // "Atomic" effects are ones that need to fire on every commit, even during
4736 // pre-rendering. We call this function when traversing a hidden tree whose
4737 // regular effects are currently disconnected.
4738 // TODO: Add special flag for atomic effects
4739 if (
4740 parentFiber.subtreeFlags & PassiveMask ||
4741 (enableProfilerTimer &&
4742 enableComponentPerformanceTrack &&
4743 parentFiber.actualDuration !== 0 &&
4744 (parentFiber.alternate === null ||
4745 parentFiber.alternate.child !== parentFiber.child))
4746 ) {
4747 let child = parentFiber.child;
4748 while (child !== null) {
4749 if (enableProfilerTimer && enableComponentPerformanceTrack) {
4750 const nextSibling = child.sibling;
4751 commitAtomicPassiveEffects(
4752 finishedRoot,
4753 child,
4754 committedLanes,
4755 committedTransitions,
4756 nextSibling !== null
4757 ? (nextSibling.actualStartTime as any as number)
4758 : endTime,
4759 );
4760 child = nextSibling;
4761 } else {
4762 commitAtomicPassiveEffects(
4763 finishedRoot,
4764 child,
4765 committedLanes,
4766 committedTransitions,
4767 endTime,
4768 );
4769 child = child.sibling;
4770 }
4771 }
4772 }
4773 }
4774
4775 function commitAtomicPassiveEffects(
4776 finishedRoot: FiberRoot,
4777 finishedWork: Fiber,
4778 committedLanes: Lanes,
4779 committedTransitions: Array<Transition> | null,
4780 endTime: number, // Profiling-only. The start time of the next Fiber or root completion.
4781 ) {
4782 const prevDeepEquality = pushDeepEquality();
4783
4784 // If this component rendered in Profiling mode (DEV or in Profiler component) then log its
4785 // render time. A render can happen even if the subtree is offscreen.
4786 if (
4787 enableProfilerTimer &&
4788 enableComponentPerformanceTrack &&
4789 (finishedWork.mode & ProfileMode) !== NoMode &&
4790 (finishedWork.actualStartTime as any as number) > 0 &&
4791 (finishedWork.flags & PerformedWork) !== NoFlags
4792 ) {
4793 logComponentRender(
4794 finishedWork,
4795 finishedWork.actualStartTime as any as number,
4796 endTime,
4797 inHydratedSubtree,
4798 committedLanes,
4799 );
4800 }
4801
4802 // "Atomic" effects are ones that need to fire on every commit, even during
4803 // pre-rendering. We call this function when traversing a hidden tree whose
4804 // regular effects are currently disconnected.
4805 const flags = finishedWork.flags;
4806 switch (finishedWork.tag) {
4807 case OffscreenComponent: {
4808 recursivelyTraverseAtomicPassiveEffects(
4809 finishedRoot,
4810 finishedWork,
4811 committedLanes,
4812 committedTransitions,
4813 endTime,
4814 );
4815 if (flags & Passive) {
4816 // TODO: Pass `current` as argument to this function
4817 const current = finishedWork.alternate;
4818 const instance: OffscreenInstance = finishedWork.stateNode;
4819 commitOffscreenPassiveMountEffects(current, finishedWork, instance);
4820 }
4821 break;
4822 }
4823 case CacheComponent: {
4824 recursivelyTraverseAtomicPassiveEffects(
4825 finishedRoot,
4826 finishedWork,
4827 committedLanes,
4828 committedTransitions,
4829 endTime,
4830 );
4831 if (flags & Passive) {
4832 // TODO: Pass `current` as argument to this function
4833 const current = finishedWork.alternate;
4834 commitCachePassiveMountEffect(current, finishedWork);
4835 }
4836 break;
4837 }
4838 default: {
4839 recursivelyTraverseAtomicPassiveEffects(
4840 finishedRoot,
4841 finishedWork,
4842 committedLanes,
4843 committedTransitions,
4844 endTime,
4845 );
4846 break;
4847 }
4848 }
4849
4850 popDeepEquality(prevDeepEquality);
4851 }
4852
4853 export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
4854 resetComponentEffectTimers();
4855 commitPassiveUnmountOnFiber(finishedWork);
4856 }
4857
4858 // If we're inside a brand new tree, or a tree that was already visible, then we
4859 // should only suspend host components that have a ShouldSuspendCommit flag.
4860 // Components without it haven't changed since the last commit, so we can skip
4861 // over those.
4862 //
4863 // When we enter a tree that is being revealed (going from hidden -> visible),
4864 // we need to suspend _any_ component that _may_ suspend. Even if they're
4865 // already in the "current" tree. Because their visibility has changed, the
4866 // browser may not have prerendered them yet. So we check the MaySuspendCommit
4867 // flag instead.
4868 //
4869 // Note that MaySuspendCommit and ShouldSuspendCommit also includes named
4870 // ViewTransitions so that we know to also visit those to collect appearing
4871 // pairs.
4872 let suspenseyCommitFlag: Flags = ShouldSuspendCommit;
4873 export function accumulateSuspenseyCommit(
4874 finishedWork: Fiber,
4875 committedLanes: Lanes,
4876 suspendedState: SuspendedState,
4877 ): void {
4878 resetAppearingViewTransitions();
4879 accumulateSuspenseyCommitOnFiber(
4880 finishedWork,
4881 committedLanes,
4882 suspendedState,
4883 );
4884 }
4885
4886 function recursivelyAccumulateSuspenseyCommit(
4887 parentFiber: Fiber,
4888 committedLanes: Lanes,
4889 suspendedState: SuspendedState,
4890 ): void {
4891 if (parentFiber.subtreeFlags & suspenseyCommitFlag) {
4892 let child = parentFiber.child;
4893 while (child !== null) {
4894 accumulateSuspenseyCommitOnFiber(child, committedLanes, suspendedState);
4895 child = child.sibling;
4896 }
4897 }
4898 }
4899
4900 function accumulateSuspenseyCommitOnFiber(
4901 fiber: Fiber,
4902 committedLanes: Lanes,
4903 suspendedState: SuspendedState,
4904 ) {
4905 switch (fiber.tag) {
4906 case HostHoistable: {
4907 recursivelyAccumulateSuspenseyCommit(
4908 fiber,
4909 committedLanes,
4910 suspendedState,
4911 );
4912 if (fiber.flags & suspenseyCommitFlag) {
4913 if (fiber.memoizedState !== null) {
4914 suspendResource(
4915 suspendedState,
4916 // This should always be set by visiting HostRoot first
4917 currentHoistableRoot as any,
4918 fiber.memoizedState,
4919 fiber.memoizedProps,
4920 );
4921 } else {
4922 const instance = fiber.stateNode;
4923 const type = fiber.type;
4924 const props = fiber.memoizedProps;
4925 // TODO: Allow sync lanes to suspend too with an opt-in.
4926 if (
4927 includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
4928 maySuspendCommitInSyncRender(type, props)
4929 ) {
4930 suspendInstance(suspendedState, instance, type, props);
4931 }
4932 }
4933 }
4934 break;
4935 }
4936 case HostComponent: {
4937 recursivelyAccumulateSuspenseyCommit(
4938 fiber,
4939 committedLanes,
4940 suspendedState,
4941 );
4942 if (fiber.flags & suspenseyCommitFlag) {
4943 const instance = fiber.stateNode;
4944 const type = fiber.type;
4945 const props = fiber.memoizedProps;
4946 // TODO: Allow sync lanes to suspend too with an opt-in.
4947 if (
4948 includesOnlySuspenseyCommitEligibleLanes(committedLanes) ||
4949 maySuspendCommitInSyncRender(type, props)
4950 ) {
4951 suspendInstance(suspendedState, instance, type, props);
4952 }
4953 }
4954 break;
4955 }
4956 case HostRoot:
4957 case HostPortal: {
4958 // $FlowFixMe[constant-condition]
4959 if (supportsResources) {
4960 const previousHoistableRoot = currentHoistableRoot;
4961 const container: Container = fiber.stateNode.containerInfo;
4962 currentHoistableRoot = getHoistableRoot(container);
4963
4964 recursivelyAccumulateSuspenseyCommit(
4965 fiber,
4966 committedLanes,
4967 suspendedState,
4968 );
4969 currentHoistableRoot = previousHoistableRoot;
4970 } else {
4971 recursivelyAccumulateSuspenseyCommit(
4972 fiber,
4973 committedLanes,
4974 suspendedState,
4975 );
4976 }
4977 break;
4978 }
4979 case OffscreenComponent: {
4980 const isHidden = (fiber.memoizedState as OffscreenState | null) !== null;
4981 if (isHidden) {
4982 // Don't suspend in hidden trees
4983 } else {
4984 const current = fiber.alternate;
4985 const wasHidden =
4986 current !== null &&
4987 (current.memoizedState as OffscreenState | null) !== null;
4988 if (wasHidden) {
4989 // This tree is being revealed. Visit all newly visible suspensey
4990 // instances, even if they're in the current tree.
4991 const prevFlags = suspenseyCommitFlag;
4992 suspenseyCommitFlag = MaySuspendCommit;
4993 recursivelyAccumulateSuspenseyCommit(
4994 fiber,
4995 committedLanes,
4996 suspendedState,
4997 );
4998 suspenseyCommitFlag = prevFlags;
4999 } else {
5000 recursivelyAccumulateSuspenseyCommit(
Showing first 5,000 of 5,618 lines. View raw