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 {InspectorData, TouchedViewDataAtPoint} from './ReactNativeTypes';
    
  11. 
    
  12. // Modules provided by RN:
    
  13. import {
    
  14.   ReactNativeViewConfigRegistry,
    
  15.   UIManager,
    
  16.   deepFreezeAndThrowOnMutationInDev,
    
  17. } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface';
    
  18. 
    
  19. import {create, diff} from './ReactNativeAttributePayload';
    
  20. import {
    
  21.   precacheFiberNode,
    
  22.   uncacheFiberNode,
    
  23.   updateFiberProps,
    
  24. } from './ReactNativeComponentTree';
    
  25. import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
    
  26. 
    
  27. import {
    
  28.   DefaultEventPriority,
    
  29.   type EventPriority,
    
  30. } from 'react-reconciler/src/ReactEventPriorities';
    
  31. import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
    
  32. 
    
  33. const {get: getViewConfigForType} = ReactNativeViewConfigRegistry;
    
  34. 
    
  35. export type Type = string;
    
  36. export type Props = Object;
    
  37. export type Container = number;
    
  38. export type Instance = ReactNativeFiberHostComponent;
    
  39. export type TextInstance = number;
    
  40. export type HydratableInstance = Instance | TextInstance;
    
  41. export type PublicInstance = Instance;
    
  42. export type HostContext = $ReadOnly<{
    
  43.   isInAParentText: boolean,
    
  44. }>;
    
  45. export type UpdatePayload = Object; // Unused
    
  46. export type ChildSet = void; // Unused
    
  47. 
    
  48. export type TimeoutHandle = TimeoutID;
    
  49. export type NoTimeout = -1;
    
  50. export type TransitionStatus = mixed;
    
  51. 
    
  52. export type RendererInspectionConfig = $ReadOnly<{
    
  53.   getInspectorDataForInstance?: (instance: Fiber | null) => InspectorData,
    
  54.   // Deprecated. Replaced with getInspectorDataForViewAtPoint.
    
  55.   getInspectorDataForViewTag?: (tag: number) => Object,
    
  56.   getInspectorDataForViewAtPoint?: (
    
  57.     inspectedView: Object,
    
  58.     locationX: number,
    
  59.     locationY: number,
    
  60.     callback: (viewData: TouchedViewDataAtPoint) => mixed,
    
  61.   ) => void,
    
  62. }>;
    
  63. 
    
  64. // Counter for uniquely identifying views.
    
  65. // % 10 === 1 means it is a rootTag.
    
  66. // % 2 === 0 means it is a Fabric tag.
    
  67. let nextReactTag = 3;
    
  68. function allocateTag() {
    
  69.   let tag = nextReactTag;
    
  70.   if (tag % 10 === 1) {
    
  71.     tag += 2;
    
  72.   }
    
  73.   nextReactTag = tag + 2;
    
  74.   return tag;
    
  75. }
    
  76. 
    
  77. function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
    
  78.   if (typeof node === 'number') {
    
  79.     // Leaf node (eg text)
    
  80.     uncacheFiberNode(node);
    
  81.   } else {
    
  82.     uncacheFiberNode((node: any)._nativeTag);
    
  83. 
    
  84.     (node: any)._children.forEach(recursivelyUncacheFiberNode);
    
  85.   }
    
  86. }
    
  87. 
    
  88. export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';
    
  89. export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
    
  90. export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
    
  91. export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
    
  92. export * from 'react-reconciler/src/ReactFiberConfigWithNoMicrotasks';
    
  93. export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
    
  94. export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
    
  95. 
    
  96. export function appendInitialChild(
    
  97.   parentInstance: Instance,
    
  98.   child: Instance | TextInstance,
    
  99. ): void {
    
  100.   parentInstance._children.push(child);
    
  101. }
    
  102. 
    
  103. export function createInstance(
    
  104.   type: string,
    
  105.   props: Props,
    
  106.   rootContainerInstance: Container,
    
  107.   hostContext: HostContext,
    
  108.   internalInstanceHandle: Object,
    
  109. ): Instance {
    
  110.   const tag = allocateTag();
    
  111.   const viewConfig = getViewConfigForType(type);
    
  112. 
    
  113.   if (__DEV__) {
    
  114.     for (const key in viewConfig.validAttributes) {
    
  115.       if (props.hasOwnProperty(key)) {
    
  116.         deepFreezeAndThrowOnMutationInDev(props[key]);
    
  117.       }
    
  118.     }
    
  119.   }
    
  120. 
    
  121.   const updatePayload = create(props, viewConfig.validAttributes);
    
  122. 
    
  123.   UIManager.createView(
    
  124.     tag, // reactTag
    
  125.     viewConfig.uiViewClassName, // viewName
    
  126.     rootContainerInstance, // rootTag
    
  127.     updatePayload, // props
    
  128.   );
    
  129. 
    
  130.   const component = new ReactNativeFiberHostComponent(
    
  131.     tag,
    
  132.     viewConfig,
    
  133.     internalInstanceHandle,
    
  134.   );
    
  135. 
    
  136.   precacheFiberNode(internalInstanceHandle, tag);
    
  137.   updateFiberProps(tag, props);
    
  138. 
    
  139.   // Not sure how to avoid this cast. Flow is okay if the component is defined
    
  140.   // in the same file but if it's external it can't see the types.
    
  141.   return ((component: any): Instance);
    
  142. }
    
  143. 
    
  144. export function createTextInstance(
    
  145.   text: string,
    
  146.   rootContainerInstance: Container,
    
  147.   hostContext: HostContext,
    
  148.   internalInstanceHandle: Object,
    
  149. ): TextInstance {
    
  150.   if (!hostContext.isInAParentText) {
    
  151.     throw new Error('Text strings must be rendered within a <Text> component.');
    
  152.   }
    
  153. 
    
  154.   const tag = allocateTag();
    
  155. 
    
  156.   UIManager.createView(
    
  157.     tag, // reactTag
    
  158.     'RCTRawText', // viewName
    
  159.     rootContainerInstance, // rootTag
    
  160.     {text: text}, // props
    
  161.   );
    
  162. 
    
  163.   precacheFiberNode(internalInstanceHandle, tag);
    
  164. 
    
  165.   return tag;
    
  166. }
    
  167. 
    
  168. export function finalizeInitialChildren(
    
  169.   parentInstance: Instance,
    
  170.   type: string,
    
  171.   props: Props,
    
  172.   hostContext: HostContext,
    
  173. ): boolean {
    
  174.   // Don't send a no-op message over the bridge.
    
  175.   if (parentInstance._children.length === 0) {
    
  176.     return false;
    
  177.   }
    
  178. 
    
  179.   // Map from child objects to native tags.
    
  180.   // Either way we need to pass a copy of the Array to prevent it from being frozen.
    
  181.   const nativeTags = parentInstance._children.map(child =>
    
  182.     typeof child === 'number'
    
  183.       ? child // Leaf node (eg text)
    
  184.       : child._nativeTag,
    
  185.   );
    
  186. 
    
  187.   UIManager.setChildren(
    
  188.     parentInstance._nativeTag, // containerTag
    
  189.     nativeTags, // reactTags
    
  190.   );
    
  191. 
    
  192.   return false;
    
  193. }
    
  194. 
    
  195. export function getRootHostContext(
    
  196.   rootContainerInstance: Container,
    
  197. ): HostContext {
    
  198.   return {isInAParentText: false};
    
  199. }
    
  200. 
    
  201. export function getChildHostContext(
    
  202.   parentHostContext: HostContext,
    
  203.   type: string,
    
  204. ): HostContext {
    
  205.   const prevIsInAParentText = parentHostContext.isInAParentText;
    
  206.   const isInAParentText =
    
  207.     type === 'AndroidTextInput' || // Android
    
  208.     type === 'RCTMultilineTextInputView' || // iOS
    
  209.     type === 'RCTSinglelineTextInputView' || // iOS
    
  210.     type === 'RCTText' ||
    
  211.     type === 'RCTVirtualText';
    
  212. 
    
  213.   if (prevIsInAParentText !== isInAParentText) {
    
  214.     return {isInAParentText};
    
  215.   } else {
    
  216.     return parentHostContext;
    
  217.   }
    
  218. }
    
  219. 
    
  220. export function getPublicInstance(instance: Instance): PublicInstance {
    
  221.   // $FlowExpectedError[prop-missing] For compatibility with Fabric
    
  222.   if (instance.canonical != null && instance.canonical.publicInstance != null) {
    
  223.     // $FlowFixMe[incompatible-return]
    
  224.     return instance.canonical.publicInstance;
    
  225.   }
    
  226. 
    
  227.   return instance;
    
  228. }
    
  229. 
    
  230. export function prepareForCommit(containerInfo: Container): null | Object {
    
  231.   // Noop
    
  232.   return null;
    
  233. }
    
  234. 
    
  235. export function resetAfterCommit(containerInfo: Container): void {
    
  236.   // Noop
    
  237. }
    
  238. 
    
  239. export const isPrimaryRenderer = true;
    
  240. export const warnsIfNotActing = true;
    
  241. 
    
  242. export const scheduleTimeout = setTimeout;
    
  243. export const cancelTimeout = clearTimeout;
    
  244. export const noTimeout = -1;
    
  245. 
    
  246. export function shouldSetTextContent(type: string, props: Props): boolean {
    
  247.   // TODO (bvaughn) Revisit this decision.
    
  248.   // Always returning false simplifies the createInstance() implementation,
    
  249.   // But creates an additional child Fiber for raw text children.
    
  250.   // No additional native views are created though.
    
  251.   // It's not clear to me which is better so I'm deferring for now.
    
  252.   // More context @ github.com/facebook/react/pull/8560#discussion_r92111303
    
  253.   return false;
    
  254. }
    
  255. 
    
  256. export function getCurrentEventPriority(): EventPriority {
    
  257.   return DefaultEventPriority;
    
  258. }
    
  259. 
    
  260. export function shouldAttemptEagerTransition(): boolean {
    
  261.   return false;
    
  262. }
    
  263. 
    
  264. // -------------------
    
  265. //     Mutation
    
  266. // -------------------
    
  267. 
    
  268. export const supportsMutation = true;
    
  269. 
    
  270. export function appendChild(
    
  271.   parentInstance: Instance,
    
  272.   child: Instance | TextInstance,
    
  273. ): void {
    
  274.   const childTag = typeof child === 'number' ? child : child._nativeTag;
    
  275.   const children = parentInstance._children;
    
  276.   const index = children.indexOf(child);
    
  277. 
    
  278.   if (index >= 0) {
    
  279.     children.splice(index, 1);
    
  280.     children.push(child);
    
  281. 
    
  282.     UIManager.manageChildren(
    
  283.       parentInstance._nativeTag, // containerTag
    
  284.       [index], // moveFromIndices
    
  285.       [children.length - 1], // moveToIndices
    
  286.       [], // addChildReactTags
    
  287.       [], // addAtIndices
    
  288.       [], // removeAtIndices
    
  289.     );
    
  290.   } else {
    
  291.     children.push(child);
    
  292. 
    
  293.     UIManager.manageChildren(
    
  294.       parentInstance._nativeTag, // containerTag
    
  295.       [], // moveFromIndices
    
  296.       [], // moveToIndices
    
  297.       [childTag], // addChildReactTags
    
  298.       [children.length - 1], // addAtIndices
    
  299.       [], // removeAtIndices
    
  300.     );
    
  301.   }
    
  302. }
    
  303. 
    
  304. export function appendChildToContainer(
    
  305.   parentInstance: Container,
    
  306.   child: Instance | TextInstance,
    
  307. ): void {
    
  308.   const childTag = typeof child === 'number' ? child : child._nativeTag;
    
  309.   UIManager.setChildren(
    
  310.     parentInstance, // containerTag
    
  311.     [childTag], // reactTags
    
  312.   );
    
  313. }
    
  314. 
    
  315. export function commitTextUpdate(
    
  316.   textInstance: TextInstance,
    
  317.   oldText: string,
    
  318.   newText: string,
    
  319. ): void {
    
  320.   UIManager.updateView(
    
  321.     textInstance, // reactTag
    
  322.     'RCTRawText', // viewName
    
  323.     {text: newText}, // props
    
  324.   );
    
  325. }
    
  326. 
    
  327. export function commitMount(
    
  328.   instance: Instance,
    
  329.   type: string,
    
  330.   newProps: Props,
    
  331.   internalInstanceHandle: Object,
    
  332. ): void {
    
  333.   // Noop
    
  334. }
    
  335. 
    
  336. export function commitUpdate(
    
  337.   instance: Instance,
    
  338.   updatePayloadTODO: Object,
    
  339.   type: string,
    
  340.   oldProps: Props,
    
  341.   newProps: Props,
    
  342.   internalInstanceHandle: Object,
    
  343. ): void {
    
  344.   const viewConfig = instance.viewConfig;
    
  345. 
    
  346.   updateFiberProps(instance._nativeTag, newProps);
    
  347. 
    
  348.   const updatePayload = diff(oldProps, newProps, viewConfig.validAttributes);
    
  349. 
    
  350.   // Avoid the overhead of bridge calls if there's no update.
    
  351.   // This is an expensive no-op for Android, and causes an unnecessary
    
  352.   // view invalidation for certain components (eg RCTTextInput) on iOS.
    
  353.   if (updatePayload != null) {
    
  354.     UIManager.updateView(
    
  355.       instance._nativeTag, // reactTag
    
  356.       viewConfig.uiViewClassName, // viewName
    
  357.       updatePayload, // props
    
  358.     );
    
  359.   }
    
  360. }
    
  361. 
    
  362. export function insertBefore(
    
  363.   parentInstance: Instance,
    
  364.   child: Instance | TextInstance,
    
  365.   beforeChild: Instance | TextInstance,
    
  366. ): void {
    
  367.   const children = (parentInstance: any)._children;
    
  368.   const index = children.indexOf(child);
    
  369. 
    
  370.   // Move existing child or add new child?
    
  371.   if (index >= 0) {
    
  372.     children.splice(index, 1);
    
  373.     const beforeChildIndex = children.indexOf(beforeChild);
    
  374.     children.splice(beforeChildIndex, 0, child);
    
  375. 
    
  376.     UIManager.manageChildren(
    
  377.       (parentInstance: any)._nativeTag, // containerID
    
  378.       [index], // moveFromIndices
    
  379.       [beforeChildIndex], // moveToIndices
    
  380.       [], // addChildReactTags
    
  381.       [], // addAtIndices
    
  382.       [], // removeAtIndices
    
  383.     );
    
  384.   } else {
    
  385.     const beforeChildIndex = children.indexOf(beforeChild);
    
  386.     children.splice(beforeChildIndex, 0, child);
    
  387. 
    
  388.     const childTag = typeof child === 'number' ? child : child._nativeTag;
    
  389. 
    
  390.     UIManager.manageChildren(
    
  391.       (parentInstance: any)._nativeTag, // containerID
    
  392.       [], // moveFromIndices
    
  393.       [], // moveToIndices
    
  394.       [childTag], // addChildReactTags
    
  395.       [beforeChildIndex], // addAtIndices
    
  396.       [], // removeAtIndices
    
  397.     );
    
  398.   }
    
  399. }
    
  400. 
    
  401. export function insertInContainerBefore(
    
  402.   parentInstance: Container,
    
  403.   child: Instance | TextInstance,
    
  404.   beforeChild: Instance | TextInstance,
    
  405. ): void {
    
  406.   // TODO (bvaughn): Remove this check when...
    
  407.   // We create a wrapper object for the container in ReactNative render()
    
  408.   // Or we refactor to remove wrapper objects entirely.
    
  409.   // For more info on pros/cons see PR #8560 description.
    
  410.   if (typeof parentInstance === 'number') {
    
  411.     throw new Error('Container does not support insertBefore operation');
    
  412.   }
    
  413. }
    
  414. 
    
  415. export function removeChild(
    
  416.   parentInstance: Instance,
    
  417.   child: Instance | TextInstance,
    
  418. ): void {
    
  419.   recursivelyUncacheFiberNode(child);
    
  420.   const children = parentInstance._children;
    
  421.   const index = children.indexOf(child);
    
  422. 
    
  423.   children.splice(index, 1);
    
  424. 
    
  425.   UIManager.manageChildren(
    
  426.     parentInstance._nativeTag, // containerID
    
  427.     [], // moveFromIndices
    
  428.     [], // moveToIndices
    
  429.     [], // addChildReactTags
    
  430.     [], // addAtIndices
    
  431.     [index], // removeAtIndices
    
  432.   );
    
  433. }
    
  434. 
    
  435. export function removeChildFromContainer(
    
  436.   parentInstance: Container,
    
  437.   child: Instance | TextInstance,
    
  438. ): void {
    
  439.   recursivelyUncacheFiberNode(child);
    
  440.   UIManager.manageChildren(
    
  441.     parentInstance, // containerID
    
  442.     [], // moveFromIndices
    
  443.     [], // moveToIndices
    
  444.     [], // addChildReactTags
    
  445.     [], // addAtIndices
    
  446.     [0], // removeAtIndices
    
  447.   );
    
  448. }
    
  449. 
    
  450. export function resetTextContent(instance: Instance): void {
    
  451.   // Noop
    
  452. }
    
  453. 
    
  454. export function hideInstance(instance: Instance): void {
    
  455.   const viewConfig = instance.viewConfig;
    
  456.   const updatePayload = create(
    
  457.     {style: {display: 'none'}},
    
  458.     viewConfig.validAttributes,
    
  459.   );
    
  460.   UIManager.updateView(
    
  461.     instance._nativeTag,
    
  462.     viewConfig.uiViewClassName,
    
  463.     updatePayload,
    
  464.   );
    
  465. }
    
  466. 
    
  467. export function hideTextInstance(textInstance: TextInstance): void {
    
  468.   throw new Error('Not yet implemented.');
    
  469. }
    
  470. 
    
  471. export function unhideInstance(instance: Instance, props: Props): void {
    
  472.   const viewConfig = instance.viewConfig;
    
  473.   const updatePayload = diff(
    
  474.     {...props, style: [props.style, {display: 'none'}]},
    
  475.     props,
    
  476.     viewConfig.validAttributes,
    
  477.   );
    
  478.   UIManager.updateView(
    
  479.     instance._nativeTag,
    
  480.     viewConfig.uiViewClassName,
    
  481.     updatePayload,
    
  482.   );
    
  483. }
    
  484. 
    
  485. export function clearContainer(container: Container): void {
    
  486.   // TODO Implement this for React Native
    
  487.   // UIManager does not expose a "remove all" type method.
    
  488. }
    
  489. 
    
  490. export function unhideTextInstance(
    
  491.   textInstance: TextInstance,
    
  492.   text: string,
    
  493. ): void {
    
  494.   throw new Error('Not yet implemented.');
    
  495. }
    
  496. 
    
  497. export function getInstanceFromNode(node: any): empty {
    
  498.   throw new Error('Not yet implemented.');
    
  499. }
    
  500. 
    
  501. export function beforeActiveInstanceBlur(internalInstanceHandle: Object) {
    
  502.   // noop
    
  503. }
    
  504. 
    
  505. export function afterActiveInstanceBlur() {
    
  506.   // noop
    
  507. }
    
  508. 
    
  509. export function preparePortalMount(portalInstance: Instance): void {
    
  510.   // noop
    
  511. }
    
  512. 
    
  513. export function detachDeletedInstance(node: Instance): void {
    
  514.   // noop
    
  515. }
    
  516. 
    
  517. export function requestPostPaintCallback(callback: (time: number) => void) {
    
  518.   // noop
    
  519. }
    
  520. 
    
  521. export function maySuspendCommit(type: Type, props: Props): boolean {
    
  522.   return false;
    
  523. }
    
  524. 
    
  525. export function preloadInstance(type: Type, props: Props): boolean {
    
  526.   // Return true to indicate it's already loaded
    
  527.   return true;
    
  528. }
    
  529. 
    
  530. export function startSuspendingCommit(): void {}
    
  531. 
    
  532. export function suspendInstance(type: Type, props: Props): void {}
    
  533. 
    
  534. export function waitForCommitToBeReady(): null {
    
  535.   return null;
    
  536. }
    
  537. 
    
  538. export const NotPendingTransition: TransitionStatus = null;