main
js 793 lines 20.7 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 {
11 Instance,
12 TextInstance,
13 ActivityInstance,
14 SuspenseInstance,
15 Container,
16 ChildSet,
17 FragmentInstanceType,
18 } from './ReactFiberConfig';
19 import type {Fiber, FiberRoot} from './ReactInternalTypes';
20
21 import {
22 HostRoot,
23 HostComponent,
24 HostHoistable,
25 HostSingleton,
26 HostText,
27 HostPortal,
28 DehydratedFragment,
29 } from './ReactWorkTags';
30 import {ContentReset, Placement} from './ReactFiberFlags';
31 import {
32 supportsMutation,
33 supportsResources,
34 supportsSingletons,
35 commitMount,
36 commitUpdate,
37 resetTextContent,
38 commitTextUpdate,
39 appendChild,
40 appendChildToContainer,
41 insertBefore,
42 insertInContainerBefore,
43 replaceContainerChildren,
44 hideDehydratedBoundary,
45 hideInstance,
46 hideTextInstance,
47 unhideDehydratedBoundary,
48 unhideInstance,
49 unhideTextInstance,
50 commitHydratedInstance,
51 commitHydratedContainer,
52 commitHydratedActivityInstance,
53 commitHydratedSuspenseInstance,
54 removeChildFromContainer,
55 removeChild,
56 acquireSingletonInstance,
57 releaseSingletonInstance,
58 isSingletonScope,
59 } from './ReactFiberConfig';
60 import {captureCommitPhaseError} from './ReactFiberWorkLoop';
61 import {trackHostMutation} from './ReactFiberMutationTracking';
62
63 import {runWithFiberInDEV} from './ReactCurrentFiber';
64 import {enableFragmentRefs} from 'shared/ReactFeatureFlags';
65 import {
66 commitNewChildToFragmentInstances,
67 getParentFragmentInstances,
68 } from './ReactFiberFragmentInstance';
69
70 export function commitHostMount(finishedWork: Fiber) {
71 const type = finishedWork.type;
72 const props = finishedWork.memoizedProps;
73 const instance: Instance = finishedWork.stateNode;
74 try {
75 if (__DEV__) {
76 runWithFiberInDEV(
77 finishedWork,
78 commitMount,
79 instance,
80 type,
81 props,
82 finishedWork,
83 );
84 } else {
85 commitMount(instance, type, props, finishedWork);
86 }
87 } catch (error) {
88 captureCommitPhaseError(finishedWork, finishedWork.return, error);
89 }
90 }
91
92 export function commitHostHydratedInstance(finishedWork: Fiber) {
93 const type = finishedWork.type;
94 const props = finishedWork.memoizedProps;
95 const instance: Instance = finishedWork.stateNode;
96 try {
97 if (__DEV__) {
98 runWithFiberInDEV(
99 finishedWork,
100 commitHydratedInstance,
101 instance,
102 type,
103 props,
104 finishedWork,
105 );
106 } else {
107 commitHydratedInstance(instance, type, props, finishedWork);
108 }
109 } catch (error) {
110 captureCommitPhaseError(finishedWork, finishedWork.return, error);
111 }
112 }
113
114 export function commitHostUpdate(
115 finishedWork: Fiber,
116 newProps: any,
117 oldProps: any,
118 ): void {
119 try {
120 if (__DEV__) {
121 runWithFiberInDEV(
122 finishedWork,
123 commitUpdate,
124 finishedWork.stateNode,
125 finishedWork.type,
126 oldProps,
127 newProps,
128 finishedWork,
129 );
130 } else {
131 commitUpdate(
132 finishedWork.stateNode,
133 finishedWork.type,
134 oldProps,
135 newProps,
136 finishedWork,
137 );
138 }
139 // Mutations are tracked manually from within commitUpdate.
140 } catch (error) {
141 captureCommitPhaseError(finishedWork, finishedWork.return, error);
142 }
143 }
144
145 export function commitHostTextUpdate(
146 finishedWork: Fiber,
147 newText: string,
148 oldText: string,
149 ) {
150 const textInstance: TextInstance = finishedWork.stateNode;
151 try {
152 if (__DEV__) {
153 runWithFiberInDEV(
154 finishedWork,
155 commitTextUpdate,
156 textInstance,
157 oldText,
158 newText,
159 );
160 } else {
161 commitTextUpdate(textInstance, oldText, newText);
162 }
163 trackHostMutation();
164 } catch (error) {
165 captureCommitPhaseError(finishedWork, finishedWork.return, error);
166 }
167 }
168
169 export function commitHostResetTextContent(finishedWork: Fiber) {
170 const instance: Instance = finishedWork.stateNode;
171 try {
172 if (__DEV__) {
173 runWithFiberInDEV(finishedWork, resetTextContent, instance);
174 } else {
175 resetTextContent(instance);
176 }
177 trackHostMutation();
178 } catch (error) {
179 captureCommitPhaseError(finishedWork, finishedWork.return, error);
180 }
181 }
182
183 export function commitShowHideSuspenseBoundary(node: Fiber, isHidden: boolean) {
184 try {
185 const instance = node.stateNode;
186 if (isHidden) {
187 if (__DEV__) {
188 runWithFiberInDEV(node, hideDehydratedBoundary, instance);
189 } else {
190 hideDehydratedBoundary(instance);
191 }
192 } else {
193 if (__DEV__) {
194 runWithFiberInDEV(node, unhideDehydratedBoundary, node.stateNode);
195 } else {
196 unhideDehydratedBoundary(node.stateNode);
197 }
198 }
199 } catch (error) {
200 captureCommitPhaseError(node, node.return, error);
201 }
202 }
203
204 export function commitShowHideHostInstance(node: Fiber, isHidden: boolean) {
205 try {
206 const instance = node.stateNode;
207 if (isHidden) {
208 if (__DEV__) {
209 runWithFiberInDEV(node, hideInstance, instance);
210 } else {
211 hideInstance(instance);
212 }
213 } else {
214 if (__DEV__) {
215 runWithFiberInDEV(
216 node,
217 unhideInstance,
218 node.stateNode,
219 node.memoizedProps,
220 );
221 } else {
222 unhideInstance(node.stateNode, node.memoizedProps);
223 }
224 }
225 } catch (error) {
226 captureCommitPhaseError(node, node.return, error);
227 }
228 }
229
230 export function commitShowHideHostTextInstance(node: Fiber, isHidden: boolean) {
231 try {
232 const instance = node.stateNode;
233 if (isHidden) {
234 if (__DEV__) {
235 runWithFiberInDEV(node, hideTextInstance, instance);
236 } else {
237 hideTextInstance(instance);
238 }
239 } else {
240 if (__DEV__) {
241 runWithFiberInDEV(
242 node,
243 unhideTextInstance,
244 instance,
245 node.memoizedProps,
246 );
247 } else {
248 unhideTextInstance(instance, node.memoizedProps);
249 }
250 }
251 trackHostMutation();
252 } catch (error) {
253 captureCommitPhaseError(node, node.return, error);
254 }
255 }
256
257 function isHostParent(fiber: Fiber): boolean {
258 return (
259 fiber.tag === HostComponent ||
260 fiber.tag === HostRoot ||
261 // $FlowFixMe[constant-condition]
262 (supportsResources ? fiber.tag === HostHoistable : false) ||
263 // $FlowFixMe[constant-condition]
264 (supportsSingletons
265 ? fiber.tag === HostSingleton && isSingletonScope(fiber.type)
266 : false) ||
267 fiber.tag === HostPortal
268 );
269 }
270
271 function getHostSibling(fiber: Fiber): ?Instance {
272 // We're going to search forward into the tree until we find a sibling host
273 // node. Unfortunately, if multiple insertions are done in a row we have to
274 // search past them. This leads to exponential search for the next sibling.
275 // TODO: Find a more efficient way to do this.
276 let node: Fiber = fiber;
277 siblings: while (true) {
278 // If we didn't find anything, let's try the next sibling.
279 while (node.sibling === null) {
280 if (node.return === null || isHostParent(node.return)) {
281 // If we pop out of the root or hit the parent the fiber we are the
282 // last sibling.
283 return null;
284 }
285 // $FlowFixMe[incompatible-type] found when upgrading Flow
286 node = node.return;
287 }
288 node.sibling.return = node.return;
289 node = node.sibling;
290 while (
291 node.tag !== HostComponent &&
292 node.tag !== HostText &&
293 node.tag !== DehydratedFragment
294 ) {
295 // If this is a host singleton we go deeper if it's not a special
296 // singleton scope. If it is a singleton scope we skip over it because
297 // you only insert against this scope when you are already inside of it
298 if (
299 // $FlowFixMe[constant-condition]
300 supportsSingletons &&
301 node.tag === HostSingleton &&
302 isSingletonScope(node.type)
303 ) {
304 continue siblings;
305 }
306
307 // If it is not host node and, we might have a host node inside it.
308 // Try to search down until we find one.
309 if (node.flags & Placement) {
310 // If we don't have a child, try the siblings instead.
311 continue siblings;
312 }
313 // If we don't have a child, try the siblings instead.
314 // We also skip portals because they are not part of this host tree.
315 if (node.child === null || node.tag === HostPortal) {
316 continue siblings;
317 } else {
318 node.child.return = node;
319 node = node.child;
320 }
321 }
322 // Check if this host node is stable or about to be placed.
323 if (!(node.flags & Placement)) {
324 // Found it!
325 return node.stateNode;
326 }
327 }
328 }
329
330 function insertOrAppendPlacementNodeIntoContainer(
331 node: Fiber,
332 before: ?Instance,
333 parent: Container,
334 parentFragmentInstances: null | Array<FragmentInstanceType>,
335 ): void {
336 const {tag} = node;
337 const isHost = tag === HostComponent || tag === HostText;
338 if (isHost) {
339 const stateNode = node.stateNode;
340 if (before) {
341 insertInContainerBefore(parent, stateNode, before);
342 } else {
343 appendChildToContainer(parent, stateNode);
344 }
345 if (enableFragmentRefs) {
346 commitNewChildToFragmentInstances(node, parentFragmentInstances);
347 }
348 trackHostMutation();
349 return;
350 } else if (tag === HostPortal) {
351 // If the insertion itself is a portal, then we don't want to traverse
352 // down its children. Instead, we'll get insertions from each child in
353 // the portal directly.
354 return;
355 }
356
357 // $FlowFixMe[constant-condition]
358 if (supportsSingletons ? tag === HostSingleton : false) {
359 if (enableFragmentRefs) {
360 // The singleton is the fragment child. Its own children are not
361 // attributed to the fragment instances above it.
362 commitNewChildToFragmentInstances(node, parentFragmentInstances);
363 parentFragmentInstances = null;
364 }
365 if (isSingletonScope(node.type)) {
366 // This singleton is the parent of deeper nodes and needs to become
367 // the parent for child insertions and appends
368 parent = node.stateNode;
369 before = null;
370 }
371 }
372
373 const child = node.child;
374 if (child !== null) {
375 insertOrAppendPlacementNodeIntoContainer(
376 child,
377 before,
378 parent,
379 parentFragmentInstances,
380 );
381 let sibling = child.sibling;
382 while (sibling !== null) {
383 insertOrAppendPlacementNodeIntoContainer(
384 sibling,
385 before,
386 parent,
387 parentFragmentInstances,
388 );
389 sibling = sibling.sibling;
390 }
391 }
392 }
393
394 function insertOrAppendPlacementNode(
395 node: Fiber,
396 before: ?Instance,
397 parent: Instance,
398 parentFragmentInstances: null | Array<FragmentInstanceType>,
399 ): void {
400 const {tag} = node;
401 const isHost = tag === HostComponent || tag === HostText;
402 if (isHost) {
403 const stateNode = node.stateNode;
404 if (before) {
405 insertBefore(parent, stateNode, before);
406 } else {
407 appendChild(parent, stateNode);
408 }
409 if (enableFragmentRefs) {
410 commitNewChildToFragmentInstances(node, parentFragmentInstances);
411 }
412 trackHostMutation();
413 return;
414 } else if (tag === HostPortal) {
415 // If the insertion itself is a portal, then we don't want to traverse
416 // down its children. Instead, we'll get insertions from each child in
417 // the portal directly.
418 return;
419 }
420
421 // $FlowFixMe[constant-condition]
422 if (supportsSingletons ? tag === HostSingleton : false) {
423 if (enableFragmentRefs) {
424 // The singleton is the fragment child. Its own children are not
425 // attributed to the fragment instances above it.
426 commitNewChildToFragmentInstances(node, parentFragmentInstances);
427 parentFragmentInstances = null;
428 }
429 if (isSingletonScope(node.type)) {
430 // This singleton is the parent of deeper nodes and needs to become
431 // the parent for child insertions and appends
432 parent = node.stateNode;
433 }
434 }
435
436 const child = node.child;
437 if (child !== null) {
438 insertOrAppendPlacementNode(child, before, parent, parentFragmentInstances);
439 let sibling = child.sibling;
440 while (sibling !== null) {
441 insertOrAppendPlacementNode(
442 sibling,
443 before,
444 parent,
445 parentFragmentInstances,
446 );
447 sibling = sibling.sibling;
448 }
449 }
450 }
451
452 function commitPlacement(finishedWork: Fiber): void {
453 // Recursively insert all host nodes into the parent.
454 let hostParentFiber;
455 let parentFiber = finishedWork.return;
456 while (parentFiber !== null) {
457 if (isHostParent(parentFiber)) {
458 hostParentFiber = parentFiber;
459 break;
460 }
461 parentFiber = parentFiber.return;
462 }
463 // Fragment ancestry is collected separately so portals remain placement
464 // parents while fragment bookkeeping still walks past them to ancestors.
465 const parentFragmentInstances = enableFragmentRefs
466 ? getParentFragmentInstances(finishedWork)
467 : null;
468
469 // $FlowFixMe[constant-condition]
470 if (!supportsMutation) {
471 if (enableFragmentRefs) {
472 commitImmutablePlacementNodeToFragmentInstances(
473 finishedWork,
474 parentFragmentInstances,
475 );
476 }
477 return;
478 }
479
480 if (hostParentFiber == null) {
481 throw new Error(
482 'Expected to find a host parent. This error is likely caused by a bug ' +
483 'in React. Please file an issue.',
484 );
485 }
486
487 switch (hostParentFiber.tag) {
488 case HostSingleton: {
489 // $FlowFixMe[constant-condition]
490 if (supportsSingletons) {
491 const parent: Instance = hostParentFiber.stateNode;
492 const before = getHostSibling(finishedWork);
493 // We only have the top Fiber that was inserted but we need to recurse down its
494 // children to find all the terminal nodes.
495 insertOrAppendPlacementNode(
496 finishedWork,
497 before,
498 parent,
499 parentFragmentInstances,
500 );
501 break;
502 }
503 // Fall through
504 }
505 case HostComponent: {
506 const parent: Instance = hostParentFiber.stateNode;
507 if (hostParentFiber.flags & ContentReset) {
508 // Reset the text content of the parent before doing any insertions
509 resetTextContent(parent);
510 // Clear ContentReset from the effect tag
511 hostParentFiber.flags &= ~ContentReset;
512 }
513
514 const before = getHostSibling(finishedWork);
515 // We only have the top Fiber that was inserted but we need to recurse down its
516 // children to find all the terminal nodes.
517 insertOrAppendPlacementNode(
518 finishedWork,
519 before,
520 parent,
521 parentFragmentInstances,
522 );
523 break;
524 }
525 case HostRoot:
526 case HostPortal: {
527 const parent: Container = hostParentFiber.stateNode.containerInfo;
528 const before = getHostSibling(finishedWork);
529 insertOrAppendPlacementNodeIntoContainer(
530 finishedWork,
531 before,
532 parent,
533 parentFragmentInstances,
534 );
535 break;
536 }
537 default:
538 throw new Error(
539 'Invalid host parent fiber. This error is likely caused by a bug ' +
540 'in React. Please file an issue.',
541 );
542 }
543 }
544
545 function commitImmutablePlacementNodeToFragmentInstances(
546 finishedWork: Fiber,
547 parentFragmentInstances: null | Array<FragmentInstanceType>,
548 ): void {
549 if (!enableFragmentRefs) {
550 return;
551 }
552 const isHost =
553 finishedWork.tag === HostComponent ||
554 // $FlowFixMe[constant-condition]
555 (supportsSingletons ? finishedWork.tag === HostSingleton : false);
556 if (isHost) {
557 // A singleton is the fragment child itself, so its own children are
558 // not attributed to the fragment instances above it.
559 commitNewChildToFragmentInstances(finishedWork, parentFragmentInstances);
560 return;
561 } else if (finishedWork.tag === HostPortal) {
562 // If the insertion itself is a portal, then we don't want to traverse
563 // down its children. Instead, we'll get insertions from each child in
564 // the portal directly.
565 return;
566 }
567
568 const child = finishedWork.child;
569 if (child !== null) {
570 commitImmutablePlacementNodeToFragmentInstances(
571 child,
572 parentFragmentInstances,
573 );
574 let sibling = child.sibling;
575 while (sibling !== null) {
576 commitImmutablePlacementNodeToFragmentInstances(
577 sibling,
578 parentFragmentInstances,
579 );
580 sibling = sibling.sibling;
581 }
582 }
583 }
584
585 export function commitHostPlacement(finishedWork: Fiber) {
586 try {
587 if (__DEV__) {
588 runWithFiberInDEV(finishedWork, commitPlacement, finishedWork);
589 } else {
590 commitPlacement(finishedWork);
591 }
592 } catch (error) {
593 captureCommitPhaseError(finishedWork, finishedWork.return, error);
594 }
595 }
596
597 export function commitHostRemoveChildFromContainer(
598 deletedFiber: Fiber,
599 nearestMountedAncestor: Fiber,
600 parentContainer: Container,
601 hostInstance: Instance | TextInstance,
602 ) {
603 try {
604 if (__DEV__) {
605 runWithFiberInDEV(
606 deletedFiber,
607 removeChildFromContainer,
608 parentContainer,
609 hostInstance,
610 );
611 } else {
612 removeChildFromContainer(parentContainer, hostInstance);
613 }
614 trackHostMutation();
615 } catch (error) {
616 captureCommitPhaseError(deletedFiber, nearestMountedAncestor, error);
617 }
618 }
619
620 export function commitHostRemoveChild(
621 deletedFiber: Fiber,
622 nearestMountedAncestor: Fiber,
623 parentInstance: Instance,
624 hostInstance: Instance | TextInstance,
625 ) {
626 try {
627 if (__DEV__) {
628 runWithFiberInDEV(
629 deletedFiber,
630 removeChild,
631 parentInstance,
632 hostInstance,
633 );
634 } else {
635 removeChild(parentInstance, hostInstance);
636 }
637 trackHostMutation();
638 } catch (error) {
639 captureCommitPhaseError(deletedFiber, nearestMountedAncestor, error);
640 }
641 }
642
643 export function commitHostRootContainerChildren(
644 root: FiberRoot,
645 finishedWork: Fiber,
646 ) {
647 const containerInfo = root.containerInfo;
648 const pendingChildren = root.pendingChildren;
649 try {
650 if (__DEV__) {
651 runWithFiberInDEV(
652 finishedWork,
653 replaceContainerChildren,
654 containerInfo,
655 pendingChildren,
656 );
657 } else {
658 replaceContainerChildren(containerInfo, pendingChildren);
659 }
660 trackHostMutation();
661 } catch (error) {
662 captureCommitPhaseError(finishedWork, finishedWork.return, error);
663 }
664 }
665
666 export function commitHostPortalContainerChildren(
667 portal: {
668 containerInfo: Container,
669 pendingChildren: ChildSet,
670 ...
671 },
672 finishedWork: Fiber,
673 pendingChildren: ChildSet,
674 ) {
675 const containerInfo = portal.containerInfo;
676 try {
677 if (__DEV__) {
678 runWithFiberInDEV(
679 finishedWork,
680 replaceContainerChildren,
681 containerInfo,
682 pendingChildren,
683 );
684 } else {
685 replaceContainerChildren(containerInfo, pendingChildren);
686 }
687 } catch (error) {
688 captureCommitPhaseError(finishedWork, finishedWork.return, error);
689 }
690 }
691
692 export function commitHostHydratedContainer(
693 root: FiberRoot,
694 finishedWork: Fiber,
695 ) {
696 try {
697 if (__DEV__) {
698 runWithFiberInDEV(
699 finishedWork,
700 commitHydratedContainer,
701 root.containerInfo,
702 );
703 } else {
704 commitHydratedContainer(root.containerInfo);
705 }
706 } catch (error) {
707 captureCommitPhaseError(finishedWork, finishedWork.return, error);
708 }
709 }
710
711 export function commitHostHydratedActivity(
712 activityInstance: ActivityInstance,
713 finishedWork: Fiber,
714 ) {
715 try {
716 if (__DEV__) {
717 runWithFiberInDEV(
718 finishedWork,
719 commitHydratedActivityInstance,
720 activityInstance,
721 );
722 } else {
723 commitHydratedActivityInstance(activityInstance);
724 }
725 } catch (error) {
726 captureCommitPhaseError(finishedWork, finishedWork.return, error);
727 }
728 }
729
730 export function commitHostHydratedSuspense(
731 suspenseInstance: SuspenseInstance,
732 finishedWork: Fiber,
733 ) {
734 try {
735 if (__DEV__) {
736 runWithFiberInDEV(
737 finishedWork,
738 commitHydratedSuspenseInstance,
739 suspenseInstance,
740 );
741 } else {
742 commitHydratedSuspenseInstance(suspenseInstance);
743 }
744 } catch (error) {
745 captureCommitPhaseError(finishedWork, finishedWork.return, error);
746 }
747 }
748
749 export function commitHostSingletonAcquisition(finishedWork: Fiber) {
750 const singleton = finishedWork.stateNode;
751 const props = finishedWork.memoizedProps;
752
753 try {
754 // This was a new mount, acquire the DOM instance and set initial properties
755 if (__DEV__) {
756 runWithFiberInDEV(
757 finishedWork,
758 acquireSingletonInstance,
759 finishedWork.type,
760 props,
761 singleton,
762 finishedWork,
763 );
764 } else {
765 acquireSingletonInstance(
766 finishedWork.type,
767 props,
768 singleton,
769 finishedWork,
770 );
771 }
772 } catch (error) {
773 captureCommitPhaseError(finishedWork, finishedWork.return, error);
774 }
775 }
776
777 export function commitHostSingletonRelease(releasingWork: Fiber) {
778 if (__DEV__) {
779 runWithFiberInDEV(
780 releasingWork,
781 releaseSingletonInstance,
782 releasingWork.stateNode,
783 releasingWork.type,
784 releasingWork.memoizedProps,
785 );
786 } else {
787 releaseSingletonInstance(
788 releasingWork.stateNode,
789 releasingWork.type,
790 releasingWork.memoizedProps,
791 );
792 }
793 }