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 {create, diff} from './ReactNativeAttributePayload';
    
  16. import {dispatchEvent} from './ReactFabricEventEmitter';
    
  17. import {
    
  18.   DefaultEventPriority,
    
  19.   DiscreteEventPriority,
    
  20.   type EventPriority,
    
  21. } from 'react-reconciler/src/ReactEventPriorities';
    
  22. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  23. import {HostText} from 'react-reconciler/src/ReactWorkTags';
    
  24. 
    
  25. // Modules provided by RN:
    
  26. import {
    
  27.   ReactNativeViewConfigRegistry,
    
  28.   deepFreezeAndThrowOnMutationInDev,
    
  29.   createPublicInstance,
    
  30.   createPublicTextInstance,
    
  31.   type PublicInstance as ReactNativePublicInstance,
    
  32.   type PublicTextInstance,
    
  33. } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
    
  34. 
    
  35. const {
    
  36.   createNode,
    
  37.   cloneNodeWithNewChildren,
    
  38.   cloneNodeWithNewChildrenAndProps,
    
  39.   cloneNodeWithNewProps,
    
  40.   createChildSet: createChildNodeSet,
    
  41.   appendChild: appendChildNode,
    
  42.   appendChildToSet: appendChildNodeToSet,
    
  43.   completeRoot,
    
  44.   registerEventHandler,
    
  45.   unstable_DefaultEventPriority: FabricDefaultPriority,
    
  46.   unstable_DiscreteEventPriority: FabricDiscretePriority,
    
  47.   unstable_getCurrentEventPriority: fabricGetCurrentEventPriority,
    
  48. } = nativeFabricUIManager;
    
  49. 
    
  50. import {
    
  51.   useMicrotasksForSchedulingInFabric,
    
  52.   passChildrenWhenCloningPersistedNodes,
    
  53. } from 'shared/ReactFeatureFlags';
    
  54. 
    
  55. const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
    
  56. 
    
  57. // Counter for uniquely identifying views.
    
  58. // % 10 === 1 means it is a rootTag.
    
  59. // % 2 === 0 means it is a Fabric tag.
    
  60. // This means that they never overlap.
    
  61. let nextReactTag = 2;
    
  62. 
    
  63. type InternalInstanceHandle = Object;
    
  64. type Node = Object;
    
  65. export type Type = string;
    
  66. export type Props = Object;
    
  67. export type Instance = {
    
  68.   // Reference to the shadow node.
    
  69.   node: Node,
    
  70.   // This object is shared by all the clones of the instance.
    
  71.   // We use it to access their shared public instance (exposed through refs)
    
  72.   // and to access its committed state for events, etc.
    
  73.   canonical: {
    
  74.     nativeTag: number,
    
  75.     viewConfig: ViewConfig,
    
  76.     currentProps: Props,
    
  77.     // Reference to the React handle (the fiber)
    
  78.     internalInstanceHandle: InternalInstanceHandle,
    
  79.     // Exposed through refs.
    
  80.     publicInstance: PublicInstance,
    
  81.   },
    
  82. };
    
  83. export type TextInstance = {
    
  84.   // Reference to the shadow node.
    
  85.   node: Node,
    
  86.   // Text instances are never cloned, so we don't need to keep a "canonical"
    
  87.   // reference to make sure all clones of the instance point to the same values.
    
  88.   publicInstance?: PublicTextInstance,
    
  89. };
    
  90. export type HydratableInstance = Instance | TextInstance;
    
  91. export type PublicInstance = ReactNativePublicInstance;
    
  92. export type Container = number;
    
  93. export type ChildSet = Object | Array<Node>;
    
  94. export type HostContext = $ReadOnly<{
    
  95.   isInAParentText: boolean,
    
  96. }>;
    
  97. export type UpdatePayload = Object;
    
  98. 
    
  99. export type TimeoutHandle = TimeoutID;
    
  100. export type NoTimeout = -1;
    
  101. export type TransitionStatus = mixed;
    
  102. 
    
  103. export type RendererInspectionConfig = $ReadOnly<{
    
  104.   getInspectorDataForInstance?: (instance: Fiber | null) => InspectorData,
    
  105.   // Deprecated. Replaced with getInspectorDataForViewAtPoint.
    
  106.   getInspectorDataForViewTag?: (tag: number) => Object,
    
  107.   getInspectorDataForViewAtPoint?: (
    
  108.     inspectedView: Object,
    
  109.     locationX: number,
    
  110.     locationY: number,
    
  111.     callback: (viewData: TouchedViewDataAtPoint) => mixed,
    
  112.   ) => void,
    
  113. }>;
    
  114. 
    
  115. // TODO: Remove this conditional once all changes have propagated.
    
  116. if (registerEventHandler) {
    
  117.   /**
    
  118.    * Register the event emitter with the native bridge
    
  119.    */
    
  120.   registerEventHandler(dispatchEvent);
    
  121. }
    
  122. 
    
  123. export * from 'react-reconciler/src/ReactFiberConfigWithNoMutation';
    
  124. export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
    
  125. export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
    
  126. export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
    
  127. export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
    
  128. export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
    
  129. 
    
  130. export function appendInitialChild(
    
  131.   parentInstance: Instance,
    
  132.   child: Instance | TextInstance,
    
  133. ): void {
    
  134.   appendChildNode(parentInstance.node, child.node);
    
  135. }
    
  136. 
    
  137. export function createInstance(
    
  138.   type: string,
    
  139.   props: Props,
    
  140.   rootContainerInstance: Container,
    
  141.   hostContext: HostContext,
    
  142.   internalInstanceHandle: InternalInstanceHandle,
    
  143. ): Instance {
    
  144.   const tag = nextReactTag;
    
  145.   nextReactTag += 2;
    
  146. 
    
  147.   const viewConfig = getViewConfigForType(type);
    
  148. 
    
  149.   if (__DEV__) {
    
  150.     for (const key in viewConfig.validAttributes) {
    
  151.       if (props.hasOwnProperty(key)) {
    
  152.         deepFreezeAndThrowOnMutationInDev(props[key]);
    
  153.       }
    
  154.     }
    
  155.   }
    
  156. 
    
  157.   const updatePayload = create(props, viewConfig.validAttributes);
    
  158. 
    
  159.   const node = createNode(
    
  160.     tag, // reactTag
    
  161.     viewConfig.uiViewClassName, // viewName
    
  162.     rootContainerInstance, // rootTag
    
  163.     updatePayload, // props
    
  164.     internalInstanceHandle, // internalInstanceHandle
    
  165.   );
    
  166. 
    
  167.   const component = createPublicInstance(
    
  168.     tag,
    
  169.     viewConfig,
    
  170.     internalInstanceHandle,
    
  171.   );
    
  172. 
    
  173.   return {
    
  174.     node: node,
    
  175.     canonical: {
    
  176.       nativeTag: tag,
    
  177.       viewConfig,
    
  178.       currentProps: props,
    
  179.       internalInstanceHandle,
    
  180.       publicInstance: component,
    
  181.     },
    
  182.   };
    
  183. }
    
  184. 
    
  185. export function createTextInstance(
    
  186.   text: string,
    
  187.   rootContainerInstance: Container,
    
  188.   hostContext: HostContext,
    
  189.   internalInstanceHandle: InternalInstanceHandle,
    
  190. ): TextInstance {
    
  191.   if (__DEV__) {
    
  192.     if (!hostContext.isInAParentText) {
    
  193.       console.error('Text strings must be rendered within a <Text> component.');
    
  194.     }
    
  195.   }
    
  196. 
    
  197.   const tag = nextReactTag;
    
  198.   nextReactTag += 2;
    
  199. 
    
  200.   const node = createNode(
    
  201.     tag, // reactTag
    
  202.     'RCTRawText', // viewName
    
  203.     rootContainerInstance, // rootTag
    
  204.     {text: text}, // props
    
  205.     internalInstanceHandle, // instance handle
    
  206.   );
    
  207. 
    
  208.   return {
    
  209.     node: node,
    
  210.   };
    
  211. }
    
  212. 
    
  213. export function finalizeInitialChildren(
    
  214.   parentInstance: Instance,
    
  215.   type: string,
    
  216.   props: Props,
    
  217.   hostContext: HostContext,
    
  218. ): boolean {
    
  219.   return false;
    
  220. }
    
  221. 
    
  222. export function getRootHostContext(
    
  223.   rootContainerInstance: Container,
    
  224. ): HostContext {
    
  225.   return {isInAParentText: false};
    
  226. }
    
  227. 
    
  228. export function getChildHostContext(
    
  229.   parentHostContext: HostContext,
    
  230.   type: string,
    
  231. ): HostContext {
    
  232.   const prevIsInAParentText = parentHostContext.isInAParentText;
    
  233.   const isInAParentText =
    
  234.     type === 'AndroidTextInput' || // Android
    
  235.     type === 'RCTMultilineTextInputView' || // iOS
    
  236.     type === 'RCTSinglelineTextInputView' || // iOS
    
  237.     type === 'RCTText' ||
    
  238.     type === 'RCTVirtualText';
    
  239. 
    
  240.   // TODO: If this is an offscreen host container, we should reuse the
    
  241.   // parent context.
    
  242. 
    
  243.   if (prevIsInAParentText !== isInAParentText) {
    
  244.     return {isInAParentText};
    
  245.   } else {
    
  246.     return parentHostContext;
    
  247.   }
    
  248. }
    
  249. 
    
  250. export function getPublicInstance(instance: Instance): null | PublicInstance {
    
  251.   if (instance.canonical != null && instance.canonical.publicInstance != null) {
    
  252.     return instance.canonical.publicInstance;
    
  253.   }
    
  254. 
    
  255.   // For compatibility with the legacy renderer, in case it's used with Fabric
    
  256.   // in the same app.
    
  257.   // $FlowExpectedError[prop-missing]
    
  258.   if (instance._nativeTag != null) {
    
  259.     // $FlowExpectedError[incompatible-return]
    
  260.     return instance;
    
  261.   }
    
  262. 
    
  263.   return null;
    
  264. }
    
  265. 
    
  266. function getPublicTextInstance(
    
  267.   textInstance: TextInstance,
    
  268.   internalInstanceHandle: InternalInstanceHandle,
    
  269. ): PublicTextInstance {
    
  270.   if (textInstance.publicInstance == null) {
    
  271.     textInstance.publicInstance = createPublicTextInstance(
    
  272.       internalInstanceHandle,
    
  273.     );
    
  274.   }
    
  275.   return textInstance.publicInstance;
    
  276. }
    
  277. 
    
  278. export function getPublicInstanceFromInternalInstanceHandle(
    
  279.   internalInstanceHandle: InternalInstanceHandle,
    
  280. ): null | PublicInstance | PublicTextInstance {
    
  281.   if (internalInstanceHandle.tag === HostText) {
    
  282.     const textInstance: TextInstance = internalInstanceHandle.stateNode;
    
  283.     return getPublicTextInstance(textInstance, internalInstanceHandle);
    
  284.   }
    
  285. 
    
  286.   const instance: Instance = internalInstanceHandle.stateNode;
    
  287.   return getPublicInstance(instance);
    
  288. }
    
  289. 
    
  290. export function prepareForCommit(containerInfo: Container): null | Object {
    
  291.   // Noop
    
  292.   return null;
    
  293. }
    
  294. 
    
  295. export function resetAfterCommit(containerInfo: Container): void {
    
  296.   // Noop
    
  297. }
    
  298. 
    
  299. export function shouldSetTextContent(type: string, props: Props): boolean {
    
  300.   // TODO (bvaughn) Revisit this decision.
    
  301.   // Always returning false simplifies the createInstance() implementation,
    
  302.   // But creates an additional child Fiber for raw text children.
    
  303.   // No additional native views are created though.
    
  304.   // It's not clear to me which is better so I'm deferring for now.
    
  305.   // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
    
  306.   return false;
    
  307. }
    
  308. 
    
  309. export function getCurrentEventPriority(): EventPriority {
    
  310.   const currentEventPriority = fabricGetCurrentEventPriority
    
  311.     ? fabricGetCurrentEventPriority()
    
  312.     : null;
    
  313. 
    
  314.   if (currentEventPriority != null) {
    
  315.     switch (currentEventPriority) {
    
  316.       case FabricDiscretePriority:
    
  317.         return DiscreteEventPriority;
    
  318.       case FabricDefaultPriority:
    
  319.       default:
    
  320.         return DefaultEventPriority;
    
  321.     }
    
  322.   }
    
  323. 
    
  324.   return DefaultEventPriority;
    
  325. }
    
  326. 
    
  327. export function shouldAttemptEagerTransition(): boolean {
    
  328.   return false;
    
  329. }
    
  330. 
    
  331. // The Fabric renderer is secondary to the existing React Native renderer.
    
  332. export const isPrimaryRenderer = false;
    
  333. 
    
  334. // The Fabric renderer shouldn't trigger missing act() warnings
    
  335. export const warnsIfNotActing = false;
    
  336. 
    
  337. export const scheduleTimeout = setTimeout;
    
  338. export const cancelTimeout = clearTimeout;
    
  339. export const noTimeout = -1;
    
  340. 
    
  341. // -------------------
    
  342. //     Persistence
    
  343. // -------------------
    
  344. 
    
  345. export const supportsPersistence = true;
    
  346. 
    
  347. export function cloneInstance(
    
  348.   instance: Instance,
    
  349.   type: string,
    
  350.   oldProps: Props,
    
  351.   newProps: Props,
    
  352.   keepChildren: boolean,
    
  353.   newChildSet: ?ChildSet,
    
  354. ): Instance {
    
  355.   const viewConfig = instance.canonical.viewConfig;
    
  356.   const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
    
  357.   // TODO: If the event handlers have changed, we need to update the current props
    
  358.   // in the commit phase but there is no host config hook to do it yet.
    
  359.   // So instead we hack it by updating it in the render phase.
    
  360.   instance.canonical.currentProps = newProps;
    
  361. 
    
  362.   const node = instance.node;
    
  363.   let clone;
    
  364.   if (keepChildren) {
    
  365.     if (updatePayload !== null) {
    
  366.       clone = cloneNodeWithNewProps(node, updatePayload);
    
  367.     } else {
    
  368.       // No changes
    
  369.       return instance;
    
  370.     }
    
  371.   } else {
    
  372.     // If passChildrenWhenCloningPersistedNodes is enabled, children will be non-null
    
  373.     if (newChildSet != null) {
    
  374.       if (updatePayload !== null) {
    
  375.         clone = cloneNodeWithNewChildrenAndProps(
    
  376.           node,
    
  377.           newChildSet,
    
  378.           updatePayload,
    
  379.         );
    
  380.       } else {
    
  381.         clone = cloneNodeWithNewChildren(node, newChildSet);
    
  382.       }
    
  383.     } else {
    
  384.       if (updatePayload !== null) {
    
  385.         clone = cloneNodeWithNewChildrenAndProps(node, updatePayload);
    
  386.       } else {
    
  387.         clone = cloneNodeWithNewChildren(node);
    
  388.       }
    
  389.     }
    
  390.   }
    
  391. 
    
  392.   return {
    
  393.     node: clone,
    
  394.     canonical: instance.canonical,
    
  395.   };
    
  396. }
    
  397. 
    
  398. export function cloneHiddenInstance(
    
  399.   instance: Instance,
    
  400.   type: string,
    
  401.   props: Props,
    
  402. ): Instance {
    
  403.   const viewConfig = instance.canonical.viewConfig;
    
  404.   const node = instance.node;
    
  405.   const updatePayload = create(
    
  406.     {style: {display: 'none'}},
    
  407.     viewConfig.validAttributes,
    
  408.   );
    
  409.   return {
    
  410.     node: cloneNodeWithNewProps(node, updatePayload),
    
  411.     canonical: instance.canonical,
    
  412.   };
    
  413. }
    
  414. 
    
  415. export function cloneHiddenTextInstance(
    
  416.   instance: Instance,
    
  417.   text: string,
    
  418. ): TextInstance {
    
  419.   throw new Error('Not yet implemented.');
    
  420. }
    
  421. 
    
  422. export function createContainerChildSet(): ChildSet {
    
  423.   if (passChildrenWhenCloningPersistedNodes) {
    
  424.     return [];
    
  425.   } else {
    
  426.     return createChildNodeSet();
    
  427.   }
    
  428. }
    
  429. 
    
  430. export function appendChildToContainerChildSet(
    
  431.   childSet: ChildSet,
    
  432.   child: Instance | TextInstance,
    
  433. ): void {
    
  434.   if (passChildrenWhenCloningPersistedNodes) {
    
  435.     childSet.push(child.node);
    
  436.   } else {
    
  437.     appendChildNodeToSet(childSet, child.node);
    
  438.   }
    
  439. }
    
  440. 
    
  441. export function finalizeContainerChildren(
    
  442.   container: Container,
    
  443.   newChildren: ChildSet,
    
  444. ): void {
    
  445.   completeRoot(container, newChildren);
    
  446. }
    
  447. 
    
  448. export function replaceContainerChildren(
    
  449.   container: Container,
    
  450.   newChildren: ChildSet,
    
  451. ): void {
    
  452.   // Noop - children will be replaced in finalizeContainerChildren
    
  453. }
    
  454. 
    
  455. export function getInstanceFromNode(node: any): empty {
    
  456.   throw new Error('Not yet implemented.');
    
  457. }
    
  458. 
    
  459. export function beforeActiveInstanceBlur(
    
  460.   internalInstanceHandle: InternalInstanceHandle,
    
  461. ) {
    
  462.   // noop
    
  463. }
    
  464. 
    
  465. export function afterActiveInstanceBlur() {
    
  466.   // noop
    
  467. }
    
  468. 
    
  469. export function preparePortalMount(portalInstance: Instance): void {
    
  470.   // noop
    
  471. }
    
  472. 
    
  473. export function detachDeletedInstance(node: Instance): void {
    
  474.   // noop
    
  475. }
    
  476. 
    
  477. export function requestPostPaintCallback(callback: (time: number) => void) {
    
  478.   // noop
    
  479. }
    
  480. 
    
  481. export function maySuspendCommit(type: Type, props: Props): boolean {
    
  482.   return false;
    
  483. }
    
  484. 
    
  485. export function preloadInstance(type: Type, props: Props): boolean {
    
  486.   return true;
    
  487. }
    
  488. 
    
  489. export function startSuspendingCommit(): void {}
    
  490. 
    
  491. export function suspendInstance(type: Type, props: Props): void {}
    
  492. 
    
  493. export function waitForCommitToBeReady(): null {
    
  494.   return null;
    
  495. }
    
  496. 
    
  497. export const NotPendingTransition: TransitionStatus = null;
    
  498. 
    
  499. // -------------------
    
  500. //     Microtasks
    
  501. // -------------------
    
  502. export const supportsMicrotasks = useMicrotasksForSchedulingInFabric;
    
  503. export const scheduleMicrotask: any =
    
  504.   typeof queueMicrotask === 'function' ? queueMicrotask : scheduleTimeout;