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 mountHoistable,173 unmountHoistable,174 prepareToCommitHoistables,175 maySuspendCommitInSyncRender,176 suspendInstance,177 suspendResource,178 resetFormInstance,179 registerSuspenseInstanceRetry,180 cancelViewTransitionName,181 cancelRootViewTransitionName,182 restoreRootViewTransitionName,183 isSingletonScope,184 updateFragmentInstanceFiber,185} from './ReactFiberConfig';186import {187 captureCommitPhaseError,188 resolveRetryWakeable,189 markCommitTimeOfFallback,190 restorePendingUpdaters,191 addTransitionStartCallbackToPendingTransition,192 addTransitionProgressCallbackToPendingTransition,193 addTransitionCompleteCallbackToPendingTransition,194 addMarkerProgressCallbackToPendingTransition,195 addMarkerIncompleteCallbackToPendingTransition,196 addMarkerCompleteCallbackToPendingTransition,197 retryDehydratedSuspenseBoundary,198 scheduleViewTransitionEvent,199} from './ReactFiberWorkLoop';200import {201 HasEffect as HookHasEffect,202 Layout as HookLayout,203 Insertion as HookInsertion,204 Passive as HookPassive,205} from './ReactHookEffectTags';206import {doesFiberContain} from './ReactFiberTreeReflection';207import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';208import {releaseCache, retainCache} from './ReactFiberCacheComponent';209import {clearTransitionsForLanes} from './ReactFiberLane';210import {211 OffscreenVisible,212 OffscreenPassiveEffectsConnected,213} from './ReactFiberOffscreenComponent';214import {215 TransitionRoot,216 TransitionTracingMarker,217} from './ReactFiberTracingMarkerComponent';218import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';219import {220 commitHookLayoutEffects,221 commitHookLayoutUnmountEffects,222 commitHookEffectListMount,223 commitHookEffectListUnmount,224 commitHookPassiveMountEffects,225 commitHookPassiveUnmountEffects,226 commitClassLayoutLifecycles,227 commitClassDidMount,228 commitClassCallbacks,229 commitClassHiddenCallbacks,230 commitClassSnapshot,231 safelyCallComponentWillUnmount,232 safelyAttachRef,233 safelyDetachRef,234 commitProfilerUpdate,235 commitProfilerPostCommit,236 commitRootCallbacks,237} from './ReactFiberCommitEffects';238import {239 commitHostMount,240 commitHostHydratedInstance,241 commitHostUpdate,242 commitHostTextUpdate,243 commitHostResetTextContent,244 commitShowHideSuspenseBoundary,245 commitShowHideHostInstance,246 commitShowHideHostTextInstance,247 commitHostPlacement,248 commitHostRootContainerChildren,249 commitHostPortalContainerChildren,250 commitHostHydratedContainer,251 commitHostHydratedActivity,252 commitHostHydratedSuspense,253 commitHostRemoveChildFromContainer,254 commitHostRemoveChild,255 commitHostSingletonAcquisition,256 commitHostSingletonRelease,257 commitFragmentInstanceDeletionEffects,258 commitFragmentInstanceInsertionEffects,259} from './ReactFiberCommitHostEffects';260import {261 trackEnterViewTransitions,262 commitEnterViewTransitions,263 commitExitViewTransitions,264 commitBeforeUpdateViewTransition,265 commitNestedViewTransitions,266 restoreEnterOrExitViewTransitions,267 restoreUpdateViewTransition,268 restoreNestedViewTransitions,269 measureUpdateViewTransition,270 measureNestedViewTransitions,271 resetAppearingViewTransitions,272 trackAppearingViewTransition,273 viewTransitionCancelableChildren,274 pushViewTransitionCancelableScope,275 popViewTransitionCancelableScope,276} from './ReactFiberCommitViewTransitions';277import {278 viewTransitionMutationContext,279 pushRootMutationContext,280 pushMutationContext,281 popMutationContext,282 rootMutationContext,283} from './ReactFiberMutationTracking';284import {285 trackNamedViewTransition,286 untrackNamedViewTransition,287} from './ReactFiberDuplicateViewTransitions';288import {markIndicatorHandled} from './ReactFiberRootScheduler';289import type {Flags} from './ReactFiberFlags';290291// Used during the commit phase to track the state of the Offscreen component stack.292// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.293let offscreenSubtreeIsHidden: boolean = false;294let offscreenSubtreeWasHidden: boolean = false;295// Track whether there's a hidden offscreen above with no HostComponent between. If so,296// it overrides the hiddenness of the HostComponent below.297let offscreenDirectParentIsHidden: boolean = false;298299// Used to track if a form needs to be reset at the end of the mutation phase.300let needsFormReset = false;301302const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;303304let nextEffect: Fiber | null = null;305306// Used for Profiling builds to track updaters.307let inProgressLanes: Lanes | null = null;308let inProgressRoot: FiberRoot | null = null;309310let focusedInstanceHandle: null | Fiber = null;311export let shouldFireAfterActiveInstanceBlur: boolean = false;312313// Used during the commit phase to track whether a parent ViewTransition component314// might have been affected by any mutations / relayouts below.315let viewTransitionContextChanged: boolean = false;316let inUpdateViewTransition: boolean = false;317let rootViewTransitionAffected: boolean = false;318let rootViewTransitionNameCanceled: boolean = false;319320function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {321 if (finishedWork.tag === ActivityComponent) {322 const prevState: ActivityState | null = current.memoizedState;323 const nextState: ActivityState | null = finishedWork.memoizedState;324 return prevState !== null && nextState === null;325 } else if (finishedWork.tag === SuspenseComponent) {326 const prevState: SuspenseState | null = current.memoizedState;327 const nextState: SuspenseState | null = finishedWork.memoizedState;328 return (329 prevState !== null &&330 prevState.dehydrated !== null &&331 (nextState === null || nextState.dehydrated === null)332 );333 } else if (finishedWork.tag === HostRoot) {334 return (335 (current.memoizedState as RootState).isDehydrated &&336 (finishedWork.flags & ForceClientRender) === NoFlags337 );338 } else {339 return false;340 }341}342343export function commitBeforeMutationEffects(344 root: FiberRoot,345 firstChild: Fiber,346 committedLanes: Lanes,347): void {348 focusedInstanceHandle = prepareForCommit(root.containerInfo);349 shouldFireAfterActiveInstanceBlur = false;350351 const isViewTransitionEligible =352 enableViewTransition &&353 includesOnlyViewTransitionEligibleLanes(committedLanes);354355 nextEffect = firstChild;356 commitBeforeMutationEffects_begin(isViewTransitionEligible);357358 // We no longer need to track the active instance fiber359 focusedInstanceHandle = null;360 // We've found any matched pairs and can now reset.361 resetAppearingViewTransitions();362}363364function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {365 // If this commit is eligible for a View Transition we look into all mutated subtrees.366 // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.367 const subtreeMask = isViewTransitionEligible368 ? BeforeAndAfterMutationTransitionMask369 : BeforeMutationMask;370 while (nextEffect !== null) {371 const fiber = nextEffect;372373 // This phase is only used for beforeActiveInstanceBlur.374 // Let's skip the whole loop if it's off.375 if (enableCreateEventHandleAPI || isViewTransitionEligible) {376 // TODO: Should wrap this in flags check, too, as optimization377 const deletions = fiber.deletions;378 if (deletions !== null) {379 for (let i = 0; i < deletions.length; i++) {380 const deletion = deletions[i];381 commitBeforeMutationEffectsDeletion(382 deletion,383 isViewTransitionEligible,384 );385 }386 }387 }388389 if (390 enableViewTransition &&391 fiber.alternate === null &&392 (fiber.flags & Placement) !== NoFlags393 ) {394 // Skip before mutation effects of the children because we don't want395 // to trigger updates of any nested view transitions and we shouldn't396 // have any other before mutation effects since snapshot effects are397 // only applied to updates. TODO: Model this using only flags.398 if (isViewTransitionEligible) {399 trackEnterViewTransitions(fiber);400 }401 commitBeforeMutationEffects_complete(isViewTransitionEligible);402 continue;403 }404405 // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.406 if (enableViewTransition && fiber.tag === OffscreenComponent) {407 const isModernRoot =408 disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;409 if (isModernRoot) {410 const current = fiber.alternate;411 const isHidden = fiber.memoizedState !== null;412 if (isHidden) {413 if (414 current !== null &&415 current.memoizedState === null &&416 isViewTransitionEligible417 ) {418 // Was previously mounted as visible but is now hidden.419 commitExitViewTransitions(current);420 }421 // Skip before mutation effects of the children because they're hidden.422 commitBeforeMutationEffects_complete(isViewTransitionEligible);423 continue;424 } else if (current !== null && current.memoizedState !== null) {425 // Was previously mounted as hidden but is now visible.426 // Skip before mutation effects of the children because we don't want427 // to trigger updates of any nested view transitions and we shouldn't428 // have any other before mutation effects since snapshot effects are429 // only applied to updates. TODO: Model this using only flags.430 if (isViewTransitionEligible) {431 trackEnterViewTransitions(fiber);432 }433 commitBeforeMutationEffects_complete(isViewTransitionEligible);434 continue;435 }436 }437 }438439 const child = fiber.child;440 if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {441 child.return = fiber;442 nextEffect = child;443 } else {444 if (isViewTransitionEligible) {445 // We are inside an updated subtree. Any mutations that affected the446 // parent HostInstance's layout or set of children (such as reorders)447 // might have also affected the positioning or size of the inner448 // ViewTransitions. Therefore we need to find them inside.449 commitNestedViewTransitions(fiber);450 }451 commitBeforeMutationEffects_complete(isViewTransitionEligible);452 }453 }454}455456function commitBeforeMutationEffects_complete(457 isViewTransitionEligible: boolean,458) {459 while (nextEffect !== null) {460 const fiber = nextEffect;461 commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);462463 const sibling = fiber.sibling;464 if (sibling !== null) {465 sibling.return = fiber.return;466 nextEffect = sibling;467 return;468 }469470 nextEffect = fiber.return;471 }472}473474function commitBeforeMutationEffectsOnFiber(475 finishedWork: Fiber,476 isViewTransitionEligible: boolean,477) {478 const current = finishedWork.alternate;479 const flags = finishedWork.flags;480481 if (enableCreateEventHandleAPI) {482 if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {483 // Check to see if the focused element was inside of a hidden (Suspense) subtree.484 // TODO: Move this out of the hot path using a dedicated effect tag.485 // TODO: This should consider Offscreen in general and not just SuspenseComponent.486 if (487 finishedWork.tag === SuspenseComponent &&488 isSuspenseBoundaryBeingHidden(current, finishedWork) &&489 // $FlowFixMe[incompatible-type] found when upgrading Flow490 doesFiberContain(finishedWork, focusedInstanceHandle)491 ) {492 shouldFireAfterActiveInstanceBlur = true;493 beforeActiveInstanceBlur(finishedWork);494 }495 }496 }497498 switch (finishedWork.tag) {499 case FunctionComponent:500 case ForwardRef:501 case SimpleMemoComponent: {502 if (!enableEffectEventMutationPhase && (flags & Update) !== NoFlags) {503 const updateQueue: FunctionComponentUpdateQueue | null =504 finishedWork.updateQueue as any;505 const eventPayloads = updateQueue !== null ? updateQueue.events : null;506 if (eventPayloads !== null) {507 for (let ii = 0; ii < eventPayloads.length; ii++) {508 const {ref, nextImpl} = eventPayloads[ii];509 ref.impl = nextImpl;510 }511 }512 }513 break;514 }515 case ClassComponent: {516 if ((flags & Snapshot) !== NoFlags) {517 if (current !== null) {518 commitClassSnapshot(finishedWork, current);519 }520 }521 break;522 }523 case HostRoot: {524 if ((flags & Snapshot) !== NoFlags) {525 // $FlowFixMe[constant-condition]526 if (supportsMutation) {527 const root = finishedWork.stateNode;528 clearContainer(root.containerInfo);529 }530 }531 break;532 }533 case HostComponent:534 case HostHoistable:535 case HostSingleton:536 case HostText:537 case HostPortal:538 case IncompleteClassComponent:539 // Nothing to do for these component types540 break;541 case ViewTransitionComponent:542 if (enableViewTransition) {543 if (isViewTransitionEligible) {544 if (current === null) {545 // This is a new mount. We should have handled this as part of the546 // Placement effect or it is deeper inside a entering transition.547 } else {548 // Something may have mutated within this subtree. This might need to cause549 // a cross-fade of this parent. We first assign old names to the550 // previous tree in the before mutation phase in case we need to.551 // TODO: This walks the tree that we might continue walking anyway.552 // We should just stash the parent ViewTransitionComponent and continue553 // walking the tree until we find HostComponent but to do that we need554 // to use a stack which requires refactoring this phase.555 commitBeforeUpdateViewTransition(current, finishedWork);556 }557 }558 break;559 }560 // Fallthrough561 default: {562 if ((flags & Snapshot) !== NoFlags) {563 throw new Error(564 'This unit of work tag should not have side-effects. This error is ' +565 'likely caused by a bug in React. Please file an issue.',566 );567 }568 }569 }570}571572function commitBeforeMutationEffectsDeletion(573 deletion: Fiber,574 isViewTransitionEligible: boolean,575) {576 if (enableCreateEventHandleAPI) {577 // TODO (effects) It would be nice to avoid calling doesFiberContain()578 // Maybe we can repurpose one of the subtreeFlags positions for this instead?579 // Use it to store which part of the tree the focused instance is in?580 // This assumes we can safely determine that instance during the "render" phase.581 if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {582 shouldFireAfterActiveInstanceBlur = true;583 beforeActiveInstanceBlur(deletion);584 }585 }586 if (isViewTransitionEligible) {587 commitExitViewTransitions(deletion);588 }589}590591function commitLayoutEffectOnFiber(592 finishedRoot: FiberRoot,593 current: Fiber | null,594 finishedWork: Fiber,595 committedLanes: Lanes,596): void {597 const prevEffectStart = pushComponentEffectStart();598 const prevEffectDuration = pushComponentEffectDuration();599 const prevEffectErrors = pushComponentEffectErrors();600 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();601 // When updating this function, also update reappearLayoutEffects, which does602 // most of the same things when an offscreen tree goes from hidden -> visible.603 const flags = finishedWork.flags;604 switch (finishedWork.tag) {605 case FunctionComponent:606 case ForwardRef:607 case SimpleMemoComponent: {608 recursivelyTraverseLayoutEffects(609 finishedRoot,610 finishedWork,611 committedLanes,612 );613 if (flags & Update) {614 commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);615 }616 break;617 }618 case ClassComponent: {619 recursivelyTraverseLayoutEffects(620 finishedRoot,621 finishedWork,622 committedLanes,623 );624 if (flags & Update) {625 commitClassLayoutLifecycles(finishedWork, current);626 }627628 if (flags & Callback) {629 commitClassCallbacks(finishedWork);630 }631632 if (flags & Ref) {633 safelyAttachRef(finishedWork, finishedWork.return);634 }635 break;636 }637 case HostRoot: {638 const prevProfilerEffectDuration = pushNestedEffectDurations();639 recursivelyTraverseLayoutEffects(640 finishedRoot,641 finishedWork,642 committedLanes,643 );644 if (flags & Callback) {645 commitRootCallbacks(finishedWork);646 }647 if (enableProfilerTimer && enableProfilerCommitHooks) {648 finishedRoot.effectDuration += popNestedEffectDurations(649 prevProfilerEffectDuration,650 );651 }652 break;653 }654 case HostSingleton: {655 // $FlowFixMe[constant-condition]656 if (supportsSingletons) {657 // We acquire the singleton instance first so it has appropriate658 // styles before other layout effects run. This isn't perfect because659 // an early sibling of the singleton may have an effect that can660 // observe the singleton before it is acquired.661 // @TODO move this to the mutation phase. The reason it isn't there yet662 // is it seemingly requires an extra traversal because we need to move the663 // disappear effect into a phase before the appear phase664 if (current === null && flags & Update) {665 // Unlike in the reappear path we only acquire on new mount666 commitHostSingletonAcquisition(finishedWork);667 }668 // We fall through to the HostComponent case below.669 }670 // Fallthrough671 }672 case HostHoistable:673 case HostComponent: {674 recursivelyTraverseLayoutEffects(675 finishedRoot,676 finishedWork,677 committedLanes,678 );679680 // Renderers may schedule work to be done after host components are mounted681 // (eg DOM renderer may schedule auto-focus for inputs and form controls).682 // These effects should only be committed when components are first mounted,683 // aka when there is no current/alternate.684 if (current === null) {685 if (flags & Update) {686 commitHostMount(finishedWork);687 } else if (flags & Hydrate) {688 commitHostHydratedInstance(finishedWork);689 }690 }691692 if (flags & Ref) {693 safelyAttachRef(finishedWork, finishedWork.return);694 }695 break;696 }697 case Profiler: {698 // TODO: Should this fire inside an offscreen tree? Or should it wait to699 // fire when the tree becomes visible again.700 if (flags & Update) {701 const prevProfilerEffectDuration = pushNestedEffectDurations();702703 recursivelyTraverseLayoutEffects(704 finishedRoot,705 finishedWork,706 committedLanes,707 );708709 const profilerInstance = finishedWork.stateNode;710711 if (enableProfilerTimer && enableProfilerCommitHooks) {712 // Propagate layout effect durations to the next nearest Profiler ancestor.713 // Do not reset these values until the next render so DevTools has a chance to read them first.714 profilerInstance.effectDuration += bubbleNestedEffectDurations(715 prevProfilerEffectDuration,716 );717 }718719 commitProfilerUpdate(720 finishedWork,721 current,722 commitStartTime,723 profilerInstance.effectDuration,724 );725 } else {726 recursivelyTraverseLayoutEffects(727 finishedRoot,728 finishedWork,729 committedLanes,730 );731 }732 break;733 }734 case ActivityComponent: {735 recursivelyTraverseLayoutEffects(736 finishedRoot,737 finishedWork,738 committedLanes,739 );740 if (flags & Update) {741 commitActivityHydrationCallbacks(finishedRoot, finishedWork);742 }743 break;744 }745 case SuspenseComponent: {746 recursivelyTraverseLayoutEffects(747 finishedRoot,748 finishedWork,749 committedLanes,750 );751 if (flags & Update) {752 commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);753 }754 if (flags & Callback) {755 // This Boundary is in fallback and has a dehydrated Suspense instance.756 // We could in theory assume the dehydrated state but we recheck it for757 // certainty.758 const finishedState: SuspenseState | null = finishedWork.memoizedState;759 if (finishedState !== null) {760 const dehydrated = finishedState.dehydrated;761 if (dehydrated !== null) {762 // Register a callback to retry this boundary once the server has sent the result.763 const retry = retryDehydratedSuspenseBoundary.bind(764 null,765 finishedWork,766 );767 registerSuspenseInstanceRetry(dehydrated, retry);768 }769 }770 }771 break;772 }773 case OffscreenComponent: {774 const isModernRoot =775 disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;776 if (isModernRoot) {777 const isHidden = finishedWork.memoizedState !== null;778 const newOffscreenSubtreeIsHidden =779 isHidden || offscreenSubtreeIsHidden;780 if (newOffscreenSubtreeIsHidden) {781 // The Offscreen tree is hidden. Skip over its layout effects.782 } else {783 // The Offscreen tree is visible.784785 const wasHidden = current !== null && current.memoizedState !== null;786 const newOffscreenSubtreeWasHidden =787 wasHidden || offscreenSubtreeWasHidden;788 const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;789 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;790 offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;791 offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;792793 if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {794 // This is the root of a reappearing boundary. As we continue795 // traversing the layout effects, we must also re-mount layout796 // effects that were unmounted when the Offscreen subtree was797 // hidden. So this is a superset of the normal commitLayoutEffects.798 const includeWorkInProgressEffects =799 (finishedWork.subtreeFlags & LayoutMask) !== NoFlags;800 recursivelyTraverseReappearLayoutEffects(801 finishedRoot,802 finishedWork,803 includeWorkInProgressEffects,804 );805 if (806 enableProfilerTimer &&807 enableProfilerCommitHooks &&808 enableComponentPerformanceTrack &&809 (finishedWork.mode & ProfileMode) !== NoMode &&810 componentEffectStartTime >= 0 &&811 componentEffectEndTime >= 0 &&812 componentEffectEndTime - componentEffectStartTime > 0.05813 ) {814 logComponentReappeared(815 finishedWork,816 componentEffectStartTime,817 componentEffectEndTime,818 );819 }820 } else {821 recursivelyTraverseLayoutEffects(822 finishedRoot,823 finishedWork,824 committedLanes,825 );826 }827 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;828 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;829 }830 } else {831 recursivelyTraverseLayoutEffects(832 finishedRoot,833 finishedWork,834 committedLanes,835 );836 }837 break;838 }839 case ViewTransitionComponent: {840 if (enableViewTransition) {841 if (__DEV__) {842 if (flags & ViewTransitionNamedStatic) {843 trackNamedViewTransition(finishedWork);844 }845 }846 recursivelyTraverseLayoutEffects(847 finishedRoot,848 finishedWork,849 committedLanes,850 );851 if (flags & Ref) {852 safelyAttachRef(finishedWork, finishedWork.return);853 }854 break;855 }856 break;857 }858 case Fragment:859 if (enableFragmentRefs) {860 if (flags & Ref) {861 safelyAttachRef(finishedWork, finishedWork.return);862 }863 }864 // Fallthrough865 default: {866 recursivelyTraverseLayoutEffects(867 finishedRoot,868 finishedWork,869 committedLanes,870 );871 break;872 }873 }874875 if (876 enableProfilerTimer &&877 enableProfilerCommitHooks &&878 enableComponentPerformanceTrack &&879 (finishedWork.mode & ProfileMode) !== NoMode &&880 componentEffectStartTime >= 0 &&881 componentEffectEndTime >= 0882 ) {883 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {884 logComponentEffect(885 finishedWork,886 componentEffectStartTime,887 componentEffectEndTime,888 componentEffectDuration,889 componentEffectErrors,890 );891 }892 if (893 // Insertion894 finishedWork.alternate === null &&895 finishedWork.return !== null &&896 finishedWork.return.alternate !== null &&897 componentEffectEndTime - componentEffectStartTime > 0.05898 ) {899 const isHydration = isHydratingParent(900 finishedWork.return.alternate,901 finishedWork.return,902 );903 if (!isHydration) {904 logComponentMount(905 finishedWork,906 componentEffectStartTime,907 componentEffectEndTime,908 );909 }910 }911 }912913 popComponentEffectStart(prevEffectStart);914 popComponentEffectDuration(prevEffectDuration);915 popComponentEffectErrors(prevEffectErrors);916 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);917}918919function abortRootTransitions(920 root: FiberRoot,921 abort: TransitionAbort,922 deletedTransitions: Set<Transition>,923 deletedOffscreenInstance: OffscreenInstance | null,924 isInDeletedTree: boolean,925) {926 if (enableTransitionTracing) {927 const rootTransitions = root.incompleteTransitions;928 deletedTransitions.forEach(transition => {929 if (rootTransitions.has(transition)) {930 const transitionInstance: TracingMarkerInstance = rootTransitions.get(931 transition,932 ) as any;933 if (transitionInstance.aborts === null) {934 transitionInstance.aborts = [];935 }936 transitionInstance.aborts.push(abort);937938 if (deletedOffscreenInstance !== null) {939 if (940 transitionInstance.pendingBoundaries !== null &&941 transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)942 ) {943 // $FlowFixMe[incompatible-use] found when upgrading Flow944 transitionInstance.pendingBoundaries.delete(945 deletedOffscreenInstance,946 );947 }948 }949 }950 });951 }952}953954function abortTracingMarkerTransitions(955 abortedFiber: Fiber,956 abort: TransitionAbort,957 deletedTransitions: Set<Transition>,958 deletedOffscreenInstance: OffscreenInstance | null,959 isInDeletedTree: boolean,960) {961 if (enableTransitionTracing) {962 const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;963 const markerTransitions = markerInstance.transitions;964 const pendingBoundaries = markerInstance.pendingBoundaries;965 if (markerTransitions !== null) {966 // TODO: Refactor this code. Is there a way to move this code to967 // the deletions phase instead of calculating it here while making sure968 // complete is called appropriately?969 deletedTransitions.forEach(transition => {970 // If one of the transitions on the tracing marker is a transition971 // that was in an aborted subtree, we will abort that tracing marker972 if (973 // $FlowFixMe[invalid-compare]974 abortedFiber !== null &&975 markerTransitions.has(transition) &&976 (markerInstance.aborts === null ||977 !markerInstance.aborts.includes(abort))978 ) {979 if (markerInstance.transitions !== null) {980 if (markerInstance.aborts === null) {981 markerInstance.aborts = [abort];982 addMarkerIncompleteCallbackToPendingTransition(983 abortedFiber.memoizedProps.name,984 markerInstance.transitions,985 markerInstance.aborts,986 );987 } else {988 markerInstance.aborts.push(abort);989 }990991 // We only want to call onTransitionProgress when the marker hasn't been992 // deleted993 if (994 deletedOffscreenInstance !== null &&995 !isInDeletedTree &&996 pendingBoundaries !== null &&997 pendingBoundaries.has(deletedOffscreenInstance)998 ) {999 pendingBoundaries.delete(deletedOffscreenInstance);10001001 addMarkerProgressCallbackToPendingTransition(1002 abortedFiber.memoizedProps.name,1003 deletedTransitions,1004 pendingBoundaries,1005 );1006 }1007 }1008 }1009 });1010 }1011 }1012}10131014function abortParentMarkerTransitionsForDeletedFiber(1015 abortedFiber: Fiber,1016 abort: TransitionAbort,1017 deletedTransitions: Set<Transition>,1018 deletedOffscreenInstance: OffscreenInstance | null,1019 isInDeletedTree: boolean,1020) {1021 if (enableTransitionTracing) {1022 // Find all pending markers that are waiting on child suspense boundaries in the1023 // aborted subtree and cancels them1024 let fiber: null | Fiber = abortedFiber;1025 while (fiber !== null) {1026 switch (fiber.tag) {1027 case TracingMarkerComponent:1028 abortTracingMarkerTransitions(1029 fiber,1030 abort,1031 deletedTransitions,1032 deletedOffscreenInstance,1033 isInDeletedTree,1034 );1035 break;1036 case HostRoot:1037 const root = fiber.stateNode;1038 abortRootTransitions(1039 root,1040 abort,1041 deletedTransitions,1042 deletedOffscreenInstance,1043 isInDeletedTree,1044 );10451046 break;1047 default:1048 break;1049 }10501051 fiber = fiber.return;1052 }1053 }1054}10551056function commitTransitionProgress(offscreenFiber: Fiber) {1057 if (enableTransitionTracing) {1058 // This function adds suspense boundaries to the root1059 // or tracing marker's pendingBoundaries map.1060 // When a suspense boundary goes from a resolved to a fallback1061 // state we add the boundary to the map, and when it goes from1062 // a fallback to a resolved state, we remove the boundary from1063 // the map.10641065 // We use stateNode on the Offscreen component as a stable object1066 // that doesnt change from render to render. This way we can1067 // distinguish between different Offscreen instances (vs. the same1068 // Offscreen instance with different fibers)1069 const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10701071 let prevState: SuspenseState | null = null;1072 const previousFiber = offscreenFiber.alternate;1073 if (previousFiber !== null && previousFiber.memoizedState !== null) {1074 prevState = previousFiber.memoizedState;1075 }1076 const nextState: SuspenseState | null = offscreenFiber.memoizedState;10771078 const wasHidden = prevState !== null;1079 const isHidden = nextState !== null;10801081 const pendingMarkers = offscreenInstance._pendingMarkers;1082 // If there is a name on the suspense boundary, store that in1083 // the pending boundaries.1084 let name = null;1085 const parent = offscreenFiber.return;1086 if (1087 parent !== null &&1088 parent.tag === SuspenseComponent &&1089 parent.memoizedProps.name1090 ) {1091 name = parent.memoizedProps.name;1092 }10931094 if (!wasHidden && isHidden) {1095 // The suspense boundaries was just hidden. Add the boundary1096 // to the pending boundary set if it's there1097 if (pendingMarkers !== null) {1098 pendingMarkers.forEach(markerInstance => {1099 const pendingBoundaries = markerInstance.pendingBoundaries;1100 const transitions = markerInstance.transitions;1101 const markerName = markerInstance.name;1102 if (1103 pendingBoundaries !== null &&1104 !pendingBoundaries.has(offscreenInstance)1105 ) {1106 pendingBoundaries.set(offscreenInstance, {1107 name,1108 });1109 if (transitions !== null) {1110 if (1111 markerInstance.tag === TransitionTracingMarker &&1112 markerName !== null1113 ) {1114 addMarkerProgressCallbackToPendingTransition(1115 markerName,1116 transitions,1117 pendingBoundaries,1118 );1119 } else if (markerInstance.tag === TransitionRoot) {1120 transitions.forEach(transition => {1121 addTransitionProgressCallbackToPendingTransition(1122 transition,1123 pendingBoundaries,1124 );1125 });1126 }1127 }1128 }1129 });1130 }1131 } else if (wasHidden && !isHidden) {1132 // The suspense boundary went from hidden to visible. Remove1133 // the boundary from the pending suspense boundaries set1134 // if it's there1135 if (pendingMarkers !== null) {1136 pendingMarkers.forEach(markerInstance => {1137 const pendingBoundaries = markerInstance.pendingBoundaries;1138 const transitions = markerInstance.transitions;1139 const markerName = markerInstance.name;1140 if (1141 pendingBoundaries !== null &&1142 pendingBoundaries.has(offscreenInstance)1143 ) {1144 pendingBoundaries.delete(offscreenInstance);1145 if (transitions !== null) {1146 if (1147 markerInstance.tag === TransitionTracingMarker &&1148 markerName !== null1149 ) {1150 addMarkerProgressCallbackToPendingTransition(1151 markerName,1152 transitions,1153 pendingBoundaries,1154 );11551156 // If there are no more unresolved suspense boundaries, the interaction1157 // is considered finished1158 if (pendingBoundaries.size === 0) {1159 if (markerInstance.aborts === null) {1160 addMarkerCompleteCallbackToPendingTransition(1161 markerName,1162 transitions,1163 );1164 }1165 markerInstance.transitions = null;1166 markerInstance.pendingBoundaries = null;1167 markerInstance.aborts = null;1168 }1169 } else if (markerInstance.tag === TransitionRoot) {1170 transitions.forEach(transition => {1171 addTransitionProgressCallbackToPendingTransition(1172 transition,1173 pendingBoundaries,1174 );1175 });1176 }1177 }1178 }1179 });1180 }1181 }1182 }1183}11841185function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {1186 // $FlowFixMe[constant-condition]1187 if (!supportsMutation) {1188 return;1189 }1190 // Finds the nearest host component children and updates their visibility1191 // to either hidden or visible.1192 let child = parentFiber.child;1193 while (child !== null) {1194 hideOrUnhideAllChildrenOnFiber(child, isHidden);1195 child = child.sibling;1196 }1197}11981199function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {1200 // $FlowFixMe[constant-condition]1201 if (!supportsMutation) {1202 return;1203 }1204 switch (fiber.tag) {1205 case HostComponent:1206 case HostHoistable: {1207 // Found the nearest host component. Hide it.1208 commitShowHideHostInstance(fiber, isHidden);1209 // Typically, only the nearest host nodes need to be hidden, since that1210 // has the effect of also hiding everything inside of them.1211 //1212 // However, there's a special case for portals, because portals do not1213 // exist in the regular host tree hierarchy; we can't assume that just1214 // because a portal's HostComponent parent in the React tree will also be1215 // a parent in the actual host tree.1216 //1217 // So, if any portals exist within the tree, regardless of how deeply1218 // nested they are, we need to repeat this algorithm for its children.1219 hideOrUnhideNearestPortals(fiber, isHidden);1220 return;1221 }1222 case HostText: {1223 commitShowHideHostTextInstance(fiber, isHidden);1224 return;1225 }1226 case DehydratedFragment: {1227 commitShowHideSuspenseBoundary(fiber, isHidden);1228 return;1229 }1230 case OffscreenComponent:1231 case LegacyHiddenComponent: {1232 const offscreenState: OffscreenState | null = fiber.memoizedState;1233 if (offscreenState !== null) {1234 // Found a nested Offscreen component that is hidden.1235 // Don't search any deeper. This tree should remain hidden.1236 } else {1237 hideOrUnhideAllChildren(fiber, isHidden);1238 }1239 return;1240 }1241 default: {1242 hideOrUnhideAllChildren(fiber, isHidden);1243 return;1244 }1245 }1246}12471248function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {1249 // $FlowFixMe[constant-condition]1250 if (!supportsMutation) {1251 return;1252 }1253 if (parentFiber.subtreeFlags & PortalStatic) {1254 let child = parentFiber.child;1255 while (child !== null) {1256 hideOrUnhideNearestPortalsOnFiber(child, isHidden);1257 child = child.sibling;1258 }1259 }1260}12611262function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {1263 // $FlowFixMe[constant-condition]1264 if (!supportsMutation) {1265 return;1266 }1267 switch (fiber.tag) {1268 case HostPortal: {1269 // Found a portal. Switch back to the normal hide/unhide algorithm to1270 // toggle the visibility of its children.1271 hideOrUnhideAllChildrenOnFiber(fiber, isHidden);1272 return;1273 }1274 case OffscreenComponent: {1275 const offscreenState: OffscreenState | null = fiber.memoizedState;1276 if (offscreenState !== null) {1277 // Found a nested Offscreen component that is hidden. Don't search any1278 // deeper. This tree should remain hidden.1279 } else {1280 hideOrUnhideNearestPortals(fiber, isHidden);1281 }1282 return;1283 }1284 default: {1285 hideOrUnhideNearestPortals(fiber, isHidden);1286 return;1287 }1288 }1289}12901291function detachFiberMutation(fiber: Fiber) {1292 // Cut off the return pointer to disconnect it from the tree.1293 // This enables us to detect and warn against state updates on an unmounted component.1294 // It also prevents events from bubbling from within disconnected components.1295 //1296 // Ideally, we should also clear the child pointer of the parent alternate to let this1297 // get GC:ed but we don't know which for sure which parent is the current1298 // one so we'll settle for GC:ing the subtree of this child.1299 // This child itself will be GC:ed when the parent updates the next time.1300 //1301 // Note that we can't clear child or sibling pointers yet.1302 // They're needed for passive effects and for findDOMNode.1303 // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).1304 //1305 // Don't reset the alternate yet, either. We need that so we can detach the1306 // alternate's fields in the passive phase. Clearing the return pointer is1307 // sufficient for findDOMNode semantics.1308 const alternate = fiber.alternate;1309 if (alternate !== null) {1310 alternate.return = null;1311 }1312 fiber.return = null;1313}13141315function detachFiberAfterEffects(fiber: Fiber) {1316 const alternate = fiber.alternate;1317 if (alternate !== null) {1318 fiber.alternate = null;1319 detachFiberAfterEffects(alternate);1320 }13211322 // Clear cyclical Fiber fields. This level alone is designed to roughly1323 // approximate the planned Fiber refactor. In that world, `setState` will be1324 // bound to a special "instance" object instead of a Fiber. The Instance1325 // object will not have any of these fields. It will only be connected to1326 // the fiber tree via a single link at the root. So if this level alone is1327 // sufficient to fix memory issues, that bodes well for our plans.1328 fiber.child = null;1329 fiber.deletions = null;1330 fiber.sibling = null;13311332 // The `stateNode` is cyclical because on host nodes it points to the host1333 // tree, which has its own pointers to children, parents, and siblings.1334 // The other host nodes also point back to fibers, so we should detach that1335 // one, too.1336 if (fiber.tag === HostComponent) {1337 const hostInstance: Instance = fiber.stateNode;1338 // $FlowFixMe[invalid-compare]1339 if (hostInstance !== null) {1340 detachDeletedInstance(hostInstance);1341 }1342 }1343 fiber.stateNode = null;13441345 if (__DEV__) {1346 fiber._debugOwner = null;1347 }13481349 // Theoretically, nothing in here should be necessary, because we already1350 // disconnected the fiber from the tree. So even if something leaks this1351 // particular fiber, it won't leak anything else.1352 fiber.return = null;1353 fiber.dependencies = null;1354 fiber.memoizedProps = null;1355 fiber.memoizedState = null;1356 fiber.pendingProps = null;1357 fiber.stateNode = null;1358 // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.1359 fiber.updateQueue = null;1360}13611362// These are tracked on the stack as we recursively traverse a1363// deleted subtree.1364// TODO: Update these during the whole mutation phase, not just during1365// a deletion.1366let hostParent: Instance | Container | null = null;1367let hostParentIsContainer: boolean = false;13681369function commitDeletionEffects(1370 root: FiberRoot,1371 returnFiber: Fiber,1372 deletedFiber: Fiber,1373) {1374 const prevEffectStart = pushComponentEffectStart();13751376 // $FlowFixMe[constant-condition]1377 if (supportsMutation) {1378 // We only have the top Fiber that was deleted but we need to recurse down its1379 // children to find all the terminal nodes.13801381 // Recursively delete all host nodes from the parent, detach refs, clean1382 // up mounted layout effects, and call componentWillUnmount.13831384 // We only need to remove the topmost host child in each branch. But then we1385 // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1386 // could split this into two separate traversals functions, where the second1387 // one doesn't include any removeChild logic. This is maybe the same1388 // function as "disappearLayoutEffects" (or whatever that turns into after1389 // the layout phase is refactored to use recursion).13901391 // Before starting, find the nearest host parent on the stack so we know1392 // which instance/container to remove the children from.1393 // TODO: Instead of searching up the fiber return path on every deletion, we1394 // can track the nearest host component on the JS stack as we traverse the1395 // tree during the commit phase. This would make insertions faster, too.1396 let parent: null | Fiber = returnFiber;1397 findParent: while (parent !== null) {1398 switch (parent.tag) {1399 case HostSingleton: {1400 // $FlowFixMe[constant-condition]1401 if (supportsSingletons) {1402 if (isSingletonScope(parent.type)) {1403 hostParent = parent.stateNode;1404 hostParentIsContainer = false;1405 break findParent;1406 }1407 break;1408 }1409 // Expected fallthrough when supportsSingletons is false1410 }1411 case HostComponent: {1412 hostParent = parent.stateNode;1413 hostParentIsContainer = false;1414 break findParent;1415 }1416 case HostRoot:1417 case HostPortal: {1418 hostParent = parent.stateNode.containerInfo;1419 hostParentIsContainer = true;1420 break findParent;1421 }1422 }1423 parent = parent.return;1424 }1425 if (hostParent === null) {1426 throw new Error(1427 'Expected to find a host parent. This error is likely caused by ' +1428 'a bug in React. Please file an issue.',1429 );1430 }14311432 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1433 hostParent = null;1434 hostParentIsContainer = false;1435 } else {1436 // Detach refs and call componentWillUnmount() on the whole subtree.1437 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1438 }14391440 if (1441 enableProfilerTimer &&1442 enableProfilerCommitHooks &&1443 enableComponentPerformanceTrack &&1444 (deletedFiber.mode & ProfileMode) !== NoMode &&1445 componentEffectStartTime >= 0 &&1446 componentEffectEndTime >= 0 &&1447 componentEffectEndTime - componentEffectStartTime > 0.051448 ) {1449 logComponentUnmount(1450 deletedFiber,1451 componentEffectStartTime,1452 componentEffectEndTime,1453 );1454 }1455 popComponentEffectStart(prevEffectStart);14561457 detachFiberMutation(deletedFiber);1458}14591460function recursivelyTraverseDeletionEffects(1461 finishedRoot: FiberRoot,1462 nearestMountedAncestor: Fiber,1463 parent: Fiber,1464) {1465 // TODO: Use a static flag to skip trees that don't have unmount effects1466 let child = parent.child;1467 while (child !== null) {1468 commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1469 child = child.sibling;1470 }1471}14721473function commitDeletionEffectsOnFiber(1474 finishedRoot: FiberRoot,1475 nearestMountedAncestor: Fiber,1476 deletedFiber: Fiber,1477) {1478 // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.1479 onCommitUnmount(deletedFiber);14801481 const prevEffectStart = pushComponentEffectStart();1482 const prevEffectDuration = pushComponentEffectDuration();1483 const prevEffectErrors = pushComponentEffectErrors();1484 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();14851486 // The cases in this outer switch modify the stack before they traverse1487 // into their subtree. There are simpler cases in the inner switch1488 // that don't modify the stack.1489 switch (deletedFiber.tag) {1490 case HostHoistable: {1491 // $FlowFixMe[constant-condition]1492 if (supportsResources) {1493 if (!offscreenSubtreeWasHidden) {1494 safelyDetachRef(deletedFiber, nearestMountedAncestor);1495 }1496 recursivelyTraverseDeletionEffects(1497 finishedRoot,1498 nearestMountedAncestor,1499 deletedFiber,1500 );1501 if (deletedFiber.memoizedState) {1502 releaseResource(deletedFiber.memoizedState);1503 } else if (deletedFiber.stateNode) {1504 unmountHoistable(deletedFiber.stateNode);1505 }1506 break;1507 }1508 // Fall through1509 }1510 case HostSingleton: {1511 // $FlowFixMe[constant-condition]1512 if (supportsSingletons) {1513 if (!offscreenSubtreeWasHidden) {1514 safelyDetachRef(deletedFiber, nearestMountedAncestor);1515 }15161517 const prevHostParent = hostParent;1518 const prevHostParentIsContainer = hostParentIsContainer;1519 if (isSingletonScope(deletedFiber.type)) {1520 hostParent = deletedFiber.stateNode;1521 hostParentIsContainer = false;1522 }1523 recursivelyTraverseDeletionEffects(1524 finishedRoot,1525 nearestMountedAncestor,1526 deletedFiber,1527 );15281529 // Normally this is called in passive unmount effect phase however with1530 // HostSingleton we warn if you acquire one that is already associated to1531 // a different fiber. To increase our chances of avoiding this, specifically1532 // if you keyed a HostSingleton so there will be a delete followed by a Placement1533 // we treat detach eagerly here1534 commitHostSingletonRelease(deletedFiber);15351536 hostParent = prevHostParent;1537 hostParentIsContainer = prevHostParentIsContainer;15381539 break;1540 }1541 // Fall through1542 }1543 case HostComponent: {1544 if (!offscreenSubtreeWasHidden) {1545 safelyDetachRef(deletedFiber, nearestMountedAncestor);1546 }1547 if (1548 enableFragmentRefs &&1549 (deletedFiber.tag === HostComponent ||1550 (enableFragmentRefsTextNodes && deletedFiber.tag === HostText))1551 ) {1552 commitFragmentInstanceDeletionEffects(deletedFiber);1553 }1554 // Intentional fallthrough to next branch1555 }1556 case HostText: {1557 // We only need to remove the nearest host child. Set the host parent1558 // to `null` on the stack to indicate that nested children don't1559 // need to be removed.1560 // $FlowFixMe[constant-condition]1561 if (supportsMutation) {1562 const prevHostParent = hostParent;1563 const prevHostParentIsContainer = hostParentIsContainer;1564 hostParent = null;1565 recursivelyTraverseDeletionEffects(1566 finishedRoot,1567 nearestMountedAncestor,1568 deletedFiber,1569 );1570 hostParent = prevHostParent;1571 hostParentIsContainer = prevHostParentIsContainer;15721573 if (hostParent !== null) {1574 // Now that all the child effects have unmounted, we can remove the1575 // node from the tree.1576 if (hostParentIsContainer) {1577 commitHostRemoveChildFromContainer(1578 deletedFiber,1579 nearestMountedAncestor,1580 hostParent as any as Container,1581 deletedFiber.stateNode as Instance | TextInstance,1582 );1583 } else {1584 commitHostRemoveChild(1585 deletedFiber,1586 nearestMountedAncestor,1587 hostParent as any as Instance,1588 deletedFiber.stateNode as Instance | TextInstance,1589 );1590 }1591 }1592 } else {1593 recursivelyTraverseDeletionEffects(1594 finishedRoot,1595 nearestMountedAncestor,1596 deletedFiber,1597 );1598 }1599 break;1600 }1601 case DehydratedFragment: {1602 if (enableSuspenseCallback) {1603 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1604 if (hydrationCallbacks !== null) {1605 try {1606 const onDeleted = hydrationCallbacks.onDeleted;1607 if (onDeleted) {1608 onDeleted(1609 deletedFiber.stateNode as SuspenseInstance | ActivityInstance,1610 );1611 }1612 } catch (error) {1613 captureCommitPhaseError(1614 deletedFiber,1615 nearestMountedAncestor,1616 error,1617 );1618 }1619 }1620 }16211622 // Dehydrated fragments don't have any children16231624 // Delete the dehydrated suspense boundary and all of its content.1625 // $FlowFixMe[constant-condition]1626 if (supportsMutation) {1627 if (hostParent !== null) {1628 if (hostParentIsContainer) {1629 clearSuspenseBoundaryFromContainer(1630 hostParent as any as Container,1631 deletedFiber.stateNode as SuspenseInstance,1632 );1633 } else {1634 clearSuspenseBoundary(1635 hostParent as any as Instance,1636 deletedFiber.stateNode as SuspenseInstance,1637 );1638 }1639 }1640 }1641 break;1642 }1643 case HostPortal: {1644 // $FlowFixMe[constant-condition]1645 if (supportsMutation) {1646 // When we go into a portal, it becomes the parent to remove from.1647 const prevHostParent = hostParent;1648 const prevHostParentIsContainer = hostParentIsContainer;1649 hostParent = deletedFiber.stateNode.containerInfo;1650 hostParentIsContainer = true;1651 recursivelyTraverseDeletionEffects(1652 finishedRoot,1653 nearestMountedAncestor,1654 deletedFiber,1655 );1656 hostParent = prevHostParent;1657 hostParentIsContainer = prevHostParentIsContainer;1658 } else {1659 // $FlowFixMe[constant-condition]1660 if (supportsPersistence) {1661 commitHostPortalContainerChildren(1662 deletedFiber.stateNode,1663 deletedFiber,1664 createContainerChildSet(),1665 );1666 }16671668 recursivelyTraverseDeletionEffects(1669 finishedRoot,1670 nearestMountedAncestor,1671 deletedFiber,1672 );1673 }1674 break;1675 }1676 case FunctionComponent:1677 case ForwardRef:1678 case MemoComponent:1679 case SimpleMemoComponent: {1680 // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.1681 commitHookEffectListUnmount(1682 HookInsertion,1683 deletedFiber,1684 nearestMountedAncestor,1685 );1686 if (!offscreenSubtreeWasHidden) {1687 commitHookLayoutUnmountEffects(1688 deletedFiber,1689 nearestMountedAncestor,1690 HookLayout,1691 );1692 }1693 recursivelyTraverseDeletionEffects(1694 finishedRoot,1695 nearestMountedAncestor,1696 deletedFiber,1697 );1698 break;1699 }1700 case ClassComponent: {1701 if (!offscreenSubtreeWasHidden) {1702 safelyDetachRef(deletedFiber, nearestMountedAncestor);1703 const instance = deletedFiber.stateNode;1704 if (typeof instance.componentWillUnmount === 'function') {1705 safelyCallComponentWillUnmount(1706 deletedFiber,1707 nearestMountedAncestor,1708 instance,1709 );1710 }1711 }1712 recursivelyTraverseDeletionEffects(1713 finishedRoot,1714 nearestMountedAncestor,1715 deletedFiber,1716 );1717 break;1718 }1719 case ScopeComponent: {1720 if (enableScopeAPI) {1721 if (!offscreenSubtreeWasHidden) {1722 safelyDetachRef(deletedFiber, nearestMountedAncestor);1723 }1724 }1725 recursivelyTraverseDeletionEffects(1726 finishedRoot,1727 nearestMountedAncestor,1728 deletedFiber,1729 );1730 break;1731 }1732 case OffscreenComponent: {1733 if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {1734 // If this offscreen component is hidden, we already unmounted it. Before1735 // deleting the children, track that it's already unmounted so that we1736 // don't attempt to unmount the effects again.1737 // TODO: If the tree is hidden, in most cases we should be able to skip1738 // over the nested children entirely. An exception is we haven't yet found1739 // the topmost host node to delete, which we already track on the stack.1740 // But the other case is portals, which need to be detached no matter how1741 // deeply they are nested. We should use a subtree flag to track whether a1742 // subtree includes a nested portal.1743 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1744 offscreenSubtreeWasHidden =1745 prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17461747 recursivelyTraverseDeletionEffects(1748 finishedRoot,1749 nearestMountedAncestor,1750 deletedFiber,1751 );1752 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;1753 } else {1754 recursivelyTraverseDeletionEffects(1755 finishedRoot,1756 nearestMountedAncestor,1757 deletedFiber,1758 );1759 }1760 break;1761 }1762 case ViewTransitionComponent: {1763 if (enableViewTransition) {1764 if (__DEV__) {1765 if (deletedFiber.flags & ViewTransitionNamedStatic) {1766 untrackNamedViewTransition(deletedFiber);1767 }1768 }1769 safelyDetachRef(deletedFiber, nearestMountedAncestor);1770 recursivelyTraverseDeletionEffects(1771 finishedRoot,1772 nearestMountedAncestor,1773 deletedFiber,1774 );1775 break;1776 }1777 // Fallthrough1778 }1779 case Fragment: {1780 if (enableFragmentRefs) {1781 if (!offscreenSubtreeWasHidden) {1782 safelyDetachRef(deletedFiber, nearestMountedAncestor);1783 }1784 recursivelyTraverseDeletionEffects(1785 finishedRoot,1786 nearestMountedAncestor,1787 deletedFiber,1788 );1789 break;1790 }1791 // Fallthrough1792 }1793 default: {1794 recursivelyTraverseDeletionEffects(1795 finishedRoot,1796 nearestMountedAncestor,1797 deletedFiber,1798 );1799 break;1800 }1801 }18021803 if (1804 enableProfilerTimer &&1805 enableProfilerCommitHooks &&1806 enableComponentPerformanceTrack &&1807 (deletedFiber.mode & ProfileMode) !== NoMode &&1808 componentEffectStartTime >= 0 &&1809 componentEffectEndTime >= 0 &&1810 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)1811 ) {1812 logComponentEffect(1813 deletedFiber,1814 componentEffectStartTime,1815 componentEffectEndTime,1816 componentEffectDuration,1817 componentEffectErrors,1818 );1819 }18201821 popComponentEffectStart(prevEffectStart);1822 popComponentEffectDuration(prevEffectDuration);1823 popComponentEffectErrors(prevEffectErrors);1824 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1825}18261827function commitSuspenseCallback(finishedWork: Fiber) {1828 // TODO: Delete this feature. It's not properly covered by DEV features.1829 const newState: SuspenseState | null = finishedWork.memoizedState;1830 if (enableSuspenseCallback && newState !== null) {1831 const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;1832 if (typeof suspenseCallback === 'function') {1833 const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;1834 if (retryQueue !== null) {1835 suspenseCallback(new Set(retryQueue));1836 }1837 } else if (__DEV__) {1838 if (suspenseCallback !== undefined) {1839 console.error('Unexpected type for suspenseCallback.');1840 }1841 }1842 }1843}18441845function commitActivityHydrationCallbacks(1846 finishedRoot: FiberRoot,1847 finishedWork: Fiber,1848) {1849 // $FlowFixMe[constant-condition]1850 if (!supportsHydration) {1851 return;1852 }1853 const newState: ActivityState | null = finishedWork.memoizedState;1854 if (newState === null) {1855 const current = finishedWork.alternate;1856 if (current !== null) {1857 const prevState: ActivityState | null = current.memoizedState;1858 if (prevState !== null) {1859 const activityInstance = prevState.dehydrated;1860 commitHostHydratedActivity(activityInstance, finishedWork);1861 if (enableSuspenseCallback) {1862 try {1863 // TODO: Delete this feature. It's not properly covered by DEV features.1864 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1865 if (hydrationCallbacks !== null) {1866 const onHydrated = hydrationCallbacks.onHydrated;1867 if (onHydrated) {1868 onHydrated(activityInstance);1869 }1870 }1871 } catch (error) {1872 captureCommitPhaseError(finishedWork, finishedWork.return, error);1873 }1874 }1875 }1876 }1877 }1878}18791880function commitSuspenseHydrationCallbacks(1881 finishedRoot: FiberRoot,1882 finishedWork: Fiber,1883) {1884 // $FlowFixMe[constant-condition]1885 if (!supportsHydration) {1886 return;1887 }1888 const newState: SuspenseState | null = finishedWork.memoizedState;1889 if (newState === null) {1890 const current = finishedWork.alternate;1891 if (current !== null) {1892 const prevState: SuspenseState | null = current.memoizedState;1893 if (prevState !== null) {1894 const suspenseInstance = prevState.dehydrated;1895 if (suspenseInstance !== null) {1896 commitHostHydratedSuspense(suspenseInstance, finishedWork);1897 if (enableSuspenseCallback) {1898 try {1899 // TODO: Delete this feature. It's not properly covered by DEV features.1900 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1901 if (hydrationCallbacks !== null) {1902 const onHydrated = hydrationCallbacks.onHydrated;1903 if (onHydrated) {1904 onHydrated(suspenseInstance);1905 }1906 }1907 } catch (error) {1908 captureCommitPhaseError(finishedWork, finishedWork.return, error);1909 }1910 }1911 }1912 }1913 }1914 }1915}19161917function getRetryCache(finishedWork: Fiber) {1918 // TODO: Unify the interface for the retry cache so we don't have to switch1919 // on the tag like this.1920 switch (finishedWork.tag) {1921 case ActivityComponent:1922 case SuspenseComponent:1923 case SuspenseListComponent: {1924 let retryCache = finishedWork.stateNode;1925 if (retryCache === null) {1926 retryCache = finishedWork.stateNode = new PossiblyWeakSet();1927 }1928 return retryCache;1929 }1930 case OffscreenComponent: {1931 const instance: OffscreenInstance = finishedWork.stateNode;1932 let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =1933 instance._retryCache;1934 if (retryCache === null) {1935 retryCache = instance._retryCache = new PossiblyWeakSet();1936 }1937 return retryCache;1938 }1939 default: {1940 throw new Error(1941 `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +1942 'bug in React.',1943 );1944 }1945 }1946}19471948function attachSuspenseRetryListeners(1949 finishedWork: Fiber,1950 wakeables: RetryQueue,1951) {1952 // If this boundary just timed out, then it will have a set of wakeables.1953 // For each wakeable, attach a listener so that when it resolves, React1954 // attempts to re-render the boundary in the primary (pre-timeout) state.1955 const retryCache = getRetryCache(finishedWork);1956 wakeables.forEach(wakeable => {1957 // Memoize using the boundary fiber to prevent redundant listeners.1958 if (!retryCache.has(wakeable)) {1959 retryCache.add(wakeable);19601961 if (enableUpdaterTracking) {1962 if (isDevToolsPresent) {1963 if (inProgressLanes !== null && inProgressRoot !== null) {1964 // If we have pending work still, associate the original updaters with it.1965 restorePendingUpdaters(inProgressRoot, inProgressLanes);1966 } else {1967 throw Error(1968 'Expected finished root and lanes to be set. This is a bug in React.',1969 );1970 }1971 }1972 }19731974 const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);1975 wakeable.then(retry, retry);1976 }1977 });1978}19791980// This function detects when a Suspense boundary goes from visible to hidden.1981// It returns false if the boundary is already hidden.1982// TODO: Use an effect tag.1983function isSuspenseBoundaryBeingHidden(1984 current: Fiber | null,1985 finishedWork: Fiber,1986): boolean {1987 if (current !== null) {1988 const oldState: SuspenseState | null = current.memoizedState;1989 if (oldState === null || oldState.dehydrated !== null) {1990 const newState: SuspenseState | null = finishedWork.memoizedState;1991 return newState !== null && newState.dehydrated === null;1992 }1993 }1994 return false;1995}19961997export function commitMutationEffects(1998 root: FiberRoot,1999 finishedWork: Fiber,2000 committedLanes: Lanes,
Findings
✓ No findings reported for this file.