1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {11 Instance,12 TextInstance,13 ActivityInstance,14 SuspenseInstance,15 Container,16 HoistableRoot,17 FormInstance,18 Props,19 SuspendedState,20} from './ReactFiberConfig';21import type {Fiber, FiberRoot} from './ReactInternalTypes';22import type {Lanes} from './ReactFiberLane';23import {24 includesLoadingIndicatorLanes,25 includesOnlySuspenseyCommitEligibleLanes,26 includesOnlyViewTransitionEligibleLanes,27} from './ReactFiberLane';28import type {ActivityState} from './ReactFiberActivityComponent';29import type {SuspenseState, RetryQueue} from './ReactFiberSuspenseComponent';30import type {UpdateQueue} from './ReactFiberClassUpdateQueue';31import type {FunctionComponentUpdateQueue} from './ReactFiberHooks';32import type {Wakeable, ViewTransitionProps} from 'shared/ReactTypes';33import type {34 OffscreenState,35 OffscreenInstance,36 OffscreenQueue,37} from './ReactFiberOffscreenComponent';38import type {Cache} from './ReactFiberCacheComponent';39import type {RootState} from './ReactFiberRoot';40import type {Transition} from 'react/src/ReactStartTransition';41import type {42 TracingMarkerInstance,43 TransitionAbort,44} from './ReactFiberTracingMarkerComponent';45import type {ViewTransitionState} from './ReactFiberViewTransitionComponent';4647import {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';65import {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';91import {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';121import {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';141import {142 logComponentRender,143 logComponentErrored,144 logComponentEffect,145 logComponentMount,146 logComponentUnmount,147 logComponentReappeared,148 logComponentDisappeared,149 pushDeepEquality,150 popDeepEquality,151} from './ReactFiberPerformanceTrack';152import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';153import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';154import {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';187import {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';201import {202 HasEffect as HookHasEffect,203 Layout as HookLayout,204 Insertion as HookInsertion,205 Passive as HookPassive,206} from './ReactHookEffectTags';207import {doesFiberContain} from './ReactFiberTreeReflection';208import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';209import {releaseCache, retainCache} from './ReactFiberCacheComponent';210import {clearTransitionsForLanes} from './ReactFiberLane';211import {212 OffscreenVisible,213 OffscreenPassiveEffectsConnected,214} from './ReactFiberOffscreenComponent';215import {216 TransitionRoot,217 TransitionTracingMarker,218} from './ReactFiberTracingMarkerComponent';219import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';220import {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';239import {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';259import {260 commitFragmentInstanceDeletionEffects,261 commitFragmentInstanceInsertionEffects,262} from './ReactFiberFragmentInstance';263import {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';280import {281 viewTransitionMutationContext,282 pushRootMutationContext,283 pushMutationContext,284 popMutationContext,285 rootMutationContext,286} from './ReactFiberMutationTracking';287import {288 trackNamedViewTransition,289 untrackNamedViewTransition,290} from './ReactFiberDuplicateViewTransitions';291import {markIndicatorHandled} from './ReactFiberRootScheduler';292import type {Flags} from './ReactFiberFlags';293294type LayoutEffectTraversalFlags = number;295296const NoLayoutEffectTraversalFlags = /* */ 0b00;297const IncludeWorkInProgressEffects = /* */ 0b01;298const IncludeHostSingletons = /* */ 0b10;299300// 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.302let offscreenSubtreeIsHidden: boolean = false;303let 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.306let offscreenDirectParentIsHidden: boolean = false;307308// Used to track if a form needs to be reset at the end of the mutation phase.309let needsFormReset = false;310311const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;312313let nextEffect: Fiber | null = null;314315// Used for Profiling builds to track updaters.316let inProgressLanes: Lanes | null = null;317let inProgressRoot: FiberRoot | null = null;318319let focusedInstanceHandle: null | Fiber = null;320export let shouldFireAfterActiveInstanceBlur: boolean = false;321322// Used during the commit phase to track whether a parent ViewTransition component323// might have been affected by any mutations / relayouts below.324let viewTransitionContextChanged: boolean = false;325let inUpdateViewTransition: boolean = false;326let rootViewTransitionAffected: boolean = false;327let rootViewTransitionNameCanceled: boolean = false;328329function 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) === NoFlags346 );347 } else {348 return false;349 }350}351352export function commitBeforeMutationEffects(353 root: FiberRoot,354 firstChild: Fiber,355 committedLanes: Lanes,356): void {357 focusedInstanceHandle = prepareForCommit(root.containerInfo);358 shouldFireAfterActiveInstanceBlur = false;359360 const isViewTransitionEligible =361 enableViewTransition &&362 includesOnlyViewTransitionEligibleLanes(committedLanes);363364 nextEffect = firstChild;365 commitBeforeMutationEffects_begin(isViewTransitionEligible);366367 // We no longer need to track the active instance fiber368 focusedInstanceHandle = null;369 // We've found any matched pairs and can now reset.370 resetAppearingViewTransitions();371}372373function 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 = isViewTransitionEligible377 ? BeforeAndAfterMutationTransitionMask378 : BeforeMutationMask;379 while (nextEffect !== null) {380 const fiber = nextEffect;381382 // 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 optimization386 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 }397398 if (399 enableViewTransition &&400 fiber.alternate === null &&401 (fiber.flags & Placement) !== NoFlags402 ) {403 // Skip before mutation effects of the children because we don't want404 // to trigger updates of any nested view transitions and we shouldn't405 // have any other before mutation effects since snapshot effects are406 // only applied to updates. TODO: Model this using only flags.407 if (isViewTransitionEligible) {408 trackEnterViewTransitions(fiber);409 }410 commitBeforeMutationEffects_complete(isViewTransitionEligible);411 continue;412 }413414 // 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 isViewTransitionEligible426 ) {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 want436 // to trigger updates of any nested view transitions and we shouldn't437 // have any other before mutation effects since snapshot effects are438 // 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 }447448 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 the455 // parent HostInstance's layout or set of children (such as reorders)456 // might have also affected the positioning or size of the inner457 // ViewTransitions. Therefore we need to find them inside.458 commitNestedViewTransitions(fiber);459 }460 commitBeforeMutationEffects_complete(isViewTransitionEligible);461 }462 }463}464465function commitBeforeMutationEffects_complete(466 isViewTransitionEligible: boolean,467) {468 while (nextEffect !== null) {469 const fiber = nextEffect;470 commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);471472 const sibling = fiber.sibling;473 if (sibling !== null) {474 sibling.return = fiber.return;475 nextEffect = sibling;476 return;477 }478479 nextEffect = fiber.return;480 }481}482483function commitBeforeMutationEffectsOnFiber(484 finishedWork: Fiber,485 isViewTransitionEligible: boolean,486) {487 const current = finishedWork.alternate;488 const flags = finishedWork.flags;489490 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 Flow499 doesFiberContain(finishedWork, focusedInstanceHandle)500 ) {501 shouldFireAfterActiveInstanceBlur = true;502 beforeActiveInstanceBlur(finishedWork);503 }504 }505 }506507 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 types549 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 the555 // Placement effect or it is deeper inside a entering transition.556 } else {557 // Something may have mutated within this subtree. This might need to cause558 // a cross-fade of this parent. We first assign old names to the559 // 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 continue562 // walking the tree until we find HostComponent but to do that we need563 // to use a stack which requires refactoring this phase.564 commitBeforeUpdateViewTransition(current, finishedWork);565 }566 }567 break;568 }569 // Fallthrough570 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}580581function 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}599600function 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 does611 // 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 }636637 if (flags & Callback) {638 commitClassCallbacks(finishedWork);639 }640641 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 appropriate667 // styles before other layout effects run. This isn't perfect because668 // an early sibling of the singleton may have an effect that can669 // observe the singleton before it is acquired.670 // @TODO move this to the mutation phase. The reason it isn't there yet671 // is it seemingly requires an extra traversal because we need to move the672 // disappear effect into a phase before the appear phase673 if (current === null && flags & Update) {674 // Unlike in the reappear path we only acquire on new mount675 commitHostSingletonAcquisition(finishedWork);676 }677 // We fall through to the HostComponent case below.678 }679 // Fallthrough680 }681 case HostHoistable:682 case HostComponent: {683 recursivelyTraverseLayoutEffects(684 finishedRoot,685 finishedWork,686 committedLanes,687 );688689 // Renderers may schedule work to be done after host components are mounted690 // (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 }700701 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 to708 // fire when the tree becomes visible again.709 if (flags & Update) {710 const prevProfilerEffectDuration = pushNestedEffectDurations();711712 recursivelyTraverseLayoutEffects(713 finishedRoot,714 finishedWork,715 committedLanes,716 );717718 const profilerInstance = finishedWork.stateNode;719720 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 }727728 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 for766 // 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.793794 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;801802 if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {803 // This is the root of a reappearing boundary. As we continue804 // traversing the layout effects, we must also re-mount layout805 // effects that were unmounted when the Offscreen subtree was806 // 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.05830 ) {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 // Fallthrough882 default: {883 recursivelyTraverseLayoutEffects(884 finishedRoot,885 finishedWork,886 committedLanes,887 );888 break;889 }890 }891892 if (893 enableProfilerTimer &&894 enableProfilerCommitHooks &&895 enableComponentPerformanceTrack &&896 (finishedWork.mode & ProfileMode) !== NoMode &&897 componentEffectStartTime >= 0 &&898 componentEffectEndTime >= 0899 ) {900 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {901 logComponentEffect(902 finishedWork,903 componentEffectStartTime,904 componentEffectEndTime,905 componentEffectDuration,906 componentEffectErrors,907 );908 }909 if (910 // Insertion911 finishedWork.alternate === null &&912 finishedWork.return !== null &&913 finishedWork.return.alternate !== null &&914 componentEffectEndTime - componentEffectStartTime > 0.05915 ) {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 }929930 popComponentEffectStart(prevEffectStart);931 popComponentEffectDuration(prevEffectDuration);932 popComponentEffectErrors(prevEffectErrors);933 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);934}935936function 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);954955 if (deletedOffscreenInstance !== null) {956 if (957 transitionInstance.pendingBoundaries !== null &&958 transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)959 ) {960 // $FlowFixMe[incompatible-use] found when upgrading Flow961 transitionInstance.pendingBoundaries.delete(962 deletedOffscreenInstance,963 );964 }965 }966 }967 });968 }969}970971function 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 to984 // the deletions phase instead of calculating it here while making sure985 // complete is called appropriately?986 deletedTransitions.forEach(transition => {987 // If one of the transitions on the tracing marker is a transition988 // that was in an aborted subtree, we will abort that tracing marker989 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 }10071008 // We only want to call onTransitionProgress when the marker hasn't been1009 // deleted1010 if (1011 deletedOffscreenInstance !== null &&1012 !isInDeletedTree &&1013 pendingBoundaries !== null &&1014 pendingBoundaries.has(deletedOffscreenInstance)1015 ) {1016 pendingBoundaries.delete(deletedOffscreenInstance);10171018 addMarkerProgressCallbackToPendingTransition(1019 abortedFiber.memoizedProps.name,1020 deletedTransitions,1021 pendingBoundaries,1022 );1023 }1024 }1025 }1026 });1027 }1028 }1029}10301031function 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 the1040 // aborted subtree and cancels them1041 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 );10621063 break;1064 default:1065 break;1066 }10671068 fiber = fiber.return;1069 }1070 }1071}10721073function commitTransitionProgress(offscreenFiber: Fiber) {1074 if (enableTransitionTracing) {1075 // This function adds suspense boundaries to the root1076 // or tracing marker's pendingBoundaries map.1077 // When a suspense boundary goes from a resolved to a fallback1078 // state we add the boundary to the map, and when it goes from1079 // a fallback to a resolved state, we remove the boundary from1080 // the map.10811082 // We use stateNode on the Offscreen component as a stable object1083 // that doesnt change from render to render. This way we can1084 // distinguish between different Offscreen instances (vs. the same1085 // Offscreen instance with different fibers)1086 const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10871088 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;10941095 const wasHidden = prevState !== null;1096 const isHidden = nextState !== null;10971098 const pendingMarkers = offscreenInstance._pendingMarkers;1099 // If there is a name on the suspense boundary, store that in1100 // the pending boundaries.1101 let name = null;1102 const parent = offscreenFiber.return;1103 if (1104 parent !== null &&1105 parent.tag === SuspenseComponent &&1106 parent.memoizedProps.name1107 ) {1108 name = parent.memoizedProps.name;1109 }11101111 if (!wasHidden && isHidden) {1112 // The suspense boundaries was just hidden. Add the boundary1113 // to the pending boundary set if it's there1114 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 !== null1130 ) {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. Remove1150 // the boundary from the pending suspense boundaries set1151 // if it's there1152 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 !== null1166 ) {1167 addMarkerProgressCallbackToPendingTransition(1168 markerName,1169 transitions,1170 pendingBoundaries,1171 );11721173 // If there are no more unresolved suspense boundaries, the interaction1174 // is considered finished1175 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}12011202function 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 visibility1208 // to either hidden or visible.1209 let child = parentFiber.child;1210 while (child !== null) {1211 hideOrUnhideAllChildrenOnFiber(child, isHidden);1212 child = child.sibling;1213 }1214}12151216function 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 that1227 // has the effect of also hiding everything inside of them.1228 //1229 // However, there's a special case for portals, because portals do not1230 // exist in the regular host tree hierarchy; we can't assume that just1231 // because a portal's HostComponent parent in the React tree will also be1232 // a parent in the actual host tree.1233 //1234 // So, if any portals exist within the tree, regardless of how deeply1235 // 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}12641265function 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}12781279function 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 to1287 // 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 any1295 // 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}13071308function 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 this1314 // get GC:ed but we don't know which for sure which parent is the current1315 // 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 the1323 // alternate's fields in the passive phase. Clearing the return pointer is1324 // sufficient for findDOMNode semantics.1325 const alternate = fiber.alternate;1326 if (alternate !== null) {1327 alternate.return = null;1328 }1329 fiber.return = null;1330}13311332function detachFiberAfterEffects(fiber: Fiber) {1333 const alternate = fiber.alternate;1334 if (alternate !== null) {1335 fiber.alternate = null;1336 detachFiberAfterEffects(alternate);1337 }13381339 // Clear cyclical Fiber fields. This level alone is designed to roughly1340 // approximate the planned Fiber refactor. In that world, `setState` will be1341 // bound to a special "instance" object instead of a Fiber. The Instance1342 // object will not have any of these fields. It will only be connected to1343 // the fiber tree via a single link at the root. So if this level alone is1344 // sufficient to fix memory issues, that bodes well for our plans.1345 fiber.child = null;1346 fiber.deletions = null;1347 fiber.sibling = null;13481349 // The `stateNode` is cyclical because on host nodes it points to the host1350 // 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 that1352 // 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;13611362 if (__DEV__) {1363 fiber._debugOwner = null;1364 }13651366 // Theoretically, nothing in here should be necessary, because we already1367 // disconnected the fiber from the tree. So even if something leaks this1368 // 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}13781379// These are tracked on the stack as we recursively traverse a1380// deleted subtree.1381// TODO: Update these during the whole mutation phase, not just during1382// a deletion.1383let hostParent: Instance | Container | null = null;1384let hostParentIsContainer: boolean = false;13851386function commitDeletionEffects(1387 root: FiberRoot,1388 returnFiber: Fiber,1389 deletedFiber: Fiber,1390) {1391 const prevEffectStart = pushComponentEffectStart();13921393 // $FlowFixMe[constant-condition]1394 if (supportsMutation) {1395 // We only have the top Fiber that was deleted but we need to recurse down its1396 // children to find all the terminal nodes.13971398 // Recursively delete all host nodes from the parent, detach refs, clean1399 // up mounted layout effects, and call componentWillUnmount.14001401 // We only need to remove the topmost host child in each branch. But then we1402 // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1403 // could split this into two separate traversals functions, where the second1404 // one doesn't include any removeChild logic. This is maybe the same1405 // function as "disappearLayoutEffects" (or whatever that turns into after1406 // the layout phase is refactored to use recursion).14071408 // Before starting, find the nearest host parent on the stack so we know1409 // which instance/container to remove the children from.1410 // TODO: Instead of searching up the fiber return path on every deletion, we1411 // can track the nearest host component on the JS stack as we traverse the1412 // 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 false1427 }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 }14481449 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 }14561457 if (1458 enableProfilerTimer &&1459 enableProfilerCommitHooks &&1460 enableComponentPerformanceTrack &&1461 (deletedFiber.mode & ProfileMode) !== NoMode &&1462 componentEffectStartTime >= 0 &&1463 componentEffectEndTime >= 0 &&1464 componentEffectEndTime - componentEffectStartTime > 0.051465 ) {1466 logComponentUnmount(1467 deletedFiber,1468 componentEffectStartTime,1469 componentEffectEndTime,1470 );1471 }1472 popComponentEffectStart(prevEffectStart);14731474 detachFiberMutation(deletedFiber);1475}14761477function 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 effects1483 let child = parent.child;1484 while (child !== null) {1485 commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1486 child = child.sibling;1487 }1488}14891490function 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);14971498 const prevEffectStart = pushComponentEffectStart();1499 const prevEffectDuration = pushComponentEffectDuration();1500 const prevEffectErrors = pushComponentEffectErrors();1501 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();15021503 // The cases in this outer switch modify the stack before they traverse1504 // into their subtree. There are simpler cases in the inner switch1505 // 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 enclosing1522 // Activity is visible. If the Activity is hidden (or has been hidden1523 // since mount), the instance was either never inserted or was1524 // detached by the disappear traversal. Skip in those cases.1525 if (!offscreenSubtreeWasHidden) {1526 unmountHoistable(deletedFiber.stateNode);1527 }1528 }1529 break;1530 }1531 // Fall through1532 }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 }15421543 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 );15541555 // Normally this is called in passive unmount effect phase however with1556 // HostSingleton we warn if you acquire one that is already associated to1557 // a different fiber. To increase our chances of avoiding this, specifically1558 // if you keyed a HostSingleton so there will be a delete followed by a Placement1559 // we treat detach eagerly here1560 commitHostSingletonRelease(deletedFiber);15611562 hostParent = prevHostParent;1563 hostParentIsContainer = prevHostParentIsContainer;15641565 break;1566 }1567 // Fall through1568 }1569 case HostComponent: {1570 if (!offscreenSubtreeWasHidden) {1571 safelyDetachRef(deletedFiber, nearestMountedAncestor);1572 }1573 if (enableFragmentRefs) {1574 commitFragmentInstanceDeletionEffects(deletedFiber);1575 }1576 // Intentional fallthrough to next branch1577 }1578 case HostText: {1579 if (1580 enableFragmentRefs &&1581 enableFragmentRefsTextNodes &&1582 // HostComponent falls through into this case.1583 deletedFiber.tag === HostText1584 ) {1585 commitFragmentInstanceDeletionEffects(deletedFiber);1586 }1587 // We only need to remove the nearest host child. Set the host parent1588 // to `null` on the stack to indicate that nested children don't1589 // 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;16021603 if (hostParent !== null) {1604 // Now that all the child effects have unmounted, we can remove the1605 // 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 }16511652 // Dehydrated fragments don't have any children16531654 // 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 }16971698 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. Before1765 // deleting the children, track that it's already unmounted so that we1766 // don't attempt to unmount the effects again.1767 // TODO: If the tree is hidden, in most cases we should be able to skip1768 // over the nested children entirely. An exception is we haven't yet found1769 // 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 how1771 // deeply they are nested. We should use a subtree flag to track whether a1772 // subtree includes a nested portal.1773 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1774 offscreenSubtreeWasHidden =1775 prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17761777 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 // Fallthrough1808 }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 // Fallthrough1822 }1823 default: {1824 recursivelyTraverseDeletionEffects(1825 finishedRoot,1826 nearestMountedAncestor,1827 deletedFiber,1828 );1829 break;1830 }1831 }18321833 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 }18501851 popComponentEffectStart(prevEffectStart);1852 popComponentEffectDuration(prevEffectDuration);1853 popComponentEffectErrors(prevEffectErrors);1854 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1855}18561857function 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}18741875function 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}19091910function 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}19461947function getRetryCache(finishedWork: Fiber) {1948 // TODO: Unify the interface for the retry cache so we don't have to switch1949 // 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}19771978function 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, React1984 // 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);19901991 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 }
Findings
✓ No findings reported for this file.