main
js 2,114 lines 76.6 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {Fiber, FiberRoot} from './ReactInternalTypes';
11 import type {RootState} from './ReactFiberRoot';
12 import type {Lanes, Lane} from './ReactFiberLane';
13 import type {ReactScopeInstance, ReactContext} from 'shared/ReactTypes';
14 import type {
15 Instance,
16 Type,
17 Props,
18 Container,
19 ChildSet,
20 Resource,
21 } from './ReactFiberConfig';
22 import type {ActivityState} from './ReactFiberActivityComponent';
23 import type {
24 SuspenseState,
25 SuspenseListRenderState,
26 RetryQueue,
27 } from './ReactFiberSuspenseComponent';
28 import type {
29 OffscreenState,
30 OffscreenQueue,
31 } from './ReactFiberOffscreenComponent';
32 import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
33 import type {Cache} from './ReactFiberCacheComponent';
34 import {
35 enableLegacyHidden,
36 enableSuspenseCallback,
37 enableScopeAPI,
38 enableProfilerTimer,
39 enableTransitionTracing,
40 passChildrenWhenCloningPersistedNodes,
41 disableLegacyMode,
42 enableViewTransition,
43 enableViewTransitionParentEnterExit,
44 enableSuspenseyImages,
45 } from 'shared/ReactFeatureFlags';
46
47 import {now} from './Scheduler';
48
49 import {
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';
80 import {
81 NoMode,
82 ConcurrentMode,
83 ProfileMode,
84 SuspenseyImagesMode,
85 } from './ReactTypeOfMode';
86 import {
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';
106
107 import {
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';
133 import {
134 getRootHostContainer,
135 popHostContext,
136 getHostContext,
137 popHostContainer,
138 } from './ReactFiberHostContext';
139 import {
140 suspenseStackCursor,
141 popSuspenseListContext,
142 popSuspenseHandler,
143 pushSuspenseListContext,
144 pushSuspenseListCatch,
145 setShallowSuspenseListContext,
146 ForceSuspenseFallback,
147 setDefaultShallowSuspenseListContext,
148 } from './ReactFiberSuspenseContext';
149 import {popHiddenContext} from './ReactFiberHiddenContext';
150 import {findFirstSuspended} from './ReactFiberSuspenseComponent';
151 import {
152 isContextProvider as isLegacyContextProvider,
153 popContext as popLegacyContext,
154 popTopLevelContextObject as popTopLevelLegacyContextObject,
155 } from './ReactFiberLegacyContext';
156 import {popProvider} from './ReactFiberNewContext';
157 import {
158 prepareToHydrateHostInstance,
159 prepareToHydrateHostTextInstance,
160 prepareToHydrateHostActivityInstance,
161 prepareToHydrateHostSuspenseInstance,
162 popHydrationState,
163 resetHydrationState,
164 getIsHydrating,
165 upgradeHydrationErrorsToRecoverable,
166 emitPendingHydrationWarnings,
167 } from './ReactFiberHydrationContext';
168 import {
169 renderHasNotSuspendedYet,
170 getRenderTargetTime,
171 getWorkInProgressTransitions,
172 shouldRemainOnPreviousScreen,
173 markSpawnedRetryLane,
174 } from './ReactFiberWorkLoop';
175 import {
176 OffscreenLane,
177 SomeRetryLane,
178 NoLanes,
179 includesSomeLane,
180 mergeLanes,
181 claimNextRetryLane,
182 includesOnlySuspenseyCommitEligibleLanes,
183 } from './ReactFiberLane';
184 import {resetChildFibers} from './ReactChildFiber';
185 import {createScopeInstance} from './ReactFiberScope';
186 import {transferActualDuration} from './ReactProfilerTimer';
187 import {popCacheProvider} from './ReactFiberCacheComponent';
188 import {popTreeContext, pushTreeFork} from './ReactFiberTreeContext';
189 import {popRootTransition, popTransition} from './ReactFiberTransition';
190 import {
191 popMarkerInstance,
192 popRootMarkerInstance,
193 } from './ReactFiberTracingMarkerComponent';
194 import {suspendCommit} from './ReactFiberThenable';
195 import type {Flags} from './ReactFiberFlags';
196
197 /**
198 * Tag the fiber with an update effect. This turns a Placement into
199 * a PlacementAndUpdate.
200 */
201 function markUpdate(workInProgress: Fiber) {
202 workInProgress.flags |= Update;
203 }
204
205 /**
206 * Tag the fiber with Cloned in persistent mode to signal that
207 * it received an update that requires a clone of the tree above.
208 */
209 function markCloned(workInProgress: Fiber) {
210 // $FlowFixMe[constant-condition]
211 if (supportsPersistence) {
212 workInProgress.flags |= Cloned;
213 }
214 }
215
216 /**
217 * In persistent mode, return whether this update needs to clone the subtree.
218 */
219 function doesRequireClone(current: null | Fiber, completedWork: Fiber) {
220 const didBailout = current !== null && current.child === completedWork.child;
221 if (didBailout) {
222 return false;
223 }
224
225 if ((completedWork.flags & ChildDeletion) !== NoFlags) {
226 return true;
227 }
228
229 // 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) !== NoFlags
237 ) {
238 return true;
239 }
240 child = child.sibling;
241 }
242 return false;
243 }
244
245 function 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 its
254 // 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 traverse
265 // down its children. Instead, we'll get insertions from each child in
266 // the portal directly.
267 // If we have a HostSingleton it will be placed independently
268 } 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 Flow
277 while (node.sibling === null) {
278 // $FlowFixMe[incompatible-use] found when upgrading Flow
279 if (node.return === null || node.return === workInProgress) {
280 return;
281 }
282 node = node.return;
283 }
284 // $FlowFixMe[incompatible-use] found when upgrading Flow
285 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 its
291 // 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 traverse
313 // down its children. Instead, we'll get insertions from each child in
314 // the portal directly.
315 } else if (
316 node.tag === OffscreenComponent &&
317 node.memoizedState !== null
318 ) {
319 // The children in this boundary are hidden. Toggle their visibility
320 // 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 Flow
340 while (node.sibling === null) {
341 // $FlowFixMe[incompatible-use] found when upgrading Flow
342 if (node.return === null || node.return === workInProgress) {
343 return;
344 }
345 node = node.return;
346 }
347 // $FlowFixMe[incompatible-use] found when upgrading Flow
348 node.sibling.return = node.return;
349 node = node.sibling;
350 }
351 }
352 }
353
354 // An unfortunate fork of appendAllChildren because we have two different parent types.
355 function 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 OffscreenComponent
362 // do not support passChildrenWhenCloningPersistedNodes. To inform the callee
363 // about their presence, we track and return if they were added to the
364 // 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 its
369 // 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 traverse
391 // down its children. Instead, we'll get insertions from each child in
392 // the portal directly.
393 } else if (
394 node.tag === OffscreenComponent &&
395 node.memoizedState !== null
396 ) {
397 // The children in this boundary are hidden. Toggle their visibility
398 // 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 );
409
410 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 Flow
421 while (node.sibling === null) {
422 // $FlowFixMe[incompatible-use] found when upgrading Flow
423 if (node.return === null || node.return === workInProgress) {
424 return hasOffscreenComponentChild;
425 }
426 node = node.return;
427 }
428 // $FlowFixMe[incompatible-use] found when upgrading Flow
429 node.sibling.return = node.return;
430 node = node.sibling;
431 }
432 }
433
434 return hasOffscreenComponentChild;
435 }
436
437 function 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 }
462
463 function 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 to
473 // 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 because
477 // we won't touch this node even if children changed.
478 return;
479 }
480
481 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();
496
497 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 }
510
511 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 }
527
528 // 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 }
551
552 // This function must be called at the very end of the complete phase, because
553 // it might throw to suspend, and if the resource immediately loads, the work
554 // loop will resume rendering as if the work-in-progress completed. So it must
555 // fully complete.
556 // TODO: This should ideally move to begin phase, but currently the instance is
557 // not created until the complete phase. For our existing use cases, host nodes
558 // that suspend don't have children, so it doesn't matter. But that might not
559 // always be true in the future.
560 function 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 === null
571 ? maySuspendCommit(type, newProps)
572 : maySuspendCommitOnUpdate(type, oldProps, newProps));
573
574 if (!maySuspend) {
575 // If this flag was set previously, we can remove it. The flag
576 // represents whether this particular set of props might ever need to
577 // suspend. The safest thing to do is for maySuspendCommit to always
578 // return true, but if the renderer is reasonably confident that the
579 // underlying resource won't be evicted, it can return false as a
580 // performance optimization.
581 workInProgress.flags &= ~MaySuspendCommit;
582 return;
583 }
584
585 // Mark this fiber with a flag. This gets set on all host instances
586 // that might possibly suspend, even if they don't need to suspend
587 // currently. We use this when revealing a prerendered tree, because
588 // even though the tree has "mounted", its resources might not have
589 // loaded yet.
590 workInProgress.flags |= MaySuspendCommit;
591
592 if (
593 includesOnlySuspenseyCommitEligibleLanes(renderLanes) ||
594 maySuspendCommitInSyncRender(type, newProps)
595 ) {
596 // preload the instance if necessary. Even if this is an urgent render there
597 // could be benefits to preloading early.
598 // @TODO we should probably do the preload in begin work
599 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-commit
608 // phase if we need to suspend anyway. Such as if it's delayed on decoding or
609 // if it was dropped from the cache while rendering due to pressure.
610 workInProgress.flags |= ShouldSuspendCommit;
611 }
612 }
613 }
614
615 function 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 }
627
628 workInProgress.flags |= MaySuspendCommit;
629
630 const isReady = preloadResource(resource);
631 if (!isReady) {
632 if (shouldRemainOnPreviousScreen()) {
633 workInProgress.flags |= ShouldSuspendCommit;
634 } else {
635 suspendCommit();
636 }
637 }
638 }
639
640 function 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 phase
648 workInProgress.flags |= Update;
649 }
650
651 // Check if we need to schedule an immediate retry. This should happen
652 // 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 claimNextRetryLane
661 // I also suspect that we need some further consolidation of offscreen
662 // and retry lanes.
663 workInProgress.tag !== OffscreenComponent
664 ? claimNextRetryLane()
665 : OffscreenLane;
666 workInProgress.lanes = mergeLanes(workInProgress.lanes, retryLane);
667
668 // Track the lanes that have been scheduled for an immediate retry so that
669 // we can mark them as suspended upon committing the root.
670 markSpawnedRetryLane(retryLane);
671 }
672 }
673
674 function 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 }
704
705 function cutOffTailIfNeeded(
706 renderState: SuspenseListRenderState,
707 hasRenderedATailFallback: boolean,
708 ) {
709 if (getIsHydrating()) {
710 // If we're hydrating, we should consume as many items as we can
711 // 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 point
721 // should be invisible. If there are already mounted boundaries
722 // anything before them are not considered for collapsing.
723 // Therefore we need to go through the whole tail to find if
724 // 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 the
734 // 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 one
739 // 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 already
746 // 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 point
755 // should be invisible. If there are already mounted boundaries
756 // anything before them are not considered for collapsing.
757 // Therefore we need to go through the whole tail to find if
758 // 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 the
768 // 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 already
774 // inserted.
775 lastTailNode.sibling = null;
776 }
777 break;
778 }
779 }
780 }
781
782 function 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 }
792
793 function bubbleProperties(completedWork: Fiber) {
794 const didBailout =
795 completedWork.alternate !== null &&
796 completedWork.alternate.child === completedWork.child;
797
798 let newChildLanes: Lanes = NoLanes;
799 let subtreeFlags: Flags = NoFlags;
800
801 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 reset
805 // profiler durations.
806 let actualDuration = completedWork.actualDuration;
807 let treeBaseDuration = completedWork.selfBaseDuration as any as number;
808
809 let child = completedWork.child;
810 while (child !== null) {
811 newChildLanes = mergeLanes(
812 newChildLanes,
813 mergeLanes(child.lanes, child.childLanes),
814 );
815
816 subtreeFlags |= child.subtreeFlags;
817 subtreeFlags |= child.flags;
818
819 // When a fiber is cloned, its actualDuration is reset to 0. This value will
820 // 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. If
822 // the fiber has not been cloned though, (meaning no work was done), then
823 // this value will reflect the amount of time spent working on a previous
824 // render. In that case it should not bubble. We determine whether it was
825 // cloned by comparing the child pointer.
826 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
827 actualDuration += child.actualDuration;
828
829 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
830 treeBaseDuration += child.treeBaseDuration;
831 child = child.sibling;
832 }
833
834 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 );
843
844 subtreeFlags |= child.subtreeFlags;
845 subtreeFlags |= child.flags;
846
847 // Update the return pointer so the tree is consistent. This is a code
848 // smell because it assumes the commit phase is never concurrent with
849 // the render phase. Will address during refactor to alternate model.
850 child.return = completedWork;
851
852 child = child.sibling;
853 }
854 }
855
856 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 reset
861 // profiler durations.
862 let treeBaseDuration = completedWork.selfBaseDuration as any as number;
863
864 let child = completedWork.child;
865 while (child !== null) {
866 newChildLanes = mergeLanes(
867 newChildLanes,
868 mergeLanes(child.lanes, child.childLanes),
869 );
870
871 // "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 other
873 // flags have a lifetime only of a single render + commit, so we should
874 // ignore them.
875 subtreeFlags |= child.subtreeFlags & StaticMask;
876 subtreeFlags |= child.flags & StaticMask;
877
878 // $FlowFixMe[unsafe-addition] addition with possible null/undefined value
879 treeBaseDuration += child.treeBaseDuration;
880 child = child.sibling;
881 }
882
883 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 );
891
892 // "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 other
894 // flags have a lifetime only of a single render + commit, so we should
895 // ignore them.
896 subtreeFlags |= child.subtreeFlags & StaticMask;
897 subtreeFlags |= child.flags & StaticMask;
898
899 // Update the return pointer so the tree is consistent. This is a code
900 // smell because it assumes the commit phase is never concurrent with
901 // the render phase. Will address during refactor to alternate model.
902 child.return = completedWork;
903
904 child = child.sibling;
905 }
906 }
907
908 completedWork.subtreeFlags |= subtreeFlags;
909 }
910
911 completedWork.childLanes = newChildLanes;
912
913 return didBailout;
914 }
915
916 function completeDehydratedActivityBoundary(
917 current: Fiber | null,
918 workInProgress: Fiber,
919 nextState: ActivityState | null,
920 ): boolean {
921 const wasHydrated = popHydrationState(workInProgress);
922
923 if (nextState !== null) {
924 // We might be inside a hydration state the first time we're picking up this
925 // 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 -= operator
944 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 hydration
954 // 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 boundary
961 // 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 -= operator
975 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 hydration
986 // attempt. If so, add them to a queue so we can log them in the
987 // commit phase. We also add them to prev state so we can get to them
988 // 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 path
995 return true;
996 }
997 }
998
999 function completeDehydratedSuspenseBoundary(
1000 current: Fiber | null,
1001 workInProgress: Fiber,
1002 nextState: SuspenseState | null,
1003 ): boolean {
1004 const wasHydrated = popHydrationState(workInProgress);
1005
1006 if (nextState !== null && nextState.dehydrated !== null) {
1007 // We might be inside a hydration state the first time we're picking up this
1008 // 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 -= operator
1027 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 hydration
1037 // 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 boundary
1044 // 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 -= operator
1058 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 hydration
1069 // attempt. If so, add them to a queue so we can log them in the
1070 // commit phase. We also add them to prev state so we can get to them
1071 // 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 path
1078 return true;
1079 }
1080 }
1081
1082 function 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 comparing
1089 // 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 only
1091 // for hydration.
1092 popTreeContext(workInProgress);
1093 switch (workInProgress.tag) {
1094 case IncompleteFunctionComponent: {
1095 if (disableLegacyMode) {
1096 break;
1097 }
1098 // Fallthrough
1099 }
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;
1121
1122 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 phase
1127 if (transitions !== null) {
1128 workInProgress.flags |= Passive;
1129 }
1130 }
1131
1132 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);
1142
1143 if (enableTransitionTracing) {
1144 popRootMarkerInstance(workInProgress);
1145 }
1146
1147 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 children
1156 // 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 for
1161 // 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 root
1168 !prevState.isDehydrated ||
1169 // Check if we reverted to client rendering (e.g. due to an error)
1170 (workInProgress.flags & ForceClientRender) !== NoFlags
1171 ) {
1172 // Schedule an effect to clear this container at the start of the
1173 // next commit. This handles the case of React rendering into a
1174 // container with previous children. It's also safe to do for
1175 // updates too, because current.child would only be null if the
1176 // previous render was null (so the container would already
1177 // be empty).
1178 workInProgress.flags |= Snapshot;
1179
1180 // If this was a forced client render, there may have been
1181 // recoverable errors during first hydration attempt. If so, add
1182 // 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 that
1193 // the pending boundaries array needs to be updated, which we only
1194 // 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 because
1204 // a HostHoistable sometimes corresponds to a Resource and sometimes
1205 // corresponds to an Instance. It can also switch during an update.
1206
1207 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 commit
1211 // @TODO refactor this block to create the instance here in complete
1212 // phase if we are not hydrating.
1213 markUpdate(workInProgress);
1214 if (nextResource !== null) {
1215 // This is a Hoistable Resource
1216
1217 // 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 Instance
1229 // 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 Resource
1244 if (nextResource !== current.memoizedState) {
1245 // we have a new Resource. we need to update
1246 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 might
1250 // throw to suspend, and if the resource immediately loads, the work loop
1251 // will resume rendering as if the work-in-progress completed. So it must
1252 // 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 Instance
1270 // 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 produces
1278 // 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 through
1301 }
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 }
1332
1333 // 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 }
1342
1343 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 because
1348 // it is used only to set text children and HostSingletons do not
1349 // 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 through
1373 }
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 }
1393
1394 // 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 }
1403
1404 const currentHostContext = getHostContext();
1405 // TODO: Move createInstance to beginWork and keep it on a context
1406 // "stack" as the parent. Then append children as we go in beginWork
1407 // or completeWork depending on whether we want to add them top->down or
1408 // 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 beginPhase
1412 // 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 part
1434 // of the initial instance creation
1435 markCloned(workInProgress);
1436 appendAllChildren(instance, workInProgress, false, false);
1437 workInProgress.stateNode = instance;
1438
1439 // 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 }
1460
1461 // This must come at the very end of the complete phase, because it might
1462 // throw to suspend, and if the resource immediately loads, the work loop
1463 // will resume rendering as if the work-in-progress completed. So it must
1464 // 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 need
1479 // 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;
1511
1512 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 treat
1523 // 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 initial
1528 // render or because something suspended.
1529 return null;
1530 }
1531 }
1532
1533 if ((workInProgress.flags & DidCapture) !== NoFlags) {
1534 // We called retryActivityComponentWithoutHydrating and tried client rendering
1535 // but now we suspended again. We should never arrive here because we should
1536 // not have pushed a suspense handler during that second pass and it should
1537 // instead have suspended above.
1538 throw new Error(
1539 'Client rendering an Activity suspended it again. This is a bug in React.',
1540 );
1541 }
1542
1543 // Continue with the normal Activity path.
1544 }
1545
1546 bubbleProperties(workInProgress);
1547 return null;
1548 }
1549 case SuspenseComponent: {
1550 const nextState: null | SuspenseState = workInProgress.memoizedState;
1551
1552 // Special path for dehydrated boundaries. We may eventually move this
1553 // to its own fiber type so that we can add other kinds of hydration
1554 // boundaries that aren't associated with a Suspense tree. In anticipation
1555 // of such a refactor, all the hydration logic is contained in
1556 // 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 treat
1572 // 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 initial
1577 // render or because something suspended.
1578 return null;
1579 }
1580 }
1581
1582 // Continue with the normal Suspense path.
1583 }
1584
1585 popSuspenseHandler(workInProgress);
1586
1587 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) !== NoMode
1593 ) {
1594 transferActualDuration(workInProgress);
1595 }
1596 // Don't bubble properties in this case.
1597 return workInProgress;
1598 }
1599
1600 const nextDidTimeout = nextState !== null;
1601 const prevDidTimeout =
1602 current !== null &&
1603 (current.memoizedState as null | SuspenseState) !== null;
1604
1605 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 !== null
1612 ) {
1613 previousCache = offscreenFiber.alternate.memoizedState.cachePool.pool;
1614 }
1615 let cache: Cache | null = null;
1616 if (
1617 offscreenFiber.memoizedState !== null &&
1618 offscreenFiber.memoizedState.cachePool !== null
1619 ) {
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 }
1627
1628 // If the suspended state of the boundary changes, we need to schedule
1629 // a passive effect, which is when we process the transitions
1630 if (nextDidTimeout !== prevDidTimeout) {
1631 if (enableTransitionTracing) {
1632 const offscreenFiber: Fiber = workInProgress.child as any;
1633 offscreenFiber.flags |= Passive;
1634 }
1635
1636 // If the suspended state of the boundary changes, we need to schedule
1637 // an effect to toggle the subtree's visibility. When we switch from
1638 // fallback -> primary, the inner Offscreen fiber schedules this effect
1639 // as part of its normal complete phase. But when we switch from
1640 // primary -> fallback, the inner Offscreen fiber does not have a complete
1641 // phase. So we need to schedule its effect here.
1642 //
1643 // We also use this flag to connect/disconnect the effects, but the same
1644 // logic applies: when re-connecting, the Offscreen fiber's complete
1645 // phase will handle scheduling the effect. It's only when the fallback
1646 // 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 }
1652
1653 const retryQueue: RetryQueue | null = workInProgress.updateQueue as any;
1654 scheduleRetryEffect(workInProgress, retryQueue);
1655
1656 if (
1657 enableSuspenseCallback &&
1658 workInProgress.updateQueue !== null &&
1659 workInProgress.memoizedProps.suspenseCallback != null
1660 ) {
1661 // Always notify the callback
1662 // TODO: Move to passive phase
1663 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 -= operator
1673 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 fiber
1692 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 are
1701 // 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);
1711
1712 const renderState: null | SuspenseListRenderState =
1713 workInProgress.memoizedState;
1714
1715 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 }
1721
1722 let didSuspendAlready = (workInProgress.flags & DidCapture) !== NoFlags;
1723
1724 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 still
1729 // suspended in the rendered set.
1730
1731 // If new content unsuspended, but there's still some content that
1732 // didn't. Then we need to do a second pass that forces everything
1733 // to keep showing their fallbacks.
1734
1735 // We might be suspended if something in this render pass suspended, or
1736 // something in the previous committed pass suspended. Otherwise,
1737 // there's no chance so we can skip the expensive call to
1738 // 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);
1750
1751 // If this is a newly suspended tree, it might not get committed as
1752 // part of the second pass. In that case nothing will subscribe to
1753 // its thenables. Instead, we'll transfer its thenables to the
1754 // SuspenseList so that it can retry if they resolve.
1755 // There might be multiple of these in the list but since we're
1756 // going to wait for all of them anyway, it doesn't really matter
1757 // which ones gets to ping. In theory we could get clever and keep
1758 // track of how many dependencies remain but it gets tricky because
1759 // in the meantime, we can add/remove/change items and dependencies.
1760 // We might bail out of the loop before finding any but that
1761 // doesn't matter since that means that the other boundaries that
1762 // 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);
1767
1768 // Rerender the whole list, but this time, we'll force fallbacks
1769 // 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);
1774
1775 // Set up the Suspense List Context to force suspense and
1776 // 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 }
1794
1795 if (renderState.tail !== null && now() > getRenderTargetTime()) {
1796 // We have already passed our CPU deadline but we still have rows
1797 // left in the tail. We'll just give up further attempts to render
1798 // the main content and only render fallbacks.
1799 workInProgress.flags |= DidCapture;
1800 didSuspendAlready = true;
1801
1802 cutOffTailIfNeeded(renderState, false);
1803
1804 // Since nothing actually suspended, there will nothing to ping this
1805 // to get it started back up to attempt the next item. While in terms
1806 // of priority this work has the same priority as this current render,
1807 // it's not part of the same transition once the transition has
1808 // committed. If it's sync, we still want to yield so that it can be
1809 // painted. Conceptually, this is really the same as pinging.
1810 // We can use any RetryLane even if it's the one currently rendering
1811 // 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;
1825
1826 // Ensure we transfer the update queue to the parent so that it doesn't
1827 // 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);
1831
1832 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 remaining
1847 // time we have to render. So rendering one more row would likely
1848 // exceed it.
1849 now() * 2 - renderState.renderingStartTime >
1850 getRenderTargetTime() &&
1851 renderLanes !== OffscreenLane
1852 ) {
1853 // We have now passed our CPU deadline and we'll just give up further
1854 // 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;
1858
1859 cutOffTailIfNeeded(renderState, false);
1860
1861 // Since nothing actually suspended, there will nothing to ping this
1862 // to get it started back up to attempt the next item. While in terms
1863 // of priority this work has the same priority as this current render,
1864 // it's not part of the same transition once the transition has
1865 // committed. If it's sync, we still want to yield so that it can be
1866 // painted. Conceptually, this is really the same as pinging.
1867 // We can use any RetryLane even if it's the one currently rendering
1868 // 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 }
1886
1887 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 so
1891 // 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;
1898
1899 // Restore the context.
1900 // TODO: We can probably just avoid popping it instead and only
1901 // 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 broken
1917 // 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 should
1919 // 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 the
1925 // tail itself, we can delete it rather than suspend the parent. So we act as a catch in that
1926 // case. For 'collapsed' we need to render at least one in suspended state, after which we'll
1927 // 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 a
1951 // 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 a
1957 // 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;
1972
1973 // Schedule a Visibility effect if the visibility has changed
1974 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 tree
1985 // is hidden.
1986 if (nextIsHidden) {
1987 workInProgress.flags |= Visibility;
1988 }
1989 }
1990 }
1991
1992 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 rendering
2000 // at offscreen priority.
2001 if (
2002 includesSomeLane(renderLanes, OffscreenLane as Lane) &&
2003 // Also don't bubble if the tree suspended
2004 (workInProgress.flags & DidCapture) === NoLanes
2005 ) {
2006 bubbleProperties(workInProgress);
2007 // Check if there was an insertion or update in the hidden subtree.
2008 // If so, we need to hide those nodes in the commit phase, so
2009 // schedule a visibility effect.
2010 if (
2011 (!enableLegacyHidden ||
2012 workInProgress.tag !== LegacyHiddenComponent) &&
2013 workInProgress.subtreeFlags & (Placement | Update)
2014 ) {
2015 workInProgress.flags |= Visibility;
2016 }
2017 }
2018 }
2019
2020 const offscreenQueue: OffscreenQueue | null =
2021 workInProgress.updateQueue as any;
2022 if (offscreenQueue !== null) {
2023 const retryQueue = offscreenQueue.retryQueue;
2024 scheduleRetryEffect(workInProgress, retryQueue);
2025 }
2026
2027 let previousCache: Cache | null = null;
2028 if (
2029 current !== null &&
2030 current.memoizedState !== null &&
2031 current.memoizedState.cachePool !== null
2032 ) {
2033 previousCache = current.memoizedState.cachePool.pool;
2034 }
2035 let cache: Cache | null = null;
2036 if (
2037 workInProgress.memoizedState !== null &&
2038 workInProgress.memoizedState.cachePool !== null
2039 ) {
2040 cache = workInProgress.memoizedState.cachePool.pool;
2041 }
2042 if (cache !== previousCache) {
2043 // Run passive effects to retain/release the cache.
2044 workInProgress.flags |= Passive;
2045 }
2046
2047 popTransition(workInProgress, current);
2048
2049 return null;
2050 }
2051 case CacheComponent: {
2052 let previousCache: Cache | null = null;
2053 if (current !== null) {
2054 previousCache = current.memoizedState.cache;
2055 }
2056 const cache: Cache = workInProgress.memoizedState.cache;
2057 if (cache !== previousCache) {
2058 // Run passive effects to retain/release the cache.
2059 workInProgress.flags |= Passive;
2060 }
2061 popCacheProvider(workInProgress, cache);
2062 bubbleProperties(workInProgress);
2063 return null;
2064 }
2065 case TracingMarkerComponent: {
2066 if (enableTransitionTracing) {
2067 const instance: TracingMarkerInstance | null = workInProgress.stateNode;
2068 if (instance !== null) {
2069 popMarkerInstance(workInProgress);
2070 }
2071 bubbleProperties(workInProgress);
2072 }
2073 return null;
2074 }
2075 case ViewTransitionComponent: {
2076 if (enableViewTransition) {
2077 // We're a component that might need an exit transition. This flag will
2078 // bubble up to the parent tree to indicate that there's a child that
2079 // might need an exit View Transition upon unmount.
2080 workInProgress.flags |= ViewTransitionStatic;
2081 if (enableViewTransitionParentEnterExit) {
2082 const props = workInProgress.pendingProps;
2083 if (
2084 props.parentEnter !== undefined ||
2085 props.parentExit !== undefined ||
2086 props.onParentEnter != null ||
2087 props.onParentExit != null ||
2088 props.onGestureParentEnter != null ||
2089 props.onGestureParentExit != null
2090 ) {
2091 workInProgress.flags |= ViewTransitionStaticParent;
2092 } else {
2093 workInProgress.flags &= ~ViewTransitionStaticParent;
2094 }
2095 }
2096 bubbleProperties(workInProgress);
2097 }
2098 return null;
2099 }
2100 case Throw: {
2101 if (!disableLegacyMode) {
2102 // Only Legacy Mode completes an errored node.
2103 return null;
2104 }
2105 }
2106 }
2107
2108 throw new Error(
2109 `Unknown unit of work tag (${workInProgress.tag}). This error is likely caused by a bug in ` +
2110 'React. Please file an issue.',
2111 );
2112 }
2113
2114 export {completeWork};