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 enableSuspenseyImages,44} from 'shared/ReactFeatureFlags';4546import {now} from './Scheduler';4748import {49 FunctionComponent,50 ClassComponent,51 HostRoot,52 HostComponent,53 HostHoistable,54 HostSingleton,55 HostText,56 HostPortal,57 ContextProvider,58 ContextConsumer,59 ForwardRef,60 Fragment,61 Mode,62 Profiler,63 SuspenseComponent,64 SuspenseListComponent,65 MemoComponent,66 SimpleMemoComponent,67 LazyComponent,68 IncompleteClassComponent,69 IncompleteFunctionComponent,70 ScopeComponent,71 OffscreenComponent,72 LegacyHiddenComponent,73 CacheComponent,74 TracingMarkerComponent,75 Throw,76 ViewTransitionComponent,77 ActivityComponent,78} from './ReactWorkTags';79import {80 NoMode,81 ConcurrentMode,82 ProfileMode,83 SuspenseyImagesMode,84} from './ReactTypeOfMode';85import {86 Placement,87 Update,88 Visibility,89 NoFlags,90 DidCapture,91 Snapshot,92 ChildDeletion,93 StaticMask,94 Passive,95 ForceClientRender,96 MaySuspendCommit,97 ScheduleRetry,98 ShouldSuspendCommit,99 Cloned,100 ViewTransitionStatic,101 Hydrate,102 PortalStatic,103} from './ReactFiberFlags';104105import {106 createInstance,107 createTextInstance,108 resolveSingletonInstance,109 appendInitialChild,110 finalizeInitialChildren,111 finalizeHydratedChildren,112 supportsMutation,113 supportsPersistence,114 supportsResources,115 supportsSingletons,116 cloneInstance,117 cloneHiddenInstance,118 cloneHiddenTextInstance,119 createContainerChildSet,120 appendChildToContainerChildSet,121 finalizeContainerChildren,122 preparePortalMount,123 prepareScopeUpdate,124 maySuspendCommit,125 maySuspendCommitOnUpdate,126 maySuspendCommitInSyncRender,127 mayResourceSuspendCommit,128 preloadInstance,129 preloadResource,130} from './ReactFiberConfig';131import {132 getRootHostContainer,133 popHostContext,134 getHostContext,135 popHostContainer,136} from './ReactFiberHostContext';137import {138 suspenseStackCursor,139 popSuspenseListContext,140 popSuspenseHandler,141 pushSuspenseListContext,142 pushSuspenseListCatch,143 setShallowSuspenseListContext,144 ForceSuspenseFallback,145 setDefaultShallowSuspenseListContext,146} from './ReactFiberSuspenseContext';147import {popHiddenContext} from './ReactFiberHiddenContext';148import {findFirstSuspended} from './ReactFiberSuspenseComponent';149import {150 isContextProvider as isLegacyContextProvider,151 popContext as popLegacyContext,152 popTopLevelContextObject as popTopLevelLegacyContextObject,153} from './ReactFiberLegacyContext';154import {popProvider} from './ReactFiberNewContext';155import {156 prepareToHydrateHostInstance,157 prepareToHydrateHostTextInstance,158 prepareToHydrateHostActivityInstance,159 prepareToHydrateHostSuspenseInstance,160 popHydrationState,161 resetHydrationState,162 getIsHydrating,163 upgradeHydrationErrorsToRecoverable,164 emitPendingHydrationWarnings,165} from './ReactFiberHydrationContext';166import {167 renderHasNotSuspendedYet,168 getRenderTargetTime,169 getWorkInProgressTransitions,170 shouldRemainOnPreviousScreen,171 markSpawnedRetryLane,172} from './ReactFiberWorkLoop';173import {174 OffscreenLane,175 SomeRetryLane,176 NoLanes,177 includesSomeLane,178 mergeLanes,179 claimNextRetryLane,180 includesOnlySuspenseyCommitEligibleLanes,181} from './ReactFiberLane';182import {resetChildFibers} from './ReactChildFiber';183import {createScopeInstance} from './ReactFiberScope';184import {transferActualDuration} from './ReactProfilerTimer';185import {popCacheProvider} from './ReactFiberCacheComponent';186import {popTreeContext, pushTreeFork} from './ReactFiberTreeContext';187import {popRootTransition, popTransition} from './ReactFiberTransition';188import {189 popMarkerInstance,190 popRootMarkerInstance,191} from './ReactFiberTracingMarkerComponent';192import {suspendCommit} from './ReactFiberThenable';193import type {Flags} from './ReactFiberFlags';194195/**196 * Tag the fiber with an update effect. This turns a Placement into197 * a PlacementAndUpdate.198 */199function markUpdate(workInProgress: Fiber) {200 workInProgress.flags |= Update;201}202203/**204 * Tag the fiber with Cloned in persistent mode to signal that205 * it received an update that requires a clone of the tree above.206 */207function markCloned(workInProgress: Fiber) {208 // $FlowFixMe[constant-condition]209 if (supportsPersistence) {210 workInProgress.flags |= Cloned;211 }212}213214/**215 * In persistent mode, return whether this update needs to clone the subtree.216 */217function doesRequireClone(current: null | Fiber, completedWork: Fiber) {218 const didBailout = current !== null && current.child === completedWork.child;219 if (didBailout) {220 return false;221 }222223 if ((completedWork.flags & ChildDeletion) !== NoFlags) {224 return true;225 }226227 // TODO: If we move the `doesRequireClone` call after `bubbleProperties`228 // then we only have to check the `completedWork.subtreeFlags`.229 let child = completedWork.child;230 while (child !== null) {231 const checkedFlags = Cloned | Visibility | Placement | ChildDeletion;232 if (233 (child.flags & checkedFlags) !== NoFlags ||234 (child.subtreeFlags & checkedFlags) !== NoFlags235 ) {236 return true;237 }238 child = child.sibling;239 }240 return false;241}242243function appendAllChildren(244 parent: Instance,245 workInProgress: Fiber,246 needsVisibilityToggle: boolean,247 isHidden: boolean,248) {249 // $FlowFixMe[constant-condition]250 if (supportsMutation) {251 // We only have the top Fiber that was created but we need recurse down its252 // children to find all the terminal nodes.253 let node = workInProgress.child;254 while (node !== null) {255 if (node.tag === HostComponent || node.tag === HostText) {256 appendInitialChild(parent, node.stateNode);257 } else if (258 node.tag === HostPortal ||259 // $FlowFixMe[constant-condition]260 (supportsSingletons ? node.tag === HostSingleton : false)261 ) {262 // If we have a portal child, then we don't want to traverse263 // down its children. Instead, we'll get insertions from each child in264 // the portal directly.265 // If we have a HostSingleton it will be placed independently266 } else if (node.child !== null) {267 node.child.return = node;268 node = node.child;269 continue;270 }271 if (node === workInProgress) {272 return;273 }274 // $FlowFixMe[incompatible-use] found when upgrading Flow275 while (node.sibling === null) {276 // $FlowFixMe[incompatible-use] found when upgrading Flow277 if (node.return === null || node.return === workInProgress) {278 return;279 }280 node = node.return;281 }282 // $FlowFixMe[incompatible-use] found when upgrading Flow283 node.sibling.return = node.return;284 node = node.sibling;285 }286 // $FlowFixMe[constant-condition]287 } else if (supportsPersistence) {288 // We only have the top Fiber that was created but we need recurse down its289 // children to find all the terminal nodes.290 let node = workInProgress.child;291 while (node !== null) {292 if (node.tag === HostComponent) {293 let instance = node.stateNode;294 if (needsVisibilityToggle && isHidden) {295 // This child is inside a timed out tree. Hide it.296 const props = node.memoizedProps;297 const type = node.type;298 instance = cloneHiddenInstance(instance, type, props);299 }300 appendInitialChild(parent, instance);301 } else if (node.tag === HostText) {302 let instance = node.stateNode;303 if (needsVisibilityToggle && isHidden) {304 // This child is inside a timed out tree. Hide it.305 const text = node.memoizedProps;306 instance = cloneHiddenTextInstance(instance, text);307 }308 appendInitialChild(parent, instance);309 } else if (node.tag === HostPortal) {310 // If we have a portal child, then we don't want to traverse311 // down its children. Instead, we'll get insertions from each child in312 // the portal directly.313 } else if (314 node.tag === OffscreenComponent &&315 node.memoizedState !== null316 ) {317 // The children in this boundary are hidden. Toggle their visibility318 // before appending.319 const child = node.child;320 if (child !== null) {321 child.return = node;322 }323 appendAllChildren(324 parent,325 node,326 /* needsVisibilityToggle */ true,327 /* isHidden */ true,328 );329 } else if (node.child !== null) {330 node.child.return = node;331 node = node.child;332 continue;333 }334 if (node === workInProgress) {335 return;336 }337 // $FlowFixMe[incompatible-use] found when upgrading Flow338 while (node.sibling === null) {339 // $FlowFixMe[incompatible-use] found when upgrading Flow340 if (node.return === null || node.return === workInProgress) {341 return;342 }343 node = node.return;344 }345 // $FlowFixMe[incompatible-use] found when upgrading Flow346 node.sibling.return = node.return;347 node = node.sibling;348 }349 }350}351352// An unfortunate fork of appendAllChildren because we have two different parent types.353function appendAllChildrenToContainer(354 containerChildSet: ChildSet,355 workInProgress: Fiber,356 needsVisibilityToggle: boolean,357 isHidden: boolean,358): boolean {359 // Host components that have their visibility toggled by an OffscreenComponent360 // do not support passChildrenWhenCloningPersistedNodes. To inform the callee361 // about their presence, we track and return if they were added to the362 // child set.363 let hasOffscreenComponentChild = false;364 // $FlowFixMe[constant-condition]365 if (supportsPersistence) {366 // We only have the top Fiber that was created but we need recurse down its367 // children to find all the terminal nodes.368 let node = workInProgress.child;369 while (node !== null) {370 if (node.tag === HostComponent) {371 let instance = node.stateNode;372 if (needsVisibilityToggle && isHidden) {373 // This child is inside a timed out tree. Hide it.374 const props = node.memoizedProps;375 const type = node.type;376 instance = cloneHiddenInstance(instance, type, props);377 }378 appendChildToContainerChildSet(containerChildSet, instance);379 } else if (node.tag === HostText) {380 let instance = node.stateNode;381 if (needsVisibilityToggle && isHidden) {382 // This child is inside a timed out tree. Hide it.383 const text = node.memoizedProps;384 instance = cloneHiddenTextInstance(instance, text);385 }386 appendChildToContainerChildSet(containerChildSet, instance);387 } else if (node.tag === HostPortal) {388 // If we have a portal child, then we don't want to traverse389 // down its children. Instead, we'll get insertions from each child in390 // the portal directly.391 } else if (392 node.tag === OffscreenComponent &&393 node.memoizedState !== null394 ) {395 // The children in this boundary are hidden. Toggle their visibility396 // before appending.397 const child = node.child;398 if (child !== null) {399 child.return = node;400 }401 appendAllChildrenToContainer(402 containerChildSet,403 node,404 /* needsVisibilityToggle */ true,405 /* isHidden */ true,406 );407408 hasOffscreenComponentChild = true;409 } else if (node.child !== null) {410 node.child.return = node;411 node = node.child;412 continue;413 }414 node = node as Fiber;415 if (node === workInProgress) {416 return hasOffscreenComponentChild;417 }418 // $FlowFixMe[incompatible-use] found when upgrading Flow419 while (node.sibling === null) {420 // $FlowFixMe[incompatible-use] found when upgrading Flow421 if (node.return === null || node.return === workInProgress) {422 return hasOffscreenComponentChild;423 }424 node = node.return;425 }426 // $FlowFixMe[incompatible-use] found when upgrading Flow427 node.sibling.return = node.return;428 node = node.sibling;429 }430 }431432 return hasOffscreenComponentChild;433}434435function updateHostContainer(current: null | Fiber, workInProgress: Fiber) {436 // $FlowFixMe[constant-condition]437 if (supportsPersistence) {438 if (doesRequireClone(current, workInProgress)) {439 const portalOrRoot: {440 containerInfo: Container,441 pendingChildren: ChildSet,442 ...443 } = workInProgress.stateNode;444 const container = portalOrRoot.containerInfo;445 const newChildSet = createContainerChildSet();446 // If children might have changed, we have to add them all to the set.447 appendAllChildrenToContainer(448 newChildSet,449 workInProgress,450 /* needsVisibilityToggle */ false,451 /* isHidden */ false,452 );453 portalOrRoot.pendingChildren = newChildSet;454 // Schedule an update on the container to swap out the container.455 markUpdate(workInProgress);456 finalizeContainerChildren(container, newChildSet);457 }458 }459}460461function updateHostComponent(462 current: Fiber,463 workInProgress: Fiber,464 type: Type,465 newProps: Props,466 renderLanes: Lanes,467) {468 // $FlowFixMe[constant-condition]469 if (supportsMutation) {470 // If we have an alternate, that means this is an update and we need to471 // schedule a side-effect to do the updates.472 const oldProps = current.memoizedProps;473 if (oldProps === newProps) {474 // In mutation mode, this is sufficient for a bailout because475 // we won't touch this node even if children changed.476 return;477 }478479 markUpdate(workInProgress);480 // $FlowFixMe[constant-condition]481 } else if (supportsPersistence) {482 const currentInstance = current.stateNode;483 const oldProps = current.memoizedProps;484 // If there are no effects associated with this node, then none of our children had any updates.485 // This guarantees that we can reuse all of them.486 const requiresClone = doesRequireClone(current, workInProgress);487 if (!requiresClone && oldProps === newProps) {488 // No changes, just reuse the existing instance.489 // Note that this might release a previous clone.490 workInProgress.stateNode = currentInstance;491 return;492 }493 const currentHostContext = getHostContext();494495 let newChildSet = null;496 let hasOffscreenComponentChild = false;497 if (requiresClone && passChildrenWhenCloningPersistedNodes) {498 markCloned(workInProgress);499 newChildSet = createContainerChildSet();500 // If children might have changed, we have to add them all to the set.501 hasOffscreenComponentChild = appendAllChildrenToContainer(502 newChildSet,503 workInProgress,504 /* needsVisibilityToggle */ false,505 /* isHidden */ false,506 );507 }508509 const newInstance = cloneInstance(510 currentInstance,511 type,512 oldProps,513 newProps,514 !requiresClone,515 !hasOffscreenComponentChild ? newChildSet : undefined,516 );517 if (newInstance === currentInstance) {518 // No changes, just reuse the existing instance.519 // Note that this might release a previous clone.520 workInProgress.stateNode = currentInstance;521 return;522 } else {523 markCloned(workInProgress);524 }525526 // Certain renderers require commit-time effects for initial mount.527 // (eg DOM renderer supports auto-focus for certain elements).528 // Make sure such renderers get scheduled for later work.529 if (530 finalizeInitialChildren(newInstance, type, newProps, currentHostContext)531 ) {532 markUpdate(workInProgress);533 }534 workInProgress.stateNode = newInstance;535 if (536 requiresClone &&537 (!passChildrenWhenCloningPersistedNodes || hasOffscreenComponentChild)538 ) {539 // If children have changed, we have to add them all to the set.540 appendAllChildren(541 newInstance,542 workInProgress,543 /* needsVisibilityToggle */ false,544 /* isHidden */ false,545 );546 }547 }548}549550// This function must be called at the very end of the complete phase, because551// it might throw to suspend, and if the resource immediately loads, the work552// loop will resume rendering as if the work-in-progress completed. So it must553// fully complete.554// TODO: This should ideally move to begin phase, but currently the instance is555// not created until the complete phase. For our existing use cases, host nodes556// that suspend don't have children, so it doesn't matter. But that might not557// always be true in the future.558function preloadInstanceAndSuspendIfNeeded(559 workInProgress: Fiber,560 type: Type,561 oldProps: null | Props,562 newProps: Props,563 renderLanes: Lanes,564) {565 const maySuspend =566 (enableSuspenseyImages ||567 (workInProgress.mode & SuspenseyImagesMode) !== NoMode) &&568 (oldProps === null569 ? maySuspendCommit(type, newProps)570 : maySuspendCommitOnUpdate(type, oldProps, newProps));571572 if (!maySuspend) {573 // If this flag was set previously, we can remove it. The flag574 // represents whether this particular set of props might ever need to575 // suspend. The safest thing to do is for maySuspendCommit to always576 // return true, but if the renderer is reasonably confident that the577 // underlying resource won't be evicted, it can return false as a578 // performance optimization.579 workInProgress.flags &= ~MaySuspendCommit;580 return;581 }582583 // Mark this fiber with a flag. This gets set on all host instances584 // that might possibly suspend, even if they don't need to suspend585 // currently. We use this when revealing a prerendered tree, because586 // even though the tree has "mounted", its resources might not have587 // loaded yet.588 workInProgress.flags |= MaySuspendCommit;589590 if (591 includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||592 maySuspendCommitInSyncRender(type, newProps)593 ) {594 // preload the instance if necessary. Even if this is an urgent render there595 // could be benefits to preloading early.596 // @TODO we should probably do the preload in begin work597 const isReady = preloadInstance(workInProgress.stateNode, type, newProps);598 if (!isReady) {599 if (shouldRemainOnPreviousScreen()) {600 workInProgress.flags |= ShouldSuspendCommit;601 } else {602 suspendCommit();603 }604 } else {605 // Even if we're ready we suspend the commit and check again in the pre-commit606 // phase if we need to suspend anyway. Such as if it's delayed on decoding or607 // if it was dropped from the cache while rendering due to pressure.608 workInProgress.flags |= ShouldSuspendCommit;609 }610 }611}612613function preloadResourceAndSuspendIfNeeded(614 workInProgress: Fiber,615 resource: Resource,616 type: Type,617 props: Props,618 renderLanes: Lanes,619) {620 // This is a fork of preloadInstanceAndSuspendIfNeeded, but for resources.621 if (!mayResourceSuspendCommit(resource)) {622 workInProgress.flags &= ~MaySuspendCommit;623 return;624 }625626 workInProgress.flags |= MaySuspendCommit;627628 const isReady = preloadResource(resource);629 if (!isReady) {630 if (shouldRemainOnPreviousScreen()) {631 workInProgress.flags |= ShouldSuspendCommit;632 } else {633 suspendCommit();634 }635 }636}637638function scheduleRetryEffect(639 workInProgress: Fiber,640 retryQueue: RetryQueue | null,641) {642 const wakeables = retryQueue;643 if (wakeables !== null) {644 // Schedule an effect to attach a retry listener to the promise.645 // TODO: Move to passive phase646 workInProgress.flags |= Update;647 }648649 // Check if we need to schedule an immediate retry. This should happen650 // whenever we unwind a suspended tree without fully rendering its siblings;651 // we need to begin the retry so we can start prerendering them.652 //653 // We also use this mechanism for Suspensey Resources (e.g. stylesheets),654 // because those don't actually block the render phase, only the commit phase.655 // So we can start rendering even before the resources are ready.656 if (workInProgress.flags & ScheduleRetry) {657 const retryLane =658 // TODO: This check should probably be moved into claimNextRetryLane659 // I also suspect that we need some further consolidation of offscreen660 // and retry lanes.661 workInProgress.tag !== OffscreenComponent662 ? claimNextRetryLane()663 : OffscreenLane;664 workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);665666 // Track the lanes that have been scheduled for an immediate retry so that667 // we can mark them as suspended upon committing the root.668 markSpawnedRetryLane(retryLane);669 }670}671672function updateHostText(673 current: Fiber,674 workInProgress: Fiber,675 oldText: string,676 newText: string,677) {678 // $FlowFixMe[constant-condition]679 if (supportsMutation) {680 // If the text differs, mark it as an update. All the work in done in commitWork.681 if (oldText !== newText) {682 markUpdate(workInProgress);683 }684 // $FlowFixMe[constant-condition]685 } else if (supportsPersistence) {686 if (oldText !== newText) {687 // If the text content differs, we'll create a new text instance for it.688 const rootContainerInstance = getRootHostContainer();689 const currentHostContext = getHostContext();690 markCloned(workInProgress);691 workInProgress.stateNode = createTextInstance(692 newText,693 rootContainerInstance,694 currentHostContext,695 workInProgress,696 );697 } else {698 workInProgress.stateNode = current.stateNode;699 }700 }701}702703function cutOffTailIfNeeded(704 renderState: SuspenseListRenderState,705 hasRenderedATailFallback: boolean,706) {707 if (getIsHydrating()) {708 // If we're hydrating, we should consume as many items as we can709 // so we don't leave any behind.710 return;711 }712 switch (renderState.tailMode) {713 case 'visible': {714 // Everything should remain as it was.715 break;716 }717 case 'collapsed': {718 // Any insertions at the end of the tail list after this point719 // should be invisible. If there are already mounted boundaries720 // anything before them are not considered for collapsing.721 // Therefore we need to go through the whole tail to find if722 // there are any.723 let tailNode = renderState.tail;724 let lastTailNode = null;725 while (tailNode !== null) {726 if (tailNode.alternate !== null) {727 lastTailNode = tailNode;728 }729 tailNode = tailNode.sibling;730 }731 // Next we're simply going to delete all insertions after the732 // last rendered item.733 if (lastTailNode === null) {734 // All remaining items in the tail are insertions.735 if (!hasRenderedATailFallback && renderState.tail !== null) {736 // We suspended during the head. We want to show at least one737 // row at the tail. So we'll keep on and cut off the rest.738 renderState.tail.sibling = null;739 } else {740 renderState.tail = null;741 }742 } else {743 // Detach the insertion after the last node that was already744 // inserted.745 lastTailNode.sibling = null;746 }747 break;748 }749 // Hidden is now the default.750 case 'hidden':751 default: {752 // Any insertions at the end of the tail list after this point753 // should be invisible. If there are already mounted boundaries754 // anything before them are not considered for collapsing.755 // Therefore we need to go through the whole tail to find if756 // there are any.757 let tailNode = renderState.tail;758 let lastTailNode = null;759 while (tailNode !== null) {760 if (tailNode.alternate !== null) {761 lastTailNode = tailNode;762 }763 tailNode = tailNode.sibling;764 }765 // Next we're simply going to delete all insertions after the766 // last rendered item.767 if (lastTailNode === null) {768 // All remaining items in the tail are insertions.769 renderState.tail = null;770 } else {771 // Detach the insertion after the last node that was already772 // inserted.773 lastTailNode.sibling = null;774 }775 break;776 }777 }778}779780function isOnlyNewMounts(tail: Fiber): boolean {781 let fiber: null | Fiber = tail;782 while (fiber !== null) {783 if (fiber.alternate !== null) {784 return false;785 }786 fiber = fiber.sibling;787 }788 return true;789}790791function bubbleProperties(completedWork: Fiber) {792 const didBailout =793 completedWork.alternate !== null &&794 completedWork.alternate.child === completedWork.child;795796 let newChildLanes: Lanes = NoLanes;797 let subtreeFlags: Flags = NoFlags;798799 if (!didBailout) {800 // Bubble up the earliest expiration time.801 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {802 // In profiling mode, resetChildExpirationTime is also used to reset803 // profiler durations.804 let actualDuration = completedWork.actualDuration;805 let treeBaseDuration = completedWork.selfBaseDuration as any as number;806807 let child = completedWork.child;808 while (child !== null) {809 newChildLanes = mergeLanes(810 newChildLanes,811 mergeLanes(child.lanes, child.childLanes),812 );813814 subtreeFlags |= child.subtreeFlags;815 subtreeFlags |= child.flags;816817 // When a fiber is cloned, its actualDuration is reset to 0. This value will818 // only be updated if work is done on the fiber (i.e. it doesn't bailout).819 // When work is done, it should bubble to the parent's actualDuration. If820 // the fiber has not been cloned though, (meaning no work was done), then821 // this value will reflect the amount of time spent working on a previous822 // render. In that case it should not bubble. We determine whether it was823 // cloned by comparing the child pointer.824 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value825 actualDuration += child.actualDuration;826827 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value828 treeBaseDuration += child.treeBaseDuration;829 child = child.sibling;830 }831832 completedWork.actualDuration = actualDuration;833 completedWork.treeBaseDuration = treeBaseDuration;834 } else {835 let child = completedWork.child;836 while (child !== null) {837 newChildLanes = mergeLanes(838 newChildLanes,839 mergeLanes(child.lanes, child.childLanes),840 );841842 subtreeFlags |= child.subtreeFlags;843 subtreeFlags |= child.flags;844845 // Update the return pointer so the tree is consistent. This is a code846 // smell because it assumes the commit phase is never concurrent with847 // the render phase. Will address during refactor to alternate model.848 child.return = completedWork;849850 child = child.sibling;851 }852 }853854 completedWork.subtreeFlags |= subtreeFlags;855 } else {856 // Bubble up the earliest expiration time.857 if (enableProfilerTimer && (completedWork.mode & ProfileMode) !== NoMode) {858 // In profiling mode, resetChildExpirationTime is also used to reset859 // profiler durations.860 let treeBaseDuration = completedWork.selfBaseDuration as any as number;861862 let child = completedWork.child;863 while (child !== null) {864 newChildLanes = mergeLanes(865 newChildLanes,866 mergeLanes(child.lanes, child.childLanes),867 );868869 // "Static" flags share the lifetime of the fiber/hook they belong to,870 // so we should bubble those up even during a bailout. All the other871 // flags have a lifetime only of a single render + commit, so we should872 // ignore them.873 subtreeFlags |= child.subtreeFlags & StaticMask;874 subtreeFlags |= child.flags & StaticMask;875876 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value877 treeBaseDuration += child.treeBaseDuration;878 child = child.sibling;879 }880881 completedWork.treeBaseDuration = treeBaseDuration;882 } else {883 let child = completedWork.child;884 while (child !== null) {885 newChildLanes = mergeLanes(886 newChildLanes,887 mergeLanes(child.lanes, child.childLanes),888 );889890 // "Static" flags share the lifetime of the fiber/hook they belong to,891 // so we should bubble those up even during a bailout. All the other892 // flags have a lifetime only of a single render + commit, so we should893 // ignore them.894 subtreeFlags |= child.subtreeFlags & StaticMask;895 subtreeFlags |= child.flags & StaticMask;896897 // Update the return pointer so the tree is consistent. This is a code898 // smell because it assumes the commit phase is never concurrent with899 // the render phase. Will address during refactor to alternate model.900 child.return = completedWork;901902 child = child.sibling;903 }904 }905906 completedWork.subtreeFlags |= subtreeFlags;907 }908909 completedWork.childLanes = newChildLanes;910911 return didBailout;912}913914function completeDehydratedActivityBoundary(915 current: Fiber | null,916 workInProgress: Fiber,917 nextState: ActivityState | null,918): boolean {919 const wasHydrated = popHydrationState(workInProgress);920921 if (nextState !== null) {922 // We might be inside a hydration state the first time we're picking up this923 // Activity boundary, and also after we've reentered it for further hydration.924 if (current === null) {925 if (!wasHydrated) {926 throw new Error(927 'A dehydrated suspense component was completed without a hydrated node. ' +928 'This is probably a bug in React.',929 );930 }931 prepareToHydrateHostActivityInstance(workInProgress);932 bubbleProperties(workInProgress);933 if (enableProfilerTimer) {934 if ((workInProgress.mode & ProfileMode) !== NoMode) {935 // $FlowFixMe[invalid-compare]936 const isTimedOutSuspense = nextState !== null;937 if (isTimedOutSuspense) {938 // Don't count time spent in a timed out Suspense subtree as part of the base duration.939 const primaryChildFragment = workInProgress.child;940 if (primaryChildFragment !== null) {941 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator942 workInProgress.treeBaseDuration -=943 primaryChildFragment.treeBaseDuration as any as number;944 }945 }946 }947 }948 return false;949 } else {950 emitPendingHydrationWarnings();951 // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration952 // state since we're now exiting out of it. popHydrationState doesn't do that for us.953 resetHydrationState();954 if ((workInProgress.flags & DidCapture) === NoFlags) {955 // This boundary did not suspend so it's now hydrated and unsuspended.956 nextState = workInProgress.memoizedState = null;957 }958 // If nothing suspended, we need to schedule an effect to mark this boundary959 // as having hydrated so events know that they're free to be invoked.960 // It's also a signal to replay events and the suspense callback.961 // If something suspended, schedule an effect to attach retry listeners.962 // So we might as well always mark this.963 workInProgress.flags |= Update;964 bubbleProperties(workInProgress);965 if (enableProfilerTimer) {966 if ((workInProgress.mode & ProfileMode) !== NoMode) {967 const isTimedOutSuspense = nextState !== null;968 if (isTimedOutSuspense) {969 // Don't count time spent in a timed out Suspense subtree as part of the base duration.970 const primaryChildFragment = workInProgress.child;971 if (primaryChildFragment !== null) {972 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator973 workInProgress.treeBaseDuration -=974 primaryChildFragment.treeBaseDuration as any as number;975 }976 }977 }978 }979 return false;980 }981 } else {982 // Successfully completed this tree. If this was a forced client render,983 // there may have been recoverable errors during first hydration984 // attempt. If so, add them to a queue so we can log them in the985 // commit phase. We also add them to prev state so we can get to them986 // from the Suspense Boundary.987 const hydrationErrors = upgradeHydrationErrorsToRecoverable();988 if (current !== null && current.memoizedState !== null) {989 const prevState: ActivityState = current.memoizedState;990 prevState.hydrationErrors = hydrationErrors;991 }992 // Fall through to normal Offscreen path993 return true;994 }995}996997function completeDehydratedSuspenseBoundary(998 current: Fiber | null,999 workInProgress: Fiber,1000 nextState: SuspenseState | null,1001): boolean {1002 const wasHydrated = popHydrationState(workInProgress);10031004 if (nextState !== null && nextState.dehydrated !== null) {1005 // We might be inside a hydration state the first time we're picking up this1006 // Suspense boundary, and also after we've reentered it for further hydration.1007 if (current === null) {1008 if (!wasHydrated) {1009 throw new Error(1010 'A dehydrated suspense component was completed without a hydrated node. ' +1011 'This is probably a bug in React.',1012 );1013 }1014 prepareToHydrateHostSuspenseInstance(workInProgress);1015 bubbleProperties(workInProgress);1016 if (enableProfilerTimer) {1017 if ((workInProgress.mode & ProfileMode) !== NoMode) {1018 // $FlowFixMe[invalid-compare]1019 const isTimedOutSuspense = nextState !== null;1020 if (isTimedOutSuspense) {1021 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1022 const primaryChildFragment = workInProgress.child;1023 if (primaryChildFragment !== null) {1024 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1025 workInProgress.treeBaseDuration -=1026 primaryChildFragment.treeBaseDuration as any as number;1027 }1028 }1029 }1030 }1031 return false;1032 } else {1033 emitPendingHydrationWarnings();1034 // We might have reentered this boundary to hydrate it. If so, we need to reset the hydration1035 // state since we're now exiting out of it. popHydrationState doesn't do that for us.1036 resetHydrationState();1037 if ((workInProgress.flags & DidCapture) === NoFlags) {1038 // This boundary did not suspend so it's now hydrated and unsuspended.1039 nextState = workInProgress.memoizedState = null;1040 }1041 // If nothing suspended, we need to schedule an effect to mark this boundary1042 // as having hydrated so events know that they're free to be invoked.1043 // It's also a signal to replay events and the suspense callback.1044 // If something suspended, schedule an effect to attach retry listeners.1045 // So we might as well always mark this.1046 workInProgress.flags |= Update;1047 bubbleProperties(workInProgress);1048 if (enableProfilerTimer) {1049 if ((workInProgress.mode & ProfileMode) !== NoMode) {1050 const isTimedOutSuspense = nextState !== null;1051 if (isTimedOutSuspense) {1052 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1053 const primaryChildFragment = workInProgress.child;1054 if (primaryChildFragment !== null) {1055 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1056 workInProgress.treeBaseDuration -=1057 primaryChildFragment.treeBaseDuration as any as number;1058 }1059 }1060 }1061 }1062 return false;1063 }1064 } else {1065 // Successfully completed this tree. If this was a forced client render,1066 // there may have been recoverable errors during first hydration1067 // attempt. If so, add them to a queue so we can log them in the1068 // commit phase. We also add them to prev state so we can get to them1069 // from the Suspense Boundary.1070 const hydrationErrors = upgradeHydrationErrorsToRecoverable();1071 if (current !== null && current.memoizedState !== null) {1072 const prevState: SuspenseState = current.memoizedState;1073 prevState.hydrationErrors = hydrationErrors;1074 }1075 // Fall through to normal Suspense path1076 return true;1077 }1078}10791080function completeWork(1081 current: Fiber | null,1082 workInProgress: Fiber,1083 renderLanes: Lanes,1084): Fiber | null {1085 const newProps = workInProgress.pendingProps;1086 // Note: This intentionally doesn't check if we're hydrating because comparing1087 // to the current tree provider fiber is just as fast and less error-prone.1088 // Ideally we would have a special version of the work loop only1089 // for hydration.1090 popTreeContext(workInProgress);1091 switch (workInProgress.tag) {1092 case IncompleteFunctionComponent: {1093 if (disableLegacyMode) {1094 break;1095 }1096 // Fallthrough1097 }1098 case LazyComponent:1099 case SimpleMemoComponent:1100 case FunctionComponent:1101 case ForwardRef:1102 case Fragment:1103 case Mode:1104 case Profiler:1105 case ContextConsumer:1106 case MemoComponent:1107 bubbleProperties(workInProgress);1108 return null;1109 case ClassComponent: {1110 const Component = workInProgress.type;1111 if (isLegacyContextProvider(Component)) {1112 popLegacyContext(workInProgress);1113 }1114 bubbleProperties(workInProgress);1115 return null;1116 }1117 case HostRoot: {1118 const fiberRoot = workInProgress.stateNode as FiberRoot;11191120 if (enableTransitionTracing) {1121 const transitions = getWorkInProgressTransitions();1122 // We set the Passive flag here because if there are new transitions,1123 // we will need to schedule callbacks and process the transitions,1124 // which we do in the passive phase1125 if (transitions !== null) {1126 workInProgress.flags |= Passive;1127 }1128 }11291130 let previousCache: Cache | null = null;1131 if (current !== null) {1132 previousCache = current.memoizedState.cache;1133 }1134 const cache: Cache = workInProgress.memoizedState.cache;1135 if (cache !== previousCache) {1136 // Run passive effects to retain/release the cache.1137 workInProgress.flags |= Passive;1138 }1139 popCacheProvider(workInProgress, cache);11401141 if (enableTransitionTracing) {1142 popRootMarkerInstance(workInProgress);1143 }11441145 popRootTransition(workInProgress, fiberRoot, renderLanes);1146 popHostContainer(workInProgress);1147 popTopLevelLegacyContextObject(workInProgress);1148 if (fiberRoot.pendingContext) {1149 fiberRoot.context = fiberRoot.pendingContext;1150 fiberRoot.pendingContext = null;1151 }1152 if (current === null || current.child === null) {1153 // If we hydrated, pop so that we can delete any remaining children1154 // that weren't hydrated.1155 const wasHydrated = popHydrationState(workInProgress);1156 if (wasHydrated) {1157 emitPendingHydrationWarnings();1158 // If we hydrated, then we'll need to schedule an update for1159 // the commit side-effects on the root.1160 markUpdate(workInProgress);1161 } else {1162 if (current !== null) {1163 const prevState: RootState = current.memoizedState;1164 if (1165 // Check if this is a client root1166 !prevState.isDehydrated ||1167 // Check if we reverted to client rendering (e.g. due to an error)1168 (workInProgress.flags & ForceClientRender) !== NoFlags1169 ) {1170 // Schedule an effect to clear this container at the start of the1171 // next commit. This handles the case of React rendering into a1172 // container with previous children. It's also safe to do for1173 // updates too, because current.child would only be null if the1174 // previous render was null (so the container would already1175 // be empty).1176 workInProgress.flags |= Snapshot;11771178 // If this was a forced client render, there may have been1179 // recoverable errors during first hydration attempt. If so, add1180 // them to a queue so we can log them in the commit phase.1181 upgradeHydrationErrorsToRecoverable();1182 }1183 }1184 }1185 }1186 updateHostContainer(current, workInProgress);1187 bubbleProperties(workInProgress);1188 if (enableTransitionTracing) {1189 if ((workInProgress.subtreeFlags & Visibility) !== NoFlags) {1190 // If any of our suspense children toggle visibility, this means that1191 // the pending boundaries array needs to be updated, which we only1192 // do in the passive phase.1193 workInProgress.flags |= Passive;1194 }1195 }1196 return null;1197 }1198 case HostHoistable: {1199 // $FlowFixMe[constant-condition]1200 if (supportsResources) {1201 // The branching here is more complicated than you might expect because1202 // a HostHoistable sometimes corresponds to a Resource and sometimes1203 // corresponds to an Instance. It can also switch during an update.12041205 const type = workInProgress.type;1206 const nextResource: Resource | null = workInProgress.memoizedState;1207 if (current === null) {1208 // We are mounting and must Update this Hoistable in this commit1209 // @TODO refactor this block to create the instance here in complete1210 // phase if we are not hydrating.1211 markUpdate(workInProgress);1212 if (nextResource !== null) {1213 // This is a Hoistable Resource12141215 // This must come at the very end of the complete phase.1216 bubbleProperties(workInProgress);1217 preloadResourceAndSuspendIfNeeded(1218 workInProgress,1219 nextResource,1220 type,1221 newProps,1222 renderLanes,1223 );1224 return null;1225 } else {1226 // This is a Hoistable Instance1227 // This must come at the very end of the complete phase.1228 bubbleProperties(workInProgress);1229 preloadInstanceAndSuspendIfNeeded(1230 workInProgress,1231 type,1232 null,1233 newProps,1234 renderLanes,1235 );1236 return null;1237 }1238 } else {1239 // This is an update.1240 if (nextResource) {1241 // This is a Resource1242 if (nextResource !== current.memoizedState) {1243 // we have a new Resource. we need to update1244 markUpdate(workInProgress);1245 // This must come at the very end of the complete phase.1246 bubbleProperties(workInProgress);1247 // This must come at the very end of the complete phase, because it might1248 // throw to suspend, and if the resource immediately loads, the work loop1249 // will resume rendering as if the work-in-progress completed. So it must1250 // fully complete.1251 preloadResourceAndSuspendIfNeeded(1252 workInProgress,1253 nextResource,1254 type,1255 newProps,1256 renderLanes,1257 );1258 return null;1259 } else {1260 // This must come at the very end of the complete phase.1261 bubbleProperties(workInProgress);1262 workInProgress.flags &= ~MaySuspendCommit;1263 return null;1264 }1265 } else {1266 const oldProps = current.memoizedProps;1267 // This is an Instance1268 // We may have props to update on the Hoistable instance.1269 // $FlowFixMe[constant-condition]1270 if (supportsMutation) {1271 if (oldProps !== newProps) {1272 markUpdate(workInProgress);1273 }1274 } else {1275 // We use the updateHostComponent path because it produces1276 // the update queue we need for Hoistables.1277 updateHostComponent(1278 current,1279 workInProgress,1280 type,1281 newProps,1282 renderLanes,1283 );1284 }1285 // This must come at the very end of the complete phase.1286 bubbleProperties(workInProgress);1287 preloadInstanceAndSuspendIfNeeded(1288 workInProgress,1289 type,1290 oldProps,1291 newProps,1292 renderLanes,1293 );1294 return null;1295 }1296 }1297 }1298 // Fall through1299 }1300 case HostSingleton: {1301 // $FlowFixMe[constant-condition]1302 if (supportsSingletons) {1303 popHostContext(workInProgress);1304 const rootContainerInstance = getRootHostContainer();1305 const type = workInProgress.type;1306 if (current !== null && workInProgress.stateNode != null) {1307 // $FlowFixMe[constant-condition]1308 if (supportsMutation) {1309 const oldProps = current.memoizedProps;1310 if (oldProps !== newProps) {1311 markUpdate(workInProgress);1312 }1313 } else {1314 updateHostComponent(1315 current,1316 workInProgress,1317 type,1318 newProps,1319 renderLanes,1320 );1321 }1322 } else {1323 if (!newProps) {1324 if (workInProgress.stateNode === null) {1325 throw new Error(1326 'We must have new props for new mounts. This error is likely ' +1327 'caused by a bug in React. Please file an issue.',1328 );1329 }13301331 // This can happen when we abort work.1332 bubbleProperties(workInProgress);1333 if (enableViewTransition) {1334 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1335 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1336 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1337 }1338 return null;1339 }13401341 const currentHostContext = getHostContext();1342 const wasHydrated = popHydrationState(workInProgress);1343 let instance: Instance;1344 if (wasHydrated) {1345 // We ignore the boolean indicating there is an updateQueue because1346 // it is used only to set text children and HostSingletons do not1347 // use them.1348 prepareToHydrateHostInstance(workInProgress, currentHostContext);1349 instance = workInProgress.stateNode;1350 } else {1351 instance = resolveSingletonInstance(1352 type,1353 newProps,1354 rootContainerInstance,1355 currentHostContext,1356 true,1357 );1358 workInProgress.stateNode = instance;1359 markUpdate(workInProgress);1360 }1361 }1362 bubbleProperties(workInProgress);1363 if (enableViewTransition) {1364 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1365 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1366 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1367 }1368 return null;1369 }1370 // Fall through1371 }1372 case HostComponent: {1373 popHostContext(workInProgress);1374 const type = workInProgress.type;1375 if (current !== null && workInProgress.stateNode != null) {1376 updateHostComponent(1377 current,1378 workInProgress,1379 type,1380 newProps,1381 renderLanes,1382 );1383 } else {1384 if (!newProps) {1385 if (workInProgress.stateNode === null) {1386 throw new Error(1387 'We must have new props for new mounts. This error is likely ' +1388 'caused by a bug in React. Please file an issue.',1389 );1390 }13911392 // This can happen when we abort work.1393 bubbleProperties(workInProgress);1394 if (enableViewTransition) {1395 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1396 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1397 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1398 }1399 return null;1400 }14011402 const currentHostContext = getHostContext();1403 // TODO: Move createInstance to beginWork and keep it on a context1404 // "stack" as the parent. Then append children as we go in beginWork1405 // or completeWork depending on whether we want to add them top->down or1406 // bottom->up. Top->down is faster in IE11.1407 const wasHydrated = popHydrationState(workInProgress);1408 if (wasHydrated) {1409 // TODO: Move this and createInstance step into the beginPhase1410 // to consolidate.1411 prepareToHydrateHostInstance(workInProgress, currentHostContext);1412 if (1413 finalizeHydratedChildren(1414 workInProgress.stateNode,1415 type,1416 newProps,1417 currentHostContext,1418 )1419 ) {1420 workInProgress.flags |= Hydrate;1421 }1422 } else {1423 const rootContainerInstance = getRootHostContainer();1424 const instance = createInstance(1425 type,1426 newProps,1427 rootContainerInstance,1428 currentHostContext,1429 workInProgress,1430 );1431 // TODO: For persistent renderers, we should pass children as part1432 // of the initial instance creation1433 markCloned(workInProgress);1434 appendAllChildren(instance, workInProgress, false, false);1435 workInProgress.stateNode = instance;14361437 // Certain renderers require commit-time effects for initial mount.1438 // (eg DOM renderer supports auto-focus for certain elements).1439 // Make sure such renderers get scheduled for later work.1440 if (1441 finalizeInitialChildren(1442 instance,1443 type,1444 newProps,1445 currentHostContext,1446 )1447 ) {1448 markUpdate(workInProgress);1449 }1450 }1451 }1452 bubbleProperties(workInProgress);1453 if (enableViewTransition) {1454 // Host Components act as their own View Transitions which doesn't run enter/exit animations.1455 // We clear any ViewTransitionStatic flag bubbled from inner View Transitions.1456 workInProgress.subtreeFlags &= ~ViewTransitionStatic;1457 }14581459 // This must come at the very end of the complete phase, because it might1460 // throw to suspend, and if the resource immediately loads, the work loop1461 // will resume rendering as if the work-in-progress completed. So it must1462 // fully complete.1463 preloadInstanceAndSuspendIfNeeded(1464 workInProgress,1465 workInProgress.type,1466 current === null ? null : current.memoizedProps,1467 workInProgress.pendingProps,1468 renderLanes,1469 );1470 return null;1471 }1472 case HostText: {1473 const newText = newProps;1474 if (current && workInProgress.stateNode != null) {1475 const oldText = current.memoizedProps;1476 // If we have an alternate, that means this is an update and we need1477 // to schedule a side-effect to do the updates.1478 updateHostText(current, workInProgress, oldText, newText);1479 } else {1480 if (typeof newText !== 'string') {1481 if (workInProgress.stateNode === null) {1482 throw new Error(1483 'We must have new props for new mounts. This error is likely ' +1484 'caused by a bug in React. Please file an issue.',1485 );1486 }1487 // This can happen when we abort work.1488 }1489 const rootContainerInstance = getRootHostContainer();1490 const currentHostContext = getHostContext();1491 const wasHydrated = popHydrationState(workInProgress);1492 if (wasHydrated) {1493 prepareToHydrateHostTextInstance(workInProgress);1494 } else {1495 markCloned(workInProgress);1496 workInProgress.stateNode = createTextInstance(1497 newText,1498 rootContainerInstance,1499 currentHostContext,1500 workInProgress,1501 );1502 }1503 }1504 bubbleProperties(workInProgress);1505 return null;1506 }1507 case ActivityComponent: {1508 const nextState: null | ActivityState = workInProgress.memoizedState;15091510 if (current === null || current.memoizedState !== null) {1511 const fallthroughToNormalOffscreenPath =1512 completeDehydratedActivityBoundary(1513 current,1514 workInProgress,1515 nextState,1516 );1517 if (!fallthroughToNormalOffscreenPath) {1518 if (workInProgress.flags & ForceClientRender) {1519 popSuspenseHandler(workInProgress);1520 // Special case. There were remaining unhydrated nodes. We treat1521 // this as a mismatch. Revert to client rendering.1522 return workInProgress;1523 } else {1524 popSuspenseHandler(workInProgress);1525 // Did not finish hydrating, either because this is the initial1526 // render or because something suspended.1527 return null;1528 }1529 }15301531 if ((workInProgress.flags & DidCapture) !== NoFlags) {1532 // We called retryActivityComponentWithoutHydrating and tried client rendering1533 // but now we suspended again. We should never arrive here because we should1534 // not have pushed a suspense handler during that second pass and it should1535 // instead have suspended above.1536 throw new Error(1537 'Client rendering an Activity suspended it again. This is a bug in React.',1538 );1539 }15401541 // Continue with the normal Activity path.1542 }15431544 bubbleProperties(workInProgress);1545 return null;1546 }1547 case SuspenseComponent: {1548 const nextState: null | SuspenseState = workInProgress.memoizedState;15491550 // Special path for dehydrated boundaries. We may eventually move this1551 // to its own fiber type so that we can add other kinds of hydration1552 // boundaries that aren't associated with a Suspense tree. In anticipation1553 // of such a refactor, all the hydration logic is contained in1554 // this branch.1555 if (1556 current === null ||1557 (current.memoizedState !== null &&1558 current.memoizedState.dehydrated !== null)1559 ) {1560 const fallthroughToNormalSuspensePath =1561 completeDehydratedSuspenseBoundary(1562 current,1563 workInProgress,1564 nextState,1565 );1566 if (!fallthroughToNormalSuspensePath) {1567 if (workInProgress.flags & ForceClientRender) {1568 popSuspenseHandler(workInProgress);1569 // Special case. There were remaining unhydrated nodes. We treat1570 // this as a mismatch. Revert to client rendering.1571 return workInProgress;1572 } else {1573 popSuspenseHandler(workInProgress);1574 // Did not finish hydrating, either because this is the initial1575 // render or because something suspended.1576 return null;1577 }1578 }15791580 // Continue with the normal Suspense path.1581 }15821583 popSuspenseHandler(workInProgress);15841585 if ((workInProgress.flags & DidCapture) !== NoFlags) {1586 // Something suspended. Re-render with the fallback children.1587 workInProgress.lanes = renderLanes;1588 if (1589 enableProfilerTimer &&1590 (workInProgress.mode & ProfileMode) !== NoMode1591 ) {1592 transferActualDuration(workInProgress);1593 }1594 // Don't bubble properties in this case.1595 return workInProgress;1596 }15971598 const nextDidTimeout = nextState !== null;1599 const prevDidTimeout =1600 current !== null &&1601 (current.memoizedState as null | SuspenseState) !== null;16021603 if (nextDidTimeout) {1604 const offscreenFiber: Fiber = workInProgress.child as any;1605 let previousCache: Cache | null = null;1606 if (1607 offscreenFiber.alternate !== null &&1608 offscreenFiber.alternate.memoizedState !== null &&1609 offscreenFiber.alternate.memoizedState.cachePool !== null1610 ) {1611 previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;1612 }1613 let cache: Cache | null = null;1614 if (1615 offscreenFiber.memoizedState !== null &&1616 offscreenFiber.memoizedState.cachePool !== null1617 ) {1618 cache = offscreenFiber.memoizedState.cachePool.pool;1619 }1620 if (cache !== previousCache) {1621 // Run passive effects to retain/release the cache.1622 offscreenFiber.flags |= Passive;1623 }1624 }16251626 // If the suspended state of the boundary changes, we need to schedule1627 // a passive effect, which is when we process the transitions1628 if (nextDidTimeout !== prevDidTimeout) {1629 if (enableTransitionTracing) {1630 const offscreenFiber: Fiber = workInProgress.child as any;1631 offscreenFiber.flags |= Passive;1632 }16331634 // If the suspended state of the boundary changes, we need to schedule1635 // an effect to toggle the subtree's visibility. When we switch from1636 // fallback -> primary, the inner Offscreen fiber schedules this effect1637 // as part of its normal complete phase. But when we switch from1638 // primary -> fallback, the inner Offscreen fiber does not have a complete1639 // phase. So we need to schedule its effect here.1640 //1641 // We also use this flag to connect/disconnect the effects, but the same1642 // logic applies: when re-connecting, the Offscreen fiber's complete1643 // phase will handle scheduling the effect. It's only when the fallback1644 // is active that we have to do anything special.1645 if (nextDidTimeout) {1646 const offscreenFiber: Fiber = workInProgress.child as any;1647 offscreenFiber.flags |= Visibility;1648 }1649 }16501651 const retryQueue: RetryQueue | null = workInProgress.updateQueue as any;1652 scheduleRetryEffect(workInProgress, retryQueue);16531654 if (1655 enableSuspenseCallback &&1656 workInProgress.updateQueue !== null &&1657 workInProgress.memoizedProps.suspenseCallback != null1658 ) {1659 // Always notify the callback1660 // TODO: Move to passive phase1661 workInProgress.flags |= Update;1662 }1663 bubbleProperties(workInProgress);1664 if (enableProfilerTimer) {1665 if ((workInProgress.mode & ProfileMode) !== NoMode) {1666 if (nextDidTimeout) {1667 // Don't count time spent in a timed out Suspense subtree as part of the base duration.1668 const primaryChildFragment = workInProgress.child;1669 if (primaryChildFragment !== null) {1670 // $FlowFixMe[unsafe-arithmetic] Flow doesn't support type casting in combination with the -= operator1671 workInProgress.treeBaseDuration -=1672 primaryChildFragment.treeBaseDuration as any as number;1673 }1674 }1675 }1676 }1677 return null;1678 }1679 case HostPortal:1680 popHostContainer(workInProgress);1681 updateHostContainer(current, workInProgress);1682 if (current === null) {1683 preparePortalMount(workInProgress.stateNode.containerInfo);1684 }1685 workInProgress.flags |= PortalStatic;1686 bubbleProperties(workInProgress);1687 return null;1688 case ContextProvider:1689 // Pop provider fiber1690 const context: ReactContext<any> = workInProgress.type;1691 popProvider(context, workInProgress);1692 bubbleProperties(workInProgress);1693 return null;1694 case IncompleteClassComponent: {1695 if (disableLegacyMode) {1696 break;1697 }1698 // Same as class component case. I put it down here so that the tags are1699 // sequential to ensure this switch is compiled to a jump table.1700 const Component = workInProgress.type;1701 if (isLegacyContextProvider(Component)) {1702 popLegacyContext(workInProgress);1703 }1704 bubbleProperties(workInProgress);1705 return null;1706 }1707 case SuspenseListComponent: {1708 popSuspenseListContext(workInProgress);17091710 const renderState: null | SuspenseListRenderState =1711 workInProgress.memoizedState;17121713 if (renderState === null) {1714 // We're running in the default, "independent" mode.1715 // We don't do anything in this mode.1716 bubbleProperties(workInProgress);1717 return null;1718 }17191720 let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;17211722 const renderedTail = renderState.rendering;1723 if (renderedTail === null) {1724 // We just rendered the head.1725 if (!didSuspendAlready) {1726 // This is the first pass. We need to figure out if anything is still1727 // suspended in the rendered set.17281729 // If new content unsuspended, but there's still some content that1730 // didn't. Then we need to do a second pass that forces everything1731 // to keep showing their fallbacks.17321733 // We might be suspended if something in this render pass suspended, or1734 // something in the previous committed pass suspended. Otherwise,1735 // there's no chance so we can skip the expensive call to1736 // findFirstSuspended.1737 const cannotBeSuspended =1738 renderHasNotSuspendedYet() &&1739 (current === null || (current.flags & DidCapture) === NoFlags);1740 if (!cannotBeSuspended) {1741 let row = workInProgress.child;1742 while (row !== null) {1743 const suspended = findFirstSuspended(row);1744 if (suspended !== null) {1745 didSuspendAlready = true;1746 workInProgress.flags |= DidCapture;1747 cutOffTailIfNeeded(renderState, false);17481749 // If this is a newly suspended tree, it might not get committed as1750 // part of the second pass. In that case nothing will subscribe to1751 // its thenables. Instead, we'll transfer its thenables to the1752 // SuspenseList so that it can retry if they resolve.1753 // There might be multiple of these in the list but since we're1754 // going to wait for all of them anyway, it doesn't really matter1755 // which ones gets to ping. In theory we could get clever and keep1756 // track of how many dependencies remain but it gets tricky because1757 // in the meantime, we can add/remove/change items and dependencies.1758 // We might bail out of the loop before finding any but that1759 // doesn't matter since that means that the other boundaries that1760 // we did find already has their listeners attached.1761 const retryQueue: RetryQueue | null =1762 suspended.updateQueue as any;1763 workInProgress.updateQueue = retryQueue;1764 scheduleRetryEffect(workInProgress, retryQueue);17651766 // Rerender the whole list, but this time, we'll force fallbacks1767 // to stay in place.1768 // Reset the effect flags before doing the second pass since that's now invalid.1769 // Reset the child fibers to their original state.1770 workInProgress.subtreeFlags = NoFlags;1771 resetChildFibers(workInProgress, renderLanes);17721773 // Set up the Suspense List Context to force suspense and1774 // immediately rerender the children.1775 pushSuspenseListContext(1776 workInProgress,1777 setShallowSuspenseListContext(1778 suspenseStackCursor.current,1779 ForceSuspenseFallback,1780 ),1781 );1782 if (getIsHydrating()) {1783 // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1784 pushTreeFork(workInProgress, renderState.treeForkCount);1785 }1786 // Don't bubble properties in this case.1787 return workInProgress.child;1788 }1789 row = row.sibling;1790 }1791 }17921793 if (renderState.tail !== null && now() > getRenderTargetTime()) {1794 // We have already passed our CPU deadline but we still have rows1795 // left in the tail. We'll just give up further attempts to render1796 // the main content and only render fallbacks.1797 workInProgress.flags |= DidCapture;1798 didSuspendAlready = true;17991800 cutOffTailIfNeeded(renderState, false);18011802 // Since nothing actually suspended, there will nothing to ping this1803 // to get it started back up to attempt the next item. While in terms1804 // of priority this work has the same priority as this current render,1805 // it's not part of the same transition once the transition has1806 // committed. If it's sync, we still want to yield so that it can be1807 // painted. Conceptually, this is really the same as pinging.1808 // We can use any RetryLane even if it's the one currently rendering1809 // since we're leaving it behind on this node.1810 workInProgress.lanes = SomeRetryLane;1811 }1812 } else {1813 cutOffTailIfNeeded(renderState, false);1814 }1815 // Next we're going to render the tail.1816 } else {1817 // Append the rendered row to the child list.1818 if (!didSuspendAlready) {1819 const suspended = findFirstSuspended(renderedTail);1820 if (suspended !== null) {1821 workInProgress.flags |= DidCapture;1822 didSuspendAlready = true;18231824 // Ensure we transfer the update queue to the parent so that it doesn't1825 // get lost if this row ends up dropped during a second pass.1826 const retryQueue: RetryQueue | null = suspended.updateQueue as any;1827 workInProgress.updateQueue = retryQueue;1828 scheduleRetryEffect(workInProgress, retryQueue);18291830 cutOffTailIfNeeded(renderState, true);1831 // This might have been modified.1832 if (1833 renderState.tail === null &&1834 renderState.tailMode !== 'collapsed' &&1835 renderState.tailMode !== 'visible' &&1836 !renderedTail.alternate &&1837 !getIsHydrating() // We don't cut it if we're hydrating.1838 ) {1839 // We're done.1840 bubbleProperties(workInProgress);1841 return null;1842 }1843 } else if (1844 // The time it took to render last row is greater than the remaining1845 // time we have to render. So rendering one more row would likely1846 // exceed it.1847 now() * 2 - renderState.renderingStartTime >1848 getRenderTargetTime() &&1849 renderLanes !== OffscreenLane1850 ) {1851 // We have now passed our CPU deadline and we'll just give up further1852 // attempts to render the main content and only render fallbacks.1853 // The assumption is that this is usually faster.1854 workInProgress.flags |= DidCapture;1855 didSuspendAlready = true;18561857 cutOffTailIfNeeded(renderState, false);18581859 // Since nothing actually suspended, there will nothing to ping this1860 // to get it started back up to attempt the next item. While in terms1861 // of priority this work has the same priority as this current render,1862 // it's not part of the same transition once the transition has1863 // committed. If it's sync, we still want to yield so that it can be1864 // painted. Conceptually, this is really the same as pinging.1865 // We can use any RetryLane even if it's the one currently rendering1866 // since we're leaving it behind on this node.1867 workInProgress.lanes = SomeRetryLane;1868 }1869 }1870 if (renderState.isBackwards) {1871 // Append to the beginning of the list.1872 renderedTail.sibling = workInProgress.child;1873 workInProgress.child = renderedTail;1874 } else {1875 const previousSibling = renderState.last;1876 if (previousSibling !== null) {1877 previousSibling.sibling = renderedTail;1878 } else {1879 workInProgress.child = renderedTail;1880 }1881 renderState.last = renderedTail;1882 }1883 }18841885 if (renderState.tail !== null) {1886 // We still have tail rows to render.1887 // Pop a row.1888 // TODO: Consider storing the first of the new mount tail in the state so1889 // that we don't have to recompute this for every row in the list.1890 const next = renderState.tail;1891 const onlyNewMounts = isOnlyNewMounts(next);1892 renderState.rendering = next;1893 renderState.tail = next.sibling;1894 renderState.renderingStartTime = now();1895 next.sibling = null;18961897 // Restore the context.1898 // TODO: We can probably just avoid popping it instead and only1899 // setting it the first time we go from not suspended to suspended.1900 let suspenseContext = suspenseStackCursor.current;1901 if (didSuspendAlready) {1902 suspenseContext = setShallowSuspenseListContext(1903 suspenseContext,1904 ForceSuspenseFallback,1905 );1906 } else {1907 suspenseContext =1908 setDefaultShallowSuspenseListContext(suspenseContext);1909 }1910 if (1911 renderState.tailMode === 'visible' ||1912 renderState.tailMode === 'collapsed' ||1913 !onlyNewMounts ||1914 // TODO: While hydrating, we still let it suspend the parent. Tail mode hidden has broken1915 // hydration anyway right now but this preserves the previous semantics out of caution.1916 // Once proper hydration is implemented, this special case should be removed as it should1917 // never be needed.1918 getIsHydrating()1919 ) {1920 pushSuspenseListContext(workInProgress, suspenseContext);1921 } else {1922 // If we are rendering in 'hidden' (default) tail mode, then we if we suspend in the1923 // tail itself, we can delete it rather than suspend the parent. So we act as a catch in that1924 // case. For 'collapsed' we need to render at least one in suspended state, after which we'll1925 // have cut off the rest to never attempt it so it never hits this case.1926 // If this is an updated node, we cannot delete it from the tail so it's effectively visible.1927 // As a consequence, if it resuspends it actually suspends the parent by taking the other path.1928 pushSuspenseListCatch(workInProgress, suspenseContext);1929 }1930 // Do a pass over the next row.1931 if (getIsHydrating()) {1932 // Re-apply tree fork since we popped the tree fork context in the beginning of this function.1933 pushTreeFork(workInProgress, renderState.treeForkCount);1934 }1935 // Don't bubble properties in this case.1936 return next;1937 }1938 bubbleProperties(workInProgress);1939 return null;1940 }1941 case ScopeComponent: {1942 if (enableScopeAPI) {1943 if (current === null) {1944 const scopeInstance: ReactScopeInstance = createScopeInstance();1945 workInProgress.stateNode = scopeInstance;1946 prepareScopeUpdate(scopeInstance, workInProgress);1947 if (workInProgress.ref !== null) {1948 // Scope components always do work in the commit phase if there's a1949 // ref attached.1950 markUpdate(workInProgress);1951 }1952 } else {1953 if (workInProgress.ref !== null) {1954 // Scope components always do work in the commit phase if there's a1955 // ref attached.1956 markUpdate(workInProgress);1957 }1958 }1959 bubbleProperties(workInProgress);1960 return null;1961 }1962 break;1963 }1964 case OffscreenComponent:1965 case LegacyHiddenComponent: {1966 popSuspenseHandler(workInProgress);1967 popHiddenContext(workInProgress);1968 const nextState: OffscreenState | null = workInProgress.memoizedState;1969 const nextIsHidden = nextState !== null;19701971 // Schedule a Visibility effect if the visibility has changed1972 if (enableLegacyHidden && workInProgress.tag === LegacyHiddenComponent) {1973 // LegacyHidden doesn't do any hiding — it only pre-renders.1974 } else {1975 if (current !== null) {1976 const prevState: OffscreenState | null = current.memoizedState;1977 const prevIsHidden = prevState !== null;1978 if (prevIsHidden !== nextIsHidden) {1979 workInProgress.flags |= Visibility;1980 }1981 } else {1982 // On initial mount, we only need a Visibility effect if the tree1983 // is hidden.1984 if (nextIsHidden) {1985 workInProgress.flags |= Visibility;1986 }1987 }1988 }19891990 if (1991 !nextIsHidden ||1992 (!disableLegacyMode &&1993 (workInProgress.mode & ConcurrentMode) === NoMode)1994 ) {1995 bubbleProperties(workInProgress);1996 } else {1997 // Don't bubble properties for hidden children unless we're rendering1998 // at offscreen priority.1999 if (2000 includesSomeLane(renderLanes, OffscreenLane as Lane) &&
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.