main
js 973 lines 26.9 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 InspectorData,
12 TouchedViewDataAtPoint,
13 ViewConfig,
14 } from './ReactNativeTypes';
15 import {dispatchEvent} from './ReactFabricEventEmitter';
16 import {
17 NoEventPriority,
18 DefaultEventPriority,
19 DiscreteEventPriority,
20 ContinuousEventPriority,
21 IdleEventPriority,
22 type EventPriority,
23 } from 'react-reconciler/src/ReactEventPriorities';
24 import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
25 import {HostText} from 'react-reconciler/src/ReactWorkTags';
26 import {
27 getFragmentParentInstanceOrContainerFiber,
28 traverseFragmentInstancesAndTextInstances,
29 } from 'react-reconciler/src/ReactFiberTreeReflection';
30
31 // Modules provided by RN:
32 import {
33 ReactNativeViewConfigRegistry,
34 deepFreezeAndThrowOnMutationInDev,
35 createPublicInstance,
36 createPublicTextInstance,
37 createAttributePayload,
38 diffAttributePayloads,
39 type PublicInstance as ReactNativePublicInstance,
40 type PublicTextInstance,
41 type PublicRootInstance,
42 } from 'react-native/react-private-interface';
43 import {
44 enableFragmentRefsInstanceHandles,
45 enableFragmentRefsTextNodes,
46 } from 'shared/ReactFeatureFlags';
47
48 const {
49 createNode,
50 cloneNodeWithNewChildren,
51 cloneNodeWithNewChildrenAndProps,
52 cloneNodeWithNewProps,
53 createChildSet: createChildNodeSet,
54 appendChild: appendChildNode,
55 appendChildToSet: appendChildNodeToSet,
56 completeRoot,
57 registerEventHandler,
58 unstable_DefaultEventPriority: FabricDefaultPriority,
59 unstable_DiscreteEventPriority: FabricDiscretePriority,
60 unstable_ContinuousEventPriority: FabricContinuousPriority,
61 unstable_IdleEventPriority: FabricIdlePriority,
62 unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
63 suspendOnActiveViewTransition: fabricSuspendOnActiveViewTransition,
64 } = nativeFabricUIManager;
65
66 import {getClosestInstanceFromNode} from './ReactFabricComponentTree';
67 import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';
68
69 import {
70 getInspectorDataForViewAtPoint,
71 getInspectorDataForInstance,
72 } from './ReactNativeFiberInspector';
73
74 import {passChildrenWhenCloningPersistedNodes} from 'shared/ReactFeatureFlags';
75 import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';
76 import type {ReactContext} from 'shared/ReactTypes';
77
78 export {default as rendererVersion} from 'shared/ReactVersion'; // TODO: Consider exporting the react-native version.
79 export const rendererPackageName = 'react-native-renderer';
80 export const extraDevToolsConfig = {
81 getInspectorDataForInstance,
82 getInspectorDataForViewAtPoint,
83 };
84
85 const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
86
87 // Counter for uniquely identifying views.
88 // % 10 === 1 means it is a rootTag.
89 // % 2 === 0 means it is a Fabric tag.
90 // This means that they never overlap.
91 let nextReactTag = 2;
92 export function allocateTag(): number {
93 const tag = nextReactTag;
94 nextReactTag += 2;
95 return tag;
96 }
97
98 type InternalInstanceHandle = Object;
99
100 export type Type = string;
101 export type Props = Object;
102 export type Instance = {
103 // Reference to the shadow node.
104 node: Node,
105 // This object is shared by all the clones of the instance.
106 // We use it to access their shared public instance (exposed through refs)
107 // and to access its committed state for events, etc.
108 canonical: {
109 nativeTag: number,
110 viewConfig: ViewConfig,
111 currentProps: Props,
112 // Reference to the React handle (the fiber)
113 internalInstanceHandle: InternalInstanceHandle,
114 // Exposed through refs. Potentially lazily created.
115 publicInstance: PublicInstance | null,
116 // This is only necessary to lazily create `publicInstance`.
117 // Will be set to `null` after that is created.
118 publicRootInstance?: PublicRootInstance | null,
119 },
120 };
121 export type TextInstance = {
122 // Reference to the shadow node.
123 node: Node,
124 // Text instances are never cloned, so we don't need to keep a "canonical"
125 // reference to make sure all clones of the instance point to the same values.
126 publicInstance?: PublicTextInstance,
127 };
128 export type HydratableInstance = Instance | TextInstance;
129 export type PublicInstance = ReactNativePublicInstance;
130 type PublicInstanceWithFragmentHandles = PublicInstance & {
131 reactFragments?: Set<FragmentInstanceType>,
132 };
133 export type Container = {
134 containerTag: number,
135 publicInstance: PublicRootInstance | null,
136 };
137 export type ChildSet = Object | Array<Node>;
138 export type HostContext = $ReadOnly<{
139 isInAParentText: boolean,
140 }>;
141 export type UpdatePayload = Object;
142
143 export type TimeoutHandle = TimeoutID;
144 export type NoTimeout = -1;
145 export type TransitionStatus = mixed;
146
147 export type RendererInspectionConfig = $ReadOnly<{
148 getInspectorDataForInstance?: (instance: Fiber | null) => InspectorData,
149 getInspectorDataForViewAtPoint?: (
150 inspectedView: Object,
151 locationX: number,
152 locationY: number,
153 callback: (viewData: TouchedViewDataAtPoint) => mixed,
154 ) => void,
155 }>;
156
157 // TODO: Remove this conditional once all changes have propagated.
158 // $FlowFixMe[constant-condition]
159 if (registerEventHandler) {
160 /**
161 * Register the event emitter with the native bridge
162 */
163 registerEventHandler(dispatchEvent);
164 }
165
166 export * from 'react-reconciler/src/ReactFiberConfigWithNoMutation';
167 export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
168 export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
169 export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
170 export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
171 export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
172 export * from './ReactFiberConfigFabricWithViewTransition';
173
174 export function appendInitialChild(
175 parentInstance: Instance,
176 child: Instance | TextInstance,
177 ): void {
178 appendChildNode(parentInstance.node, child.node);
179 }
180
181 const PROD_HOST_CONTEXT: HostContext = {isInAParentText: true};
182
183 export function createInstance(
184 type: string,
185 props: Props,
186 rootContainerInstance: Container,
187 hostContext: HostContext,
188 internalInstanceHandle: InternalInstanceHandle,
189 ): Instance {
190 const tag = allocateTag();
191
192 const viewConfig = getViewConfigForType(type);
193
194 if (__DEV__) {
195 for (const key in viewConfig.validAttributes) {
196 if (props.hasOwnProperty(key)) {
197 deepFreezeAndThrowOnMutationInDev(props[key]);
198 }
199 }
200 }
201
202 const updatePayload = createAttributePayload(
203 props,
204 viewConfig.validAttributes,
205 );
206
207 const node = createNode(
208 tag, // reactTag
209 viewConfig.uiViewClassName, // viewName
210 rootContainerInstance.containerTag, // rootTag
211 updatePayload, // props
212 internalInstanceHandle, // internalInstanceHandle
213 );
214
215 return {
216 node: node,
217 canonical: {
218 nativeTag: tag,
219 viewConfig,
220 currentProps: props,
221 internalInstanceHandle,
222 publicInstance: null,
223 publicRootInstance: rootContainerInstance.publicInstance,
224 },
225 };
226 }
227
228 export function createTextInstance(
229 text: string,
230 rootContainerInstance: Container,
231 hostContext: HostContext,
232 internalInstanceHandle: InternalInstanceHandle,
233 ): TextInstance {
234 if (__DEV__) {
235 if (!hostContext.isInAParentText) {
236 console.error('Text strings must be rendered within a <Text> component.');
237 }
238 }
239
240 const tag = allocateTag();
241
242 const node = createNode(
243 tag, // reactTag
244 'RCTRawText', // viewName
245 rootContainerInstance.containerTag, // rootTag
246 {text: text}, // props
247 internalInstanceHandle, // instance handle
248 );
249
250 return {
251 node: node,
252 };
253 }
254
255 export function finalizeInitialChildren(
256 parentInstance: Instance,
257 type: string,
258 props: Props,
259 hostContext: HostContext,
260 ): boolean {
261 return false;
262 }
263
264 export function getRootHostContext(
265 rootContainerInstance: Container,
266 ): HostContext {
267 if (__DEV__) {
268 return {isInAParentText: false};
269 }
270
271 return PROD_HOST_CONTEXT;
272 }
273
274 export function getChildHostContext(
275 parentHostContext: HostContext,
276 type: string,
277 ): HostContext {
278 if (__DEV__) {
279 const prevIsInAParentText = parentHostContext.isInAParentText;
280 const isInAParentText =
281 type === 'AndroidTextInput' || // Android
282 type === 'RCTMultilineTextInputView' || // iOS
283 type === 'RCTSelectableText' ||
284 type === 'RCTSinglelineTextInputView' || // iOS
285 type === 'RCTText' ||
286 type === 'RCTVirtualText';
287
288 // TODO: If this is an offscreen host container, we should reuse the
289 // parent context.
290
291 if (prevIsInAParentText !== isInAParentText) {
292 return {isInAParentText};
293 }
294 }
295
296 return parentHostContext;
297 }
298
299 export function getPublicInstance(instance: Instance): null | PublicInstance {
300 if (instance.canonical != null) {
301 if (instance.canonical.publicInstance == null) {
302 instance.canonical.publicInstance = createPublicInstance(
303 instance.canonical.nativeTag,
304 instance.canonical.viewConfig,
305 instance.canonical.internalInstanceHandle,
306 instance.canonical.publicRootInstance ?? null,
307 );
308 // This was only necessary to create the public instance.
309 instance.canonical.publicRootInstance = null;
310 }
311
312 return instance.canonical.publicInstance;
313 }
314
315 // Handle root containers
316 if (instance.containerInfo != null) {
317 return instance.containerInfo.publicInstance;
318 }
319
320 return null;
321 }
322
323 function getPublicTextInstance(
324 textInstance: TextInstance,
325 internalInstanceHandle: InternalInstanceHandle,
326 ): PublicTextInstance {
327 if (textInstance.publicInstance == null) {
328 textInstance.publicInstance = createPublicTextInstance(
329 internalInstanceHandle,
330 );
331 }
332 return textInstance.publicInstance;
333 }
334
335 export function getPublicInstanceFromInternalInstanceHandle(
336 internalInstanceHandle: InternalInstanceHandle,
337 ): null | PublicInstance | PublicTextInstance {
338 const instance = internalInstanceHandle.stateNode;
339
340 // React resets all the fields in the fiber when the component is unmounted
341 // to prevent memory leaks.
342 if (instance == null) {
343 return null;
344 }
345
346 if (internalInstanceHandle.tag === HostText) {
347 const textInstance: TextInstance = instance;
348 return getPublicTextInstance(textInstance, internalInstanceHandle);
349 }
350
351 const elementInstance: Instance = internalInstanceHandle.stateNode;
352 return getPublicInstance(elementInstance);
353 }
354
355 function getPublicInstanceFromHostFiber(fiber: Fiber): PublicInstance {
356 const publicInstance = getPublicInstance(fiber.stateNode);
357 if (publicInstance === null) {
358 throw new Error('Expected to find a host node. This is a bug in React.');
359 }
360 return publicInstance;
361 }
362
363 export function prepareForCommit(containerInfo: Container): null | Object {
364 // Noop
365 return null;
366 }
367
368 export function resetAfterCommit(containerInfo: Container): void {
369 // Noop
370 }
371
372 export function shouldSetTextContent(type: string, props: Props): boolean {
373 // TODO (bvaughn) Revisit this decision.
374 // Always returning false simplifies the createInstance() implementation,
375 // But creates an additional child Fiber for raw text children.
376 // No additional native views are created though.
377 // It's not clear to me which is better so I'm deferring for now.
378 // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
379 return false;
380 }
381
382 let currentUpdatePriority: EventPriority = NoEventPriority;
383 export function setCurrentUpdatePriority(newPriority: EventPriority): void {
384 currentUpdatePriority = newPriority;
385 }
386
387 export function getCurrentUpdatePriority(): EventPriority {
388 return currentUpdatePriority;
389 }
390
391 export function resolveUpdatePriority(): EventPriority {
392 if (currentUpdatePriority !== NoEventPriority) {
393 return currentUpdatePriority;
394 }
395
396 // $FlowFixMe[constant-condition]
397 const currentEventPriority = fabricGetCurrentEventPriority
398 ? fabricGetCurrentEventPriority()
399 : null;
400
401 if (currentEventPriority != null) {
402 switch (currentEventPriority) {
403 case FabricDiscretePriority:
404 return DiscreteEventPriority;
405 case FabricContinuousPriority:
406 return ContinuousEventPriority;
407 case FabricIdlePriority:
408 return IdleEventPriority;
409 case FabricDefaultPriority:
410 default:
411 return DefaultEventPriority;
412 }
413 }
414
415 return DefaultEventPriority;
416 }
417
418 let schedulerEvent: void | Event = undefined;
419 export function trackSchedulerEvent(): void {
420 schedulerEvent = global.event;
421 }
422
423 function getEventType(event: Event): null | string {
424 if (event.type) {
425 return event.type;
426 }
427
428 // Legacy implementation. RN does not define the `type` property on the event object yet.
429 // $FlowExpectedError[prop-missing]
430 const dispatchConfig = event.dispatchConfig;
431 if (
432 dispatchConfig == null ||
433 dispatchConfig.phasedRegistrationNames == null
434 ) {
435 return null;
436 }
437
438 const rawEventType =
439 dispatchConfig.phasedRegistrationNames.bubbled ||
440 dispatchConfig.phasedRegistrationNames.captured;
441 if (!rawEventType) {
442 return null;
443 }
444
445 if (rawEventType.startsWith('on')) {
446 return rawEventType.slice(2).toLowerCase();
447 }
448
449 return rawEventType.toLowerCase();
450 }
451
452 export function resolveEventType(): null | string {
453 const event = global.event;
454 return event && event !== schedulerEvent ? getEventType(event) : null;
455 }
456
457 export function resolveEventTimeStamp(): number {
458 const event = global.event;
459 return event && event !== schedulerEvent ? event.timeStamp : -1.1;
460 }
461
462 export function shouldAttemptEagerTransition(): boolean {
463 return false;
464 }
465
466 // The Fabric renderer is secondary to the existing React Native renderer.
467 export const isPrimaryRenderer = false;
468
469 // The Fabric renderer shouldn't trigger missing act() warnings
470 export const warnsIfNotActing = false;
471
472 export const scheduleTimeout = setTimeout;
473 export const cancelTimeout = clearTimeout;
474 export const noTimeout: -1 = -1;
475
476 // -------------------
477 // Persistence
478 // -------------------
479
480 export const supportsPersistence = true;
481
482 export function cloneInstance(
483 instance: Instance,
484 type: string,
485 oldProps: Props,
486 newProps: Props,
487 keepChildren: boolean,
488 newChildSet: ?ChildSet,
489 ): Instance {
490 const viewConfig = instance.canonical.viewConfig;
491 const updatePayload = diffAttributePayloads(
492 oldProps,
493 newProps,
494 viewConfig.validAttributes,
495 );
496 // TODO: If the event handlers have changed, we need to update the current props
497 // in the commit phase but there is no host config hook to do it yet.
498 // So instead we hack it by updating it in the render phase.
499 instance.canonical.currentProps = newProps;
500
501 const node = instance.node;
502 let clone;
503 if (keepChildren) {
504 if (updatePayload !== null) {
505 clone = cloneNodeWithNewProps(node, updatePayload);
506 } else {
507 // No changes
508 return instance;
509 }
510 } else {
511 // If passChildrenWhenCloningPersistedNodes is enabled, children will be non-null
512 if (newChildSet != null) {
513 if (updatePayload !== null) {
514 clone = cloneNodeWithNewChildrenAndProps(
515 node,
516 newChildSet,
517 updatePayload,
518 );
519 } else {
520 clone = cloneNodeWithNewChildren(node, newChildSet);
521 }
522 } else {
523 if (updatePayload !== null) {
524 clone = cloneNodeWithNewChildrenAndProps(node, updatePayload);
525 } else {
526 clone = cloneNodeWithNewChildren(node);
527 }
528 }
529 }
530
531 return {
532 node: clone,
533 canonical: instance.canonical,
534 };
535 }
536
537 export function cloneHiddenInstance(
538 instance: Instance,
539 type: string,
540 props: Props,
541 ): Instance {
542 const viewConfig = instance.canonical.viewConfig;
543 const node = instance.node;
544 const updatePayload = createAttributePayload(
545 {style: {display: 'none'}},
546 viewConfig.validAttributes,
547 );
548 return {
549 node: cloneNodeWithNewProps(node, updatePayload),
550 canonical: instance.canonical,
551 };
552 }
553
554 export function cloneHiddenTextInstance(
555 instance: Instance,
556 text: string,
557 ): TextInstance {
558 throw new Error('Not yet implemented.');
559 }
560
561 export function createContainerChildSet(): ChildSet {
562 if (passChildrenWhenCloningPersistedNodes) {
563 return [];
564 } else {
565 return createChildNodeSet();
566 }
567 }
568
569 export function appendChildToContainerChildSet(
570 childSet: ChildSet,
571 child: Instance | TextInstance,
572 ): void {
573 if (passChildrenWhenCloningPersistedNodes) {
574 childSet.push(child.node);
575 } else {
576 appendChildNodeToSet(childSet, child.node);
577 }
578 }
579
580 export function finalizeContainerChildren(
581 container: Container,
582 newChildren: ChildSet,
583 ): void {
584 // Noop - children will be replaced in replaceContainerChildren
585 }
586
587 export function replaceContainerChildren(
588 container: Container,
589 newChildren: ChildSet,
590 ): void {
591 completeRoot(container.containerTag, newChildren);
592 }
593
594 export {getClosestInstanceFromNode as getInstanceFromNode};
595
596 export function beforeActiveInstanceBlur(
597 internalInstanceHandle: InternalInstanceHandle,
598 ) {
599 // noop
600 }
601
602 export function afterActiveInstanceBlur() {
603 // noop
604 }
605
606 export function preparePortalMount(portalInstance: Instance): void {
607 // noop
608 }
609
610 export function detachDeletedInstance(node: Instance): void {
611 // noop
612 }
613
614 export function requestPostPaintCallback(callback: (time: number) => void) {
615 // noop
616 }
617
618 export function maySuspendCommit(type: Type, props: Props): boolean {
619 return false;
620 }
621
622 export function maySuspendCommitOnUpdate(
623 type: Type,
624 oldProps: Props,
625 newProps: Props,
626 ): boolean {
627 return false;
628 }
629
630 export function maySuspendCommitInSyncRender(
631 type: Type,
632 props: Props,
633 ): boolean {
634 return false;
635 }
636
637 export function preloadInstance(
638 instance: Instance,
639 type: Type,
640 props: Props,
641 ): boolean {
642 return true;
643 }
644
645 export opaque type SuspendedState = null;
646
647 export function startSuspendingCommit(): SuspendedState {
648 return null;
649 }
650
651 export function suspendInstance(
652 state: SuspendedState,
653 instance: Instance,
654 type: Type,
655 props: Props,
656 ): void {}
657
658 export function suspendOnActiveViewTransition(
659 state: SuspendedState,
660 container: Container,
661 ): void {
662 if (fabricSuspendOnActiveViewTransition != null) {
663 fabricSuspendOnActiveViewTransition();
664 }
665 }
666
667 export function waitForCommitToBeReady(
668 state: SuspendedState,
669 timeoutOffset: number,
670 ): null {
671 return null;
672 }
673
674 export function getSuspendedCommitReason(
675 state: SuspendedState,
676 rootContainer: Container,
677 ): null | string {
678 return null;
679 }
680
681 export type FragmentInstanceType = {
682 _fragmentFiber: Fiber,
683 _observers: null | Set<IntersectionObserver>,
684 observeUsing: (observer: IntersectionObserver) => void,
685 unobserveUsing: (observer: IntersectionObserver) => void,
686 compareDocumentPosition: (otherNode: PublicInstance) => number,
687 getRootNode(getRootNodeOptions?: {
688 composed: boolean,
689 }): Node | FragmentInstanceType,
690 getClientRects: () => Array<DOMRect>,
691 };
692
693 function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) {
694 this._fragmentFiber = fragmentFiber;
695 this._observers = null;
696 }
697
698 // $FlowFixMe[prop-missing]
699 FragmentInstance.prototype.observeUsing = function (
700 this: FragmentInstanceType,
701 observer: IntersectionObserver,
702 ): void {
703 if (this._observers === null) {
704 this._observers = new Set();
705 }
706 this._observers.add(observer);
707 traverseFragmentInstancesAndTextInstances(
708 this._fragmentFiber,
709 observeChild,
710 observer,
711 );
712 };
713 function observeChild(child: Fiber, observer: IntersectionObserver) {
714 // $FlowFixMe[incompatible-type]
715 const publicInstance = getPublicInstanceFromHostFiber(child);
716 // $FlowFixMe[incompatible-type] DOM types expect Element
717 observer.observe(publicInstance);
718 return false;
719 }
720 // $FlowFixMe[prop-missing]
721 FragmentInstance.prototype.unobserveUsing = function (
722 this: FragmentInstanceType,
723 observer: IntersectionObserver,
724 ): void {
725 const observers = this._observers;
726 if (observers === null || !observers.has(observer)) {
727 if (__DEV__) {
728 console.error(
729 'You are calling unobserveUsing() with an observer that is not being observed with this fragment ' +
730 'instance. First attach the observer with observeUsing()',
731 );
732 }
733 } else {
734 observers.delete(observer);
735 traverseFragmentInstancesAndTextInstances(
736 this._fragmentFiber,
737 unobserveChild,
738 observer,
739 );
740 }
741 };
742 function unobserveChild(child: Fiber, observer: IntersectionObserver) {
743 // $FlowFixMe[incompatible-type]
744 const publicInstance = getPublicInstanceFromHostFiber(child);
745 // $FlowFixMe[incompatible-type] DOM types expect Element
746 observer.unobserve(publicInstance);
747 return false;
748 }
749
750 // $FlowFixMe[prop-missing]
751 FragmentInstance.prototype.compareDocumentPosition = function (
752 this: FragmentInstanceType,
753 otherNode: PublicInstance,
754 ): number {
755 const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
756 this._fragmentFiber,
757 );
758 if (parentHostFiber === null) {
759 return Node.DOCUMENT_POSITION_DISCONNECTED;
760 }
761 const children: Array<Fiber> = [];
762 traverseFragmentInstancesAndTextInstances(
763 this._fragmentFiber,
764 collectChildren,
765 children,
766 );
767 if (children.length === 0) {
768 const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
769 return compareDocumentPositionForEmptyFragment<PublicInstance>(
770 this._fragmentFiber,
771 parentHostInstance,
772 otherNode,
773 getPublicInstanceFromHostFiber,
774 );
775 }
776
777 const firstInstance = getPublicInstanceFromHostFiber(children[0]);
778 const lastInstance = getPublicInstanceFromHostFiber(
779 children[children.length - 1],
780 );
781
782 // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
783 // $FlowFixMe[prop-missing]
784 const firstResult = firstInstance.compareDocumentPosition(otherNode);
785 // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
786 // $FlowFixMe[prop-missing]
787 const lastResult = lastInstance.compareDocumentPosition(otherNode);
788
789 const otherNodeIsFirstOrLastChild =
790 firstInstance === otherNode || lastInstance === otherNode;
791 const otherNodeIsWithinFirstOrLastChild =
792 firstResult & Node.DOCUMENT_POSITION_CONTAINED_BY ||
793 lastResult & Node.DOCUMENT_POSITION_CONTAINED_BY;
794 const otherNodeIsBetweenFirstAndLastChildren =
795 firstResult & Node.DOCUMENT_POSITION_FOLLOWING &&
796 lastResult & Node.DOCUMENT_POSITION_PRECEDING;
797 let result;
798 if (
799 otherNodeIsFirstOrLastChild ||
800 otherNodeIsWithinFirstOrLastChild ||
801 otherNodeIsBetweenFirstAndLastChildren
802 ) {
803 result = Node.DOCUMENT_POSITION_CONTAINED_BY;
804 } else {
805 result = firstResult;
806 }
807
808 return result;
809 };
810
811 function collectChildren(child: Fiber, collection: Array<Fiber>): boolean {
812 collection.push(child);
813 return false;
814 }
815
816 // $FlowFixMe[prop-missing]
817 FragmentInstance.prototype.getRootNode = function (
818 this: FragmentInstanceType,
819 getRootNodeOptions?: {composed: boolean},
820 ): Node | FragmentInstanceType {
821 const parentHostFiber = getFragmentParentInstanceOrContainerFiber(
822 this._fragmentFiber,
823 );
824 if (parentHostFiber === null) {
825 return this;
826 }
827 const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber);
828 // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
829 const rootNode = parentHostInstance.getRootNode(getRootNodeOptions) as Node;
830 return rootNode;
831 };
832
833 // $FlowFixMe[prop-missing]
834 FragmentInstance.prototype.getClientRects = function (
835 this: FragmentInstanceType,
836 ): Array<DOMRect> {
837 const rects: Array<DOMRect> = [];
838 traverseFragmentInstancesAndTextInstances(
839 this._fragmentFiber,
840 collectClientRects,
841 rects,
842 );
843 return rects;
844 };
845 function collectClientRects(child: Fiber, rects: Array<DOMRect>): boolean {
846 const instance = getPublicInstanceFromHostFiber(child);
847
848 // getBoundingClientRect is available on Fabric instances while getClientRects is not.
849 // This should work as a substitute in this case because the only equivalent of a multi-rect
850 // element in RN would be a nested Text component.
851 // Since we only use top-level nodes here, we can assume that getBoundingClientRect is sufficient.
852 // $FlowFixMe[method-unbinding]
853 // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque
854 rects.push(instance.getBoundingClientRect());
855 return false;
856 }
857
858 function addFragmentHandleToFiber(
859 child: Fiber,
860 fragmentInstance: FragmentInstanceType,
861 ): boolean {
862 if (enableFragmentRefsInstanceHandles) {
863 const instance = getPublicInstanceFromHostFiber(
864 child,
865 ) as any as PublicInstanceWithFragmentHandles;
866 if (instance != null) {
867 addFragmentHandleToInstance(instance, fragmentInstance);
868 }
869 }
870 return false;
871 }
872
873 function addFragmentHandleToInstance(
874 instance: PublicInstanceWithFragmentHandles,
875 fragmentInstance: FragmentInstanceType,
876 ): void {
877 if (enableFragmentRefsInstanceHandles) {
878 if (instance.reactFragments == null) {
879 instance.reactFragments = new Set();
880 }
881 instance.reactFragments.add(fragmentInstance);
882 }
883 }
884
885 export function createFragmentInstance(
886 fragmentFiber: Fiber,
887 ): FragmentInstanceType {
888 const fragmentInstance = new (FragmentInstance as any)(fragmentFiber);
889 if (enableFragmentRefsInstanceHandles) {
890 traverseFragmentInstancesAndTextInstances(
891 fragmentFiber,
892 addFragmentHandleToFiber,
893 fragmentInstance,
894 );
895 }
896 return fragmentInstance;
897 }
898
899 export function updateFragmentInstanceFiber(
900 fragmentFiber: Fiber,
901 instance: FragmentInstanceType,
902 ): void {
903 instance._fragmentFiber = fragmentFiber;
904 }
905
906 export function commitNewChildToFragmentInstance(
907 childInstance: Instance | TextInstance,
908 fragmentInstance: FragmentInstanceType,
909 ): void {
910 // Text nodes are not observable
911 if (enableFragmentRefsTextNodes && childInstance.canonical == null) {
912 return;
913 }
914 const instance: Instance = childInstance as any;
915 const publicInstance = getPublicInstance(instance);
916 if (fragmentInstance._observers !== null) {
917 if (publicInstance == null) {
918 throw new Error('Expected to find a host node. This is a bug in React.');
919 }
920 // $FlowFixMe[incompatible-type]
921 fragmentInstance._observers.forEach(observer => {
922 // $FlowFixMe[incompatible-type] Element types are behind a flag in RN
923 observer.observe(publicInstance);
924 });
925 }
926 if (enableFragmentRefsInstanceHandles) {
927 addFragmentHandleToInstance(
928 publicInstance as any as PublicInstanceWithFragmentHandles,
929 fragmentInstance,
930 );
931 }
932 }
933
934 export function deleteChildFromFragmentInstance(
935 childInstance: Instance | TextInstance,
936 fragmentInstance: FragmentInstanceType,
937 ): void {
938 // Text nodes are not observable
939 if (enableFragmentRefsTextNodes && childInstance.canonical == null) {
940 return;
941 }
942 const instance: Instance = childInstance as any;
943 const publicInstance = getPublicInstance(
944 instance,
945 ) as any as PublicInstanceWithFragmentHandles;
946 if (enableFragmentRefsInstanceHandles) {
947 if (publicInstance.reactFragments != null) {
948 publicInstance.reactFragments.delete(fragmentInstance);
949 }
950 }
951 }
952
953 export const NotPendingTransition: TransitionStatus = null;
954 export const HostTransitionContext: ReactContext<TransitionStatus> = {
955 $$typeof: REACT_CONTEXT_TYPE,
956 Provider: null as any,
957 Consumer: null as any,
958 _currentValue: NotPendingTransition,
959 _currentValue2: NotPendingTransition,
960 _threadCount: 0,
961 };
962
963 export type FormInstance = Instance;
964 export function resetFormInstance(form: Instance): void {}
965
966 // -------------------
967 // Microtasks
968 // -------------------
969
970 export const supportsMicrotasks: boolean = true;
971
972 export const scheduleMicrotask: any =
973 typeof queueMicrotask === 'function' ? queueMicrotask : scheduleTimeout;