From 1729b499ed8cd456ef3d8bccd413cdecd9931abc Mon Sep 17 00:00:00 2001 From: Ruslan Lesiutin Date: Mon, 4 Dec 2023 17:22:03 +0000 Subject: [PATCH] feat[Fabric/Paper]: support isChildPublicInstance api method (#27783) Adds `isChildPublicInstance` method to both renderers (Fabric and Paper), which will receive 2 public instances and return if first argument is an ancestor of the second, based on fibers. This will be used as a fallback when DOM node APIs are not available: for Paper renderer or for Fabric without DOM node APIs. How it is going to be used: to determine which `AppContainer` component in RN is responsible for highlighting an inspected element on the screen. --- .../react-native-renderer/src/ReactFabric.js | 3 ++ .../src/ReactNativePublicCompat.js | 53 +++++++++++++++++++ .../src/ReactNativeRenderer.js | 3 ++ scripts/flow/react-native-host-hooks.js | 3 ++ 4 files changed, 62 insertions(+) diff --git a/packages/react-native-renderer/src/ReactFabric.js b/packages/react-native-renderer/src/ReactFabric.js index 8e6b99ce83a78..3c2eff2dbc9bb 100644 --- a/packages/react-native-renderer/src/ReactFabric.js +++ b/packages/react-native-renderer/src/ReactFabric.js @@ -39,6 +39,7 @@ import { dispatchCommand, sendAccessibilityEvent, getNodeFromInternalInstanceHandle, + isChildPublicInstance, } from './ReactNativePublicCompat'; import {getPublicInstanceFromInternalInstanceHandle} from './ReactFiberConfigFabric'; @@ -128,6 +129,8 @@ export { // instance handles we use to dispatch events. This provides a way to access // the public instances we created from them (potentially created lazily). getPublicInstanceFromInternalInstanceHandle, + // DEV-only: + isChildPublicInstance, }; injectIntoDevTools({ diff --git a/packages/react-native-renderer/src/ReactNativePublicCompat.js b/packages/react-native-renderer/src/ReactNativePublicCompat.js index 88377dc75e527..38e820dc30f0c 100644 --- a/packages/react-native-renderer/src/ReactNativePublicCompat.js +++ b/packages/react-native-renderer/src/ReactNativePublicCompat.js @@ -8,6 +8,8 @@ */ import type {Node, HostComponent} from './ReactNativeTypes'; +import type {PublicInstance as FabricPublicInstance} from './ReactFiberConfigFabric'; +import type {PublicInstance as PaperPublicInstance} from './ReactFiberConfigNative'; import type {ElementRef, ElementType} from 'react'; // Modules provided by RN: @@ -16,15 +18,19 @@ import { legacySendAccessibilityEvent, getNodeFromPublicInstance, getNativeTagFromPublicInstance, + getInternalInstanceHandleFromPublicInstance, } from 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface'; import { findHostInstance, findHostInstanceWithWarning, } from 'react-reconciler/src/ReactFiberReconciler'; +import {doesFiberContain} from 'react-reconciler/src/ReactFiberTreeReflection'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import getComponentNameFromType from 'shared/getComponentNameFromType'; +import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent'; + const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; export function findHostInstance_DEPRECATED( @@ -218,3 +224,50 @@ export function getNodeFromInternalInstanceHandle( internalInstanceHandle.stateNode.node ); } + +// Remove this once Paper is no longer supported and DOM Node API are enabled by default in RN. +export function isChildPublicInstance( + parentInstance: FabricPublicInstance | PaperPublicInstance, + childInstance: FabricPublicInstance | PaperPublicInstance, +): boolean { + if (__DEV__) { + // Paper + if ( + parentInstance instanceof ReactNativeFiberHostComponent || + childInstance instanceof ReactNativeFiberHostComponent + ) { + if ( + parentInstance instanceof ReactNativeFiberHostComponent && + childInstance instanceof ReactNativeFiberHostComponent + ) { + return doesFiberContain( + parentInstance._internalFiberInstanceHandleDEV, + childInstance._internalFiberInstanceHandleDEV, + ); + } + + // Means that one instance is from Fabric and other is from Paper. + return false; + } + + const parentInternalInstanceHandle = + getInternalInstanceHandleFromPublicInstance(parentInstance); + const childInternalInstanceHandle = + getInternalInstanceHandleFromPublicInstance(childInstance); + + // Fabric + if ( + parentInternalInstanceHandle != null && + childInternalInstanceHandle != null + ) { + return doesFiberContain( + parentInternalInstanceHandle, + childInternalInstanceHandle, + ); + } + + return false; + } else { + throw new Error('isChildPublicInstance() is not available in production.'); + } +} diff --git a/packages/react-native-renderer/src/ReactNativeRenderer.js b/packages/react-native-renderer/src/ReactNativeRenderer.js index bdc09843950cf..d8eb76bc23a36 100644 --- a/packages/react-native-renderer/src/ReactNativeRenderer.js +++ b/packages/react-native-renderer/src/ReactNativeRenderer.js @@ -44,6 +44,7 @@ import { findNodeHandle, dispatchCommand, sendAccessibilityEvent, + isChildPublicInstance, } from './ReactNativePublicCompat'; // $FlowFixMe[missing-local-annot] @@ -137,6 +138,8 @@ export { // This export is typically undefined in production builds. // See the "enableGetInspectorDataForInstanceInProduction" flag. getInspectorDataForInstance, + // DEV-only: + isChildPublicInstance, }; injectIntoDevTools({ diff --git a/scripts/flow/react-native-host-hooks.js b/scripts/flow/react-native-host-hooks.js index 7f53e5199439e..a6e41c2a51e1c 100644 --- a/scripts/flow/react-native-host-hooks.js +++ b/scripts/flow/react-native-host-hooks.js @@ -159,6 +159,9 @@ declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInterface' declare export function createPublicTextInstance( internalInstanceHandle: mixed, ): PublicTextInstance; + declare export function getInternalInstanceHandleFromPublicInstance( + publicInstance: PublicInstance, + ): ?Object; } declare module 'react-native/Libraries/ReactPrivate/ReactNativePrivateInitializeCore' {