From 484026be891d11afd2dcf69c31c1d75ff019fe14 Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Tue, 10 Feb 2026 19:12:14 -0800 Subject: [PATCH] Add updateRuntimeShadowNodeReferencesOnCommitThread feature flag (#55515) Summary: Add updateRuntimeShadowNodeReferencesOnCommitThread feature flag to enable syncing the shadow nodes on commits running on the allowed thread. This feature flag is more flexible than updateRuntimeShadowNodeReferencesOnCommit, which only would sync on commits originating from the React renderer. Changelog: [Internal] Differential Revision: D92913632 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +++++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++++- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +++++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 ++- .../featureflags/ReactNativeFeatureFlags.cpp | 6 ++- .../featureflags/ReactNativeFeatureFlags.h | 7 +++- .../ReactNativeFeatureFlagsAccessor.cpp | 40 ++++++++++++++----- .../ReactNativeFeatureFlagsAccessor.h | 6 ++- .../ReactNativeFeatureFlagsDefaults.h | 6 ++- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++++- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +++- .../NativeReactNativeFeatureFlags.h | 4 +- .../react/renderer/core/ShadowNode.cpp | 10 ++++- .../react/renderer/core/ShadowNode.h | 2 + .../renderer/mounting/updateMountedFlag.cpp | 16 ++++++-- .../ReactNativeFeatureFlags.config.js | 11 +++++ .../featureflags/ReactNativeFeatureFlags.js | 7 +++- .../specs/NativeReactNativeFeatureFlags.js | 3 +- 23 files changed, 170 insertions(+), 35 deletions(-) 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 f8a35b7364be..74b86e799810 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<<049cc0a1aa5ab53ad2ab11c359f49827>> */ /** @@ -444,6 +444,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean = accessor.updateRuntimeShadowNodeReferencesOnCommit() + /** + * When enabled, runtime shadow node references will be updated during the commit only on the allowed thread. + */ + @JvmStatic + public fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean = accessor.updateRuntimeShadowNodeReferencesOnCommitThread() + /** * In Bridgeless mode, use the always available javascript error reporting pipeline. */ 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 ba177143a0ff..765b88debb10 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<<9158cc3ba1ea8ea65140b582a02a5112>> + * @generated SignedSource<<6d1fc28e24576c1a63dceaa8c974cc1f>> */ /** @@ -89,6 +89,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null + private var updateRuntimeShadowNodeReferencesOnCommitThreadCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null @@ -721,6 +722,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean { + var cached = updateRuntimeShadowNodeReferencesOnCommitThreadCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.updateRuntimeShadowNodeReferencesOnCommitThread() + updateRuntimeShadowNodeReferencesOnCommitThreadCache = cached + } + return cached + } + override fun useAlwaysAvailableJSErrorHandling(): Boolean { var cached = useAlwaysAvailableJSErrorHandlingCache 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 845adfa884ac..f25107cc6feb 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<<8f93f5e6ce460e2337bbdc51bde69558>> + * @generated SignedSource<<26410c9e406969734991456f635be4af>> */ /** @@ -166,6 +166,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean + @DoNotStrip @JvmStatic public external fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean + @DoNotStrip @JvmStatic public external fun useAlwaysAvailableJSErrorHandling(): Boolean @DoNotStrip @JvmStatic public external fun useFabricInterop(): 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 6ecf6f204ca5..2bb5cab976f7 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<> */ /** @@ -161,6 +161,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean = false + override fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean = false + override fun useAlwaysAvailableJSErrorHandling(): Boolean = false override fun useFabricInterop(): Boolean = true 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 e868aeaf90b1..ffe01d0c090f 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<<377c50a9a022344cba12a1ad6c23b68d>> + * @generated SignedSource<> */ /** @@ -93,6 +93,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var skipActivityIdentityAssertionOnHostPauseCache: Boolean? = null private var traceTurboModulePromiseRejectionsOnAndroidCache: Boolean? = null private var updateRuntimeShadowNodeReferencesOnCommitCache: Boolean? = null + private var updateRuntimeShadowNodeReferencesOnCommitThreadCache: Boolean? = null private var useAlwaysAvailableJSErrorHandlingCache: Boolean? = null private var useFabricInteropCache: Boolean? = null private var useNativeViewConfigsInBridgelessModeCache: Boolean? = null @@ -794,6 +795,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean { + var cached = updateRuntimeShadowNodeReferencesOnCommitThreadCache + if (cached == null) { + cached = currentProvider.updateRuntimeShadowNodeReferencesOnCommitThread() + accessedFeatureFlags.add("updateRuntimeShadowNodeReferencesOnCommitThread") + updateRuntimeShadowNodeReferencesOnCommitThreadCache = cached + } + return cached + } + override fun useAlwaysAvailableJSErrorHandling(): Boolean { var cached = useAlwaysAvailableJSErrorHandlingCache 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 dc52f2bc89cc..cb9050023baa 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<> */ /** @@ -161,6 +161,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun updateRuntimeShadowNodeReferencesOnCommit(): Boolean + @DoNotStrip public fun updateRuntimeShadowNodeReferencesOnCommitThread(): Boolean + @DoNotStrip public fun useAlwaysAvailableJSErrorHandling(): Boolean @DoNotStrip public fun useFabricInterop(): Boolean 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 cd76ee7a11f3..a861c047e82b 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<<3c268f88d2df4ad8500e29b91f1fecc0>> + * @generated SignedSource<<698b775afb6ad9295c181ea3d12118d3>> */ /** @@ -453,6 +453,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool updateRuntimeShadowNodeReferencesOnCommitThread() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("updateRuntimeShadowNodeReferencesOnCommitThread"); + return method(javaProvider_); + } + bool useAlwaysAvailableJSErrorHandling() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("useAlwaysAvailableJSErrorHandling"); @@ -862,6 +868,11 @@ bool JReactNativeFeatureFlagsCxxInterop::updateRuntimeShadowNodeReferencesOnComm return ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit(); } +bool JReactNativeFeatureFlagsCxxInterop::updateRuntimeShadowNodeReferencesOnCommitThread( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommitThread(); +} + bool JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling(); @@ -1150,6 +1161,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "updateRuntimeShadowNodeReferencesOnCommit", JReactNativeFeatureFlagsCxxInterop::updateRuntimeShadowNodeReferencesOnCommit), + makeNativeMethod( + "updateRuntimeShadowNodeReferencesOnCommitThread", + JReactNativeFeatureFlagsCxxInterop::updateRuntimeShadowNodeReferencesOnCommitThread), makeNativeMethod( "useAlwaysAvailableJSErrorHandling", JReactNativeFeatureFlagsCxxInterop::useAlwaysAvailableJSErrorHandling), 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 e8c1425a8375..260c4f4ea049 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<<7be3a44c6ffc2c9d390f66902ef80d30>> */ /** @@ -237,6 +237,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool updateRuntimeShadowNodeReferencesOnCommit( facebook::jni::alias_ref); + static bool updateRuntimeShadowNodeReferencesOnCommitThread( + facebook::jni::alias_ref); + static bool useAlwaysAvailableJSErrorHandling( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 7142531ea712..8c2a06e0224d 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<<88b22330e8486a820eb42b59fb7279e4>> */ /** @@ -302,6 +302,10 @@ bool ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit() { return getAccessor().updateRuntimeShadowNodeReferencesOnCommit(); } +bool ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommitThread() { + return getAccessor().updateRuntimeShadowNodeReferencesOnCommitThread(); +} + bool ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling() { return getAccessor().useAlwaysAvailableJSErrorHandling(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index da397f200696..902ac9787e74 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<> */ /** @@ -384,6 +384,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool updateRuntimeShadowNodeReferencesOnCommit(); + /** + * When enabled, runtime shadow node references will be updated during the commit only on the allowed thread. + */ + RN_EXPORT static bool updateRuntimeShadowNodeReferencesOnCommitThread(); + /** * In Bridgeless mode, use the always available javascript error reporting pipeline. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index f34c02b3bc0e..5b23711102e9 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<<7abeb70e59a3c4953c501011bd858479>> + * @generated SignedSource<<65ca2267e4bd81dcc21f35bbfc880ba8>> */ /** @@ -1271,6 +1271,24 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommitThread() { + auto flagValue = updateRuntimeShadowNodeReferencesOnCommitThread_.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(69, "updateRuntimeShadowNodeReferencesOnCommitThread"); + + flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); + updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { auto flagValue = useAlwaysAvailableJSErrorHandling_.load(); @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::useAlwaysAvailableJSErrorHandling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(70, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::useFabricInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "useFabricInterop"); + markFlagAsAccessed(71, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(72, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "useNestedScrollViewAndroid"); + markFlagAsAccessed(73, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "useSharedAnimatedBackend"); + markFlagAsAccessed(74, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(75, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "useTurboModuleInterop"); + markFlagAsAccessed(76, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1406,7 +1424,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "useTurboModules"); + markFlagAsAccessed(77, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1424,7 +1442,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(77, "viewCullingOutsetRatio"); + markFlagAsAccessed(78, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1442,7 +1460,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(78, "virtualViewPrerenderRatio"); + markFlagAsAccessed(79, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index d20f0b9f024c..1c89cab6d623 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<<366f2d2074e2af1d9dd4244ac1454ac0>> + * @generated SignedSource<<1aed21628664154ea7f66f15b1f760c2>> */ /** @@ -101,6 +101,7 @@ class ReactNativeFeatureFlagsAccessor { bool skipActivityIdentityAssertionOnHostPause(); bool traceTurboModulePromiseRejectionsOnAndroid(); bool updateRuntimeShadowNodeReferencesOnCommit(); + bool updateRuntimeShadowNodeReferencesOnCommitThread(); bool useAlwaysAvailableJSErrorHandling(); bool useFabricInterop(); bool useNativeViewConfigsInBridgelessMode(); @@ -122,7 +123,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 79> accessedFeatureFlags_; + std::array, 80> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -193,6 +194,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> skipActivityIdentityAssertionOnHostPause_; std::atomic> traceTurboModulePromiseRejectionsOnAndroid_; std::atomic> updateRuntimeShadowNodeReferencesOnCommit_; + std::atomic> updateRuntimeShadowNodeReferencesOnCommitThread_; std::atomic> useAlwaysAvailableJSErrorHandling_; std::atomic> useFabricInterop_; std::atomic> useNativeViewConfigsInBridgelessMode_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 346ad8fe1228..43e9c6556b1c 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<<1c98cfbb8e70c8685faafd078002f866>> + * @generated SignedSource<> */ /** @@ -303,6 +303,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool updateRuntimeShadowNodeReferencesOnCommitThread() override { + return false; + } + bool useAlwaysAvailableJSErrorHandling() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 1365c1992036..293067a1e2a4 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.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<<3dfbafd135e89a02524f732bb5914696>> + * @generated SignedSource<> */ /** @@ -666,6 +666,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::updateRuntimeShadowNodeReferencesOnCommit(); } + bool updateRuntimeShadowNodeReferencesOnCommitThread() override { + auto value = values_["updateRuntimeShadowNodeReferencesOnCommitThread"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::updateRuntimeShadowNodeReferencesOnCommitThread(); + } + bool useAlwaysAvailableJSErrorHandling() override { auto value = values_["useAlwaysAvailableJSErrorHandling"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 0186d27441ed..7ec722a2d328 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<<5ede2745ad5f87162c36363215bf5c4f>> + * @generated SignedSource<<0308cb63246c4576859383adc3cc9593>> */ /** @@ -94,6 +94,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool skipActivityIdentityAssertionOnHostPause() = 0; virtual bool traceTurboModulePromiseRejectionsOnAndroid() = 0; virtual bool updateRuntimeShadowNodeReferencesOnCommit() = 0; + virtual bool updateRuntimeShadowNodeReferencesOnCommitThread() = 0; virtual bool useAlwaysAvailableJSErrorHandling() = 0; virtual bool useFabricInterop() = 0; virtual bool useNativeViewConfigsInBridgelessMode() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 654dc480c7a8..4a08c4efb5ef 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<<0301dd1333d9607a0027c0d30ff48140>> + * @generated SignedSource<> */ /** @@ -389,6 +389,11 @@ bool NativeReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit( return ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit(); } +bool NativeReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommitThread( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommitThread(); +} + bool NativeReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::useAlwaysAvailableJSErrorHandling(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 85962927e711..70e9f241ad60 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<<7226a320306724ab7d4ee9836a5d35a2>> + * @generated SignedSource<<1fa17a0bb7f29a34541d362d1ecdc788>> */ /** @@ -174,6 +174,8 @@ class NativeReactNativeFeatureFlags bool updateRuntimeShadowNodeReferencesOnCommit(jsi::Runtime& runtime); + bool updateRuntimeShadowNodeReferencesOnCommitThread(jsi::Runtime& runtime); + bool useAlwaysAvailableJSErrorHandling(jsi::Runtime& runtime); bool useFabricInterop(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp index 8f0420c4d01e..1490d8746549 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.cpp @@ -33,6 +33,10 @@ thread_local bool useRuntimeShadowNodeReferenceUpdateOnThread{false}; // NOLINT useRuntimeShadowNodeReferenceUpdateOnThread = isEnabled; } +/* static */ bool ShadowNode::getUseRuntimeShadowNodeReferenceUpdateOnThread() { + return useRuntimeShadowNodeReferenceUpdateOnThread; +} + ShadowNode::SharedListOfShared ShadowNode::emptySharedShadowNodeSharedList() { static const auto emptySharedShadowNodeSharedList = std::make_shared>>(); @@ -339,7 +343,9 @@ void ShadowNode::transferRuntimeShadowNodeReference( destinationShadowNode->runtimeShadowNodeReference_ = runtimeShadowNodeReference_; - if (!ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit()) { + if (!ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit() && + !ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommitThread()) { updateRuntimeShadowNodeReference(destinationShadowNode); } } @@ -348,6 +354,8 @@ void ShadowNode::transferRuntimeShadowNodeReference( const std::shared_ptr& destinationShadowNode, const ShadowNodeFragment& fragment) const { if ((ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit() || + ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommitThread() || useRuntimeShadowNodeReferenceUpdateOnThread) && fragment.runtimeShadowNodeReference) { transferRuntimeShadowNodeReference(destinationShadowNode); diff --git a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h index 44303d534d92..ca0c1be6762e 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ShadowNode.h @@ -68,6 +68,8 @@ class ShadowNode : public Sealable, public DebugStringConvertible, public jsi::N static void setUseRuntimeShadowNodeReferenceUpdateOnThread(bool isEnabled); + static bool getUseRuntimeShadowNodeReferenceUpdateOnThread(); + #pragma mark - Constructors /* diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/updateMountedFlag.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/updateMountedFlag.cpp index 48da4ff0c2de..55ab4b7c4cd0 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/updateMountedFlag.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/updateMountedFlag.cpp @@ -50,8 +50,12 @@ void updateMountedFlag( newChild->setMounted(true); oldChild->setMounted(false); - if (commitSource == ShadowTreeCommitSource::React && - ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit()) { + if ((commitSource == ShadowTreeCommitSource::React && + ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommit()) || + (ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommitThread() && + ShadowNode::getUseRuntimeShadowNodeReferenceUpdateOnThread())) { newChild->updateRuntimeShadowNodeReference(newChild); } @@ -66,8 +70,12 @@ void updateMountedFlag( const auto& newChild = newChildren[index]; newChild->setMounted(true); - if (commitSource == ShadowTreeCommitSource::React && - ReactNativeFeatureFlags::updateRuntimeShadowNodeReferencesOnCommit()) { + if ((commitSource == ShadowTreeCommitSource::React && + ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommit()) || + (ReactNativeFeatureFlags:: + updateRuntimeShadowNodeReferencesOnCommitThread() && + ShadowNode::getUseRuntimeShadowNodeReferenceUpdateOnThread())) { newChild->updateRuntimeShadowNodeReference(newChild); } diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index e8bf4fe37a2f..66e90ed44575 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -785,6 +785,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + updateRuntimeShadowNodeReferencesOnCommitThread: { + defaultValue: false, + metadata: { + dateAdded: '2026-02-11', + description: + 'When enabled, runtime shadow node references will be updated during the commit only on the allowed thread.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, useAlwaysAvailableJSErrorHandling: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 7a461bdf9a9a..222e08e78284 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<<4680a90fe6a071f6352de95fc1ca01ba>> + * @generated SignedSource<<80f3fba939ccfd246ed809592ac30fad>> * @flow strict * @noformat */ @@ -115,6 +115,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ skipActivityIdentityAssertionOnHostPause: Getter, traceTurboModulePromiseRejectionsOnAndroid: Getter, updateRuntimeShadowNodeReferencesOnCommit: Getter, + updateRuntimeShadowNodeReferencesOnCommitThread: Getter, useAlwaysAvailableJSErrorHandling: Getter, useFabricInterop: Getter, useNativeViewConfigsInBridgelessMode: Getter, @@ -462,6 +463,10 @@ export const traceTurboModulePromiseRejectionsOnAndroid: Getter = creat * When enabled, runtime shadow node references will be updated during the commit. This allows running RSNRU from any thread without corrupting the renderer state. */ export const updateRuntimeShadowNodeReferencesOnCommit: Getter = createNativeFlagGetter('updateRuntimeShadowNodeReferencesOnCommit', false); +/** + * When enabled, runtime shadow node references will be updated during the commit only on the allowed thread. + */ +export const updateRuntimeShadowNodeReferencesOnCommitThread: Getter = createNativeFlagGetter('updateRuntimeShadowNodeReferencesOnCommitThread', false); /** * In Bridgeless mode, use the always available javascript error reporting pipeline. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 5cc6227520b1..01ef6a7f3a4e 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<<52a7b2aed229778e887fd25d25982250>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -94,6 +94,7 @@ export interface Spec extends TurboModule { +skipActivityIdentityAssertionOnHostPause?: () => boolean; +traceTurboModulePromiseRejectionsOnAndroid?: () => boolean; +updateRuntimeShadowNodeReferencesOnCommit?: () => boolean; + +updateRuntimeShadowNodeReferencesOnCommitThread?: () => boolean; +useAlwaysAvailableJSErrorHandling?: () => boolean; +useFabricInterop?: () => boolean; +useNativeViewConfigsInBridgelessMode?: () => boolean;