Use strict equality (===) to prevent type coercion bugs
const didBailout = current !== null && current.child === completedWork.child;
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 {Fiber, FiberRoot} from './ReactInternalTypes';11import type {RootState} from './ReactFiberRoot';12import type {Lanes, Lane} from './ReactFiberLane';13import type {ReactScopeInstance, ReactContext} from 'shared/ReactTypes';14import type {15 Instance,16 Type,17 Props,18 Container,19 ChildSet,20 Resource,21} from './ReactFiberConfig';22import type {ActivityState} from './ReactFiberActivityComponent';23import type {24 SuspenseState,25 SuspenseListRenderState,26 RetryQueue,27} from './ReactFiberSuspenseComponent';28import type {29 OffscreenState,30 OffscreenQueue,31} from './ReactFiberOffscreenComponent';32import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';33import type {Cache} from './ReactFiberCacheComponent';34import {35 enableLegacyHidden,36 enableSuspenseCallback,37 enableScopeAPI,38 enableProfilerTimer,39 enableTransitionTracing,40 passChildrenWhenCloningPersistedNodes,41 disableLegacyMode,42 enableViewTransition,43 enableViewTransitionParentEnterExit,44 enableSuspenseyImages,45} from 'shared/ReactFeatureFlags';4647import {now} from './Scheduler';4849import {50 FunctionComponent,51 ClassComponent,52 HostRoot,53 HostComponent,54 HostHoistable,55 HostSingleton,56 HostText,57 HostPortal,58 ContextProvider,59 ContextConsumer,60 ForwardRef,61 Fragment,62 Mode,63 Profiler,64 SuspenseComponent,65 SuspenseListComponent,66 MemoComponent,67 SimpleMemoComponent,68 LazyComponent,69 IncompleteClassComponent,70 IncompleteFunctionComponent,71 ScopeComponent,72 OffscreenComponent,73 LegacyHiddenComponent,74 CacheComponent,75 TracingMarkerComponent,76 Throw,77 ViewTransitionComponent,78 ActivityComponent,79} from './ReactWorkTags';80import {81 NoMode,82 ConcurrentMode,83 ProfileMode,84 SuspenseyImagesMode,85} from './ReactTypeOfMode';86import {87 Placement,88 Update,89 Visibility,90 NoFlags,91 DidCapture,92 Snapshot,93 ChildDeletion,94 StaticMask,95 Passive,96 ForceClientRender,97 MaySuspendCommit,98 ScheduleRetry,99 ShouldSuspendCommit,100 Cloned,101 ViewTransitionStatic,102 ViewTransitionStaticParent,103 Hydrate,104 PortalStatic,105} from './ReactFiberFlags';106107import {108 createInstance,109 createTextInstance,110 resolveSingletonInstance,111 appendInitialChild,112 finalizeInitialChildren,113 finalizeHydratedChildren,114 supportsMutation,115 supportsPersistence,116 supportsResources,117 supportsSingletons,118 cloneInstance,119 cloneHiddenInstance,120 cloneHiddenTextInstance,121 createContainerChildSet,122 appendChildToContainerChildSet,123 finalizeContainerChildren,124 preparePortalMount,125 prepareScopeUpdate,126 maySuspendCommit,127 maySuspendCommitOnUpdate,128 maySuspendCommitInSyncRender,129 mayResourceSuspendCommit,130 preloadInstance,131 preloadResource,132} from './ReactFiberConfig';133import {134 getRootHostContainer,135 popHostContext,136 getHostContext,137 popHostContainer,138} from './ReactFiberHostContext';139import {140 suspenseStackCursor,141 popSuspenseListContext,142 popSuspenseHandler,143 pushSuspenseListContext,144 pushSuspenseListCatch,145 setShallowSuspenseListContext,146 ForceSuspenseFallback,147 setDefaultShallowSuspenseListContext,148} from './ReactFiberSuspenseContext';149import {popHiddenContext} from './ReactFiberHiddenContext';150import {findFirstSuspended} from './ReactFiberSuspenseComponent';151import {152 isContextProvider as isLegacyContextProvider,153 popContext as popLegacyContext,154 popTopLevelContextObject as popTopLevelLegacyContextObject,155} from './ReactFiberLegacyContext';156import {popProvider} from './ReactFiberNewContext';157import {158 prepareToHydrateHostInstance,159 prepareToHydrateHostTextInstance,160 prepareToHydrateHostActivityInstance,161 prepareToHydrateHostSuspenseInstance,162 popHydrationState,163 resetHydrationState,164 getIsHydrating,165 upgradeHydrationErrorsToRecoverable,166 emitPendingHydrationWarnings,167} from './ReactFiberHydrationContext';168import {169 renderHasNotSuspendedYet,170 getRenderTargetTime,171 getWorkInProgressTransitions,172 shouldRemainOnPreviousScreen,173 markSpawnedRetryLane,174} from './ReactFiberWorkLoop';175import {176 OffscreenLane,177 SomeRetryLane,178 NoLanes,179 includesSomeLane,180 mergeLanes,181 claimNextRetryLane,182 includesOnlySuspenseyCommitEligibleLanes,183} from './ReactFiberLane';184import {resetChildFibers} from './ReactChildFiber';185import {createScopeInstance} from './ReactFiberScope';186import {transferActualDuration} from './ReactProfilerTimer';187import {popCacheProvider} from './ReactFiberCacheComponent';188import {popTreeContext, pushTreeFork} from './ReactFiberTreeContext';189import {popRootTransition, popTransition} from './ReactFiberTransition';190import {191 popMarkerInstance,192 popRootMarkerInstance,193} from './ReactFiberTracingMarkerComponent';194import {suspendCommit} from './ReactFiberThenable';195import type {Flags} from './ReactFiberFlags';196197/**198 * Tag the fiber with an update effect. This turns a Placement into199 * a PlacementAndUpdate.200 */201function markUpdate(workInProgress: Fiber) {202 workInProgress.flags |= Update;203}204205/**206 * Tag the fiber with Cloned in persistent mode to signal that207 * it received an update that requires a clone of the tree above.208 */209function markCloned(workInProgress: Fiber) {210 // $FlowFixMe[constant-condition]211 if (supportsPersistence) {212 workInProgress.flags |= Cloned;213 }214}215216/**217 * In persistent mode, return whether this update needs to clone the subtree.218 */219function doesRequireClone(current: null | Fiber, completedWork: Fiber) {220 const didBailout = current !== null && current.child === completedWork.child;221 if (didBailout) {222 return false;223 }224225 if ((completedWork.flags & ChildDeletion) !== NoFlags) {226 return true;227 }228229 // TODO: If we move the `doesRequireClone` call after `bubbleProperties`230 // then we only have to check the `completedWork.subtreeFlags`.231 let child = completedWork.child;232 while (child !== null) {233 const checkedFlags = Cloned | Visibility | Placement | ChildDeletion;234 if (235 (child.flags & checkedFlags) !== NoFlags ||236 (child.subtreeFlags & checkedFlags) !== NoFlags237 ) {238 return true;239 }240 child = child.sibling;241 }242 return false;243}244245function appendAllChildren(246 parent: Instance,247 workInProgress: Fiber,248 needsVisibilityToggle: boolean,249 isHidden: boolean,250) {251 // $FlowFixMe[constant-condition]252 if (supportsMutation) {253 // We only have the top Fiber that was created but we need recurse down its254 // children to find all the terminal nodes.255 let node = workInProgress.child;256 while (node !== null) {257 if (node.tag === HostComponent || node.tag === HostText) {258 appendInitialChild(parent, node.stateNode);259 } else if (260 node.tag === HostPortal ||261 // $FlowFixMe[constant-condition]262 (supportsSingletons ? node.tag === HostSingleton : false)263 ) {264 // If we have a portal child, then we don't want to traverse265 // down its children. Instead, we'll get insertions from each child in266 // the portal directly.267 // If we have a HostSingleton it will be placed independently268 } else if (node.child !== null) {269 node.child.return = node;270 node = node.child;271 continue;272 }273 if (node === workInProgress) {274 return;275 }276 // $FlowFixMe[incompatible-use] found when upgrading Flow277 while (node.sibling === null) {278 // $FlowFixMe[incompatible-use] found when upgrading Flow279 if (node.return === null || node.return === workInProgress) {280 return;281 }282 node = node.return;283 }284 // $FlowFixMe[incompatible-use] found when upgrading Flow285 node.sibling.return = node.return;286 node = node.sibling;287 }288 // $FlowFixMe[constant-condition]289 } else if (supportsPersistence) {290 // We only have the top Fiber that was created but we need recurse down its291 // children to find all the terminal nodes.292 let node = workInProgress.child;293 while (node !== null) {294 if (node.tag === HostComponent) {295 let instance = node.stateNode;296 if (needsVisibilityToggle && isHidden) {297 // This child is inside a timed out tree. Hide it.298 const props = node.memoizedProps;299 const type = node.type;300 instance = cloneHiddenInstance(instance, type, props);301 }302 appendInitialChild(parent, instance);303 } else if (node.tag === HostText) {304 let instance = node.stateNode;305 if (needsVisibilityToggle && isHidden) {306 // This child is inside a timed out tree. Hide it.307 const text = node.memoizedProps;308 instance = cloneHiddenTextInstance(instance, text);309 }310 appendInitialChild(parent, instance);311 } else if (node.tag === HostPortal) {312 // If we have a portal child, then we don't want to traverse313 // down its children. Instead, we'll get insertions from each child in314 // the portal directly.315 } else if (316 node.tag === OffscreenComponent &&317 node.memoizedState !== null318 ) {319 // The children in this boundary are hidden. Toggle their visibility320 // before appending.321 const child = node.child;322 if (child !== null) {323 child.return = node;324 }325 appendAllChildren(326 parent,327 node,328 /* needsVisibilityToggle */ true,329 /* isHidden */ true,330 );331 } else if (node.child !== null) {332 node.child.return = node;333 node = node.child;334 continue;335 }336 if (node === workInProgress) {337 return;338 }339 // $FlowFixMe[incompatible-use] found when upgrading Flow340 while (node.sibling === null) {341 // $FlowFixMe[incompatible-use] found when upgrading Flow342 if (node.return === null || node.return === workInProgress) {343 return;344 }345 node = node.return;346 }347 // $FlowFixMe[incompatible-use] found when upgrading Flow348 node.sibling.return = node.return;349 node = node.sibling;350 }351 }352}353354// An unfortunate fork of appendAllChildren because we have two different parent types.355function appendAllChildrenToContainer(356 containerChildSet: ChildSet,357 workInProgress: Fiber,358 needsVisibilityToggle: boolean,359 isHidden: boolean,360): boolean {361 // Host components that have their visibility toggled by an OffscreenComponent362 // do not support passChildrenWhenCloningPersistedNodes. To inform the callee363 // about their presence, we track and return if they were added to the364 // child set.365 let hasOffscreenComponentChild = false;366 // $FlowFixMe[constant-condition]367 if (supportsPersistence) {368 // We only have the top Fiber that was created but we need recurse down its369 // children to find all the terminal nodes.370 let node = workInProgress.child;371 while (node !== null) {372 if (node.tag === HostComponent) {373 let instance = node.stateNode;374 if (needsVisibilityToggle && isHidden) {375 // This child is inside a timed out tree. Hide it.376 const props = node.memoizedProps;377 const type = node.type;378 instance = cloneHiddenInstance(instance, type, props);379 }380 appendChildToContainerChildSet(containerChildSet, instance);381 } else if (node.tag === HostText) {382 let instance = node.stateNode;383 if (needsVisibilityToggle && isHidden) {384 // This child is inside a timed out tree. Hide it.385 const text = node.memoizedProps;386 instance = cloneHiddenTextInstance(instance, text);387 }388 appendChildToContainerChildSet(containerChildSet, instance);389 } else if (node.tag === HostPortal) {390 // If we have a portal child, then we don't want to traverse391 // down its children. Instead, we'll get insertions from each child in392 // the portal directly.393 } else if (394 node.tag === OffscreenComponent &&395 node.memoizedState !== null396 ) {397 // The children in this boundary are hidden. Toggle their visibility398 // before appending.399 const child = node.child;400 if (child !== null) {401 child.return = node;402 }403 appendAllChildrenToContainer(404 containerChildSet,405 node,406 /* needsVisibilityToggle */ true,407 /* isHidden */ true,408 );409410 hasOffscreenComponentChild = true;411 } else if (node.child !== null) {412 node.child.return = node;413 node = node.child;414 continue;415 }416 node = node as Fiber;417 if (node === workInProgress) {418 return hasOffscreenComponentChild;419 }420 // $FlowFixMe[incompatible-use] found when upgrading Flow421 while (node.sibling === null) {422 // $FlowFixMe[incompatible-use] found when upgrading Flow423 if (node.return === null || node.return === workInProgress) {424 return hasOffscreenComponentChild;425 }426 node = node.return;427 }428 // $FlowFixMe[incompatible-use] found when upgrading Flow429 node.sibling.return = node.return;430 node = node.sibling;431 }432 }433434 return hasOffscreenComponentChild;435}436437function updateHostContainer(current: null | Fiber, workInProgress: Fiber) {438 // $FlowFixMe[constant-condition]439 if (supportsPersistence) {440 if (doesRequireClone(current, workInProgress)) {441 const portalOrRoot: {442 containerInfo: Container,443 pendingChildren: ChildSet,444 ...445 } = workInProgress.stateNode;446 const container = portalOrRoot.containerInfo;447 const newChildSet = createContainerChildSet();448 // If children might have changed, we have to add them all to the set.449 appendAllChildrenToContainer(450 newChildSet,451 workInProgress,452 /* needsVisibilityToggle */ false,453 /* isHidden */ false,454 );455 portalOrRoot.pendingChildren = newChildSet;456 // Schedule an update on the container to swap out the container.457 markUpdate(workInProgress);458 finalizeContainerChildren(container, newChildSet);459 }460 }461}462463function updateHostComponent(464 current: Fiber,465 workInProgress: Fiber,466 type: Type,467 newProps: Props,468 renderLanes: Lanes,469) {470 // $FlowFixMe[constant-condition]471 if (supportsMutation) {472 // If we have an alternate, that means this is an update and we need to473 // schedule a side-effect to do the updates.474 const oldProps = current.memoizedProps;475 if (oldProps === newProps) {476 // In mutation mode, this is sufficient for a bailout because477 // we won't touch this node even if children changed.478 return;479 }480481 markUpdate(workInProgress);482 // $FlowFixMe[constant-condition]483 } else if (supportsPersistence) {484 const currentInstance = current.stateNode;485 const oldProps = current.memoizedProps;486 // If there are no effects associated with this node, then none of our children had any updates.487 // This guarantees that we can reuse all of them.488 const requiresClone = doesRequireClone(current, workInProgress);489 if (!requiresClone && oldProps === newProps) {490 // No changes, just reuse the existing instance.491 // Note that this might release a previous clone.492 workInProgress.stateNode = currentInstance;493 return;494 }495 const currentHostContext = getHostContext();496497 let newChildSet = null;498 let hasOffscreenComponentChild = false;499 if (requiresClone && passChildrenWhenCloningPersistedNodes) {500 markCloned(workInProgress);501 newChildSet = createContainerChildSet();502 // If children might have changed, we have to add them all to the set.503 hasOffscreenComponentChild = appendAllChildrenToContainer(504 newChildSet,505 workInProgress,506 /* needsVisibilityToggle */ false,507 /* isHidden */ false,508 );509 }510511 const newInstance = cloneInstance(512 currentInstance,513 type,514 oldProps,515 newProps,516 !requiresClone,517 !hasOffscreenComponentChild ? newChildSet : undefined,518 );519 if (newInstance === currentInstance) {520 // No changes, just reuse the existing instance.521 // Note that this might release a previous clone.522 workInProgress.stateNode = currentInstance;523 return;524 } else {525 markCloned(workInProgress);526 }527528 // Certain renderers require commit-time effects for initial mount.529 // (eg DOM renderer supports auto-focus for certain elements).530 // Make sure such renderers get scheduled for later work.531 if (532 finalizeInitialChildren(newInstance, type, newProps, currentHostContext)533 ) {534 markUpdate(workInProgress);535 }536 workInProgress.stateNode = newInstance;537 if (538 requiresClone &&539 (!passChildrenWhenCloningPersistedNodes || hasOffscreenComponentChild)540 ) {541 // If children have changed, we have to add them all to the set.542 appendAllChildren(543 newInstance,544 workInProgress,545 /* needsVisibilityToggle */ false,546 /* isHidden */ false,547 );548 }549 }550}551552// This function must be called at the very end of the complete phase, because553// it might throw to suspend, and if the resource immediately loads, the work554// loop will resume rendering as if the work-in-progress completed. So it must555// fully complete.556// TODO: This should ideally move to begin phase, but currently the instance is557// not created until the complete phase. For our existing use cases, host nodes558// that suspend don't have children, so it doesn't matter. But that might not559// always be true in the future.560function preloadInstanceAndSuspendIfNeeded(561 workInProgress: Fiber,562 type: Type,563 oldProps: null | Props,564 newProps: Props,565 renderLanes: Lanes,566) {567 const maySuspend =568 (enableSuspenseyImages ||569 (workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&570 (oldProps === null571 ? maySuspendCommit(type, newProps)572 : maySuspendCommitOnUpdate(type, oldProps, newProps));573574 if (!maySuspend) {575 // If this flag was set previously, we can remove it. The flag576 // represents whether this particular set of props might ever need to577 // suspend. The safest thing to do is for maySuspendCommit to always578 // return true, but if the renderer is reasonably confident that the579 // underlying resource won't be evicted, it can return false as a580 // performance optimization.581 workInProgress.flags &= ~MaySuspendCommit;582 return;583 }584585 // Mark this fiber with a flag. This gets set on all host instances586 // that might possibly suspend, even if they don't need to suspend587 // currently. We use this when revealing a prerendered tree, because588 // even though the tree has "mounted", its resources might not have589 // loaded yet.590 workInProgress.flags |= MaySuspendCommit;591592 if (593 includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||594 maySuspendCommitInSyncRender(type, newProps)595 ) {596 // preload the instance if necessary. Even if this is an urgent render there597 // could be benefits to preloading early.598 // @TODO we should probably do the preload in begin work599 const isReady = preloadInstance(workInProgress.stateNode, type, newProps);600 if (!isReady) {601 if (shouldRemainOnPreviousScreen()) {602 workInProgress.flags |= ShouldSuspendCommit;603 } else {604 suspendCommit();605 }606 } else {607 // Even if we're ready we suspend the commit and check again in the pre-commit608 // phase if we need to suspend anyway. Such as if it's delayed on decoding or609 // if it was dropped from the cache while rendering due to pressure.610 workInProgress.flags |= ShouldSuspendCommit;611 }612 }613}614615function preloadResourceAndSuspendIfNeeded(616 workInProgress: Fiber,617 resource: Resource,618 type: Type,619 props: Props,620 renderLanes: Lanes,621) {622 // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.623 if (!mayResourceSuspendCommit(resource)) {624 workInProgress.flags &= ~MaySuspendCommit;625 return;626 }627628 workInProgress.flags |= MaySuspendCommit;629630 const isReady = preloadResource(resource);631 if (!isReady) {632 if (shouldRemainOnPreviousScreen()) {633 workInProgress.flags |= ShouldSuspendCommit;634 } else {635 suspendCommit();636 }637 }638}639640function scheduleRetryEffect(641 workInProgress: Fiber,642 retryQueue: RetryQueue | null,643) {644 const wakeables = retryQueue;645 if (wakeables !== null) {646 // Schedule an effect to attach a retry listener to the promise.647 // TODO: Move to passive phase648 workInProgress.flags |= Update;649 }650651 // Check if we need to schedule an immediate retry. This should happen652 // whenever we unwind a suspended tree without fully rendering its siblings;653 // we need to begin the retry so we can start prerendering them.654 //655 // We also use this mechanism for Suspensey Resources (e.g. stylesheets),656 // because those don't actually block the render phase, only the commit phase.657 // So we can start rendering even before the resources are ready.658 if (workInProgress.flags & ScheduleRetry) {659 const retryLane =660 // TODO: This check should probably be moved into claimNextRetryLane661 // I also suspect that we need some further consolidation of offscreen662 // and retry lanes.663 workInProgress.tag !== OffscreenComponent664 ? claimNextRetryLane()665 : OffscreenLane;666 workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);667668 // Track the lanes that have been scheduled for an immediate retry so that669 // we can mark them as suspended upon committing the root.670 markSpawnedRetryLane(retryLane);671 }672}673674function updateHostText(675 current: Fiber,676 workInProgress: Fiber,677 oldText: string,678 newText: string,679) {680 // $FlowFixMe[constant-condition]681 if (supportsMutation) {682 // If the text differs, mark it as an update. All the work in done in commitWork.683 if (oldText !== newText) {684 markUpdate(workInProgress);685 }686 // $FlowFixMe[constant-condition]687 } else if (supportsPersistence) {688 if (oldText !== newText) {689 // If the text content differs, we'll create a new text instance for it.690 const rootContainerInstance = getRootHostContainer();691 const currentHostContext = getHostContext();692 markCloned(workInProgress);693 workInProgress.stateNode = createTextInstance(694 newText,695 rootContainerInstance,696 currentHostContext,697 workInProgress,698 );699 } else {700 workInProgress.stateNode = current.stateNode;701 }702 }703}704705function cutOffTailIfNeeded(706 renderState: SuspenseListRenderState,707 hasRenderedATailFallback: boolean,708) {709 if (getIsHydrating()) {710 // If we're hydrating, we should consume as many items as we can711 // so we don't leave any behind.712 return;713 }714 switch (renderState.tailMode) {715 case 'visible': {716 // Everything should remain as it was.717 break;718 }719 case 'collapsed': {720 // Any insertions at the end of the tail list after this point721 // should be invisible. If there are already mounted boundaries722 // anything before them are not considered for collapsing.723 // Therefore we need to go through the whole tail to find if724 // there are any.725 let tailNode = renderState.tail;726 let lastTailNode = null;727 while (tailNode !== null) {728 if (tailNode.alternate !== null) {729 lastTailNode = tailNode;730 }731 tailNode = tailNode.sibling;732 }733 // Next we're simply going to delete all insertions after the734 // last rendered item.735 if (lastTailNode === null) {736 // All remaining items in the tail are insertions.737 if (!hasRenderedATailFallback && renderState.tail !== null) {738 // We suspended during the head. We want to show at least one739 // row at the tail. So we'll keep on and cut off the rest.740 renderState.tail.sibling = null;741 } else {742 renderState.tail = null;743 }744 } else {745 // Detach the insertion after the last node that was already746 // inserted.747 lastTailNode.sibling = null;748 }749 break;750 }751 // Hidden is now the default.752 case 'hidden':753 default: {754 // Any insertions at the end of the tail list after this point755 // should be invisible. If there are already mounted boundaries756 // anything before them are not considered for collapsing.757 // Therefore we need to go through the whole tail to find if758 // there are any.759 let tailNode = renderState.tail;760 let lastTailNode = null;761 while (tailNode !== null) {762 if (tailNode.alternate !== null) {763 lastTailNode = tailNode;764 }765 tailNode = tailNode.sibling;766 }767 // Next we're simply going to delete all insertions after the768 // last rendered item.769 if (lastTailNode === null) {770 // All remaining items in the tail are insertions.771 renderState.tail = null;772 } else {773 // Detach the insertion after the last node that was already774 // inserted.775 lastTailNode.sibling = null;776 }777 break;778 }779 }780}781782function isOnlyNewMounts(tail: Fiber): boolean {783 let fiber: null | Fiber = tail;784 while (fiber !== null) {785 if (fiber.alternate !== null) {786 return false;787 }788 fiber = fiber.sibling;789 }790 return true;791}792793function bubbleProperties(completedWork: Fiber) {794 const didBailout =795 completedWork.alternate !== null &&796 completedWork.alternate.child === completedWork.child;797798 let newChildLanes: Lanes = NoLanes;799 let subtreeFlags: Flags = NoFlags;800801 if (!didBailout) {802 // Bubble up the earliest expiration time.803 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {804 // In profiling mode, resetChildExpirationTime is also used to reset805 // profiler durations.806 let actualDuration = completedWork.actualDuration;807 let treeBaseDuration = completedWork.selfBaseDuration as any as number;808809 let child = completedWork.child;810 while (child !== null) {811 newChildLanes = mergeLanes(812 newChildLanes,813 mergeLanes(child.lanes, child.childLanes),814 );815816 subtreeFlags |= child.subtreeFlags;817 subtreeFlags |= child.flags;818819 // When a fiber is cloned, its actualDuration is reset to 0. This value will820 // only be updated if work is done on the fiber (i.e. it doesn't bailout).821 // When work is done, it should bubble to the parent's actualDuration. If822 // the fiber has not been cloned though, (meaning no work was done), then823 // this value will reflect the amount of time spent working on a previous824 // render. In that case it should not bubble. We determine whether it was825 // cloned by comparing the child pointer.826 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value827 actualDuration += child.actualDuration;828829 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value830 treeBaseDuration += child.treeBaseDuration;831 child = child.sibling;832 }833834 completedWork.actualDuration = actualDuration;835 completedWork.treeBaseDuration = treeBaseDuration;836 } else {837 let child = completedWork.child;838 while (child !== null) {839 newChildLanes = mergeLanes(840 newChildLanes,841 mergeLanes(child.lanes, child.childLanes),842 );843844 subtreeFlags |= child.subtreeFlags;845 subtreeFlags |= child.flags;846847 // Update the return pointer so the tree is consistent. This is a code848 // smell because it assumes the commit phase is never concurrent with849 // the render phase. Will address during refactor to alternate model.850 child.return = completedWork;851852 child = child.sibling;853 }854 }855856 completedWork.subtreeFlags |= subtreeFlags;857 } else {858 // Bubble up the earliest expiration time.859 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {860 // In profiling mode, resetChildExpirationTime is also used to reset861 // profiler durations.862 let treeBaseDuration = completedWork.selfBaseDuration as any as number;863864 let child = completedWork.child;865 while (child !== null) {866 newChildLanes = mergeLanes(867 newChildLanes,868 mergeLanes(child.lanes, child.childLanes),869 );870871 // "Static" flags share the lifetime of the fiber/hook they belong to,872 // so we should bubble those up even during a bailout. All the other873 // flags have a lifetime only of a single render + commit, so we should874 // ignore them.875 subtreeFlags |= child.subtreeFlags & StaticMask;876 subtreeFlags |= child.flags & StaticMask;877878 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value879 treeBaseDuration += child.treeBaseDuration;880 child = child.sibling;881 }882883 completedWork.treeBaseDuration = treeBaseDuration;884 } else {885 let child = completedWork.child;886 while (child !== null) {887 newChildLanes = mergeLanes(888 newChildLanes,889 mergeLanes(child.lanes, child.childLanes),890 );891892 // "Static" flags share the lifetime of the fiber/hook they belong to,893 // so we should bubble those up even during a bailout. All the other894 // flags have a lifetime only of a single render + commit, so we should895 // ignore them.896 subtreeFlags |= child.subtreeFlags & StaticMask;897 subtreeFlags |= child.flags & StaticMask;898899 // Update the return pointer so the tree is consistent. This is a code900 // smell because it assumes the commit phase is never concurrent with901 // the render phase. Will address during refactor to alternate model.902 child.return = completedWork;903904 child = child.sibling;905 }906 }907908 completedWork.subtreeFlags |= subtreeFlags;909 }910911 completedWork.childLanes = newChildLanes;912913 return didBailout;914}915916function completeDehydratedActivityBoundary(917 current: Fiber | null,918 workInProgress: Fiber,919 nextState: ActivityState | null,920): boolean {921 const wasHydrated = popHydrationState(workInProgress);922923 if (nextState !== null) {924 // We might be inside a hydration state the first time we're picking up this925 // Activity boundary, and also after we've reentered it for further hydration.926 if (current === null) {927 if (!wasHydrated) {928 throw new Error(929 'A dehydrated suspense component was completed without a hydrated node. ' +930 'This is probably a bug in React.',931 );932 }933 prepareToHydrateHostActivityInstance(workInProgress);934 bubbleProperties(workInProgress);935 if (enableProfilerTimer) {936 if ((workInProgress.mode & ProfileMode) !== NoMode) {937 // $FlowFixMe[invalid-compare]938 const isTimedOutSuspense = nextState !== null;939 if (isTimedOutSuspense) {940 // Don't count time spent in a timed out Suspense subtree as part of the base duration.941 const primaryChildFragment = workInProgress.child;942 if (primaryChildFragment !== null) {943 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator944 workInProgress.treeBaseDuration -=945 primaryChildFragment.treeBaseDuration as any as number;946 }947 }948 }949 }950 return false;951 } else {952 emitPendingHydrationWarnings();953 // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration954 // state since we're now exiting out of it. popHydrationState doesn't do that for us.955 resetHydrationState();956 if ((workInProgress.flags & DidCapture) === NoFlags) {957 // This boundary did not suspend so it's now hydrated and unsuspended.958 nextState = workInProgress.memoizedState = null;959 }960 // If nothing suspended, we need to schedule an effect to mark this boundary961 // as having hydrated so events know that they're free to be invoked.962 // It's also a signal to replay events and the suspense callback.963 // If something suspended, schedule an effect to attach retry listeners.964 // So we might as well always mark this.965 workInProgress.flags |= Update;966 bubbleProperties(workInProgress);967 if (enableProfilerTimer) {968 if ((workInProgress.mode & ProfileMode) !== NoMode) {969 const isTimedOutSuspense = nextState !== null;970 if (isTimedOutSuspense) {971 // Don't count time spent in a timed out Suspense subtree as part of the base duration.972 const primaryChildFragment = workInProgress.child;973 if (primaryChildFragment !== null) {974 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator975 workInProgress.treeBaseDuration -=976 primaryChildFragment.treeBaseDuration as any as number;977 }978 }979 }980 }981 return false;982 }983 } else {984 // Successfully completed this tree. If this was a forced client render,985 // there may have been recoverable errors during first hydration986 // attempt. If so, add them to a queue so we can log them in the987 // commit phase. We also add them to prev state so we can get to them988 // from the Suspense Boundary.989 const hydrationErrors = upgradeHydrationErrorsToRecoverable();990 if (current !== null && current.memoizedState !== null) {991 const prevState: ActivityState = current.memoizedState;992 prevState.hydrationErrors = hydrationErrors;993 }994 // Fall through to normal Offscreen path995 return true;996 }997}998999function completeDehydratedSuspenseBoundary(1000 current: Fiber | null,1001 workInProgress: Fiber,1002 nextState: SuspenseState | null,1003): boolean {1004 const wasHydrated = popHydrationState(workInProgress);10051006 if (nextState !== null && nextState.dehydrated !== null) {1007 // We might be inside a hydration state the first time we're picking up this1008 // Suspense boundary, and also after we've reentered it for further hydration.1009 if (current === null) {1010 if (!wasHydrated) {1011 throw new Error(1012 'A dehydrated suspense component was completed without a hydrated node. ' +1013 'This is probably a bug in React.',1014 );1015 }1016 prepareToHydrateHostSuspenseInstance(workInProgress);1017 bubbleProperties(workInProgress);1018 if (enableProfilerTimer) {1019 if ((workInProgress.mode & ProfileMode) !== NoMode) {1020 // $FlowFixMe[invalid-compare]1021 const isTimedOutSuspense = nextState !== null;1022 if (isTimedOutSuspense) {1023 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1024 const primaryChildFragment = workInProgress.child;1025 if (primaryChildFragment !== null) {1026 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1027 workInProgress.treeBaseDuration -=1028 primaryChildFragment.treeBaseDuration as any as number;1029 }1030 }1031 }1032 }1033 return false;1034 } else {1035 emitPendingHydrationWarnings();1036 // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration1037 // state since we're now exiting out of it. popHydrationState doesn't do that for us.1038 resetHydrationState();1039 if ((workInProgress.flags & DidCapture) === NoFlags) {1040 // This boundary did not suspend so it's now hydrated and unsuspended.1041 nextState = workInProgress.memoizedState = null;1042 }1043 // If nothing suspended, we need to schedule an effect to mark this boundary1044 // as having hydrated so events know that they're free to be invoked.1045 // It's also a signal to replay events and the suspense callback.1046 // If something suspended, schedule an effect to attach retry listeners.1047 // So we might as well always mark this.1048 workInProgress.flags |= Update;1049 bubbleProperties(workInProgress);1050 if (enableProfilerTimer) {1051 if ((workInProgress.mode & ProfileMode) !== NoMode) {1052 const isTimedOutSuspense = nextState !== null;1053 if (isTimedOutSuspense) {1054 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1055 const primaryChildFragment = workInProgress.child;1056 if (primaryChildFragment !== null) {1057 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1058 workInProgress.treeBaseDuration -=1059 primaryChildFragment.treeBaseDuration as any as number;1060 }1061 }1062 }1063 }1064 return false;1065 }1066 } else {1067 // Successfully completed this tree. If this was a forced client render,1068 // there may have been recoverable errors during first hydration1069 // attempt. If so, add them to a queue so we can log them in the1070 // commit phase. We also add them to prev state so we can get to them1071 // from the Suspense Boundary.1072 const hydrationErrors = upgradeHydrationErrorsToRecoverable();1073 if (current !== null && current.memoizedState !== null) {1074 const prevState: SuspenseState = current.memoizedState;1075 prevState.hydrationErrors = hydrationErrors;1076 }1077 // Fall through to normal Suspense path1078 return true;1079 }1080}10811082function completeWork(1083 current: Fiber | null,1084 workInProgress: Fiber,1085 renderLanes: Lanes,1086): Fiber | null {1087 const newProps = workInProgress.pendingProps;1088 // Note: This intentionally doesn't check if we're hydrating because comparing1089 // to the current tree provider fiber is just as fast and less error-prone.1090 // Ideally we would have a special version of the work loop only1091 // for hydration.1092 popTreeContext(workInProgress);1093 switch (workInProgress.tag) {1094 case IncompleteFunctionComponent: {1095 if (disableLegacyMode) {1096 break;1097 }1098 // Fallthrough1099 }1100 case LazyComponent:1101 case SimpleMemoComponent:1102 case FunctionComponent:1103 case ForwardRef:1104 case Fragment:1105 case Mode:1106 case Profiler:1107 case ContextConsumer:1108 case MemoComponent:1109 bubbleProperties(workInProgress);1110 return null;1111 case ClassComponent: {1112 const Component = workInProgress.type;1113 if (isLegacyContextProvider(Component)) {1114 popLegacyContext(workInProgress);1115 }1116 bubbleProperties(workInProgress);1117 return null;1118 }1119 case HostRoot: {1120 const fiberRoot = workInProgress.stateNode as FiberRoot;11211122 if (enableTransitionTracing) {1123 const transitions = getWorkInProgressTransitions();1124 // We set the Passive flag here because if there are new transitions,1125 // we will need to schedule callbacks and process the transitions,1126 // which we do in the passive phase1127 if (transitions !== null) {1128 workInProgress.flags |= Passive;1129 }1130 }11311132 let previousCache: Cache | null = null;1133 if (current !== null) {1134 previousCache = current.memoizedState.cache;1135 }1136 const cache: Cache = workInProgress.memoizedState.cache;1137 if (cache !== previousCache) {1138 // Run passive effects to retain/release the cache.1139 workInProgress.flags |= Passive;1140 }1141 popCacheProvider(workInProgress, cache);11421143 if (enableTransitionTracing) {1144 popRootMarkerInstance(workInProgress);1145 }11461147 popRootTransition(workInProgress, fiberRoot, renderLanes);1148 popHostContainer(workInProgress);1149 popTopLevelLegacyContextObject(workInProgress);1150 if (fiberRoot.pendingContext) {1151 fiberRoot.context = fiberRoot.pendingContext;1152 fiberRoot.pendingContext = null;1153 }1154 if (current === null || current.child === null) {1155 // If we hydrated, pop so that we can delete any remaining children1156 // that weren't hydrated.1157 const wasHydrated = popHydrationState(workInProgress);1158 if (wasHydrated) {1159 emitPendingHydrationWarnings();1160 // If we hydrated, then we'll need to schedule an update for1161 // the commit side-effects on the root.1162 markUpdate(workInProgress);1163 } else {1164 if (current !== null) {1165 const prevState: RootState = current.memoizedState;1166 if (1167 // Check if this is a client root1168 !prevState.isDehydrated ||1169 // Check if we reverted to client rendering (e.g. due to an error)1170 (workInProgress.flags & ForceClientRender) !== NoFlags1171 ) {1172 // Schedule an effect to clear this container at the start of the1173 // next commit. This handles the case of React rendering into a1174 // container with previous children. It's also safe to do for1175 // updates too, because current.child would only be null if the1176 // previous render was null (so the container would already1177 // be empty).1178 workInProgress.flags |= Snapshot;11791180 // If this was a forced client render, there may have been1181 // recoverable errors during first hydration attempt. If so, add1182 // them to a queue so we can log them in the commit phase.1183 upgradeHydrationErrorsToRecoverable();1184 }1185 }1186 }1187 }1188 updateHostContainer(current, workInProgress);1189 bubbleProperties(workInProgress);1190 if (enableTransitionTracing) {1191 if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {1192 // If any of our suspense children toggle visibility, this means that1193 // the pending boundaries array needs to be updated, which we only1194 // do in the passive phase.1195 workInProgress.flags |= Passive;1196 }1197 }1198 return null;1199 }1200 case HostHoistable: {1201 // $FlowFixMe[constant-condition]1202 if (supportsResources) {1203 // The branching here is more complicated than you might expect because1204 // a HostHoistable sometimes corresponds to a Resource and sometimes1205 // corresponds to an Instance. It can also switch during an update.12061207 const type = workInProgress.type;1208 const nextResource: Resource | null = workInProgress.memoizedState;1209 if (current === null) {1210 // We are mounting and must Update this Hoistable in this commit1211 // @TODO refactor this block to create the instance here in complete1212 // phase if we are not hydrating.1213 markUpdate(workInProgress);1214 if (nextResource !== null) {1215 // This is a Hoistable Resource12161217 // This must come at the very end of the complete phase.1218 bubbleProperties(workInProgress);1219 preloadResourceAndSuspendIfNeeded(1220 workInProgress,1221 nextResource,1222 type,1223 newProps,1224 renderLanes,1225 );1226 return null;1227 } else {1228 // This is a Hoistable Instance1229 // This must come at the very end of the complete phase.1230 bubbleProperties(workInProgress);1231 preloadInstanceAndSuspendIfNeeded(1232 workInProgress,1233 type,1234 null,1235 newProps,1236 renderLanes,1237 );1238 return null;1239 }1240 } else {1241 // This is an update.1242 if (nextResource) {1243 // This is a Resource1244 if (nextResource !== current.memoizedState) {1245 // we have a new Resource. we need to update1246 markUpdate(workInProgress);1247 // This must come at the very end of the complete phase.1248 bubbleProperties(workInProgress);1249 // This must come at the very end of the complete phase, because it might1250 // throw to suspend, and if the resource immediately loads, the work loop1251 // will resume rendering as if the work-in-progress completed. So it must1252 // fully complete.1253 preloadResourceAndSuspendIfNeeded(1254 workInProgress,1255 nextResource,1256 type,1257 newProps,1258 renderLanes,1259 );1260 return null;1261 } else {1262 // This must come at the very end of the complete phase.1263 bubbleProperties(workInProgress);1264 workInProgress.flags &= ~MaySuspendCommit;1265 return null;1266 }1267 } else {1268 const oldProps = current.memoizedProps;1269 // This is an Instance1270 // We may have props to update on the Hoistable instance.1271 // $FlowFixMe[constant-condition]1272 if (supportsMutation) {1273 if (oldProps !== newProps) {1274 markUpdate(workInProgress);1275 }1276 } else {1277 // We use the updateHostComponent path because it produces1278 // the update queue we need for Hoistables.1279 updateHostComponent(1280 current,1281 workInProgress,1282 type,1283 newProps,1284 renderLanes,1285 );1286 }1287 // This must come at the very end of the complete phase.1288 bubbleProperties(workInProgress);1289 preloadInstanceAndSuspendIfNeeded(1290 workInProgress,1291 type,1292 oldProps,1293 newProps,1294 renderLanes,1295 );1296 return null;1297 }1298 }1299 }1300 // Fall through1301 }1302 case HostSingleton: {1303 // $FlowFixMe[constant-condition]1304 if (supportsSingletons) {1305 popHostContext(workInProgress);1306 const rootContainerInstance = getRootHostContainer();1307 const type = workInProgress.type;1308 if (current !== null && workInProgress.stateNode != null) {1309 // $FlowFixMe[constant-condition]1310 if (supportsMutation) {1311 const oldProps = current.memoizedProps;1312 if (oldProps !== newProps) {1313 markUpdate(workInProgress);1314 }1315 } else {1316 updateHostComponent(1317 current,1318 workInProgress,1319 type,1320 newProps,1321 renderLanes,1322 );1323 }1324 } else {1325 if (!newProps) {1326 if (workInProgress.stateNode === null) {1327 throw new Error(1328 'We must have new props for new mounts. This error is likely ' +1329 'caused by a bug in React. Please file an issue.',1330 );1331 }13321333 // This can happen when we abort work.1334 bubbleProperties(workInProgress);1335 if (enableViewTransition) {1336 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1337 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1338 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1339 }1340 return null;1341 }13421343 const currentHostContext = getHostContext();1344 const wasHydrated = popHydrationState(workInProgress);1345 let instance: Instance;1346 if (wasHydrated) {1347 // We ignore the boolean indicating there is an updateQueue because1348 // it is used only to set text children and HostSingletons do not1349 // use them.1350 prepareToHydrateHostInstance(workInProgress, currentHostContext);1351 instance = workInProgress.stateNode;1352 } else {1353 instance = resolveSingletonInstance(1354 type,1355 newProps,1356 rootContainerInstance,1357 currentHostContext,1358 true,1359 );1360 workInProgress.stateNode = instance;1361 markUpdate(workInProgress);1362 }1363 }1364 bubbleProperties(workInProgress);1365 if (enableViewTransition) {1366 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1367 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1368 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1369 }1370 return null;1371 }1372 // Fall through1373 }1374 case HostComponent: {1375 popHostContext(workInProgress);1376 const type = workInProgress.type;1377 if (current !== null && workInProgress.stateNode != null) {1378 updateHostComponent(1379 current,1380 workInProgress,1381 type,1382 newProps,1383 renderLanes,1384 );1385 } else {1386 if (!newProps) {1387 if (workInProgress.stateNode === null) {1388 throw new Error(1389 'We must have new props for new mounts. This error is likely ' +1390 'caused by a bug in React. Please file an issue.',1391 );1392 }13931394 // This can happen when we abort work.1395 bubbleProperties(workInProgress);1396 if (enableViewTransition) {1397 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1398 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1399 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1400 }1401 return null;1402 }14031404 const currentHostContext = getHostContext();1405 // TODO: Move createInstance to beginWork and keep it on a context1406 // "stack" as the parent. Then append children as we go in beginWork1407 // or completeWork depending on whether we want to add them top->down or1408 // bottom->up. Top->down is faster in IE11.1409 const wasHydrated = popHydrationState(workInProgress);1410 if (wasHydrated) {1411 // TODO: Move this and createInstance step into the beginPhase1412 // to consolidate.1413 prepareToHydrateHostInstance(workInProgress, currentHostContext);1414 if (1415 finalizeHydratedChildren(1416 workInProgress.stateNode,1417 type,1418 newProps,1419 currentHostContext,1420 )1421 ) {1422 workInProgress.flags |= Hydrate;1423 }1424 } else {1425 const rootContainerInstance = getRootHostContainer();1426 const instance = createInstance(1427 type,1428 newProps,1429 rootContainerInstance,1430 currentHostContext,1431 workInProgress,1432 );1433 // TODO: For persistent renderers, we should pass children as part1434 // of the initial instance creation1435 markCloned(workInProgress);1436 appendAllChildren(instance, workInProgress, false, false);1437 workInProgress.stateNode = instance;14381439 // Certain renderers require commit-time effects for initial mount.1440 // (eg DOM renderer supports auto-focus for certain elements).1441 // Make sure such renderers get scheduled for later work.1442 if (1443 finalizeInitialChildren(1444 instance,1445 type,1446 newProps,1447 currentHostContext,1448 )1449 ) {1450 markUpdate(workInProgress);1451 }1452 }1453 }1454 bubbleProperties(workInProgress);1455 if (enableViewTransition) {1456 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1457 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1458 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1459 }14601461 // This must come at the very end of the complete phase, because it might1462 // throw to suspend, and if the resource immediately loads, the work loop1463 // will resume rendering as if the work-in-progress completed. So it must1464 // fully complete.1465 preloadInstanceAndSuspendIfNeeded(1466 workInProgress,1467 workInProgress.type,1468 current === null ? null : current.memoizedProps,1469 workInProgress.pendingProps,1470 renderLanes,1471 );1472 return null;1473 }1474 case HostText: {1475 const newText = newProps;1476 if (current && workInProgress.stateNode != null) {1477 const oldText = current.memoizedProps;1478 // If we have an alternate, that means this is an update and we need1479 // to schedule a side-effect to do the updates.1480 updateHostText(current, workInProgress, oldText, newText);1481 } else {1482 if (typeof newText !== 'string') {1483 if (workInProgress.stateNode === null) {1484 throw new Error(1485 'We must have new props for new mounts. This error is likely ' +1486 'caused by a bug in React. Please file an issue.',1487 );1488 }1489 // This can happen when we abort work.1490 }1491 const rootContainerInstance = getRootHostContainer();1492 const currentHostContext = getHostContext();1493 const wasHydrated = popHydrationState(workInProgress);1494 if (wasHydrated) {1495 prepareToHydrateHostTextInstance(workInProgress);1496 } else {1497 markCloned(workInProgress);1498 workInProgress.stateNode = createTextInstance(1499 newText,1500 rootContainerInstance,1501 currentHostContext,1502 workInProgress,1503 );1504 }1505 }1506 bubbleProperties(workInProgress);1507 return null;1508 }1509 case ActivityComponent: {1510 const nextState: null | ActivityState = workInProgress.memoizedState;15111512 if (current === null || current.memoizedState !== null) {1513 const fallthroughToNormalOffscreenPath =1514 completeDehydratedActivityBoundary(1515 current,1516 workInProgress,1517 nextState,1518 );1519 if (!fallthroughToNormalOffscreenPath) {1520 if (workInProgress.flags & ForceClientRender) {1521 popSuspenseHandler(workInProgress);1522 // Special case. There were remaining unhydrated nodes. We treat1523 // this as a mismatch. Revert to client rendering.1524 return workInProgress;1525 } else {1526 popSuspenseHandler(workInProgress);1527 // Did not finish hydrating, either because this is the initial1528 // render or because something suspended.1529 return null;1530 }1531 }15321533 if ((workInProgress.flags & DidCapture) !== NoFlags) {1534 // We called retryActivityComponentWithoutHydrating and tried client rendering1535 // but now we suspended again. We should never arrive here because we should1536 // not have pushed a suspense handler during that second pass and it should1537 // instead have suspended above.1538 throw new Error(1539 'Client rendering an Activity suspended it again. This is a bug in React.',1540 );1541 }15421543 // Continue with the normal Activity path.1544 }15451546 bubbleProperties(workInProgress);1547 return null;1548 }1549 case SuspenseComponent: {1550 const nextState: null | SuspenseState = workInProgress.memoizedState;15511552 // Special path for dehydrated boundaries. We may eventually move this1553 // to its own fiber type so that we can add other kinds of hydration1554 // boundaries that aren't associated with a Suspense tree. In anticipation1555 // of such a refactor, all the hydration logic is contained in1556 // this branch.1557 if (1558 current === null ||1559 (current.memoizedState !== null &&1560 current.memoizedState.dehydrated !== null)1561 ) {1562 const fallthroughToNormalSuspensePath =1563 completeDehydratedSuspenseBoundary(1564 current,1565 workInProgress,1566 nextState,1567 );1568 if (!fallthroughToNormalSuspensePath) {1569 if (workInProgress.flags & ForceClientRender) {1570 popSuspenseHandler(workInProgress);1571 // Special case. There were remaining unhydrated nodes. We treat1572 // this as a mismatch. Revert to client rendering.1573 return workInProgress;1574 } else {1575 popSuspenseHandler(workInProgress);1576 // Did not finish hydrating, either because this is the initial1577 // render or because something suspended.1578 return null;1579 }1580 }15811582 // Continue with the normal Suspense path.1583 }15841585 popSuspenseHandler(workInProgress);15861587 if ((workInProgress.flags & DidCapture) !== NoFlags) {1588 // Something suspended. Re-render with the fallback children.1589 workInProgress.lanes = renderLanes;1590 if (1591 enableProfilerTimer &&1592 (workInProgress.mode & ProfileMode) !== NoMode1593 ) {1594 transferActualDuration(workInProgress);1595 }1596 // Don't bubble properties in this case.1597 return workInProgress;1598 }15991600 const nextDidTimeout = nextState !== null;1601 const prevDidTimeout =1602 current !== null &&1603 (current.memoizedState as null | SuspenseState) !== null;16041605 if (nextDidTimeout) {1606 const offscreenFiber: Fiber = workInProgress.child as any;1607 let previousCache: Cache | null = null;1608 if (1609 offscreenFiber.alternate !== null &&1610 offscreenFiber.alternate.memoizedState !== null &&1611 offscreenFiber.alternate.memoizedState.cachePool !== null1612 ) {1613 previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;1614 }1615 let cache: Cache | null = null;1616 if (1617 offscreenFiber.memoizedState !== null &&1618 offscreenFiber.memoizedState.cachePool !== null1619 ) {1620 cache = offscreenFiber.memoizedState.cachePool.pool;1621 }1622 if (cache !== previousCache) {1623 // Run passive effects to retain/release the cache.1624 offscreenFiber.flags |= Passive;1625 }1626 }16271628 // If the suspended state of the boundary changes, we need to schedule1629 // a passive effect, which is when we process the transitions1630 if (nextDidTimeout !== prevDidTimeout) {1631 if (enableTransitionTracing) {1632 const offscreenFiber: Fiber = workInProgress.child as any;1633 offscreenFiber.flags |= Passive;1634 }16351636 // If the suspended state of the boundary changes, we need to schedule1637 // an effect to toggle the subtree's visibility. When we switch from1638 // fallback -> primary, the inner Offscreen fiber schedules this effect1639 // as part of its normal complete phase. But when we switch from1640 // primary -> fallback, the inner Offscreen fiber does not have a complete1641 // phase. So we need to schedule its effect here.1642 //1643 // We also use this flag to connect/disconnect the effects, but the same1644 // logic applies: when re-connecting, the Offscreen fiber's complete1645 // phase will handle scheduling the effect. It's only when the fallback1646 // is active that we have to do anything special.1647 if (nextDidTimeout) {1648 const offscreenFiber: Fiber = workInProgress.child as any;1649 offscreenFiber.flags |= Visibility;1650 }1651 }16521653 const retryQueue: RetryQueue | null = workInProgress.updateQueue as any;1654 scheduleRetryEffect(workInProgress, retryQueue);16551656 if (1657 enableSuspenseCallback &&1658 workInProgress.updateQueue !== null &&1659 workInProgress.memoizedProps.suspenseCallback != null1660 ) {1661 // Always notify the callback1662 // TODO: Move to passive phase1663 workInProgress.flags |= Update;1664 }1665 bubbleProperties(workInProgress);1666 if (enableProfilerTimer) {1667 if ((workInProgress.mode & ProfileMode) !== NoMode) {1668 if (nextDidTimeout) {1669 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1670 const primaryChildFragment = workInProgress.child;1671 if (primaryChildFragment !== null) {1672 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1673 workInProgress.treeBaseDuration -=1674 primaryChildFragment.treeBaseDuration as any as number;1675 }1676 }1677 }1678 }1679 return null;1680 }1681 case HostPortal:1682 popHostContainer(workInProgress);1683 updateHostContainer(current, workInProgress);1684 if (current === null) {1685 preparePortalMount(workInProgress.stateNode.containerInfo);1686 }1687 workInProgress.flags |= PortalStatic;1688 bubbleProperties(workInProgress);1689 return null;1690 case ContextProvider:1691 // Pop provider fiber1692 const context: ReactContext<any> = workInProgress.type;1693 popProvider(context, workInProgress);1694 bubbleProperties(workInProgress);1695 return null;1696 case IncompleteClassComponent: {1697 if (disableLegacyMode) {1698 break;1699 }1700 // Same as class component case. I put it down here so that the tags are1701 // sequential to ensure this switch is compiled to a jump table.1702 const Component = workInProgress.type;1703 if (isLegacyContextProvider(Component)) {1704 popLegacyContext(workInProgress);1705 }1706 bubbleProperties(workInProgress);1707 return null;1708 }1709 case SuspenseListComponent: {1710 popSuspenseListContext(workInProgress);17111712 const renderState: null | SuspenseListRenderState =1713 workInProgress.memoizedState;17141715 if (renderState === null) {1716 // We're running in the default, "independent" mode.1717 // We don't do anything in this mode.1718 bubbleProperties(workInProgress);1719 return null;1720 }17211722 let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;17231724 const renderedTail = renderState.rendering;1725 if (renderedTail === null) {1726 // We just rendered the head.1727 if (!didSuspendAlready) {1728 // This is the first pass. We need to figure out if anything is still1729 // suspended in the rendered set.17301731 // If new content unsuspended, but there's still some content that1732 // didn't. Then we need to do a second pass that forces everything1733 // to keep showing their fallbacks.17341735 // We might be suspended if something in this render pass suspended, or1736 // something in the previous committed pass suspended. Otherwise,1737 // there's no chance so we can skip the expensive call to1738 // findFirstSuspended.1739 const cannotBeSuspended =1740 renderHasNotSuspendedYet() &&1741 (current === null || (current.flags & DidCapture) === NoFlags);1742 if (!cannotBeSuspended) {1743 let row = workInProgress.child;1744 while (row !== null) {1745 const suspended = findFirstSuspended(row);1746 if (suspended !== null) {1747 didSuspendAlready = true;1748 workInProgress.flags |= DidCapture;1749 cutOffTailIfNeeded(renderState, false);17501751 // If this is a newly suspended tree, it might not get committed as1752 // part of the second pass. In that case nothing will subscribe to1753 // its thenables. Instead, we'll transfer its thenables to the1754 // SuspenseList so that it can retry if they resolve.1755 // There might be multiple of these in the list but since we're1756 // going to wait for all of them anyway, it doesn't really matter1757 // which ones gets to ping. In theory we could get clever and keep1758 // track of how many dependencies remain but it gets tricky because1759 // in the meantime, we can add/remove/change items and dependencies.1760 // We might bail out of the loop before finding any but that1761 // doesn't matter since that means that the other boundaries that1762 // we did find already has their listeners attached.1763 const retryQueue: RetryQueue | null =1764 suspended.updateQueue as any;1765 workInProgress.updateQueue = retryQueue;1766 scheduleRetryEffect(workInProgress, retryQueue);17671768 // Rerender the whole list, but this time, we'll force fallbacks1769 // to stay in place.1770 // Reset the effect flags before doing the second pass since that's now invalid.1771 // Reset the child fibers to their original state.1772 workInProgress.subtreeFlags = NoFlags;1773 resetChildFibers(workInProgress, renderLanes);17741775 // Set up the Suspense List Context to force suspense and1776 // immediately rerender the children.1777 pushSuspenseListContext(1778 workInProgress,1779 setShallowSuspenseListContext(1780 suspenseStackCursor.current,1781 ForceSuspenseFallback,1782 ),1783 );1784 if (getIsHydrating()) {1785 // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1786 pushTreeFork(workInProgress, renderState.treeForkCount);1787 }1788 // Don't bubble properties in this case.1789 return workInProgress.child;1790 }1791 row = row.sibling;1792 }1793 }17941795 if (renderState.tail !== null && now() > getRenderTargetTime()) {1796 // We have already passed our CPU deadline but we still have rows1797 // left in the tail. We'll just give up further attempts to render1798 // the main content and only render fallbacks.1799 workInProgress.flags |= DidCapture;1800 didSuspendAlready = true;18011802 cutOffTailIfNeeded(renderState, false);18031804 // Since nothing actually suspended, there will nothing to ping this1805 // to get it started back up to attempt the next item. While in terms1806 // of priority this work has the same priority as this current render,1807 // it's not part of the same transition once the transition has1808 // committed. If it's sync, we still want to yield so that it can be1809 // painted. Conceptually, this is really the same as pinging.1810 // We can use any RetryLane even if it's the one currently rendering1811 // since we're leaving it behind on this node.1812 workInProgress.lanes = SomeRetryLane;1813 }1814 } else {1815 cutOffTailIfNeeded(renderState, false);1816 }1817 // Next we're going to render the tail.1818 } else {1819 // Append the rendered row to the child list.1820 if (!didSuspendAlready) {1821 const suspended = findFirstSuspended(renderedTail);1822 if (suspended !== null) {1823 workInProgress.flags |= DidCapture;1824 didSuspendAlready = true;18251826 // Ensure we transfer the update queue to the parent so that it doesn't1827 // get lost if this row ends up dropped during a second pass.1828 const retryQueue: RetryQueue | null = suspended.updateQueue as any;1829 workInProgress.updateQueue = retryQueue;1830 scheduleRetryEffect(workInProgress, retryQueue);18311832 cutOffTailIfNeeded(renderState, true);1833 // This might have been modified.1834 if (1835 renderState.tail === null &&1836 renderState.tailMode !== 'collapsed' &&1837 renderState.tailMode !== 'visible' &&1838 !renderedTail.alternate &&1839 !getIsHydrating() // We don't cut it if we're hydrating.1840 ) {1841 // We're done.1842 bubbleProperties(workInProgress);1843 return null;1844 }1845 } else if (1846 // The time it took to render last row is greater than the remaining1847 // time we have to render. So rendering one more row would likely1848 // exceed it.1849 now() * 2 - renderState.renderingStartTime >1850 getRenderTargetTime() &&1851 renderLanes !== OffscreenLane1852 ) {1853 // We have now passed our CPU deadline and we'll just give up further1854 // attempts to render the main content and only render fallbacks.1855 // The assumption is that this is usually faster.1856 workInProgress.flags |= DidCapture;1857 didSuspendAlready = true;18581859 cutOffTailIfNeeded(renderState, false);18601861 // Since nothing actually suspended, there will nothing to ping this1862 // to get it started back up to attempt the next item. While in terms1863 // of priority this work has the same priority as this current render,1864 // it's not part of the same transition once the transition has1865 // committed. If it's sync, we still want to yield so that it can be1866 // painted. Conceptually, this is really the same as pinging.1867 // We can use any RetryLane even if it's the one currently rendering1868 // since we're leaving it behind on this node.1869 workInProgress.lanes = SomeRetryLane;1870 }1871 }1872 if (renderState.isBackwards) {1873 // Append to the beginning of the list.1874 renderedTail.sibling = workInProgress.child;1875 workInProgress.child = renderedTail;1876 } else {1877 const previousSibling = renderState.last;1878 if (previousSibling !== null) {1879 previousSibling.sibling = renderedTail;1880 } else {1881 workInProgress.child = renderedTail;1882 }1883 renderState.last = renderedTail;1884 }1885 }18861887 if (renderState.tail !== null) {1888 // We still have tail rows to render.1889 // Pop a row.1890 // TODO: Consider storing the first of the new mount tail in the state so1891 // that we don't have to recompute this for every row in the list.1892 const next = renderState.tail;1893 const onlyNewMounts = isOnlyNewMounts(next);1894 renderState.rendering = next;1895 renderState.tail = next.sibling;1896 renderState.renderingStartTime = now();1897 next.sibling = null;18981899 // Restore the context.1900 // TODO: We can probably just avoid popping it instead and only1901 // setting it the first time we go from not suspended to suspended.1902 let suspenseContext = suspenseStackCursor.current;1903 if (didSuspendAlready) {1904 suspenseContext = setShallowSuspenseListContext(1905 suspenseContext,1906 ForceSuspenseFallback,1907 );1908 } else {1909 suspenseContext =1910 setDefaultShallowSuspenseListContext(suspenseContext);1911 }1912 if (1913 renderState.tailMode === 'visible' ||1914 renderState.tailMode === 'collapsed' ||1915 !onlyNewMounts ||1916 // TODO: While hydrating, we still let it suspend the parent. Tail mode hidden has broken1917 // hydration anyway right now but this preserves the previous semantics out of caution.1918 // Once proper hydration is implemented, this special case should be removed as it should1919 // never be needed.1920 getIsHydrating()1921 ) {1922 pushSuspenseListContext(workInProgress, suspenseContext);1923 } else {1924 // If we are rendering in 'hidden' (default) tail mode, then we if we suspend in the1925 // tail itself, we can delete it rather than suspend the parent. So we act as a catch in that1926 // case. For 'collapsed' we need to render at least one in suspended state, after which we'll1927 // have cut off the rest to never attempt it so it never hits this case.1928 // If this is an updated node, we cannot delete it from the tail so it's effectively visible.1929 // As a consequence, if it resuspends it actually suspends the parent by taking the other path.1930 pushSuspenseListCatch(workInProgress, suspenseContext);1931 }1932 // Do a pass over the next row.1933 if (getIsHydrating()) {1934 // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1935 pushTreeFork(workInProgress, renderState.treeForkCount);1936 }1937 // Don't bubble properties in this case.1938 return next;1939 }1940 bubbleProperties(workInProgress);1941 return null;1942 }1943 case ScopeComponent: {1944 if (enableScopeAPI) {1945 if (current === null) {1946 const scopeInstance: ReactScopeInstance = createScopeInstance();1947 workInProgress.stateNode = scopeInstance;1948 prepareScopeUpdate(scopeInstance, workInProgress);1949 if (workInProgress.ref !== null) {1950 // Scope components always do work in the commit phase if there's a1951 // ref attached.1952 markUpdate(workInProgress);1953 }1954 } else {1955 if (workInProgress.ref !== null) {1956 // Scope components always do work in the commit phase if there's a1957 // ref attached.1958 markUpdate(workInProgress);1959 }1960 }1961 bubbleProperties(workInProgress);1962 return null;1963 }1964 break;1965 }1966 case OffscreenComponent:1967 case LegacyHiddenComponent: {1968 popSuspenseHandler(workInProgress);1969 popHiddenContext(workInProgress);1970 const nextState: OffscreenState | null = workInProgress.memoizedState;1971 const nextIsHidden = nextState !== null;19721973 // Schedule a Visibility effect if the visibility has changed1974 if (enableLegacyHidden && workInProgress.tag === LegacyHiddenComponent) {1975 // LegacyHidden doesn't do any hiding — it only pre-renders.1976 } else {1977 if (current !== null) {1978 const prevState: OffscreenState | null = current.memoizedState;1979 const prevIsHidden = prevState !== null;1980 if (prevIsHidden !== nextIsHidden) {1981 workInProgress.flags |= Visibility;1982 }1983 } else {1984 // On initial mount, we only need a Visibility effect if the tree1985 // is hidden.1986 if (nextIsHidden) {1987 workInProgress.flags |= Visibility;1988 }1989 }1990 }19911992 if (1993 !nextIsHidden ||1994 (!disableLegacyMode &&1995 (workInProgress.mode & ConcurrentMode) === NoMode)1996 ) {1997 bubbleProperties(workInProgress);1998 } else {1999 // Don't bubble properties for hidden children unless we're rendering2000 // at offscreen priority.
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.