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 enableProfilerTimer,51 enableProfilerCommitHooks,52 enableSuspenseCallback,53 enableScopeAPI,54 enableUpdaterTracking,55 enableTransitionTracing,56 enableLegacyHidden,57 disableLegacyMode,58 enableComponentPerformanceTrack,59 enableViewTransition,60 enableDefaultTransitionIndicator,61} from 'shared/ReactFeatureFlags';62import {63 FunctionComponent,64 ForwardRef,65 ClassComponent,66 HostRoot,67 HostComponent,68 HostHoistable,69 HostSingleton,70 HostText,71 HostPortal,72 Profiler,73 ActivityComponent,74 SuspenseComponent,75 DehydratedFragment,76 IncompleteClassComponent,77 MemoComponent,78 SimpleMemoComponent,79 SuspenseListComponent,80 ScopeComponent,81 OffscreenComponent,82 LegacyHiddenComponent,83 CacheComponent,84 TracingMarkerComponent,85 ViewTransitionComponent,86 Fragment,87} from './ReactWorkTags';88import {89 NoFlags,90 ContentReset,91 Placement,92 ChildDeletion,93 Snapshot,94 Update,95 Hydrate,96 Callback,97 Ref,98 Hydrating,99 Passive,100 BeforeMutationMask,101 BeforeAndAfterMutationTransitionMask,102 MutationMask,103 LayoutMask,104 PassiveMask,105 PassiveTransitionMask,106 Visibility,107 ShouldSuspendCommit,108 MaySuspendCommit,109 FormReset,110 Cloned,111 PerformedWork,112 ForceClientRender,113 DidCapture,114 AffectedParentLayout,115 ViewTransitionNamedStatic,116 PortalStatic,117} from './ReactFiberFlags';118import {119 commitStartTime,120 pushNestedEffectDurations,121 popNestedEffectDurations,122 bubbleNestedEffectDurations,123 resetComponentEffectTimers,124 pushComponentEffectStart,125 popComponentEffectStart,126 pushComponentEffectDuration,127 popComponentEffectDuration,128 pushComponentEffectErrors,129 popComponentEffectErrors,130 pushComponentEffectDidSpawnUpdate,131 popComponentEffectDidSpawnUpdate,132 componentEffectStartTime,133 componentEffectEndTime,134 componentEffectDuration,135 componentEffectErrors,136 componentEffectSpawnedUpdate,137} from './ReactProfilerTimer';138import {139 logComponentRender,140 logComponentErrored,141 logComponentEffect,142 logComponentMount,143 logComponentUnmount,144 logComponentReappeared,145 logComponentDisappeared,146 pushDeepEquality,147 popDeepEquality,148} from './ReactFiberPerformanceTrack';149import {ConcurrentMode, NoMode, ProfileMode} from './ReactTypeOfMode';150import {deferHiddenCallbacks} from './ReactFiberClassUpdateQueue';151import {152 supportsMutation,153 supportsPersistence,154 supportsHydration,155 supportsResources,156 supportsSingletons,157 clearSuspenseBoundary,158 clearSuspenseBoundaryFromContainer,159 createContainerChildSet,160 clearContainer,161 prepareScopeUpdate,162 prepareForCommit,163 beforeActiveInstanceBlur,164 detachDeletedInstance,165 getHoistableRoot,166 acquireResource,167 releaseResource,168 hydrateHoistable,169 createHoistableInstance,170 mountHoistable,171 unmountHoistable,172 prepareToCommitHoistables,173 maySuspendCommitInSyncRender,174 suspendInstance,175 suspendResource,176 resetFormInstance,177 registerSuspenseInstanceRetry,178 cancelViewTransitionName,179 cancelRootViewTransitionName,180 restoreRootViewTransitionName,181 isSingletonScope,182 updateFragmentInstanceFiber,183} from './ReactFiberConfig';184import {185 captureCommitPhaseError,186 resolveRetryWakeable,187 markCommitTimeOfFallback,188 restorePendingUpdaters,189 addTransitionStartCallbackToPendingTransition,190 addTransitionProgressCallbackToPendingTransition,191 addTransitionCompleteCallbackToPendingTransition,192 addMarkerProgressCallbackToPendingTransition,193 addMarkerIncompleteCallbackToPendingTransition,194 addMarkerCompleteCallbackToPendingTransition,195 retryDehydratedSuspenseBoundary,196 scheduleViewTransitionEvent,197} from './ReactFiberWorkLoop';198import {199 HasEffect as HookHasEffect,200 Layout as HookLayout,201 Insertion as HookInsertion,202 Passive as HookPassive,203} from './ReactHookEffectTags';204import {doesFiberContain} from './ReactFiberTreeReflection';205import {isDevToolsPresent, onCommitUnmount} from './ReactFiberDevToolsHook';206import {releaseCache, retainCache} from './ReactFiberCacheComponent';207import {clearTransitionsForLanes} from './ReactFiberLane';208import {209 OffscreenVisible,210 OffscreenPassiveEffectsConnected,211} from './ReactFiberOffscreenComponent';212import {213 TransitionRoot,214 TransitionTracingMarker,215} from './ReactFiberTracingMarkerComponent';216import {getViewTransitionClassName} from './ReactFiberViewTransitionComponent';217import {218 commitHookLayoutEffects,219 commitHookLayoutUnmountEffects,220 commitHookEffectListMount,221 commitHookEffectListUnmount,222 commitHookPassiveMountEffects,223 commitHookPassiveUnmountEffects,224 commitClassLayoutLifecycles,225 commitClassDidMount,226 commitClassCallbacks,227 commitClassHiddenCallbacks,228 commitClassSnapshot,229 safelyCallComponentWillUnmount,230 safelyAttachRef,231 safelyDetachRef,232 commitProfilerUpdate,233 commitProfilerPostCommit,234 commitRootCallbacks,235} from './ReactFiberCommitEffects';236import {237 commitHostMount,238 commitHostHydratedInstance,239 commitHostUpdate,240 commitHostTextUpdate,241 commitHostResetTextContent,242 commitShowHideSuspenseBoundary,243 commitShowHideHostInstance,244 commitShowHideHostTextInstance,245 commitHostPlacement,246 commitHostRootContainerChildren,247 commitHostPortalContainerChildren,248 commitHostHydratedContainer,249 commitHostHydratedActivity,250 commitHostHydratedSuspense,251 commitHostRemoveChildFromContainer,252 commitHostRemoveChild,253 commitHostSingletonAcquisition,254 commitHostSingletonRelease,255} from './ReactFiberCommitHostEffects';256import {257 commitFragmentInstanceDeletionEffects,258 commitFragmentInstanceInsertionEffects,259} from './ReactFiberFragmentInstance';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';290291type LayoutEffectTraversalFlags = number;292293const NoLayoutEffectTraversalFlags = /* */ 0b00;294const IncludeWorkInProgressEffects = /* */ 0b01;295const IncludeHostSingletons = /* */ 0b10;296297// Used during the commit phase to track the state of the Offscreen component stack.298// Allows us to avoid traversing the return path to find the nearest Offscreen ancestor.299let offscreenSubtreeIsHidden: boolean = false;300let offscreenSubtreeWasHidden: boolean = false;301// Track whether there's a hidden offscreen above with no HostComponent between. If so,302// it overrides the hiddenness of the HostComponent below.303let offscreenDirectParentIsHidden: boolean = false;304305// Used to track if a form needs to be reset at the end of the mutation phase.306let needsFormReset = false;307308const PossiblyWeakSet = typeof WeakSet === 'function' ? WeakSet : Set;309310let nextEffect: Fiber | null = null;311312// Used for Profiling builds to track updaters.313let inProgressLanes: Lanes | null = null;314let inProgressRoot: FiberRoot | null = null;315316let focusedInstanceHandle: null | Fiber = null;317export let shouldFireAfterActiveInstanceBlur: boolean = false;318319// Used during the commit phase to track whether a parent ViewTransition component320// might have been affected by any mutations / relayouts below.321let viewTransitionContextChanged: boolean = false;322let inUpdateViewTransition: boolean = false;323let rootViewTransitionAffected: boolean = false;324let rootViewTransitionNameCanceled: boolean = false;325326function isHydratingParent(current: Fiber, finishedWork: Fiber): boolean {327 if (finishedWork.tag === ActivityComponent) {328 const prevState: ActivityState | null = current.memoizedState;329 const nextState: ActivityState | null = finishedWork.memoizedState;330 return prevState !== null && nextState === null;331 } else if (finishedWork.tag === SuspenseComponent) {332 const prevState: SuspenseState | null = current.memoizedState;333 const nextState: SuspenseState | null = finishedWork.memoizedState;334 return (335 prevState !== null &&336 prevState.dehydrated !== null &&337 (nextState === null || nextState.dehydrated === null)338 );339 } else if (finishedWork.tag === HostRoot) {340 return (341 (current.memoizedState as RootState).isDehydrated &&342 (finishedWork.flags & ForceClientRender) === NoFlags343 );344 } else {345 return false;346 }347}348349export function commitBeforeMutationEffects(350 root: FiberRoot,351 firstChild: Fiber,352 committedLanes: Lanes,353): void {354 focusedInstanceHandle = prepareForCommit(root.containerInfo);355 shouldFireAfterActiveInstanceBlur = false;356357 const isViewTransitionEligible =358 enableViewTransition &&359 includesOnlyViewTransitionEligibleLanes(committedLanes);360361 nextEffect = firstChild;362 commitBeforeMutationEffects_begin(isViewTransitionEligible);363364 // We no longer need to track the active instance fiber365 focusedInstanceHandle = null;366 // We've found any matched pairs and can now reset.367 resetAppearingViewTransitions();368}369370function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {371 // If this commit is eligible for a View Transition we look into all mutated subtrees.372 // TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.373 const subtreeMask = isViewTransitionEligible374 ? BeforeAndAfterMutationTransitionMask375 : BeforeMutationMask;376 while (nextEffect !== null) {377 const fiber = nextEffect;378379 // This phase is only used for beforeActiveInstanceBlur.380 // Let's skip the whole loop if it's off.381 if (enableCreateEventHandleAPI || isViewTransitionEligible) {382 // TODO: Should wrap this in flags check, too, as optimization383 const deletions = fiber.deletions;384 if (deletions !== null) {385 for (let i = 0; i < deletions.length; i++) {386 const deletion = deletions[i];387 commitBeforeMutationEffectsDeletion(388 deletion,389 isViewTransitionEligible,390 );391 }392 }393 }394395 if (396 enableViewTransition &&397 fiber.alternate === null &&398 (fiber.flags & Placement) !== NoFlags399 ) {400 // Skip before mutation effects of the children because we don't want401 // to trigger updates of any nested view transitions and we shouldn't402 // have any other before mutation effects since snapshot effects are403 // only applied to updates. TODO: Model this using only flags.404 if (isViewTransitionEligible) {405 trackEnterViewTransitions(fiber);406 }407 commitBeforeMutationEffects_complete(isViewTransitionEligible);408 continue;409 }410411 // TODO: This should really unify with the switch in commitBeforeMutationEffectsOnFiber recursively.412 if (enableViewTransition && fiber.tag === OffscreenComponent) {413 const isModernRoot =414 disableLegacyMode || (fiber.mode & ConcurrentMode) !== NoMode;415 if (isModernRoot) {416 const current = fiber.alternate;417 const isHidden = fiber.memoizedState !== null;418 if (isHidden) {419 if (420 current !== null &&421 current.memoizedState === null &&422 isViewTransitionEligible423 ) {424 // Was previously mounted as visible but is now hidden.425 commitExitViewTransitions(current);426 }427 // Skip before mutation effects of the children because they're hidden.428 commitBeforeMutationEffects_complete(isViewTransitionEligible);429 continue;430 } else if (current !== null && current.memoizedState !== null) {431 // Was previously mounted as hidden but is now visible.432 // Skip before mutation effects of the children because we don't want433 // to trigger updates of any nested view transitions and we shouldn't434 // have any other before mutation effects since snapshot effects are435 // only applied to updates. TODO: Model this using only flags.436 if (isViewTransitionEligible) {437 trackEnterViewTransitions(fiber);438 }439 commitBeforeMutationEffects_complete(isViewTransitionEligible);440 continue;441 }442 }443 }444445 const child = fiber.child;446 if ((fiber.subtreeFlags & subtreeMask) !== NoFlags && child !== null) {447 child.return = fiber;448 nextEffect = child;449 } else {450 if (isViewTransitionEligible) {451 // We are inside an updated subtree. Any mutations that affected the452 // parent HostInstance's layout or set of children (such as reorders)453 // might have also affected the positioning or size of the inner454 // ViewTransitions. Therefore we need to find them inside.455 commitNestedViewTransitions(fiber);456 }457 commitBeforeMutationEffects_complete(isViewTransitionEligible);458 }459 }460}461462function commitBeforeMutationEffects_complete(463 isViewTransitionEligible: boolean,464) {465 while (nextEffect !== null) {466 const fiber = nextEffect;467 commitBeforeMutationEffectsOnFiber(fiber, isViewTransitionEligible);468469 const sibling = fiber.sibling;470 if (sibling !== null) {471 sibling.return = fiber.return;472 nextEffect = sibling;473 return;474 }475476 nextEffect = fiber.return;477 }478}479480function commitBeforeMutationEffectsOnFiber(481 finishedWork: Fiber,482 isViewTransitionEligible: boolean,483) {484 const current = finishedWork.alternate;485 const flags = finishedWork.flags;486487 if (enableCreateEventHandleAPI) {488 if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {489 // Check to see if the focused element was inside of a hidden (Suspense) subtree.490 // TODO: Move this out of the hot path using a dedicated effect tag.491 // TODO: This should consider Offscreen in general and not just SuspenseComponent.492 if (493 finishedWork.tag === SuspenseComponent &&494 isSuspenseBoundaryBeingHidden(current, finishedWork) &&495 // $FlowFixMe[incompatible-type] found when upgrading Flow496 doesFiberContain(finishedWork, focusedInstanceHandle)497 ) {498 shouldFireAfterActiveInstanceBlur = true;499 beforeActiveInstanceBlur(finishedWork);500 }501 }502 }503504 switch (finishedWork.tag) {505 case ClassComponent: {506 if ((flags & Snapshot) !== NoFlags) {507 if (current !== null) {508 commitClassSnapshot(finishedWork, current);509 }510 }511 break;512 }513 case HostRoot: {514 if ((flags & Snapshot) !== NoFlags) {515 if (supportsMutation) {516 const root = finishedWork.stateNode;517 clearContainer(root.containerInfo);518 }519 }520 break;521 }522 case FunctionComponent:523 case ForwardRef:524 case SimpleMemoComponent:525 case HostComponent:526 case HostHoistable:527 case HostSingleton:528 case HostText:529 case HostPortal:530 case IncompleteClassComponent:531 // Nothing to do for these component types532 break;533 case ViewTransitionComponent:534 if (enableViewTransition) {535 if (isViewTransitionEligible) {536 if (current === null) {537 // This is a new mount. We should have handled this as part of the538 // Placement effect or it is deeper inside a entering transition.539 } else {540 // Something may have mutated within this subtree. This might need to cause541 // a cross-fade of this parent. We first assign old names to the542 // previous tree in the before mutation phase in case we need to.543 // TODO: This walks the tree that we might continue walking anyway.544 // We should just stash the parent ViewTransitionComponent and continue545 // walking the tree until we find HostComponent but to do that we need546 // to use a stack which requires refactoring this phase.547 commitBeforeUpdateViewTransition(current, finishedWork);548 }549 }550 break;551 }552 // Fallthrough553 default: {554 if ((flags & Snapshot) !== NoFlags) {555 throw new Error(556 'This unit of work tag should not have side-effects. This error is ' +557 'likely caused by a bug in React. Please file an issue.',558 );559 }560 }561 }562}563564function commitBeforeMutationEffectsDeletion(565 deletion: Fiber,566 isViewTransitionEligible: boolean,567) {568 if (enableCreateEventHandleAPI) {569 // TODO (effects) It would be nice to avoid calling doesFiberContain()570 // Maybe we can repurpose one of the subtreeFlags positions for this instead?571 // Use it to store which part of the tree the focused instance is in?572 // This assumes we can safely determine that instance during the "render" phase.573 if (doesFiberContain(deletion, focusedInstanceHandle as any as Fiber)) {574 shouldFireAfterActiveInstanceBlur = true;575 beforeActiveInstanceBlur(deletion);576 }577 }578 if (isViewTransitionEligible) {579 commitExitViewTransitions(deletion);580 }581}582583function commitLayoutEffectOnFiber(584 finishedRoot: FiberRoot,585 current: Fiber | null,586 finishedWork: Fiber,587 committedLanes: Lanes,588): void {589 const prevEffectStart = pushComponentEffectStart();590 const prevEffectDuration = pushComponentEffectDuration();591 const prevEffectErrors = pushComponentEffectErrors();592 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();593 // When updating this function, also update reappearLayoutEffects, which does594 // most of the same things when an offscreen tree goes from hidden -> visible.595 const flags = finishedWork.flags;596 switch (finishedWork.tag) {597 case FunctionComponent:598 case ForwardRef:599 case SimpleMemoComponent: {600 recursivelyTraverseLayoutEffects(601 finishedRoot,602 finishedWork,603 committedLanes,604 );605 if (flags & Update) {606 commitHookLayoutEffects(finishedWork, HookLayout | HookHasEffect);607 }608 break;609 }610 case ClassComponent: {611 recursivelyTraverseLayoutEffects(612 finishedRoot,613 finishedWork,614 committedLanes,615 );616 if (flags & Update) {617 commitClassLayoutLifecycles(finishedWork, current);618 }619620 if (flags & Callback) {621 commitClassCallbacks(finishedWork);622 }623624 if (flags & Ref) {625 safelyAttachRef(finishedWork, finishedWork.return);626 }627 break;628 }629 case HostRoot: {630 const prevProfilerEffectDuration = pushNestedEffectDurations();631 recursivelyTraverseLayoutEffects(632 finishedRoot,633 finishedWork,634 committedLanes,635 );636 if (flags & Callback) {637 commitRootCallbacks(finishedWork);638 }639 if (enableProfilerTimer && enableProfilerCommitHooks) {640 finishedRoot.effectDuration += popNestedEffectDurations(641 prevProfilerEffectDuration,642 );643 }644 break;645 }646 case HostSingleton: {647 if (supportsSingletons) {648 // We acquire the singleton instance first so it has appropriate649 // styles before other layout effects run. This isn't perfect because650 // an early sibling of the singleton may have an effect that can651 // observe the singleton before it is acquired.652 // @TODO move this to the mutation phase. The reason it isn't there yet653 // is it seemingly requires an extra traversal because we need to move the654 // disappear effect into a phase before the appear phase655 if (current === null && flags & Update) {656 // Unlike in the reappear path we only acquire on new mount657 commitHostSingletonAcquisition(finishedWork);658 }659 // We fall through to the HostComponent case below.660 }661 // Fallthrough662 }663 case HostHoistable:664 case HostComponent: {665 recursivelyTraverseLayoutEffects(666 finishedRoot,667 finishedWork,668 committedLanes,669 );670671 // Renderers may schedule work to be done after host components are mounted672 // (eg DOM renderer may schedule auto-focus for inputs and form controls).673 // These effects should only be committed when components are first mounted,674 // aka when there is no current/alternate.675 if (current === null) {676 if (flags & Update) {677 commitHostMount(finishedWork);678 } else if (flags & Hydrate) {679 commitHostHydratedInstance(finishedWork);680 }681 }682683 if (flags & Ref) {684 safelyAttachRef(finishedWork, finishedWork.return);685 }686 break;687 }688 case Profiler: {689 // TODO: Should this fire inside an offscreen tree? Or should it wait to690 // fire when the tree becomes visible again.691 if (flags & Update) {692 const prevProfilerEffectDuration = pushNestedEffectDurations();693694 recursivelyTraverseLayoutEffects(695 finishedRoot,696 finishedWork,697 committedLanes,698 );699700 const profilerInstance = finishedWork.stateNode;701702 if (enableProfilerTimer && enableProfilerCommitHooks) {703 // Propagate layout effect durations to the next nearest Profiler ancestor.704 // Do not reset these values until the next render so DevTools has a chance to read them first.705 profilerInstance.effectDuration += bubbleNestedEffectDurations(706 prevProfilerEffectDuration,707 );708 }709710 commitProfilerUpdate(711 finishedWork,712 current,713 commitStartTime,714 profilerInstance.effectDuration,715 );716 } else {717 recursivelyTraverseLayoutEffects(718 finishedRoot,719 finishedWork,720 committedLanes,721 );722 }723 break;724 }725 case ActivityComponent: {726 recursivelyTraverseLayoutEffects(727 finishedRoot,728 finishedWork,729 committedLanes,730 );731 if (flags & Update) {732 commitActivityHydrationCallbacks(finishedRoot, finishedWork);733 }734 break;735 }736 case SuspenseComponent: {737 recursivelyTraverseLayoutEffects(738 finishedRoot,739 finishedWork,740 committedLanes,741 );742 if (flags & Update) {743 commitSuspenseHydrationCallbacks(finishedRoot, finishedWork);744 }745 if (flags & Callback) {746 // This Boundary is in fallback and has a dehydrated Suspense instance.747 // We could in theory assume the dehydrated state but we recheck it for748 // certainty.749 const finishedState: SuspenseState | null = finishedWork.memoizedState;750 if (finishedState !== null) {751 const dehydrated = finishedState.dehydrated;752 if (dehydrated !== null) {753 // Register a callback to retry this boundary once the server has sent the result.754 const retry = retryDehydratedSuspenseBoundary.bind(755 null,756 finishedWork,757 );758 registerSuspenseInstanceRetry(dehydrated, retry);759 }760 }761 }762 break;763 }764 case OffscreenComponent: {765 const isModernRoot =766 disableLegacyMode || (finishedWork.mode & ConcurrentMode) !== NoMode;767 if (isModernRoot) {768 const isHidden = finishedWork.memoizedState !== null;769 const newOffscreenSubtreeIsHidden =770 isHidden || offscreenSubtreeIsHidden;771 if (newOffscreenSubtreeIsHidden) {772 // The Offscreen tree is hidden. Skip over its layout effects.773 } else {774 // The Offscreen tree is visible.775776 const wasHidden = current !== null && current.memoizedState !== null;777 const newOffscreenSubtreeWasHidden =778 wasHidden || offscreenSubtreeWasHidden;779 const prevOffscreenSubtreeIsHidden = offscreenSubtreeIsHidden;780 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;781 offscreenSubtreeIsHidden = newOffscreenSubtreeIsHidden;782 offscreenSubtreeWasHidden = newOffscreenSubtreeWasHidden;783784 if (offscreenSubtreeWasHidden && !prevOffscreenSubtreeWasHidden) {785 // This is the root of a reappearing boundary. As we continue786 // traversing the layout effects, we must also re-mount layout787 // effects that were unmounted when the Offscreen subtree was788 // hidden. So this is a superset of the normal commitLayoutEffects.789 let layoutEffectTraversalFlags: LayoutEffectTraversalFlags;790 if (supportsSingletons) {791 layoutEffectTraversalFlags = IncludeHostSingletons;792 } else {793 layoutEffectTraversalFlags = NoLayoutEffectTraversalFlags;794 }795 if ((finishedWork.subtreeFlags & LayoutMask) !== NoFlags) {796 layoutEffectTraversalFlags |= IncludeWorkInProgressEffects;797 }798 recursivelyTraverseReappearLayoutEffects(799 finishedRoot,800 finishedWork,801 layoutEffectTraversalFlags,802 );803 if (804 enableProfilerTimer &&805 enableProfilerCommitHooks &&806 enableComponentPerformanceTrack &&807 (finishedWork.mode & ProfileMode) !== NoMode &&808 componentEffectStartTime >= 0 &&809 componentEffectEndTime >= 0 &&810 componentEffectEndTime - componentEffectStartTime > 0.05811 ) {812 logComponentReappeared(813 finishedWork,814 componentEffectStartTime,815 componentEffectEndTime,816 );817 }818 } else {819 recursivelyTraverseLayoutEffects(820 finishedRoot,821 finishedWork,822 committedLanes,823 );824 }825 offscreenSubtreeIsHidden = prevOffscreenSubtreeIsHidden;826 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;827 }828 } else {829 recursivelyTraverseLayoutEffects(830 finishedRoot,831 finishedWork,832 committedLanes,833 );834 }835 break;836 }837 case ViewTransitionComponent: {838 if (enableViewTransition) {839 if (__DEV__) {840 if (flags & ViewTransitionNamedStatic) {841 trackNamedViewTransition(finishedWork);842 }843 }844 recursivelyTraverseLayoutEffects(845 finishedRoot,846 finishedWork,847 committedLanes,848 );849 if (flags & Ref) {850 safelyAttachRef(finishedWork, finishedWork.return);851 }852 break;853 }854 break;855 }856 case Fragment:857 if (flags & Ref) {858 safelyAttachRef(finishedWork, finishedWork.return);859 }860 // Fallthrough861 default: {862 recursivelyTraverseLayoutEffects(863 finishedRoot,864 finishedWork,865 committedLanes,866 );867 break;868 }869 }870871 if (872 enableProfilerTimer &&873 enableProfilerCommitHooks &&874 enableComponentPerformanceTrack &&875 (finishedWork.mode & ProfileMode) !== NoMode &&876 componentEffectStartTime >= 0 &&877 componentEffectEndTime >= 0878 ) {879 if (componentEffectSpawnedUpdate || componentEffectDuration > 0.05) {880 logComponentEffect(881 finishedWork,882 componentEffectStartTime,883 componentEffectEndTime,884 componentEffectDuration,885 componentEffectErrors,886 );887 }888 if (889 // Insertion890 finishedWork.alternate === null &&891 finishedWork.return !== null &&892 finishedWork.return.alternate !== null &&893 componentEffectEndTime - componentEffectStartTime > 0.05894 ) {895 const isHydration = isHydratingParent(896 finishedWork.return.alternate,897 finishedWork.return,898 );899 if (!isHydration) {900 logComponentMount(901 finishedWork,902 componentEffectStartTime,903 componentEffectEndTime,904 );905 }906 }907 }908909 popComponentEffectStart(prevEffectStart);910 popComponentEffectDuration(prevEffectDuration);911 popComponentEffectErrors(prevEffectErrors);912 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);913}914915function abortRootTransitions(916 root: FiberRoot,917 abort: TransitionAbort,918 deletedTransitions: Set<Transition>,919 deletedOffscreenInstance: OffscreenInstance | null,920 isInDeletedTree: boolean,921) {922 if (enableTransitionTracing) {923 const rootTransitions = root.incompleteTransitions;924 deletedTransitions.forEach(transition => {925 if (rootTransitions.has(transition)) {926 const transitionInstance: TracingMarkerInstance = rootTransitions.get(927 transition,928 ) as any;929 if (transitionInstance.aborts === null) {930 transitionInstance.aborts = [];931 }932 transitionInstance.aborts.push(abort);933934 if (deletedOffscreenInstance !== null) {935 if (936 transitionInstance.pendingBoundaries !== null &&937 transitionInstance.pendingBoundaries.has(deletedOffscreenInstance)938 ) {939 // $FlowFixMe[incompatible-use] found when upgrading Flow940 transitionInstance.pendingBoundaries.delete(941 deletedOffscreenInstance,942 );943 }944 }945 }946 });947 }948}949950function abortTracingMarkerTransitions(951 abortedFiber: Fiber,952 abort: TransitionAbort,953 deletedTransitions: Set<Transition>,954 deletedOffscreenInstance: OffscreenInstance | null,955 isInDeletedTree: boolean,956) {957 if (enableTransitionTracing) {958 const markerInstance: TracingMarkerInstance = abortedFiber.stateNode;959 const markerTransitions = markerInstance.transitions;960 const pendingBoundaries = markerInstance.pendingBoundaries;961 if (markerTransitions !== null) {962 // TODO: Refactor this code. Is there a way to move this code to963 // the deletions phase instead of calculating it here while making sure964 // complete is called appropriately?965 deletedTransitions.forEach(transition => {966 // If one of the transitions on the tracing marker is a transition967 // that was in an aborted subtree, we will abort that tracing marker968 if (969 // $FlowFixMe[invalid-compare]970 abortedFiber !== null &&971 markerTransitions.has(transition) &&972 (markerInstance.aborts === null ||973 !markerInstance.aborts.includes(abort))974 ) {975 if (markerInstance.transitions !== null) {976 if (markerInstance.aborts === null) {977 markerInstance.aborts = [abort];978 addMarkerIncompleteCallbackToPendingTransition(979 abortedFiber.memoizedProps.name,980 markerInstance.transitions,981 markerInstance.aborts,982 );983 } else {984 markerInstance.aborts.push(abort);985 }986987 // We only want to call onTransitionProgress when the marker hasn't been988 // deleted989 if (990 deletedOffscreenInstance !== null &&991 !isInDeletedTree &&992 pendingBoundaries !== null &&993 pendingBoundaries.has(deletedOffscreenInstance)994 ) {995 pendingBoundaries.delete(deletedOffscreenInstance);996997 addMarkerProgressCallbackToPendingTransition(998 abortedFiber.memoizedProps.name,999 deletedTransitions,1000 pendingBoundaries,1001 );1002 }1003 }1004 }1005 });1006 }1007 }1008}10091010function abortParentMarkerTransitionsForDeletedFiber(1011 abortedFiber: Fiber,1012 abort: TransitionAbort,1013 deletedTransitions: Set<Transition>,1014 deletedOffscreenInstance: OffscreenInstance | null,1015 isInDeletedTree: boolean,1016) {1017 if (enableTransitionTracing) {1018 // Find all pending markers that are waiting on child suspense boundaries in the1019 // aborted subtree and cancels them1020 let fiber: null | Fiber = abortedFiber;1021 while (fiber !== null) {1022 switch (fiber.tag) {1023 case TracingMarkerComponent:1024 abortTracingMarkerTransitions(1025 fiber,1026 abort,1027 deletedTransitions,1028 deletedOffscreenInstance,1029 isInDeletedTree,1030 );1031 break;1032 case HostRoot:1033 const root = fiber.stateNode;1034 abortRootTransitions(1035 root,1036 abort,1037 deletedTransitions,1038 deletedOffscreenInstance,1039 isInDeletedTree,1040 );10411042 break;1043 default:1044 break;1045 }10461047 fiber = fiber.return;1048 }1049 }1050}10511052function commitTransitionProgress(offscreenFiber: Fiber) {1053 if (enableTransitionTracing) {1054 // This function adds suspense boundaries to the root1055 // or tracing marker's pendingBoundaries map.1056 // When a suspense boundary goes from a resolved to a fallback1057 // state we add the boundary to the map, and when it goes from1058 // a fallback to a resolved state, we remove the boundary from1059 // the map.10601061 // We use stateNode on the Offscreen component as a stable object1062 // that doesnt change from render to render. This way we can1063 // distinguish between different Offscreen instances (vs. the same1064 // Offscreen instance with different fibers)1065 const offscreenInstance: OffscreenInstance = offscreenFiber.stateNode;10661067 let prevState: SuspenseState | null = null;1068 const previousFiber = offscreenFiber.alternate;1069 if (previousFiber !== null && previousFiber.memoizedState !== null) {1070 prevState = previousFiber.memoizedState;1071 }1072 const nextState: SuspenseState | null = offscreenFiber.memoizedState;10731074 const wasHidden = prevState !== null;1075 const isHidden = nextState !== null;10761077 const pendingMarkers = offscreenInstance._pendingMarkers;1078 // If there is a name on the suspense boundary, store that in1079 // the pending boundaries.1080 let name = null;1081 const parent = offscreenFiber.return;1082 if (1083 parent !== null &&1084 parent.tag === SuspenseComponent &&1085 parent.memoizedProps.name1086 ) {1087 name = parent.memoizedProps.name;1088 }10891090 if (!wasHidden && isHidden) {1091 // The suspense boundaries was just hidden. Add the boundary1092 // to the pending boundary set if it's there1093 if (pendingMarkers !== null) {1094 pendingMarkers.forEach(markerInstance => {1095 const pendingBoundaries = markerInstance.pendingBoundaries;1096 const transitions = markerInstance.transitions;1097 const markerName = markerInstance.name;1098 if (1099 pendingBoundaries !== null &&1100 !pendingBoundaries.has(offscreenInstance)1101 ) {1102 pendingBoundaries.set(offscreenInstance, {1103 name,1104 });1105 if (transitions !== null) {1106 if (1107 markerInstance.tag === TransitionTracingMarker &&1108 markerName !== null1109 ) {1110 addMarkerProgressCallbackToPendingTransition(1111 markerName,1112 transitions,1113 pendingBoundaries,1114 );1115 } else if (markerInstance.tag === TransitionRoot) {1116 transitions.forEach(transition => {1117 addTransitionProgressCallbackToPendingTransition(1118 transition,1119 pendingBoundaries,1120 );1121 });1122 }1123 }1124 }1125 });1126 }1127 } else if (wasHidden && !isHidden) {1128 // The suspense boundary went from hidden to visible. Remove1129 // the boundary from the pending suspense boundaries set1130 // if it's there1131 if (pendingMarkers !== null) {1132 pendingMarkers.forEach(markerInstance => {1133 const pendingBoundaries = markerInstance.pendingBoundaries;1134 const transitions = markerInstance.transitions;1135 const markerName = markerInstance.name;1136 if (1137 pendingBoundaries !== null &&1138 pendingBoundaries.has(offscreenInstance)1139 ) {1140 pendingBoundaries.delete(offscreenInstance);1141 if (transitions !== null) {1142 if (1143 markerInstance.tag === TransitionTracingMarker &&1144 markerName !== null1145 ) {1146 addMarkerProgressCallbackToPendingTransition(1147 markerName,1148 transitions,1149 pendingBoundaries,1150 );11511152 // If there are no more unresolved suspense boundaries, the interaction1153 // is considered finished1154 if (pendingBoundaries.size === 0) {1155 if (markerInstance.aborts === null) {1156 addMarkerCompleteCallbackToPendingTransition(1157 markerName,1158 transitions,1159 );1160 }1161 markerInstance.transitions = null;1162 markerInstance.pendingBoundaries = null;1163 markerInstance.aborts = null;1164 }1165 } else if (markerInstance.tag === TransitionRoot) {1166 transitions.forEach(transition => {1167 addTransitionProgressCallbackToPendingTransition(1168 transition,1169 pendingBoundaries,1170 );1171 });1172 }1173 }1174 }1175 });1176 }1177 }1178 }1179}11801181function hideOrUnhideAllChildren(parentFiber: Fiber, isHidden: boolean) {1182 if (!supportsMutation) {1183 return;1184 }1185 // Finds the nearest host component children and updates their visibility1186 // to either hidden or visible.1187 let child = parentFiber.child;1188 while (child !== null) {1189 hideOrUnhideAllChildrenOnFiber(child, isHidden);1190 child = child.sibling;1191 }1192}11931194function hideOrUnhideAllChildrenOnFiber(fiber: Fiber, isHidden: boolean) {1195 if (!supportsMutation) {1196 return;1197 }1198 switch (fiber.tag) {1199 case HostComponent:1200 case HostHoistable: {1201 // Found the nearest host component. Hide it.1202 commitShowHideHostInstance(fiber, isHidden);1203 // Typically, only the nearest host nodes need to be hidden, since that1204 // has the effect of also hiding everything inside of them.1205 //1206 // However, there's a special case for portals, because portals do not1207 // exist in the regular host tree hierarchy; we can't assume that just1208 // because a portal's HostComponent parent in the React tree will also be1209 // a parent in the actual host tree.1210 //1211 // So, if any portals exist within the tree, regardless of how deeply1212 // nested they are, we need to repeat this algorithm for its children.1213 hideOrUnhideNearestPortals(fiber, isHidden);1214 return;1215 }1216 case HostText: {1217 commitShowHideHostTextInstance(fiber, isHidden);1218 return;1219 }1220 case DehydratedFragment: {1221 commitShowHideSuspenseBoundary(fiber, isHidden);1222 return;1223 }1224 case OffscreenComponent:1225 case LegacyHiddenComponent: {1226 const offscreenState: OffscreenState | null = fiber.memoizedState;1227 if (offscreenState !== null) {1228 // Found a nested Offscreen component that is hidden.1229 // Don't search any deeper. This tree should remain hidden.1230 } else {1231 hideOrUnhideAllChildren(fiber, isHidden);1232 }1233 return;1234 }1235 default: {1236 hideOrUnhideAllChildren(fiber, isHidden);1237 return;1238 }1239 }1240}12411242function hideOrUnhideNearestPortals(parentFiber: Fiber, isHidden: boolean) {1243 if (!supportsMutation) {1244 return;1245 }1246 if (parentFiber.subtreeFlags & PortalStatic) {1247 let child = parentFiber.child;1248 while (child !== null) {1249 hideOrUnhideNearestPortalsOnFiber(child, isHidden);1250 child = child.sibling;1251 }1252 }1253}12541255function hideOrUnhideNearestPortalsOnFiber(fiber: Fiber, isHidden: boolean) {1256 if (!supportsMutation) {1257 return;1258 }1259 switch (fiber.tag) {1260 case HostPortal: {1261 // Found a portal. Switch back to the normal hide/unhide algorithm to1262 // toggle the visibility of its children.1263 hideOrUnhideAllChildrenOnFiber(fiber, isHidden);1264 return;1265 }1266 case OffscreenComponent: {1267 const offscreenState: OffscreenState | null = fiber.memoizedState;1268 if (offscreenState !== null) {1269 // Found a nested Offscreen component that is hidden. Don't search any1270 // deeper. This tree should remain hidden.1271 } else {1272 hideOrUnhideNearestPortals(fiber, isHidden);1273 }1274 return;1275 }1276 default: {1277 hideOrUnhideNearestPortals(fiber, isHidden);1278 return;1279 }1280 }1281}12821283function detachFiberMutation(fiber: Fiber) {1284 // Cut off the return pointer to disconnect it from the tree.1285 // This enables us to detect and warn against state updates on an unmounted component.1286 // It also prevents events from bubbling from within disconnected components.1287 //1288 // Ideally, we should also clear the child pointer of the parent alternate to let this1289 // get GC:ed but we don't know which for sure which parent is the current1290 // one so we'll settle for GC:ing the subtree of this child.1291 // This child itself will be GC:ed when the parent updates the next time.1292 //1293 // Note that we can't clear child or sibling pointers yet.1294 // They're needed for passive effects and for findDOMNode.1295 // We defer those fields, and all other cleanup, to the passive phase (see detachFiberAfterEffects).1296 //1297 // Don't reset the alternate yet, either. We need that so we can detach the1298 // alternate's fields in the passive phase. Clearing the return pointer is1299 // sufficient for findDOMNode semantics.1300 const alternate = fiber.alternate;1301 if (alternate !== null) {1302 alternate.return = null;1303 }1304 fiber.return = null;1305}13061307function detachFiberAfterEffects(fiber: Fiber) {1308 const alternate = fiber.alternate;1309 if (alternate !== null) {1310 fiber.alternate = null;1311 detachFiberAfterEffects(alternate);1312 }13131314 // Clear cyclical Fiber fields. This level alone is designed to roughly1315 // approximate the planned Fiber refactor. In that world, `setState` will be1316 // bound to a special "instance" object instead of a Fiber. The Instance1317 // object will not have any of these fields. It will only be connected to1318 // the fiber tree via a single link at the root. So if this level alone is1319 // sufficient to fix memory issues, that bodes well for our plans.1320 fiber.child = null;1321 fiber.deletions = null;1322 fiber.sibling = null;13231324 // The `stateNode` is cyclical because on host nodes it points to the host1325 // tree, which has its own pointers to children, parents, and siblings.1326 // The other host nodes also point back to fibers, so we should detach that1327 // one, too.1328 if (fiber.tag === HostComponent) {1329 const hostInstance: Instance = fiber.stateNode;1330 // $FlowFixMe[invalid-compare]1331 if (hostInstance !== null) {1332 detachDeletedInstance(hostInstance);1333 }1334 }1335 fiber.stateNode = null;13361337 if (__DEV__) {1338 fiber._debugOwner = null;1339 }13401341 // Theoretically, nothing in here should be necessary, because we already1342 // disconnected the fiber from the tree. So even if something leaks this1343 // particular fiber, it won't leak anything else.1344 fiber.return = null;1345 fiber.dependencies = null;1346 fiber.memoizedProps = null;1347 fiber.memoizedState = null;1348 fiber.pendingProps = null;1349 fiber.stateNode = null;1350 // TODO: Move to `commitPassiveUnmountInsideDeletedTreeOnFiber` instead.1351 fiber.updateQueue = null;1352}13531354// These are tracked on the stack as we recursively traverse a1355// deleted subtree.1356// TODO: Update these during the whole mutation phase, not just during1357// a deletion.1358let hostParent: Instance | Container | null = null;1359let hostParentIsContainer: boolean = false;13601361function commitDeletionEffects(1362 root: FiberRoot,1363 returnFiber: Fiber,1364 deletedFiber: Fiber,1365) {1366 const prevEffectStart = pushComponentEffectStart();13671368 if (supportsMutation) {1369 // We only have the top Fiber that was deleted but we need to recurse down its1370 // children to find all the terminal nodes.13711372 // Recursively delete all host nodes from the parent, detach refs, clean1373 // up mounted layout effects, and call componentWillUnmount.13741375 // We only need to remove the topmost host child in each branch. But then we1376 // still need to keep traversing to unmount effects, refs, and cWU. TODO: We1377 // could split this into two separate traversals functions, where the second1378 // one doesn't include any removeChild logic. This is maybe the same1379 // function as "disappearLayoutEffects" (or whatever that turns into after1380 // the layout phase is refactored to use recursion).13811382 // Before starting, find the nearest host parent on the stack so we know1383 // which instance/container to remove the children from.1384 // TODO: Instead of searching up the fiber return path on every deletion, we1385 // can track the nearest host component on the JS stack as we traverse the1386 // tree during the commit phase. This would make insertions faster, too.1387 let parent: null | Fiber = returnFiber;1388 findParent: while (parent !== null) {1389 switch (parent.tag) {1390 case HostSingleton: {1391 if (supportsSingletons) {1392 if (isSingletonScope(parent.type)) {1393 hostParent = parent.stateNode;1394 hostParentIsContainer = false;1395 break findParent;1396 }1397 break;1398 }1399 // Expected fallthrough when supportsSingletons is false1400 }1401 case HostComponent: {1402 hostParent = parent.stateNode;1403 hostParentIsContainer = false;1404 break findParent;1405 }1406 case HostRoot:1407 case HostPortal: {1408 hostParent = parent.stateNode.containerInfo;1409 hostParentIsContainer = true;1410 break findParent;1411 }1412 }1413 parent = parent.return;1414 }1415 if (hostParent === null) {1416 throw new Error(1417 'Expected to find a host parent. This error is likely caused by ' +1418 'a bug in React. Please file an issue.',1419 );1420 }14211422 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1423 hostParent = null;1424 hostParentIsContainer = false;1425 } else {1426 // Detach refs and call componentWillUnmount() on the whole subtree.1427 commitDeletionEffectsOnFiber(root, returnFiber, deletedFiber);1428 }14291430 if (1431 enableProfilerTimer &&1432 enableProfilerCommitHooks &&1433 enableComponentPerformanceTrack &&1434 (deletedFiber.mode & ProfileMode) !== NoMode &&1435 componentEffectStartTime >= 0 &&1436 componentEffectEndTime >= 0 &&1437 componentEffectEndTime - componentEffectStartTime > 0.051438 ) {1439 logComponentUnmount(1440 deletedFiber,1441 componentEffectStartTime,1442 componentEffectEndTime,1443 );1444 }1445 popComponentEffectStart(prevEffectStart);14461447 detachFiberMutation(deletedFiber);1448}14491450function recursivelyTraverseDeletionEffects(1451 finishedRoot: FiberRoot,1452 nearestMountedAncestor: Fiber,1453 parent: Fiber,1454) {1455 // TODO: Use a static flag to skip trees that don't have unmount effects1456 let child = parent.child;1457 while (child !== null) {1458 commitDeletionEffectsOnFiber(finishedRoot, nearestMountedAncestor, child);1459 child = child.sibling;1460 }1461}14621463function commitDeletionEffectsOnFiber(1464 finishedRoot: FiberRoot,1465 nearestMountedAncestor: Fiber,1466 deletedFiber: Fiber,1467) {1468 // TODO: Delete this Hook once new DevTools ships everywhere. No longer needed.1469 onCommitUnmount(deletedFiber);14701471 const prevEffectStart = pushComponentEffectStart();1472 const prevEffectDuration = pushComponentEffectDuration();1473 const prevEffectErrors = pushComponentEffectErrors();1474 const prevEffectDidSpawnUpdate = pushComponentEffectDidSpawnUpdate();14751476 // The cases in this outer switch modify the stack before they traverse1477 // into their subtree. There are simpler cases in the inner switch1478 // that don't modify the stack.1479 switch (deletedFiber.tag) {1480 case HostHoistable: {1481 if (supportsResources) {1482 if (!offscreenSubtreeWasHidden) {1483 safelyDetachRef(deletedFiber, nearestMountedAncestor);1484 }1485 recursivelyTraverseDeletionEffects(1486 finishedRoot,1487 nearestMountedAncestor,1488 deletedFiber,1489 );1490 if (deletedFiber.memoizedState) {1491 releaseResource(deletedFiber.memoizedState);1492 } else if (deletedFiber.stateNode) {1493 // A Hoistable Instance lives in document.head only when its enclosing1494 // Activity is visible. If the Activity is hidden (or has been hidden1495 // since mount), the instance was either never inserted or was1496 // detached by the disappear traversal. Skip in those cases.1497 if (!offscreenSubtreeWasHidden) {1498 unmountHoistable(deletedFiber.stateNode);1499 }1500 }1501 break;1502 }1503 // Fall through1504 }1505 case HostSingleton: {1506 if (supportsSingletons) {1507 if (!offscreenSubtreeWasHidden) {1508 safelyDetachRef(deletedFiber, nearestMountedAncestor);1509 }1510 commitFragmentInstanceDeletionEffects(deletedFiber);15111512 const prevHostParent = hostParent;1513 const prevHostParentIsContainer = hostParentIsContainer;1514 if (isSingletonScope(deletedFiber.type)) {1515 hostParent = deletedFiber.stateNode;1516 hostParentIsContainer = false;1517 }1518 recursivelyTraverseDeletionEffects(1519 finishedRoot,1520 nearestMountedAncestor,1521 deletedFiber,1522 );15231524 // Normally this is called in passive unmount effect phase however with1525 // HostSingleton we warn if you acquire one that is already associated to1526 // a different fiber. To increase our chances of avoiding this, specifically1527 // if you keyed a HostSingleton so there will be a delete followed by a Placement1528 // we treat detach eagerly here1529 commitHostSingletonRelease(deletedFiber);15301531 hostParent = prevHostParent;1532 hostParentIsContainer = prevHostParentIsContainer;15331534 break;1535 }1536 // Fall through1537 }1538 case HostComponent: {1539 if (!offscreenSubtreeWasHidden) {1540 safelyDetachRef(deletedFiber, nearestMountedAncestor);1541 }1542 commitFragmentInstanceDeletionEffects(deletedFiber);1543 // Intentional fallthrough to next branch1544 }1545 case HostText: {1546 // HostComponent falls through into this case.1547 if (deletedFiber.tag === HostText) {1548 commitFragmentInstanceDeletionEffects(deletedFiber);1549 }1550 // We only need to remove the nearest host child. Set the host parent1551 // to `null` on the stack to indicate that nested children don't1552 // need to be removed.1553 if (supportsMutation) {1554 const prevHostParent = hostParent;1555 const prevHostParentIsContainer = hostParentIsContainer;1556 hostParent = null;1557 recursivelyTraverseDeletionEffects(1558 finishedRoot,1559 nearestMountedAncestor,1560 deletedFiber,1561 );1562 hostParent = prevHostParent;1563 hostParentIsContainer = prevHostParentIsContainer;15641565 if (hostParent !== null) {1566 // Now that all the child effects have unmounted, we can remove the1567 // node from the tree.1568 if (hostParentIsContainer) {1569 commitHostRemoveChildFromContainer(1570 deletedFiber,1571 nearestMountedAncestor,1572 hostParent as any as Container,1573 deletedFiber.stateNode as Instance | TextInstance,1574 );1575 } else {1576 commitHostRemoveChild(1577 deletedFiber,1578 nearestMountedAncestor,1579 hostParent as any as Instance,1580 deletedFiber.stateNode as Instance | TextInstance,1581 );1582 }1583 }1584 } else {1585 recursivelyTraverseDeletionEffects(1586 finishedRoot,1587 nearestMountedAncestor,1588 deletedFiber,1589 );1590 }1591 break;1592 }1593 case DehydratedFragment: {1594 if (enableSuspenseCallback) {1595 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1596 if (hydrationCallbacks !== null) {1597 try {1598 const onDeleted = hydrationCallbacks.onDeleted;1599 if (onDeleted) {1600 onDeleted(1601 deletedFiber.stateNode as SuspenseInstance | ActivityInstance,1602 );1603 }1604 } catch (error) {1605 captureCommitPhaseError(1606 deletedFiber,1607 nearestMountedAncestor,1608 error,1609 );1610 }1611 }1612 }16131614 // Dehydrated fragments don't have any children16151616 // Delete the dehydrated suspense boundary and all of its content.1617 if (supportsMutation) {1618 if (hostParent !== null) {1619 if (hostParentIsContainer) {1620 clearSuspenseBoundaryFromContainer(1621 hostParent as any as Container,1622 deletedFiber.stateNode as SuspenseInstance,1623 );1624 } else {1625 clearSuspenseBoundary(1626 hostParent as any as Instance,1627 deletedFiber.stateNode as SuspenseInstance,1628 );1629 }1630 }1631 }1632 break;1633 }1634 case HostPortal: {1635 if (supportsMutation) {1636 // When we go into a portal, it becomes the parent to remove from.1637 const prevHostParent = hostParent;1638 const prevHostParentIsContainer = hostParentIsContainer;1639 hostParent = deletedFiber.stateNode.containerInfo;1640 hostParentIsContainer = true;1641 recursivelyTraverseDeletionEffects(1642 finishedRoot,1643 nearestMountedAncestor,1644 deletedFiber,1645 );1646 hostParent = prevHostParent;1647 hostParentIsContainer = prevHostParentIsContainer;1648 } else {1649 if (supportsPersistence) {1650 commitHostPortalContainerChildren(1651 deletedFiber.stateNode,1652 deletedFiber,1653 createContainerChildSet(),1654 );1655 }16561657 recursivelyTraverseDeletionEffects(1658 finishedRoot,1659 nearestMountedAncestor,1660 deletedFiber,1661 );1662 }1663 break;1664 }1665 case FunctionComponent:1666 case ForwardRef:1667 case MemoComponent:1668 case SimpleMemoComponent: {1669 // TODO: Use a commitHookInsertionUnmountEffects wrapper to record timings.1670 commitHookEffectListUnmount(1671 HookInsertion,1672 deletedFiber,1673 nearestMountedAncestor,1674 );1675 if (!offscreenSubtreeWasHidden) {1676 commitHookLayoutUnmountEffects(1677 deletedFiber,1678 nearestMountedAncestor,1679 HookLayout,1680 );1681 }1682 recursivelyTraverseDeletionEffects(1683 finishedRoot,1684 nearestMountedAncestor,1685 deletedFiber,1686 );1687 break;1688 }1689 case ClassComponent: {1690 if (!offscreenSubtreeWasHidden) {1691 safelyDetachRef(deletedFiber, nearestMountedAncestor);1692 const instance = deletedFiber.stateNode;1693 if (typeof instance.componentWillUnmount === 'function') {1694 safelyCallComponentWillUnmount(1695 deletedFiber,1696 nearestMountedAncestor,1697 instance,1698 );1699 }1700 }1701 recursivelyTraverseDeletionEffects(1702 finishedRoot,1703 nearestMountedAncestor,1704 deletedFiber,1705 );1706 break;1707 }1708 case ScopeComponent: {1709 if (enableScopeAPI) {1710 if (!offscreenSubtreeWasHidden) {1711 safelyDetachRef(deletedFiber, nearestMountedAncestor);1712 }1713 }1714 recursivelyTraverseDeletionEffects(1715 finishedRoot,1716 nearestMountedAncestor,1717 deletedFiber,1718 );1719 break;1720 }1721 case OffscreenComponent: {1722 if (disableLegacyMode || deletedFiber.mode & ConcurrentMode) {1723 // If this offscreen component is hidden, we already unmounted it. Before1724 // deleting the children, track that it's already unmounted so that we1725 // don't attempt to unmount the effects again.1726 // TODO: If the tree is hidden, in most cases we should be able to skip1727 // over the nested children entirely. An exception is we haven't yet found1728 // the topmost host node to delete, which we already track on the stack.1729 // But the other case is portals, which need to be detached no matter how1730 // deeply they are nested. We should use a subtree flag to track whether a1731 // subtree includes a nested portal.1732 const prevOffscreenSubtreeWasHidden = offscreenSubtreeWasHidden;1733 offscreenSubtreeWasHidden =1734 prevOffscreenSubtreeWasHidden || deletedFiber.memoizedState !== null;17351736 recursivelyTraverseDeletionEffects(1737 finishedRoot,1738 nearestMountedAncestor,1739 deletedFiber,1740 );1741 offscreenSubtreeWasHidden = prevOffscreenSubtreeWasHidden;1742 } else {1743 recursivelyTraverseDeletionEffects(1744 finishedRoot,1745 nearestMountedAncestor,1746 deletedFiber,1747 );1748 }1749 break;1750 }1751 case ViewTransitionComponent: {1752 if (enableViewTransition) {1753 if (__DEV__) {1754 if (deletedFiber.flags & ViewTransitionNamedStatic) {1755 untrackNamedViewTransition(deletedFiber);1756 }1757 }1758 safelyDetachRef(deletedFiber, nearestMountedAncestor);1759 recursivelyTraverseDeletionEffects(1760 finishedRoot,1761 nearestMountedAncestor,1762 deletedFiber,1763 );1764 break;1765 }1766 // Fallthrough1767 }1768 case Fragment: {1769 if (!offscreenSubtreeWasHidden) {1770 safelyDetachRef(deletedFiber, nearestMountedAncestor);1771 }1772 recursivelyTraverseDeletionEffects(1773 finishedRoot,1774 nearestMountedAncestor,1775 deletedFiber,1776 );1777 break;1778 }1779 default: {1780 recursivelyTraverseDeletionEffects(1781 finishedRoot,1782 nearestMountedAncestor,1783 deletedFiber,1784 );1785 break;1786 }1787 }17881789 if (1790 enableProfilerTimer &&1791 enableProfilerCommitHooks &&1792 enableComponentPerformanceTrack &&1793 (deletedFiber.mode & ProfileMode) !== NoMode &&1794 componentEffectStartTime >= 0 &&1795 componentEffectEndTime >= 0 &&1796 (componentEffectSpawnedUpdate || componentEffectDuration > 0.05)1797 ) {1798 logComponentEffect(1799 deletedFiber,1800 componentEffectStartTime,1801 componentEffectEndTime,1802 componentEffectDuration,1803 componentEffectErrors,1804 );1805 }18061807 popComponentEffectStart(prevEffectStart);1808 popComponentEffectDuration(prevEffectDuration);1809 popComponentEffectErrors(prevEffectErrors);1810 popComponentEffectDidSpawnUpdate(prevEffectDidSpawnUpdate);1811}18121813function commitSuspenseCallback(finishedWork: Fiber) {1814 // TODO: Delete this feature. It's not properly covered by DEV features.1815 const newState: SuspenseState | null = finishedWork.memoizedState;1816 if (enableSuspenseCallback && newState !== null) {1817 const suspenseCallback = finishedWork.memoizedProps.suspenseCallback;1818 if (typeof suspenseCallback === 'function') {1819 const retryQueue: RetryQueue | null = finishedWork.updateQueue as any;1820 if (retryQueue !== null) {1821 suspenseCallback(new Set(retryQueue));1822 }1823 } else if (__DEV__) {1824 if (suspenseCallback !== undefined) {1825 console.error('Unexpected type for suspenseCallback.');1826 }1827 }1828 }1829}18301831function commitActivityHydrationCallbacks(1832 finishedRoot: FiberRoot,1833 finishedWork: Fiber,1834) {1835 if (!supportsHydration) {1836 return;1837 }1838 const newState: ActivityState | null = finishedWork.memoizedState;1839 if (newState === null) {1840 const current = finishedWork.alternate;1841 if (current !== null) {1842 const prevState: ActivityState | null = current.memoizedState;1843 if (prevState !== null) {1844 const activityInstance = prevState.dehydrated;1845 commitHostHydratedActivity(activityInstance, finishedWork);1846 if (enableSuspenseCallback) {1847 try {1848 // TODO: Delete this feature. It's not properly covered by DEV features.1849 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1850 if (hydrationCallbacks !== null) {1851 const onHydrated = hydrationCallbacks.onHydrated;1852 if (onHydrated) {1853 onHydrated(activityInstance);1854 }1855 }1856 } catch (error) {1857 captureCommitPhaseError(finishedWork, finishedWork.return, error);1858 }1859 }1860 }1861 }1862 }1863}18641865function commitSuspenseHydrationCallbacks(1866 finishedRoot: FiberRoot,1867 finishedWork: Fiber,1868) {1869 if (!supportsHydration) {1870 return;1871 }1872 const newState: SuspenseState | null = finishedWork.memoizedState;1873 if (newState === null) {1874 const current = finishedWork.alternate;1875 if (current !== null) {1876 const prevState: SuspenseState | null = current.memoizedState;1877 if (prevState !== null) {1878 const suspenseInstance = prevState.dehydrated;1879 if (suspenseInstance !== null) {1880 commitHostHydratedSuspense(suspenseInstance, finishedWork);1881 if (enableSuspenseCallback) {1882 try {1883 // TODO: Delete this feature. It's not properly covered by DEV features.1884 const hydrationCallbacks = finishedRoot.hydrationCallbacks;1885 if (hydrationCallbacks !== null) {1886 const onHydrated = hydrationCallbacks.onHydrated;1887 if (onHydrated) {1888 onHydrated(suspenseInstance);1889 }1890 }1891 } catch (error) {1892 captureCommitPhaseError(finishedWork, finishedWork.return, error);1893 }1894 }1895 }1896 }1897 }1898 }1899}19001901function getRetryCache(finishedWork: Fiber) {1902 // TODO: Unify the interface for the retry cache so we don't have to switch1903 // on the tag like this.1904 switch (finishedWork.tag) {1905 case ActivityComponent:1906 case SuspenseComponent:1907 case SuspenseListComponent: {1908 let retryCache = finishedWork.stateNode;1909 if (retryCache === null) {1910 retryCache = finishedWork.stateNode = new PossiblyWeakSet();1911 }1912 return retryCache;1913 }1914 case OffscreenComponent: {1915 const instance: OffscreenInstance = finishedWork.stateNode;1916 let retryCache: null | Set<Wakeable> | WeakSet<Wakeable> =1917 instance._retryCache;1918 if (retryCache === null) {1919 retryCache = instance._retryCache = new PossiblyWeakSet();1920 }1921 return retryCache;1922 }1923 default: {1924 throw new Error(1925 `Unexpected Suspense handler tag (${finishedWork.tag}). This is a ` +1926 'bug in React.',1927 );1928 }1929 }1930}19311932function attachSuspenseRetryListeners(1933 finishedWork: Fiber,1934 wakeables: RetryQueue,1935) {1936 // If this boundary just timed out, then it will have a set of wakeables.1937 // For each wakeable, attach a listener so that when it resolves, React1938 // attempts to re-render the boundary in the primary (pre-timeout) state.1939 const retryCache = getRetryCache(finishedWork);1940 wakeables.forEach(wakeable => {1941 // Memoize using the boundary fiber to prevent redundant listeners.1942 if (!retryCache.has(wakeable)) {1943 retryCache.add(wakeable);19441945 if (enableUpdaterTracking) {1946 if (isDevToolsPresent) {1947 if (inProgressLanes !== null && inProgressRoot !== null) {1948 // If we have pending work still, associate the original updaters with it.1949 restorePendingUpdaters(inProgressRoot, inProgressLanes);1950 } else {1951 throw Error(1952 'Expected finished root and lanes to be set. This is a bug in React.',1953 );1954 }1955 }1956 }19571958 const retry = resolveRetryWakeable.bind(null, finishedWork, wakeable);1959 wakeable.then(retry, retry);1960 }1961 });1962}19631964// This function detects when a Suspense boundary goes from visible to hidden.1965// It returns false if the boundary is already hidden.1966// TODO: Use an effect tag.1967function isSuspenseBoundaryBeingHidden(1968 current: Fiber | null,1969 finishedWork: Fiber,1970): boolean {1971 if (current !== null) {1972 const oldState: SuspenseState | null = current.memoizedState;1973 if (oldState === null || oldState.dehydrated !== null) {1974 const newState: SuspenseState | null = finishedWork.memoizedState;1975 return newState !== null && newState.dehydrated === null;1976 }1977 }1978 return false;1979}19801981export function commitMutationEffects(1982 root: FiberRoot,1983 finishedWork: Fiber,1984 committedLanes: Lanes,1985) {1986 inProgressLanes = committedLanes;1987 inProgressRoot = root;19881989 rootViewTransitionAffected = false;1990 inUpdateViewTransition = false;19911992 resetComponentEffectTimers();19931994 commitMutationEffectsOnFiber(finishedWork, root, committedLanes);19951996 inProgressLanes = null;1997 inProgressRoot = null;1998}19992000function recursivelyTraverseMutationEffects(
Findings
✓ No findings reported for this file.