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..3a73ee13681b 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -563,6 +563,7 @@ android { "react_cxxreactpackage", "react_render_animations", "react_render_core", + "react_render_consistency", "react_render_graphics", "rrc_image", "rrc_root", @@ -580,6 +581,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..5c0d6a9cd7fa 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt +++ b/packages/react-native/ReactAndroid/src/main/jni/CMakeLists.txt @@ -79,6 +79,8 @@ 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/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..05d0e7259e59 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,10 +277,17 @@ 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.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 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/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/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..c57725e3e638 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/CMakeLists.txt @@ -27,6 +27,8 @@ 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_graphics diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index 0ad24461223a..4f000b89246a 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -29,6 +29,15 @@ 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 +48,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 +177,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 +191,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); + } }); } @@ -408,6 +427,24 @@ int UIManager::compareDocumentPosition( return DOCUMENT_POSITION_FOLLOWING; } +ShadowTreeRevisionConsistencyManager* +UIManager::getShadowTreeRevisionConsistencyManager() { + return lazyShadowTreeRevisionConsistencyManager_.get(); +} + +ShadowTreeRevisionProvider* UIManager::getShadowTreeRevisionProvider() { + if (lazyShadowTreeRevisionConsistencyManager_ != nullptr) { + return lazyShadowTreeRevisionConsistencyManager_.get(); + } else if (latestShadowTreeRevisionProvider_ != nullptr) { + return latestShadowTreeRevisionProvider_.get(); + } + + LOG(ERROR) << "Unexpected state found in UIManager where both " + << "lazyShadowTreeRevisionConsistencyManager_ and " + << "latestShadowTreeRevisionProvider_ were null"; + return nullptr; +} + ShadowNode::Shared UIManager::findNodeAtPoint( const ShadowNode::Shared& node, Point point) const { diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.h index c0f443ff568e..936b963a4cee 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 @@ -106,6 +110,10 @@ class UIManager final : public ShadowTreeDelegate { const ShadowNode& shadowNode, const ShadowNode& otherShadowNode) const; + ShadowTreeRevisionConsistencyManager* + getShadowTreeRevisionConsistencyManager(); + ShadowTreeRevisionProvider* getShadowTreeRevisionProvider(); + #pragma mark - Surface Start & Stop void startSurface( @@ -152,7 +160,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 +246,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/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/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;