diff --git a/packages/react-native/React/React-RCTFabric.podspec b/packages/react-native/React/React-RCTFabric.podspec index 9a28eeec44dc..7cfb18a8dd1d 100644 --- a/packages/react-native/React/React-RCTFabric.podspec +++ b/packages/react-native/React/React-RCTFabric.podspec @@ -87,6 +87,7 @@ Pod::Spec.new do |s| add_dependency(s, "React-debug") add_dependency(s, "React-utils") add_dependency(s, "React-rendererdebug") + add_dependency(s, "React-rendererconsistency") add_dependency(s, "React-runtimescheduler") add_dependency(s, "React-jsinspector", :framework_name => 'jsinspector_modern') diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 711181509ede..989df452c3aa 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -563,6 +563,8 @@ android { "react_cxxreactpackage", "react_render_animations", "react_render_core", + "react_render_consistency", + "react_render_dom", "react_render_graphics", "rrc_image", "rrc_root", @@ -580,6 +582,7 @@ android { "react_nativemodule_core", "react_render_imagemanager", "react_render_uimanager", + "react_render_uimanager_consistency", "react_render_scheduler", "react_render_mounting", "hermes_executor", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index a14811239d8f..50eabadaab68 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<20c445fde7a1c2607b58b7797346431f>> */ /** @@ -76,6 +76,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableSpannableBuildingUnification(): Boolean = accessor.enableSpannableBuildingUnification() + /** + * Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution). + */ + @JvmStatic + public fun enableUIConsistency(): Boolean = accessor.enableUIConsistency() + /** * Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt index e65fa7974529..7df37086a8a0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<7ae379135157666d9646f1d8eeec9989>> + * @generated SignedSource<<6e973dcdcdfae14c77a6130207333551>> */ /** @@ -28,6 +28,7 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso private var enableMicrotasksCache: Boolean? = null private var enableMountHooksAndroidCache: Boolean? = null private var enableSpannableBuildingUnificationCache: Boolean? = null + private var enableUIConsistencyCache: Boolean? = null private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null private var inspectorEnableModernCDPRegistryCache: Boolean? = null private var useModernRuntimeSchedulerCache: Boolean? = null @@ -104,6 +105,15 @@ public class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAccesso return cached } + override fun enableUIConsistency(): Boolean { + var cached = enableUIConsistencyCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableUIConsistency() + enableUIConsistencyCache = cached + } + return cached + } + override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean { var cached = inspectorEnableCxxInspectorPackagerConnectionCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt index d6093108c73d..465c71cf32c9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsCxxInterop.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<244a0656beee8e018585bdd4bb4e5cd1>> + * @generated SignedSource<<0d34567fc05555ae401e7f5180dc8771>> */ /** @@ -44,6 +44,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableSpannableBuildingUnification(): Boolean + @DoNotStrip @JvmStatic public external fun enableUIConsistency(): Boolean + @DoNotStrip @JvmStatic public external fun inspectorEnableCxxInspectorPackagerConnection(): Boolean @DoNotStrip @JvmStatic public external fun inspectorEnableModernCDPRegistry(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt index 7a2ac9d16016..1e0f7a392d8a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsDefaults.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -39,6 +39,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableSpannableBuildingUnification(): Boolean = false + override fun enableUIConsistency(): Boolean = false + override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean = false override fun inspectorEnableModernCDPRegistry(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index cae01383bef9..698251ab09d0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<919eb0f27540e5dd7a1e028663c23264>> + * @generated SignedSource<<8908de9b9d0186f1916d8b55d5854cb2>> */ /** @@ -32,6 +32,7 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces private var enableMicrotasksCache: Boolean? = null private var enableMountHooksAndroidCache: Boolean? = null private var enableSpannableBuildingUnificationCache: Boolean? = null + private var enableUIConsistencyCache: Boolean? = null private var inspectorEnableCxxInspectorPackagerConnectionCache: Boolean? = null private var inspectorEnableModernCDPRegistryCache: Boolean? = null private var useModernRuntimeSchedulerCache: Boolean? = null @@ -116,6 +117,16 @@ public class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableUIConsistency(): Boolean { + var cached = enableUIConsistencyCache + if (cached == null) { + cached = currentProvider.enableUIConsistency() + accessedFeatureFlags.add("enableUIConsistency") + enableUIConsistencyCache = cached + } + return cached + } + override fun inspectorEnableCxxInspectorPackagerConnection(): Boolean { var cached = inspectorEnableCxxInspectorPackagerConnectionCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 451f64be6d98..1bd4b3ad6a42 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<7555a704535615fcea44c1261095419a>> */ /** @@ -39,6 +39,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableSpannableBuildingUnification(): Boolean + @DoNotStrip public fun enableUIConsistency(): Boolean + @DoNotStrip public fun inspectorEnableCxxInspectorPackagerConnection(): Boolean @DoNotStrip public fun inspectorEnableModernCDPRegistry(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt index af48dbd60eee..1e0a86425d98 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -79,6 +79,9 @@ add_react_common_subdir(react/renderer/scheduler) add_react_common_subdir(react/renderer/telemetry) add_react_common_subdir(react/renderer/uimanager) add_react_common_subdir(react/renderer/core) +add_react_common_subdir(react/renderer/consistency) +add_react_common_subdir(react/renderer/uimanager/consistency) +add_react_common_subdir(react/renderer/dom) add_react_common_subdir(react/renderer/element) add_react_common_subdir(react/renderer/graphics) add_react_common_subdir(react/renderer/debug) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp index 4add6436bbd4..d24467eb2056 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<393c6cf93399cfe0b0533927877531d5>> + * @generated SignedSource<> */ /** @@ -87,6 +87,12 @@ class ReactNativeFeatureFlagsProviderHolder return method(javaProvider_); } + bool enableUIConsistency() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableUIConsistency"); + return method(javaProvider_); + } + bool inspectorEnableCxxInspectorPackagerConnection() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("inspectorEnableCxxInspectorPackagerConnection"); @@ -149,6 +155,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableSpannableBuildingUnification( return ReactNativeFeatureFlags::enableSpannableBuildingUnification(); } +bool JReactNativeFeatureFlagsCxxInterop::enableUIConsistency( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableUIConsistency(); +} + bool JReactNativeFeatureFlagsCxxInterop::inspectorEnableCxxInspectorPackagerConnection( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection(); @@ -205,6 +216,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableSpannableBuildingUnification", JReactNativeFeatureFlagsCxxInterop::enableSpannableBuildingUnification), + makeNativeMethod( + "enableUIConsistency", + JReactNativeFeatureFlagsCxxInterop::enableUIConsistency), makeNativeMethod( "inspectorEnableCxxInspectorPackagerConnection", JReactNativeFeatureFlagsCxxInterop::inspectorEnableCxxInspectorPackagerConnection), diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h index d710f2c40522..27e41e73a4e4 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -54,6 +54,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableSpannableBuildingUnification( facebook::jni::alias_ref); + static bool enableUIConsistency( + facebook::jni::alias_ref); + static bool inspectorEnableCxxInspectorPackagerConnection( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/React-Fabric.podspec b/packages/react-native/ReactCommon/React-Fabric.podspec index 6662a301291e..d7af3b162a30 100644 --- a/packages/react-native/ReactCommon/React-Fabric.podspec +++ b/packages/react-native/ReactCommon/React-Fabric.podspec @@ -213,7 +213,7 @@ Pod::Spec.new do |s| sss.header_dir = "react/renderer/components/textinput" end - + ss.subspec "unimplementedview" do |sss| sss.dependency folly_dep_name, folly_version sss.compiler_flags = folly_compiler_flags @@ -277,13 +277,33 @@ Pod::Spec.new do |s| end s.subspec "uimanager" do |ss| + ss.subspec "consistency" do |sss| + sss.dependency folly_dep_name, folly_version + sss.compiler_flags = folly_compiler_flags + sss.source_files = "react/renderer/uimanager/consistency/*.{m,mm,cpp,h}" + sss.header_dir = "react/renderer/uimanager/consistency" + end + ss.dependency folly_dep_name, folly_version + ss.dependency "React-rendererconsistency" + ss.dependency "React-Fabric/dom" ss.compiler_flags = folly_compiler_flags - ss.source_files = "react/renderer/uimanager/**/*.{m,mm,cpp,h}" - ss.exclude_files = "react/renderer/uimanager/tests" + ss.source_files = "react/renderer/uimanager/*.{m,mm,cpp,h}" ss.header_dir = "react/renderer/uimanager" end + s.subspec "dom" do |ss| + ss.dependency folly_dep_name, folly_version + ss.dependency "React-Fabric/components/root" + ss.dependency "React-Fabric/components/text" + ss.dependency "React-Fabric/core" + ss.dependency "React-graphics" + ss.compiler_flags = folly_compiler_flags + ss.source_files = "react/renderer/dom/**/*.{m,mm,cpp,h}" + ss.exclude_files = "react/renderer/dom/tests" + ss.header_dir = "react/renderer/dom" + end + s.subspec "telemetry" do |ss| ss.dependency folly_dep_name, folly_version ss.compiler_flags = folly_compiler_flags diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 163c9e238845..2a4c352c5a4e 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -53,6 +53,10 @@ bool ReactNativeFeatureFlags::enableSpannableBuildingUnification() { return getAccessor().enableSpannableBuildingUnification(); } +bool ReactNativeFeatureFlags::enableUIConsistency() { + return getAccessor().enableUIConsistency(); +} + bool ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection() { return getAccessor().inspectorEnableCxxInspectorPackagerConnection(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 9f51ae019bc4..0354f83b5473 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -77,6 +77,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableSpannableBuildingUnification(); + /** + * Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution). + */ + RN_EXPORT static bool enableUIConsistency(); + /** * Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 9adeb8f60d42..f7a4feb4dbbd 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<2ff39fd4c8330ddca994fc40cdeaaf4c>> */ /** @@ -173,6 +173,24 @@ bool ReactNativeFeatureFlagsAccessor::enableSpannableBuildingUnification() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableUIConsistency() { + auto flagValue = enableUIConsistency_.load(); + + if (!flagValue.has_value()) { + // This block is not exclusive but it is not necessary. + // If multiple threads try to initialize the feature flag, we would only + // be accessing the provider multiple times but the end state of this + // instance and the returned flag value would be the same. + + markFlagAsAccessed(8, "enableUIConsistency"); + + flagValue = currentProvider_->enableUIConsistency(); + enableUIConsistency_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::inspectorEnableCxxInspectorPackagerConnection() { auto flagValue = inspectorEnableCxxInspectorPackagerConnection_.load(); @@ -182,7 +200,7 @@ bool ReactNativeFeatureFlagsAccessor::inspectorEnableCxxInspectorPackagerConnect // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(8, "inspectorEnableCxxInspectorPackagerConnection"); + markFlagAsAccessed(9, "inspectorEnableCxxInspectorPackagerConnection"); flagValue = currentProvider_->inspectorEnableCxxInspectorPackagerConnection(); inspectorEnableCxxInspectorPackagerConnection_ = flagValue; @@ -200,7 +218,7 @@ bool ReactNativeFeatureFlagsAccessor::inspectorEnableModernCDPRegistry() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(9, "inspectorEnableModernCDPRegistry"); + markFlagAsAccessed(10, "inspectorEnableModernCDPRegistry"); flagValue = currentProvider_->inspectorEnableModernCDPRegistry(); inspectorEnableModernCDPRegistry_ = flagValue; @@ -218,7 +236,7 @@ bool ReactNativeFeatureFlagsAccessor::useModernRuntimeScheduler() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(10, "useModernRuntimeScheduler"); + markFlagAsAccessed(11, "useModernRuntimeScheduler"); flagValue = currentProvider_->useModernRuntimeScheduler(); useModernRuntimeScheduler_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index 5199fc1ca593..dc3b1f0b99cb 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -39,6 +39,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableMicrotasks(); bool enableMountHooksAndroid(); bool enableSpannableBuildingUnification(); + bool enableUIConsistency(); bool inspectorEnableCxxInspectorPackagerConnection(); bool inspectorEnableModernCDPRegistry(); bool useModernRuntimeScheduler(); @@ -52,7 +53,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 11> accessedFeatureFlags_; + std::array, 12> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> batchRenderingUpdatesInEventLoop_; @@ -62,6 +63,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableMicrotasks_; std::atomic> enableMountHooksAndroid_; std::atomic> enableSpannableBuildingUnification_; + std::atomic> enableUIConsistency_; std::atomic> inspectorEnableCxxInspectorPackagerConnection_; std::atomic> inspectorEnableModernCDPRegistry_; std::atomic> useModernRuntimeScheduler_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 85f6d322e89d..0115a835c636 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<62de1b0e27590ad769296358a4f42c7a>> + * @generated SignedSource<<1450d89abc68821fb348574016874719>> */ /** @@ -59,6 +59,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableUIConsistency() override { + return false; + } + bool inspectorEnableCxxInspectorPackagerConnection() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 48ec9f28697f..cbf80cf641e0 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<3117fc0389416297369a47ee480eb906>> + * @generated SignedSource<> */ /** @@ -33,6 +33,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableMicrotasks() = 0; virtual bool enableMountHooksAndroid() = 0; virtual bool enableSpannableBuildingUnification() = 0; + virtual bool enableUIConsistency() = 0; virtual bool inspectorEnableCxxInspectorPackagerConnection() = 0; virtual bool inspectorEnableModernCDPRegistry() = 0; virtual bool useModernRuntimeScheduler() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp index 2c26e9c415d9..79cf1bf09db3 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/dom/NativeDOM.cpp @@ -6,10 +6,11 @@ */ #include "NativeDOM.h" +#include +#include #include #include #include -#include #include #ifdef RN_DISABLE_OSS_PLUGIN_HEADER @@ -22,9 +23,15 @@ std::shared_ptr NativeDOMModuleProvider( } namespace { -facebook::react::UIManager& getUIManagerFromRuntime( - facebook::jsi::Runtime& runtime) { - return facebook::react::UIManagerBinding::getBinding(runtime)->getUIManager(); +using namespace facebook::react; + +RootShadowNode::Shared getCurrentShadowTreeRevision( + facebook::jsi::Runtime& runtime, + SurfaceId surfaceId) { + auto& uiManager = + facebook::react::UIManagerBinding::getBinding(runtime)->getUIManager(); + auto shadowTreeRevisionProvider = uiManager.getShadowTreeRevisionProvider(); + return shadowTreeRevisionProvider->getCurrentRevision(surfaceId); } facebook::react::PointerEventsProcessor& getPointerEventsProcessorFromRuntime( @@ -32,6 +39,24 @@ facebook::react::PointerEventsProcessor& getPointerEventsProcessorFromRuntime( return facebook::react::UIManagerBinding::getBinding(runtime) ->getPointerEventsProcessor(); } + +std::vector getArrayOfInstanceHandlesFromShadowNodes( + const ShadowNode::ListOfShared& nodes, + facebook::jsi::Runtime& runtime) { + // JSI doesn't support adding elements to an array after creation, + // so we need to accumulate the values in a vector and then create + // the array when we know the size. + std::vector nonNullInstanceHandles; + nonNullInstanceHandles.reserve(nodes.size()); + for (const auto& shadowNode : nodes) { + auto instanceHandle = (*shadowNode).getInstanceHandle(runtime); + if (!instanceHandle.isNull()) { + nonNullInstanceHandles.push_back(std::move(instanceHandle)); + } + } + + return nonNullInstanceHandles; +} } // namespace namespace facebook::react { @@ -43,41 +68,49 @@ std::optional NativeDOM::getParentNode( jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - auto parentShadowNode = - getUIManagerFromRuntime(rt).getNewestParentOfShadowNode(*shadowNode); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + return std::nullopt; + } - // shadowNode is a RootShadowNode - if (!parentShadowNode) { + auto parentShadowNode = dom::getParentNode(currentRevision, *shadowNode); + if (parentShadowNode == nullptr) { return std::nullopt; } - return (*parentShadowNode).getInstanceHandle(rt); + return parentShadowNode->getInstanceHandle(rt); } std::optional> NativeDOM::getChildNodes( jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + return std::nullopt; + } - auto newestCloneOfShadowNode = - getUIManagerFromRuntime(rt).getNewestCloneOfShadowNode(*shadowNode); + auto childNodes = dom::getChildNodes(currentRevision, *shadowNode); // There's no version of this node in the current shadow tree - if (newestCloneOfShadowNode == nullptr) { + if (!childNodes) { return std::nullopt; } - auto childShadowNodes = newestCloneOfShadowNode->getChildren(); - return getArrayOfInstanceHandlesFromShadowNodes(childShadowNodes, rt); + return getArrayOfInstanceHandlesFromShadowNodes(childNodes.value(), rt); } bool NativeDOM::isConnected(jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + return false; + } - auto newestCloneOfShadowNode = - getUIManagerFromRuntime(rt).getNewestCloneOfShadowNode(*shadowNode); - - return newestCloneOfShadowNode != nullptr; + return dom::isConnected(currentRevision, *shadowNode); } double NativeDOM::compareDocumentPosition( @@ -86,23 +119,27 @@ double NativeDOM::compareDocumentPosition( jsi::Value otherShadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); auto otherShadowNode = shadowNodeFromValue(rt, otherShadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (otherShadowNode == nullptr || currentRevision == nullptr) { + return 0; + } - auto documentPosition = getUIManagerFromRuntime(rt).compareDocumentPosition( - *shadowNode, *otherShadowNode); - - return documentPosition; + return dom::compareDocumentPosition( + currentRevision, *shadowNode, *otherShadowNode); } std::string NativeDOM::getTextContent( jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + return ""; + } - auto textContent = - getUIManagerFromRuntime(rt).getTextContentInNewestCloneOfShadowNode( - *shadowNode); - - return textContent; + return dom::getTextContent(currentRevision, *shadowNode); } std::optionalgetSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - auto frame = layoutMetrics.frame; - return std::tuple{ - frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}; + return dom::getBoundingClientRect( + currentRevision, *shadowNode, includeTransform); } std::optional> NativeDOM::getOffset(jsi::Runtime& rt, jsi::Value shadowNodeValue) { - auto& uiManager = getUIManagerFromRuntime(rt); auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - auto newestCloneOfShadowNode = - uiManager.getNewestCloneOfShadowNode(*shadowNode); - auto newestPositionedAncestorOfShadowNode = - uiManager.getNewestPositionedAncestorOfShadowNode(*shadowNode); - // The node is no longer part of an active shadow tree, or it is the - // root node - if (newestCloneOfShadowNode == nullptr || - newestPositionedAncestorOfShadowNode == nullptr) { + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - // If the node is not displayed (itself or any of its ancestors has - // "display: none"), this returns an empty layout metrics object. - auto shadowNodeLayoutMetricsRelativeToRoot = - uiManager.getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ false}); - if (shadowNodeLayoutMetricsRelativeToRoot == EmptyLayoutMetrics) { - return std::nullopt; - } + auto offset = dom::getOffset(currentRevision, *shadowNode); - auto positionedAncestorLayoutMetricsRelativeToRoot = - uiManager.getRelativeLayoutMetrics( - *newestPositionedAncestorOfShadowNode, - nullptr, - {/* .includeTransform = */ false}); - if (positionedAncestorLayoutMetricsRelativeToRoot == EmptyLayoutMetrics) { + if (!offset) { return std::nullopt; } - auto shadowNodeOriginRelativeToRoot = - shadowNodeLayoutMetricsRelativeToRoot.frame.origin; - auto positionedAncestorOriginRelativeToRoot = - positionedAncestorLayoutMetricsRelativeToRoot.frame.origin; - - // On the Web, offsets are computed from the inner border of the - // parent. - auto offsetTop = shadowNodeOriginRelativeToRoot.y - - positionedAncestorOriginRelativeToRoot.y - - positionedAncestorLayoutMetricsRelativeToRoot.borderWidth.top; - auto offsetLeft = shadowNodeOriginRelativeToRoot.x - - positionedAncestorOriginRelativeToRoot.x - - positionedAncestorLayoutMetricsRelativeToRoot.borderWidth.left; + auto& offsetValue = offset.value(); return std::tuple{ - (*newestPositionedAncestorOfShadowNode).getInstanceHandle(rt), - offsetTop, - offsetLeft}; + std::get<0>(offsetValue)->getInstanceHandle(rt), + std::get<1>(offsetValue), + std::get<2>(offsetValue)}; } std::optional> NativeDOM::getScrollPosition(jsi::Runtime& rt, jsi::Value shadowNodeValue) { - auto& uiManager = getUIManagerFromRuntime(rt); auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - auto newestCloneOfShadowNode = - uiManager.getNewestCloneOfShadowNode(*shadowNode); - // The node is no longer part of an active shadow tree, or it is the - // root node - if (newestCloneOfShadowNode == nullptr) { - return std::nullopt; - } - - // If the node is not displayed (itself or any of its ancestors has - // "display: none"), this returns an empty layout metrics object. - auto layoutMetrics = uiManager.getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); - - if (layoutMetrics == EmptyLayoutMetrics) { + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - auto layoutableShadowNode = - dynamic_cast(newestCloneOfShadowNode.get()); - // This should never happen - if (layoutableShadowNode == nullptr) { - return std::nullopt; - } - - auto scrollPosition = layoutableShadowNode->getContentOriginOffset(); - - return std::tuple{ - scrollPosition.x == 0 ? 0 : -scrollPosition.x, - scrollPosition.y == 0 ? 0 : -scrollPosition.y}; + return dom::getScrollPosition(currentRevision, *shadowNode); } std::optional> NativeDOM::getScrollSize(jsi::Runtime& rt, jsi::Value shadowNodeValue) { - auto& uiManager = getUIManagerFromRuntime(rt); auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - auto newestCloneOfShadowNode = - uiManager.getNewestCloneOfShadowNode(*shadowNode); - // The node is no longer part of an active shadow tree, or it is the - // root node - if (newestCloneOfShadowNode == nullptr) { - return std::nullopt; - } - - // If the node is not displayed (itself or any of its ancestors has - // "display: none"), this returns an empty layout metrics object. - auto layoutMetrics = uiManager.getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ false}); - - if (layoutMetrics == EmptyLayoutMetrics || - layoutMetrics.displayType == DisplayType::Inline) { + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - auto layoutableShadowNode = dynamic_cast( - newestCloneOfShadowNode.get()); - // This should never happen - if (layoutableShadowNode == nullptr) { - return std::nullopt; - } - - Size scrollSize = getScrollableContentBounds( - layoutableShadowNode->getContentBounds(), layoutMetrics) - .size; - - return std::tuple{ - std::round(scrollSize.width), std::round(scrollSize.height)}; + return dom::getScrollSize(currentRevision, *shadowNode); } std::optional> NativeDOM::getInnerSize(jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - // If the node is not displayed (itself or any of its ancestors has - // "display: none"), this returns an empty layout metrics object. - auto layoutMetrics = getUIManagerFromRuntime(rt).getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ false}); - - if (layoutMetrics == EmptyLayoutMetrics || - layoutMetrics.displayType == DisplayType::Inline) { + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - auto paddingFrame = layoutMetrics.getPaddingFrame(); - - return std::tuple{ - std::round(paddingFrame.size.width), - std::round(paddingFrame.size.height)}; + return dom::getInnerSize(currentRevision, *shadowNode); } std::optional> NativeDOM::getBorderSize(jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - // If the node is not displayed (itself or any of its ancestors has - // "display: none"), this returns an empty layout metrics object. - auto layoutMetrics = getUIManagerFromRuntime(rt).getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ false}); - - if (layoutMetrics == EmptyLayoutMetrics || - layoutMetrics.displayType == DisplayType::Inline) { + auto currentRevision = + getCurrentShadowTreeRevision(rt, shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { return std::nullopt; } - return std::tuple{ - std::round(layoutMetrics.borderWidth.top), - std::round(layoutMetrics.borderWidth.right), - std::round(layoutMetrics.borderWidth.bottom), - std::round(layoutMetrics.borderWidth.left)}; + return dom::getBorderSize(currentRevision, *shadowNode); } std::string NativeDOM::getTagName( jsi::Runtime& rt, jsi::Value shadowNodeValue) { auto shadowNode = shadowNodeFromValue(rt, shadowNodeValue); - - std::string canonicalComponentName = shadowNode->getComponentName(); - - // FIXME(T162807327): Remove Android-specific prefixes and unify - // shadow node implementations - if (canonicalComponentName == "AndroidTextInput") { - canonicalComponentName = "TextInput"; - } else if (canonicalComponentName == "AndroidSwitch") { - canonicalComponentName = "Switch"; - } - - // Prefix with RN: - canonicalComponentName.insert(0, "RN:"); - - return canonicalComponentName; + return dom::getTagName(*shadowNode); } bool NativeDOM::hasPointerCapture( diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 30c6d98f483d..8a957343c12f 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<27923a2dbf1dcbad238a4d06ebb54fb5>> + * @generated SignedSource<<7e09a7ad1d178850bdcf73da3eb5623b>> */ /** @@ -77,6 +77,11 @@ bool NativeReactNativeFeatureFlags::enableSpannableBuildingUnification( return ReactNativeFeatureFlags::enableSpannableBuildingUnification(); } +bool NativeReactNativeFeatureFlags::enableUIConsistency( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableUIConsistency(); +} + bool NativeReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::inspectorEnableCxxInspectorPackagerConnection(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index ffe0f7044d54..bdc52a818d5c 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<53251a11631eccf1fcc334b14f9ca4c6>> + * @generated SignedSource<> */ /** @@ -51,6 +51,8 @@ class NativeReactNativeFeatureFlags bool enableSpannableBuildingUnification(jsi::Runtime& runtime); + bool enableUIConsistency(jsi::Runtime& runtime); + bool inspectorEnableCxxInspectorPackagerConnection(jsi::Runtime& runtime); bool inspectorEnableModernCDPRegistry(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt new file mode 100644 index 000000000000..4273bb456145 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/consistency/CMakeLists.txt @@ -0,0 +1,20 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic + -DLOG_TAG=\"Fabric\") + +file(GLOB react_render_consistency_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_render_consistency SHARED ${react_render_consistency_SRC}) + +target_include_directories(react_render_consistency PUBLIC ${REACT_COMMON_DIR}) diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec b/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec new file mode 100644 index 000000000000..873eb7c96818 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/consistency/React-rendererconsistency.podspec @@ -0,0 +1,45 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "..", "..", "..", "..", "package.json"))) +version = package['version'] + +source = { :git => 'https://github.com/facebook/react-native.git' } +if version == '1000.0.0' + # This is an unpublished version, use the latest commit hash of the react-native repo, which we’re presumably in. + source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1") +else + source[:tag] = "v#{version}" +end + +header_search_paths = [] + +if ENV['USE_FRAMEWORKS'] + header_search_paths << "\"$(PODS_TARGET_SRCROOT)/../../..\"" # this is needed to allow the rendererconsistency access its own files +end + +Pod::Spec.new do |s| + s.name = "React-rendererconsistency" + s.version = version + s.summary = "Fabric UI consistency primitives" + s.homepage = "https://reactnative.dev/" + s.license = package["license"] + s.author = "Meta Platforms, Inc. and its affiliates" + s.platforms = min_supported_versions + s.source = source + s.source_files = "**/*.{cpp,h}" + s.header_dir = "react/renderer/consistency" + s.exclude_files = "tests" + s.pod_target_xcconfig = { + "CLANG_CXX_LANGUAGE_STANDARD" => "c++20", + "HEADER_SEARCH_PATHS" => header_search_paths.join(' ')} + + if ENV['USE_FRAMEWORKS'] + s.module_name = "React_rendererconsistency" + s.header_mappings_dir = "../../.." + end +end diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/ScopedShadowTreeRevisionLock.h b/packages/react-native/ReactCommon/react/renderer/consistency/ScopedShadowTreeRevisionLock.h new file mode 100644 index 000000000000..2bc68d8a2bef --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/consistency/ScopedShadowTreeRevisionLock.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include + +namespace facebook::react { + +/** + * This is a RAII class that locks the shadow tree revisions during its + * lifetime. + * + * @example + * { + * ScopedShadowTreeRevisionLock lock(consistencyManager); + * runJavaScriptTask(); // During this execution, the lock will be active. + * } + */ +class ScopedShadowTreeRevisionLock { + public: + explicit ScopedShadowTreeRevisionLock( + ShadowTreeRevisionConsistencyManager* consistencyManager) noexcept + : consistencyManager_(consistencyManager) { + if (consistencyManager_ != nullptr) { + consistencyManager_->lockRevisions(); + } + } + + // Non-movable + ScopedShadowTreeRevisionLock(const ScopedShadowTreeRevisionLock&) = delete; + ScopedShadowTreeRevisionLock(ScopedShadowTreeRevisionLock&&) = delete; + + // Non-copyable + ScopedShadowTreeRevisionLock& operator=(const ScopedShadowTreeRevisionLock&) = + delete; + ScopedShadowTreeRevisionLock& operator=(ScopedShadowTreeRevisionLock&&) = + delete; + + ~ScopedShadowTreeRevisionLock() noexcept { + if (consistencyManager_ != nullptr) { + consistencyManager_->unlockRevisions(); + } + } + + private: + ShadowTreeRevisionConsistencyManager* consistencyManager_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.cpp b/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.cpp new file mode 100644 index 000000000000..686a0bfc0c8c --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.cpp @@ -0,0 +1,14 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "ShadowTreeRevisionConsistencyManager.h" + +namespace facebook::react { + +// This is a placeholder for CMakeFile not to complain + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h b/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h new file mode 100644 index 000000000000..d5afba016f64 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/consistency/ShadowTreeRevisionConsistencyManager.h @@ -0,0 +1,24 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +namespace facebook::react { + +/** + * This interface is used for UI consistency, indicating the timeframe where + * users should see the same revision of the shadow tree. + */ +class ShadowTreeRevisionConsistencyManager { + public: + virtual ~ShadowTreeRevisionConsistencyManager() = default; + + virtual void lockRevisions() = 0; + virtual void unlockRevisions() = 0; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt new file mode 100644 index 000000000000..2bc7a84189b1 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/dom/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic + -DLOG_TAG=\"Fabric\") + +file(GLOB react_render_dom_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_render_dom STATIC ${react_render_dom_SRC}) + +target_include_directories(react_render_dom PUBLIC ${REACT_COMMON_DIR}) + +target_link_libraries(react_render_dom + react_render_core + react_render_graphics + rrc_root + rrc_text) diff --git a/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp b/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp new file mode 100644 index 000000000000..89405542e1e0 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/dom/DOM.cpp @@ -0,0 +1,603 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "DOM.h" +#include +#include +#include +#include +#include + +namespace { + +using namespace facebook::react; + +// To prevent ambiguity with built-in MacOS types. +using facebook::react::Point; +using facebook::react::Rect; +using facebook::react::Size; + +constexpr uint_fast16_t DOCUMENT_POSITION_DISCONNECTED = 1; +constexpr uint_fast16_t DOCUMENT_POSITION_PRECEDING = 2; +constexpr uint_fast16_t DOCUMENT_POSITION_FOLLOWING = 4; +constexpr uint_fast16_t DOCUMENT_POSITION_CONTAINS = 8; +constexpr uint_fast16_t DOCUMENT_POSITION_CONTAINED_BY = 16; + +ShadowNode::Shared getShadowNodeInRevision( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + // If the given shadow node is of the same family as the root shadow node, + // return the latest root shadow node + if (ShadowNode::sameFamily(*currentRevision, shadowNode)) { + return currentRevision; + } + + auto ancestors = shadowNode.getFamily().getAncestors(*currentRevision); + + if (ancestors.empty()) { + return nullptr; + } + + auto pair = ancestors.rbegin(); + return pair->first.get().getChildren().at(pair->second); +} + +ShadowNode::Shared getParentShadowNodeInRevision( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + // If the given shadow node is of the same family as the root shadow node, + // return the latest root shadow node + if (ShadowNode::sameFamily(*currentRevision, shadowNode)) { + return currentRevision; + } + + auto ancestors = shadowNode.getFamily().getAncestors(*currentRevision); + + if (ancestors.empty()) { + return nullptr; + } + + if (ancestors.size() == 1) { + // The parent is the shadow root + return currentRevision; + } + + auto parentOfParentPair = ancestors[ancestors.size() - 2]; + return parentOfParentPair.first.get().getChildren().at( + parentOfParentPair.second); +} + +ShadowNode::Shared getPositionedAncestorOfShadowNodeInRevision( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto ancestors = shadowNode.getFamily().getAncestors(*currentRevision); + + if (ancestors.empty()) { + return nullptr; + } + + for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { + const auto layoutableAncestorShadowNode = + dynamic_cast(&(it->first.get())); + if (layoutableAncestorShadowNode == nullptr) { + return nullptr; + } + if (layoutableAncestorShadowNode->getLayoutMetrics().positionType != + PositionType::Static) { + // We have found our nearest positioned ancestor, now to get a shared + // pointer of it + it++; + if (it != ancestors.rend()) { + return it->first.get().getChildren().at(it->second); + } + // else the positioned ancestor is the root which we return outside of the + // loop + } + } + + // If there is no positioned ancestor then we just consider the root + // to be one + return currentRevision; +} + +void getTextContentInShadowNode( + const ShadowNode& shadowNode, + std::string& result) { + auto rawTextShadowNode = dynamic_cast(&shadowNode); + + if (rawTextShadowNode != nullptr) { + result.append(rawTextShadowNode->getConcreteProps().text); + } + + for (const auto& childNode : shadowNode.getChildren()) { + getTextContentInShadowNode(*childNode, result); + } +} + +LayoutMetrics getRelativeLayoutMetrics( + const ShadowNode& ancestorNode, + const ShadowNode& shadowNode, + LayoutableShadowNode::LayoutInspectingPolicy policy) { + auto layoutableAncestorShadowNode = + dynamic_cast(&ancestorNode); + + if (layoutableAncestorShadowNode == nullptr) { + return EmptyLayoutMetrics; + } + + return LayoutableShadowNode::computeRelativeLayoutMetrics( + shadowNode.getFamily(), *layoutableAncestorShadowNode, policy); +} + +Rect getScrollableContentBounds( + Rect contentBounds, + LayoutMetrics layoutMetrics) { + auto paddingFrame = layoutMetrics.getPaddingFrame(); + + auto paddingBottom = + layoutMetrics.contentInsets.bottom - layoutMetrics.borderWidth.bottom; + auto paddingLeft = + layoutMetrics.contentInsets.left - layoutMetrics.borderWidth.left; + auto paddingRight = + layoutMetrics.contentInsets.right - layoutMetrics.borderWidth.right; + + auto minY = paddingFrame.getMinY(); + auto maxY = + std::max(paddingFrame.getMaxY(), contentBounds.getMaxY() + paddingBottom); + + auto minX = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft + ? std::min(paddingFrame.getMinX(), contentBounds.getMinX() - paddingLeft) + : paddingFrame.getMinX(); + auto maxX = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft + ? paddingFrame.getMaxX() + : std::max( + paddingFrame.getMaxX(), contentBounds.getMaxX() + paddingRight); + + return Rect{Point{minX, minY}, Size{maxX - minX, maxY - minY}}; +} + +} // namespace + +namespace facebook::react::dom { + +ShadowNode::Shared getParentNode( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + return getParentShadowNodeInRevision(currentRevision, shadowNode); +} + +std::optional> getChildNodes( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + return shadowNodeInCurrentRevision->getChildren(); +} + +bool isConnected( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + return shadowNodeInCurrentRevision != nullptr; +} + +uint_fast16_t compareDocumentPosition( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + const ShadowNode& otherShadowNode) { + if (shadowNode.getSurfaceId() != otherShadowNode.getSurfaceId()) { + return DOCUMENT_POSITION_DISCONNECTED; + } + + // Quick check for node vs. itself + if (&shadowNode == &otherShadowNode) { + return 0; + } + + auto ancestors = shadowNode.getFamily().getAncestors(*currentRevision); + if (ancestors.empty()) { + return DOCUMENT_POSITION_DISCONNECTED; + } + + auto otherAncestors = + otherShadowNode.getFamily().getAncestors(*currentRevision); + if (ancestors.empty()) { + return DOCUMENT_POSITION_DISCONNECTED; + } + + // Consume all common ancestors + size_t i = 0; + while (i < ancestors.size() && i < otherAncestors.size() && + ancestors[i].second == otherAncestors[i].second) { + i++; + } + + if (i == ancestors.size()) { + return (DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING); + } + + if (i == otherAncestors.size()) { + return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); + } + + if (ancestors[i].second > otherAncestors[i].second) { + return DOCUMENT_POSITION_PRECEDING; + } + + return DOCUMENT_POSITION_FOLLOWING; +} + +std::string getTextContent( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + std::string result; + + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + + if (shadowNodeInCurrentRevision != nullptr) { + getTextContentInShadowNode(*shadowNodeInCurrentRevision, result); + } + + return result; +} + +std::optional> +getBoundingClientRect( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + bool includeTransform) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + shadowNode, + {.includeTransform = includeTransform, .includeViewportOffset = true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto frame = layoutMetrics.frame; + return std::tuple{ + frame.origin.x, frame.origin.y, frame.size.width, frame.size.height}; +} + +std::optional> +getOffset( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + auto positionedAncestorOfShadowNodeInCurrentRevision = + getPositionedAncestorOfShadowNodeInRevision(currentRevision, shadowNode); + + // The node is no longer part of an active shadow tree, or it is the + // root node + if (shadowNodeInCurrentRevision == nullptr || + positionedAncestorOfShadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none"), this returns an empty layout metrics object. + auto shadowNodeLayoutMetricsRelativeToRoot = getRelativeLayoutMetrics( + *currentRevision, shadowNode, {.includeTransform = false}); + if (shadowNodeLayoutMetricsRelativeToRoot == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto positionedAncestorLayoutMetricsRelativeToRoot = getRelativeLayoutMetrics( + *currentRevision, + *positionedAncestorOfShadowNodeInCurrentRevision, + {.includeTransform = false}); + if (positionedAncestorLayoutMetricsRelativeToRoot == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto shadowNodeOriginRelativeToRoot = + shadowNodeLayoutMetricsRelativeToRoot.frame.origin; + auto positionedAncestorOriginRelativeToRoot = + positionedAncestorLayoutMetricsRelativeToRoot.frame.origin; + + // On the Web, offsets are computed from the inner border of the + // parent. + auto offsetTop = shadowNodeOriginRelativeToRoot.y - + positionedAncestorOriginRelativeToRoot.y - + positionedAncestorLayoutMetricsRelativeToRoot.borderWidth.top; + auto offsetLeft = shadowNodeOriginRelativeToRoot.x - + positionedAncestorOriginRelativeToRoot.x - + positionedAncestorLayoutMetricsRelativeToRoot.borderWidth.left; + + return std::tuple{ + positionedAncestorOfShadowNodeInCurrentRevision, offsetTop, offsetLeft}; +} + +std::optional> +getScrollPosition( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none"), this returns an empty layout metrics object. + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto layoutableShadowNode = dynamic_cast( + shadowNodeInCurrentRevision.get()); + // This should never happen + if (layoutableShadowNode == nullptr) { + return std::nullopt; + } + + auto scrollPosition = layoutableShadowNode->getContentOriginOffset(); + + return std::tuple{ + scrollPosition.x == 0 ? 0 : -scrollPosition.x, + scrollPosition.y == 0 ? 0 : -scrollPosition.y}; +} + +std::optional> +getScrollSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none"), this returns an empty layout metrics object. + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = false}); + + if (layoutMetrics == EmptyLayoutMetrics || + layoutMetrics.displayType == DisplayType::Inline) { + return std::nullopt; + } + + auto layoutableShadowNode = dynamic_cast( + shadowNodeInCurrentRevision.get()); + // This should never happen + if (layoutableShadowNode == nullptr) { + return std::nullopt; + } + + Size scrollSize = getScrollableContentBounds( + layoutableShadowNode->getContentBounds(), layoutMetrics) + .size; + + return std::tuple{ + std::round(scrollSize.width), std::round(scrollSize.height)}; +} + +std::optional> getInnerSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none"), this returns an empty layout metrics object. + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = false}); + + if (layoutMetrics == EmptyLayoutMetrics || + layoutMetrics.displayType == DisplayType::Inline) { + return std::nullopt; + } + + auto paddingFrame = layoutMetrics.getPaddingFrame(); + + return std::tuple{ + std::round(paddingFrame.size.width), + std::round(paddingFrame.size.height)}; +} + +std::optional> +getBorderSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + // If the node is not displayed (itself or any of its ancestors has + // "display: none"), this returns an empty layout metrics object. + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = false}); + + if (layoutMetrics == EmptyLayoutMetrics || + layoutMetrics.displayType == DisplayType::Inline) { + return std::nullopt; + } + + return std::tuple{ + std::round(layoutMetrics.borderWidth.top), + std::round(layoutMetrics.borderWidth.right), + std::round(layoutMetrics.borderWidth.bottom), + std::round(layoutMetrics.borderWidth.left)}; +} + +std::string getTagName(const ShadowNode& shadowNode) { + std::string canonicalComponentName = shadowNode.getComponentName(); + + // FIXME(T162807327): Remove Android-specific prefixes and unify + // shadow node implementations + if (canonicalComponentName == "AndroidTextInput") { + canonicalComponentName = "TextInput"; + } else if (canonicalComponentName == "AndroidSwitch") { + canonicalComponentName = "Switch"; + } + + // Prefix with RN: + canonicalComponentName.insert(0, "RN:"); + + return canonicalComponentName; +} + +std::optional> +measure( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = true, .includeViewportOffset = false}); + + if (layoutMetrics == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto layoutableShadowNode = dynamic_cast( + shadowNodeInCurrentRevision.get()); + Point originRelativeToParent = layoutableShadowNode != nullptr + ? layoutableShadowNode->getLayoutMetrics().frame.origin + : Point(); + + auto frame = layoutMetrics.frame; + + return std::tuple{ + (double)originRelativeToParent.x, + (double)originRelativeToParent.y, + (double)frame.size.width, + (double)frame.size.height, + (double)frame.origin.x, + (double)frame.origin.y}; +} + +std::optional> +measureInWindow( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + auto layoutMetrics = getRelativeLayoutMetrics( + *currentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = true, .includeViewportOffset = true}); + + if (layoutMetrics == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto frame = layoutMetrics.frame; + return std::tuple{ + (double)frame.origin.x, + (double)frame.origin.y, + (double)frame.size.width, + (double)frame.size.height, + }; +} + +std::optional> +measureLayout( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + const ShadowNode& relativeToShadowNode) { + auto shadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, shadowNode); + if (shadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + auto relativeToShadowNodeInCurrentRevision = + getShadowNodeInRevision(currentRevision, relativeToShadowNode); + if (relativeToShadowNodeInCurrentRevision == nullptr) { + return std::nullopt; + } + + auto layoutMetrics = getRelativeLayoutMetrics( + *relativeToShadowNodeInCurrentRevision, + *shadowNodeInCurrentRevision, + {.includeTransform = false}); + + if (layoutMetrics == EmptyLayoutMetrics) { + return std::nullopt; + } + + auto frame = layoutMetrics.frame; + + return std::tuple{ + (double)frame.origin.x, + (double)frame.origin.y, + (double)frame.size.width, + (double)frame.size.height, + }; +} + +} // namespace facebook::react::dom diff --git a/packages/react-native/ReactCommon/react/renderer/dom/DOM.h b/packages/react-native/ReactCommon/react/renderer/dom/DOM.h new file mode 100644 index 000000000000..9952d6d5b191 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/dom/DOM.h @@ -0,0 +1,119 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace facebook::react::dom { + +ShadowNode::Shared getParentNode( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> getChildNodes( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +bool isConnected( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +uint_fast16_t compareDocumentPosition( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + const ShadowNode& otherShadowNode); + +std::string getTextContent( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +getBoundingClientRect( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + bool includeTransform); + +std::optional> +getOffset( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +getScrollPosition( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +getScrollSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> getInnerSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +getBorderSize( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::string getTagName(const ShadowNode& shadowNode); + +// Non-standard methods from React Native + +std::optional> +measure( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +measureInWindow( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode); + +std::optional> +measureLayout( + const RootShadowNode::Shared& currentRevision, + const ShadowNode& shadowNode, + const ShadowNode& relativeToShadowNode); + +} // namespace facebook::react::dom diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt index 85f9f2c125ff..9149d39eeabe 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(react_render_runtimescheduler callinvoker jsi react_debug + react_render_consistency react_render_debug react_utils react_featureflags diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec index 642d71980a94..739502a89036 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/React-runtimescheduler.podspec @@ -60,6 +60,7 @@ Pod::Spec.new do |s| s.dependency "glog" s.dependency "RCT-Folly", folly_version s.dependency "React-jsi" + s.dependency "React-rendererconsistency" add_dependency(s, "React-debug") if ENV["USE_HERMES"] == nil || ENV["USE_HERMES"] == "1" diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp index 4f2cf5d8646d..ffad447dc366 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp @@ -84,4 +84,11 @@ void RuntimeScheduler::scheduleRenderingUpdate( std::move(renderingUpdate)); } +void RuntimeScheduler::setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) { + return runtimeSchedulerImpl_->setShadowTreeRevisionConsistencyManager( + shadowTreeRevisionConsistencyManager); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h index 115bc3ec3d99..cdb321cca02a 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include @@ -35,6 +36,8 @@ class RuntimeSchedulerBase { virtual void callExpiredTasks(jsi::Runtime& runtime) = 0; virtual void scheduleRenderingUpdate( RuntimeSchedulerRenderingUpdate&& renderingUpdate) = 0; + virtual void setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* provider) = 0; }; // This is a proxy for RuntimeScheduler implementation, which will be selected @@ -127,6 +130,10 @@ class RuntimeScheduler final : RuntimeSchedulerBase { void scheduleRenderingUpdate( RuntimeSchedulerRenderingUpdate&& renderingUpdate) override; + void setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) override; + private: // Actual implementation, stored as a unique pointer to simplify memory // management. diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp index 325de9f44c27..2d13b9b8e645 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp @@ -8,6 +8,7 @@ #include "RuntimeScheduler_Legacy.h" #include "SchedulerPriorityUtils.h" +#include #include #include #include "ErrorUtils.h" @@ -30,7 +31,11 @@ void RuntimeScheduler_Legacy::scheduleWork(RawCallback&& callback) noexcept { [this, callback = std::move(callback)](jsi::Runtime& runtime) { SystraceSection s2("RuntimeScheduler::scheduleWork callback"); runtimeAccessRequests_ -= 1; - callback(runtime); + { + ScopedShadowTreeRevisionLock revisionLock( + shadowTreeRevisionConsistencyManager_); + callback(runtime); + } startWorkLoop(runtime); }); } @@ -104,7 +109,11 @@ void RuntimeScheduler_Legacy::executeNowOnTheSameThread( "RuntimeScheduler::executeNowOnTheSameThread callback"); runtimeAccessRequests_ -= 1; - callback(runtime); + { + ScopedShadowTreeRevisionLock revisionLock( + shadowTreeRevisionConsistencyManager_); + callback(runtime); + } }); // Resume work loop if needed. In synchronous mode @@ -145,6 +154,12 @@ void RuntimeScheduler_Legacy::scheduleRenderingUpdate( } } +void RuntimeScheduler_Legacy::setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) { + shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager; +} + #pragma mark - Private void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() { @@ -195,13 +210,19 @@ void RuntimeScheduler_Legacy::executeTask( didUserCallbackTimeout); currentPriority_ = task->priority; - auto result = task->execute(runtime, didUserCallbackTimeout); - if (result.isObject() && result.getObject(runtime).isFunction(runtime)) { - task->callback = result.getObject(runtime).getFunction(runtime); - } else { - if (taskQueue_.top() == task) { - taskQueue_.pop(); + { + ScopedShadowTreeRevisionLock revisionLock( + shadowTreeRevisionConsistencyManager_); + + auto result = task->execute(runtime, didUserCallbackTimeout); + + if (result.isObject() && result.getObject(runtime).isFunction(runtime)) { + task->callback = result.getObject(runtime).getFunction(runtime); + } else { + if (taskQueue_.top() == task) { + taskQueue_.pop(); + } } } } diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h index fc436b5d2283..d5f673d39b66 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -105,6 +106,10 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase { void scheduleRenderingUpdate( RuntimeSchedulerRenderingUpdate&& renderingUpdate) override; + void setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) override; + private: std::priority_queue< std::shared_ptr, @@ -151,6 +156,9 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase { * This flag is set while performing work, to prevent re-entrancy. */ std::atomic_bool isPerformingWork_{false}; + + ShadowTreeRevisionConsistencyManager* shadowTreeRevisionConsistencyManager_{ + nullptr}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 76011d0a272f..c07e2f8c2e9c 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -153,6 +154,12 @@ void RuntimeScheduler_Modern::scheduleRenderingUpdate( } } +void RuntimeScheduler_Modern::setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) { + shadowTreeRevisionConsistencyManager_ = shadowTreeRevisionConsistencyManager; +} + #pragma mark - Private void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr task) { @@ -253,16 +260,21 @@ void RuntimeScheduler_Modern::executeTask( currentTask_ = task; currentPriority_ = task->priority; - executeMacrotask(runtime, task, didUserCallbackTimeout); + { + ScopedShadowTreeRevisionLock revisionLock( + shadowTreeRevisionConsistencyManager_); - if (ReactNativeFeatureFlags::enableMicrotasks()) { - // "Perform a microtask checkpoint" step. - performMicrotaskCheckpoint(runtime); - } + executeMacrotask(runtime, task, didUserCallbackTimeout); - if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) { - // "Update the rendering" step. - updateRendering(); + if (ReactNativeFeatureFlags::enableMicrotasks()) { + // "Perform a microtask checkpoint" step. + performMicrotaskCheckpoint(runtime); + } + + if (ReactNativeFeatureFlags::batchRenderingUpdatesInEventLoop()) { + // "Update the rendering" step. + updateRendering(); + } } } diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h index d31707f5217c..372d8c5da19a 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h @@ -8,6 +8,7 @@ #pragma once #include +#include #include #include #include @@ -122,6 +123,10 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase { void scheduleRenderingUpdate( RuntimeSchedulerRenderingUpdate&& renderingUpdate) override; + void setShadowTreeRevisionConsistencyManager( + ShadowTreeRevisionConsistencyManager* + shadowTreeRevisionConsistencyManager) override; + private: std::atomic syncTaskRequests_{0}; @@ -184,6 +189,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase { bool isWorkLoopScheduled_{false}; std::queue pendingRenderingUpdates_; + ShadowTreeRevisionConsistencyManager* shadowTreeRevisionConsistencyManager_{ + nullptr}; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt index 38fdcb66e863..44fda8bc85fc 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/CMakeLists.txt @@ -25,6 +25,7 @@ target_link_libraries(react_render_scheduler jsi react_config react_debug + react_featureflags react_render_componentregistry react_render_core react_render_debug diff --git a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 908e01904dd5..9f5dc0e75f4c 100644 --- a/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -54,6 +55,11 @@ Scheduler::Scheduler( ? weakRuntimeScheduler.value().lock() : nullptr; + if (runtimeScheduler && ReactNativeFeatureFlags::enableUIConsistency()) { + runtimeScheduler->setShadowTreeRevisionConsistencyManager( + uiManager->getShadowTreeRevisionConsistencyManager()); + } + auto eventPipe = [uiManager, runtimeScheduler = runtimeScheduler.get()]( jsi::Runtime& runtime, const EventTarget* eventTarget, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt index badeb64afcfd..15df5f7c984f 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt @@ -27,8 +27,11 @@ target_link_libraries(react_render_uimanager react_debug react_featureflags react_render_componentregistry + react_render_consistency + react_render_uimanager_consistency react_render_core react_render_debug + react_render_dom react_render_graphics react_render_leakchecker react_render_runtimescheduler diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 0ad24461223a..6e909a73902b 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -24,11 +24,14 @@ #include namespace { -constexpr int DOCUMENT_POSITION_DISCONNECTED = 1; -constexpr int DOCUMENT_POSITION_PRECEDING = 2; -constexpr int DOCUMENT_POSITION_FOLLOWING = 4; -constexpr int DOCUMENT_POSITION_CONTAINS = 8; -constexpr int DOCUMENT_POSITION_CONTAINED_BY = 16; +std::unique_ptr constructLeakCheckerIfNeeded( + const facebook::react::RuntimeExecutor& runtimeExecutor) { +#ifdef REACT_NATIVE_DEBUG + return std::make_unique(runtimeExecutor); +#else + return {}; +#endif +} } // namespace namespace facebook::react { @@ -39,23 +42,25 @@ namespace facebook::react { // isHostObject method) ShadowNodeListWrapper::~ShadowNodeListWrapper() = default; -static std::unique_ptr constructLeakCheckerIfNeeded( - const RuntimeExecutor& runtimeExecutor) { -#ifdef REACT_NATIVE_DEBUG - return std::make_unique(runtimeExecutor); -#else - return {}; -#endif -} - UIManager::UIManager( const RuntimeExecutor& runtimeExecutor, BackgroundExecutor backgroundExecutor, ContextContainer::Shared contextContainer) : runtimeExecutor_(runtimeExecutor), + shadowTreeRegistry_(), backgroundExecutor_(std::move(backgroundExecutor)), contextContainer_(std::move(contextContainer)), - leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)) {} + leakChecker_(constructLeakCheckerIfNeeded(runtimeExecutor)), + lazyShadowTreeRevisionConsistencyManager_( + ReactNativeFeatureFlags::enableUIConsistency() + ? std::make_unique( + shadowTreeRegistry_) + : nullptr), + latestShadowTreeRevisionProvider_( + ReactNativeFeatureFlags::enableUIConsistency() + ? nullptr + : std::make_unique( + shadowTreeRegistry_)) {} UIManager::~UIManager() { LOG(WARNING) << "UIManager::~UIManager() was called (address: " << this @@ -166,11 +171,11 @@ void UIManager::appendChild( void UIManager::completeSurface( SurfaceId surfaceId, const ShadowNode::UnsharedListOfShared& rootChildren, - ShadowTree::CommitOptions commitOptions) const { + ShadowTree::CommitOptions commitOptions) { SystraceSection s("UIManager::completeSurface", "surfaceId", surfaceId); shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { - shadowTree.commit( + auto result = shadowTree.commit( [&](RootShadowNode const& oldRootShadowNode) { return std::make_shared( oldRootShadowNode, @@ -180,6 +185,14 @@ void UIManager::completeSurface( }); }, commitOptions); + + if (result == ShadowTree::CommitStatus::Succeeded && + lazyShadowTreeRevisionConsistencyManager_ != nullptr) { + // It's safe to update the visible revision of the shadow tree immediately + // after we commit a specific one. + lazyShadowTreeRevisionConsistencyManager_->updateCurrentRevision( + surfaceId, shadowTree.getCurrentRevision().rootShadowNode); + } }); } @@ -276,136 +289,22 @@ ShadowNode::Shared UIManager::getNewestCloneOfShadowNode( return pair->first.get().getChildren().at(pair->second); } -ShadowNode::Shared UIManager::getNewestParentOfShadowNode( - const ShadowNode& shadowNode) const { - auto ancestorShadowNode = ShadowNode::Shared{}; - shadowTreeRegistry_.visit( - shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) { - ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode; - }); - - if (!ancestorShadowNode) { - return nullptr; - } - - auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode); - - if (ancestors.empty()) { - return nullptr; - } - - if (ancestors.size() == 1) { - // The parent is the shadow root - return ancestorShadowNode; - } - - auto parentOfParentPair = ancestors[ancestors.size() - 2]; - return parentOfParentPair.first.get().getChildren().at( - parentOfParentPair.second); -} - -ShadowNode::Shared UIManager::getNewestPositionedAncestorOfShadowNode( - const ShadowNode& shadowNode) const { - auto rootShadowNode = ShadowNode::Shared{}; - shadowTreeRegistry_.visit( - shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) { - rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; - }); - - if (!rootShadowNode) { - return nullptr; - } - - auto ancestors = shadowNode.getFamily().getAncestors(*rootShadowNode); - - if (ancestors.empty()) { - return nullptr; - } - - for (auto it = ancestors.rbegin(); it != ancestors.rend(); it++) { - const auto layoutableAncestorShadowNode = - dynamic_cast(&(it->first.get())); - if (layoutableAncestorShadowNode == nullptr) { - return nullptr; - } - if (layoutableAncestorShadowNode->getLayoutMetrics().positionType != - PositionType::Static) { - // We have found our nearest positioned ancestor, now to get a shared - // pointer of it - it++; - if (it != ancestors.rend()) { - return it->first.get().getChildren().at(it->second); - } - // else the positioned ancestor is the root which we return outside of the - // loop - } - } - - // If there is no positioned ancestor then we just consider the root - // to be one - return rootShadowNode; -} - -std::string UIManager::getTextContentInNewestCloneOfShadowNode( - const ShadowNode& shadowNode) const { - auto newestCloneOfShadowNode = getNewestCloneOfShadowNode(shadowNode); - std::string result; - getTextContentInShadowNode(*newestCloneOfShadowNode, result); - return result; +ShadowTreeRevisionConsistencyManager* +UIManager::getShadowTreeRevisionConsistencyManager() { + return lazyShadowTreeRevisionConsistencyManager_.get(); } -int UIManager::compareDocumentPosition( - const ShadowNode& shadowNode, - const ShadowNode& otherShadowNode) const { - // Quick check for node vs. itself - if (&shadowNode == &otherShadowNode) { - return 0; - } - - if (shadowNode.getSurfaceId() != otherShadowNode.getSurfaceId()) { - return DOCUMENT_POSITION_DISCONNECTED; - } - - auto ancestorShadowNode = ShadowNode::Shared{}; - shadowTreeRegistry_.visit( - shadowNode.getSurfaceId(), [&](const ShadowTree& shadowTree) { - ancestorShadowNode = shadowTree.getCurrentRevision().rootShadowNode; - }); - if (!ancestorShadowNode) { - return DOCUMENT_POSITION_DISCONNECTED; - } - - auto ancestors = shadowNode.getFamily().getAncestors(*ancestorShadowNode); - if (ancestors.empty()) { - return DOCUMENT_POSITION_DISCONNECTED; +ShadowTreeRevisionProvider* UIManager::getShadowTreeRevisionProvider() { + if (lazyShadowTreeRevisionConsistencyManager_ != nullptr) { + return lazyShadowTreeRevisionConsistencyManager_.get(); + } else if (latestShadowTreeRevisionProvider_ != nullptr) { + return latestShadowTreeRevisionProvider_.get(); } - auto otherAncestors = - otherShadowNode.getFamily().getAncestors(*ancestorShadowNode); - if (ancestors.empty()) { - return DOCUMENT_POSITION_DISCONNECTED; - } - - // Consume all common ancestors - size_t i = 0; - while (i < ancestors.size() && i < otherAncestors.size() && - ancestors[i].second == otherAncestors[i].second) { - i++; - } - - if (i == ancestors.size()) { - return (DOCUMENT_POSITION_CONTAINED_BY | DOCUMENT_POSITION_FOLLOWING); - } - - if (i == otherAncestors.size()) { - return (DOCUMENT_POSITION_CONTAINS | DOCUMENT_POSITION_PRECEDING); - } - - if (ancestors[i].second > otherAncestors[i].second) { - return DOCUMENT_POSITION_PRECEDING; - } - - return DOCUMENT_POSITION_FOLLOWING; + LOG(ERROR) << "Unexpected state found in UIManager where both " + << "lazyShadowTreeRevisionConsistencyManager_ and " + << "latestShadowTreeRevisionProvider_ were null"; + return nullptr; } ShadowNode::Shared UIManager::findNodeAtPoint( diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index c0f443ff568e..d32dca94b190 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,9 @@ #include #include #include +#include +#include +#include #include #include @@ -93,18 +97,9 @@ class UIManager final : public ShadowTreeDelegate { ShadowNode::Shared getNewestCloneOfShadowNode( const ShadowNode& shadowNode) const; - ShadowNode::Shared getNewestParentOfShadowNode( - const ShadowNode& shadowNode) const; - - ShadowNode::Shared getNewestPositionedAncestorOfShadowNode( - const ShadowNode& shadowNode) const; - - std::string getTextContentInNewestCloneOfShadowNode( - const ShadowNode& shadowNode) const; - - int compareDocumentPosition( - const ShadowNode& shadowNode, - const ShadowNode& otherShadowNode) const; + ShadowTreeRevisionConsistencyManager* + getShadowTreeRevisionConsistencyManager(); + ShadowTreeRevisionProvider* getShadowTreeRevisionProvider(); #pragma mark - Surface Start & Stop @@ -152,7 +147,7 @@ class UIManager final : public ShadowTreeDelegate { void completeSurface( SurfaceId surfaceId, const ShadowNode::UnsharedListOfShared& rootChildren, - ShadowTree::CommitOptions commitOptions) const; + ShadowTree::CommitOptions commitOptions); void setIsJSResponder( const ShadowNode::Shared& shadowNode, @@ -238,6 +233,11 @@ class UIManager final : public ShadowTreeDelegate { mutable std::vector mountHooks_; std::unique_ptr leakChecker_; + + std::unique_ptr + lazyShadowTreeRevisionConsistencyManager_; + std::unique_ptr + latestShadowTreeRevisionProvider_; }; } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp index 2f9e26e0868b..7b6b038c3850 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerBinding.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -653,28 +654,38 @@ jsi::Value UIManagerBinding::get( size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNodeFromValue(runtime, arguments[0]), - shadowNodeFromValue(runtime, arguments[1]).get(), - {/* .includeTransform = */ false}); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); + auto relativeToShadowNode = + shadowNodeFromValue(runtime, arguments[1]); + auto onFailFunction = + arguments[2].getObject(runtime).getFunction(runtime); + auto onSuccessFunction = + arguments[3].getObject(runtime).getFunction(runtime); - if (layoutMetrics == EmptyLayoutMetrics) { - auto onFailFunction = - arguments[2].getObject(runtime).getFunction(runtime); + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { onFailFunction.call(runtime); return jsi::Value::undefined(); } - auto onSuccessFunction = - arguments[3].getObject(runtime).getFunction(runtime); - auto frame = layoutMetrics.frame; + auto result = dom::measureLayout( + currentRevision, *shadowNode, *relativeToShadowNode); + + if (!result) { + onFailFunction.call(runtime); + return jsi::Value::undefined(); + } + + auto [x, y, width, height] = result.value(); onSuccessFunction.call( runtime, - {jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}}); + {jsi::Value{runtime, x}, + jsi::Value{runtime, y}, + jsi::Value{runtime, width}, + jsi::Value{runtime, height}}); return jsi::Value::undefined(); }); } @@ -693,33 +704,34 @@ jsi::Value UIManagerBinding::get( validateArgumentCount(runtime, methodName, paramCount, count); auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNode, nullptr, {/* .includeTransform = */ true}); - auto onSuccessFunction = + auto callbackFunction = arguments[1].getObject(runtime).getFunction(runtime); - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); + if (currentRevision == nullptr) { + callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0}); return jsi::Value::undefined(); } - auto newestCloneOfShadowNode = - uiManager->getNewestCloneOfShadowNode(*shadowNode); - auto layoutableShadowNode = dynamic_cast( - newestCloneOfShadowNode.get()); - Point originRelativeToParent = layoutableShadowNode != nullptr - ? layoutableShadowNode->getLayoutMetrics().frame.origin - : Point(); + auto result = dom::measure(currentRevision, *shadowNode); - auto frame = layoutMetrics.frame; - onSuccessFunction.call( + if (!result) { + callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + return jsi::Value::undefined(); + } + + auto [x, y, width, height, pageX, pageY] = result.value(); + + callbackFunction.call( runtime, - {jsi::Value{runtime, (double)originRelativeToParent.x}, - jsi::Value{runtime, (double)originRelativeToParent.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}, - jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}}); + {jsi::Value{runtime, x}, + jsi::Value{runtime, y}, + jsi::Value{runtime, width}, + jsi::Value{runtime, height}, + jsi::Value{runtime, pageX}, + jsi::Value{runtime, pageY}}); return jsi::Value::undefined(); }); } @@ -737,27 +749,33 @@ jsi::Value UIManagerBinding::get( size_t count) { validateArgumentCount(runtime, methodName, paramCount, count); - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNodeFromValue(runtime, arguments[0]), - nullptr, - {/* .includeTransform = */ true, - /* .includeViewportOffset = */ true}); - - auto onSuccessFunction = + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); + auto callbackFunction = arguments[1].getObject(runtime).getFunction(runtime); - if (layoutMetrics == EmptyLayoutMetrics) { - onSuccessFunction.call(runtime, {0, 0, 0, 0}); + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); + + if (currentRevision == nullptr) { + callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0}); return jsi::Value::undefined(); } - auto frame = layoutMetrics.frame; - onSuccessFunction.call( + auto result = dom::measureInWindow(currentRevision, *shadowNode); + if (!result) { + callbackFunction.call(runtime, {0, 0, 0, 0, 0, 0}); + return jsi::Value::undefined(); + } + + auto [x, y, width, height] = result.value(); + + callbackFunction.call( runtime, - {jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}}); + {jsi::Value{runtime, x}, + jsi::Value{runtime, y}, + jsi::Value{runtime, width}, + jsi::Value{runtime, height}}); return jsi::Value::undefined(); }); } @@ -868,25 +886,32 @@ jsi::Value UIManagerBinding::get( size_t count) -> jsi::Value { validateArgumentCount(runtime, methodName, paramCount, count); + auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); bool includeTransform = arguments[1].getBool(); - auto layoutMetrics = uiManager->getRelativeLayoutMetrics( - *shadowNodeFromValue(runtime, arguments[0]), - nullptr, - {/* .includeTransform = */ includeTransform, - /* .includeViewportOffset = */ true}); + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); - if (layoutMetrics == EmptyLayoutMetrics) { + if (currentRevision == nullptr) { return jsi::Value::undefined(); } - auto frame = layoutMetrics.frame; + auto result = dom::getBoundingClientRect( + currentRevision, *shadowNode, includeTransform); + + if (!result) { + return jsi::Value::undefined(); + } + + auto [x, y, width, height] = result.value(); + return jsi::Array::createWithElements( runtime, - jsi::Value{runtime, (double)frame.origin.x}, - jsi::Value{runtime, (double)frame.origin.y}, - jsi::Value{runtime, (double)frame.size.width}, - jsi::Value{runtime, (double)frame.size.height}); + jsi::Value{runtime, x}, + jsi::Value{runtime, y}, + jsi::Value{runtime, width}, + jsi::Value{runtime, height}); }); } @@ -909,10 +934,18 @@ jsi::Value UIManagerBinding::get( auto shadowNode = shadowNodeFromValue(runtime, arguments[0]); auto otherShadowNode = shadowNodeFromValue(runtime, arguments[1]); - auto documentPosition = - uiManager->compareDocumentPosition(*shadowNode, *otherShadowNode); + auto currentRevision = + uiManager->getShadowTreeRevisionProvider()->getCurrentRevision( + shadowNode->getSurfaceId()); + + double documentPosition = 0; + + if (currentRevision != nullptr) { + documentPosition = (double)dom::compareDocumentPosition( + currentRevision, *shadowNode, *otherShadowNode); + } - return jsi::Value(documentPosition); + return {documentPosition}; }); } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt new file mode 100644 index 000000000000..543713ef0b81 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +cmake_minimum_required(VERSION 3.13) +set(CMAKE_VERBOSE_MAKEFILE on) + +add_compile_options( + -fexceptions + -frtti + -std=c++20 + -Wall + -Wpedantic + -DLOG_TAG=\"Fabric\") + +file(GLOB react_render_uimanager_consistency_SRC CONFIGURE_DEPENDS *.cpp) +add_library(react_render_uimanager_consistency SHARED ${react_render_uimanager_consistency_SRC}) + +target_include_directories(react_render_uimanager_consistency PUBLIC ${REACT_COMMON_DIR}) + +target_link_libraries(react_render_uimanager_consistency + glog + rrc_root + react_render_consistency + react_render_mounting) diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.cpp new file mode 100644 index 000000000000..3bf77f2a9be9 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LatestShadowTreeRevisionProvider.h" + +namespace facebook::react { + +LatestShadowTreeRevisionProvider::LatestShadowTreeRevisionProvider( + ShadowTreeRegistry& shadowTreeRegistry) + : shadowTreeRegistry_(shadowTreeRegistry) {} + +#pragma mark - ShadowTreeRevisionProvider + +RootShadowNode::Shared LatestShadowTreeRevisionProvider::getCurrentRevision( + SurfaceId surfaceId) { + RootShadowNode::Shared rootShadowNode; + + shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { + rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; + }); + + return rootShadowNode; +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.h b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.h new file mode 100644 index 000000000000..30b688e1ca43 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LatestShadowTreeRevisionProvider.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include + +namespace facebook::react { + +/** + * This is a drop-in replacement for `LazyShadowTreeRevisionConsistencyManager` + * that preserves the current behavior (always providing the latest committed + * revision instead of locking to a specific one). + */ +class LatestShadowTreeRevisionProvider : public ShadowTreeRevisionProvider { + public: + explicit LatestShadowTreeRevisionProvider( + ShadowTreeRegistry& shadowTreeRegistry); + +#pragma mark - ShadowTreeRevisionProvider + + RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override; + + private: + ShadowTreeRegistry& shadowTreeRegistry_; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp new file mode 100644 index 000000000000..c195a9b91369 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#include "LazyShadowTreeRevisionConsistencyManager.h" +#include + +namespace facebook::react { + +LazyShadowTreeRevisionConsistencyManager:: + LazyShadowTreeRevisionConsistencyManager( + ShadowTreeRegistry& shadowTreeRegistry) + : shadowTreeRegistry_(shadowTreeRegistry) {} + +void LazyShadowTreeRevisionConsistencyManager::updateCurrentRevision( + SurfaceId surfaceId, + RootShadowNode::Shared rootShadowNode) { + capturedRootShadowNodesForConsistency_.emplace( + surfaceId, std::move(rootShadowNode)); +} + +#pragma mark - ShadowTreeRevisionProvider + +RootShadowNode::Shared +LazyShadowTreeRevisionConsistencyManager::getCurrentRevision( + SurfaceId surfaceId) { + auto it = capturedRootShadowNodesForConsistency_.find(surfaceId); + if (it != capturedRootShadowNodesForConsistency_.end()) { + return it->second; + } + + RootShadowNode::Shared rootShadowNode; + + shadowTreeRegistry_.visit(surfaceId, [&](const ShadowTree& shadowTree) { + rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode; + }); + + capturedRootShadowNodesForConsistency_.emplace(surfaceId, rootShadowNode); + + return rootShadowNode; +} + +#pragma mark - ConsistentShadowTreeRevisionProvider + +void LazyShadowTreeRevisionConsistencyManager::lockRevisions() { + if (isLocked_) { + LOG(WARNING) + << "LazyShadowTreeRevisionConsistencyManager::lockRevisions() called without unlocking a previous lock"; + return; + } + + // We actually capture the state lazily the first time we access it, so we + // don't need to do anything here. + isLocked_ = true; +} + +void LazyShadowTreeRevisionConsistencyManager::unlockRevisions() { + if (!isLocked_) { + LOG(WARNING) + << "LazyShadowTreeRevisionConsistencyManager::unlockRevisions() called without a previous lock"; + // We don't return here because we want to do the cleanup anyway + // to free up resources. + } + + isLocked_ = false; + capturedRootShadowNodesForConsistency_.clear(); +} + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h new file mode 100644 index 000000000000..d507263cce55 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/LazyShadowTreeRevisionConsistencyManager.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace facebook::react { + +/** + * This class implements UI consistency for the JavaScript thread. + * This implementation forces JavaScript to see a stable revision of the shadow + * tree for a given surface ID, only updating it when React commits a new tree + * or between JS tasks. + */ +class LazyShadowTreeRevisionConsistencyManager + : public ShadowTreeRevisionConsistencyManager, + public ShadowTreeRevisionProvider { + public: + explicit LazyShadowTreeRevisionConsistencyManager( + ShadowTreeRegistry& shadowTreeRegistry); + + void updateCurrentRevision( + SurfaceId surfaceId, + RootShadowNode::Shared rootShadowNode); + +#pragma mark - ShadowTreeRevisionProvider + + RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) override; + +#pragma mark - ShadowTreeRevisionConsistencyManager + + void lockRevisions() override; + void unlockRevisions() override; + + private: + std::unordered_map + capturedRootShadowNodesForConsistency_; + ShadowTreeRegistry& shadowTreeRegistry_; + bool isLocked_{false}; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h new file mode 100644 index 000000000000..66c36d4aa916 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/consistency/ShadowTreeRevisionProvider.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once + +#include +#include +#include + +namespace facebook::react { + +/** + * This interface is used for UI consistency, indicating the revision of the + * shadow tree that a caller should have access to. + */ +class ShadowTreeRevisionProvider { + public: + virtual ~ShadowTreeRevisionProvider() = default; + + virtual RootShadowNode::Shared getCurrentRevision(SurfaceId surfaceId) = 0; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h index 7984b902bc27..03552d2b140f 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/primitives.h @@ -11,10 +11,7 @@ #include #include #include -#include -#include #include -#include namespace facebook::react { @@ -166,70 +163,4 @@ inline static folly::dynamic commandArgsFromValue( const jsi::Value& value) { return jsi::dynamicFromValue(runtime, value); } - -inline static std::vector getArrayOfInstanceHandlesFromShadowNodes( - const ShadowNode::ListOfShared& nodes, - jsi::Runtime& runtime) { - // JSI doesn't support adding elements to an array after creation, - // so we need to accumulate the values in a vector and then create - // the array when we know the size. - std::vector nonNullInstanceHandles; - nonNullInstanceHandles.reserve(nodes.size()); - for (const auto& shadowNode : nodes) { - auto instanceHandle = (*shadowNode).getInstanceHandle(runtime); - if (!instanceHandle.isNull()) { - nonNullInstanceHandles.push_back(std::move(instanceHandle)); - } - } - - return nonNullInstanceHandles; -} - -inline static void getTextContentInShadowNode( - const ShadowNode& shadowNode, - std::string& result) { - auto rawTextShadowNode = dynamic_cast(&shadowNode); - if (rawTextShadowNode != nullptr) { - result.append(rawTextShadowNode->getConcreteProps().text); - } - - for (const auto& childNode : shadowNode.getChildren()) { - getTextContentInShadowNode(*childNode.get(), result); - } -} - -inline static Rect getScrollableContentBounds( - Rect contentBounds, - LayoutMetrics layoutMetrics) { - auto paddingFrame = layoutMetrics.getPaddingFrame(); - - auto paddingBottom = - layoutMetrics.contentInsets.bottom - layoutMetrics.borderWidth.bottom; - auto paddingLeft = - layoutMetrics.contentInsets.left - layoutMetrics.borderWidth.left; - auto paddingRight = - layoutMetrics.contentInsets.right - layoutMetrics.borderWidth.right; - - auto minY = paddingFrame.getMinY(); - auto maxY = - std::max(paddingFrame.getMaxY(), contentBounds.getMaxY() + paddingBottom); - - auto minX = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft - ? std::min(paddingFrame.getMinX(), contentBounds.getMinX() - paddingLeft) - : paddingFrame.getMinX(); - auto maxX = layoutMetrics.layoutDirection == LayoutDirection::RightToLeft - ? paddingFrame.getMaxX() - : std::max( - paddingFrame.getMaxX(), contentBounds.getMaxX() + paddingRight); - - return Rect{Point{minX, minY}, Size{maxX - minX, maxY - minY}}; -} - -inline static Size getScrollSize( - LayoutMetrics layoutMetrics, - Rect contentBounds) { - auto scrollableContentBounds = - getScrollableContentBounds(contentBounds, layoutMetrics); - return scrollableContentBounds.size; -} } // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index bec7fcf7f234..91e3dd69c8f3 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -67,6 +67,11 @@ const definitions: FeatureFlagDefinitions = { description: 'Uses new, deduplicated logic for constructing Android Spannables from text fragments', }, + enableUIConsistency: { + defaultValue: false, + description: + 'Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution).', + }, inspectorEnableCxxInspectorPackagerConnection: { defaultValue: false, description: diff --git a/packages/react-native/scripts/react_native_pods.rb b/packages/react-native/scripts/react_native_pods.rb index 462048d89962..cacfbda1ffe7 100644 --- a/packages/react-native/scripts/react_native_pods.rb +++ b/packages/react-native/scripts/react_native_pods.rb @@ -147,6 +147,7 @@ def use_react_native! ( pod 'React-runtimeexecutor', :path => "#{prefix}/ReactCommon/runtimeexecutor" pod 'React-runtimescheduler', :path => "#{prefix}/ReactCommon/react/renderer/runtimescheduler" pod 'React-rendererdebug', :path => "#{prefix}/ReactCommon/react/renderer/debug" + pod 'React-rendererconsistency', :path => "#{prefix}/ReactCommon/react/renderer/consistency" pod 'React-perflogger', :path => "#{prefix}/ReactCommon/reactperflogger" pod 'React-logger', :path => "#{prefix}/ReactCommon/logger" pod 'ReactCommon/turbomodule/core', :path => "#{prefix}/ReactCommon", :modular_headers => true diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 3d68cd7572a3..dffe28a86f1b 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> * @flow strict-local */ @@ -48,6 +48,7 @@ export type ReactNativeFeatureFlags = { enableMicrotasks: Getter, enableMountHooksAndroid: Getter, enableSpannableBuildingUnification: Getter, + enableUIConsistency: Getter, inspectorEnableCxxInspectorPackagerConnection: Getter, inspectorEnableModernCDPRegistry: Getter, useModernRuntimeScheduler: Getter, @@ -125,6 +126,10 @@ export const enableMountHooksAndroid: Getter = createNativeFlagGetter(' * Uses new, deduplicated logic for constructing Android Spannables from text fragments */ export const enableSpannableBuildingUnification: Getter = createNativeFlagGetter('enableSpannableBuildingUnification', false); +/** + * Ensures that JavaScript always has a consistent view of the state of the UI (e.g.: commits done in other threads are not immediately propagated to JS during its execution). + */ +export const enableUIConsistency: Getter = createNativeFlagGetter('enableUIConsistency', false); /** * Flag determining if the C++ implementation of InspectorPackagerConnection should be used instead of the per-platform one. This flag is global and should not be changed across React Host lifetimes. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 24c095337f81..ac17e38ee024 100644 --- a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<8fcd655a9837ad155fd71efe8b05e3d0>> + * @generated SignedSource<<2f4c06a0e456d55a4bbd6b8c48c7f22d>> * @flow strict-local */ @@ -31,6 +31,7 @@ export interface Spec extends TurboModule { +enableMicrotasks?: () => boolean; +enableMountHooksAndroid?: () => boolean; +enableSpannableBuildingUnification?: () => boolean; + +enableUIConsistency?: () => boolean; +inspectorEnableCxxInspectorPackagerConnection?: () => boolean; +inspectorEnableModernCDPRegistry?: () => boolean; +useModernRuntimeScheduler?: () => boolean;