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 43ca6dc4f166..ab9cfbaa5c42 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<<99a7d3e814f4b037ed4496b6eee4f264>> + * @generated SignedSource<<4ee7b2e0a88bc5087e0739658810dee5>> */ /** @@ -360,6 +360,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableVirtualViewDebugFeatures(): Boolean = accessor.enableVirtualViewDebugFeatures() + /** + * Fix incorrect parentTag passed as parentTagForUpdate in the unflatten-unflatten branch of calculateShadowViewMutationsFlattener, which causes UPDATE mutations to reference a parent being created in the same batch. + */ + @JvmStatic + public fun fixDifferentiatorParentTagForUnflattenCase(): Boolean = accessor.fixDifferentiatorParentTagForUnflattenCase() + /** * Fix a use-after-free race condition in findShadowNodeByTag_DEPRECATED by using getCurrentRevision() instead of tryCommit() with a raw pointer. */ 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 be22235e4470..f208f2ed8de0 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<> + * @generated SignedSource<<87405910d0abf422badc45d1510ad702>> */ /** @@ -75,6 +75,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableViewRecyclingForViewCache: Boolean? = null private var enableVirtualViewContainerStateExperimentalCache: Boolean? = null private var enableVirtualViewDebugFeaturesCache: Boolean? = null + private var fixDifferentiatorParentTagForUnflattenCaseCache: Boolean? = null private var fixFindShadowNodeByTagRaceConditionCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fixYogaFlexBasisFitContentInMainAxisCache: Boolean? = null @@ -605,6 +606,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun fixDifferentiatorParentTagForUnflattenCase(): Boolean { + var cached = fixDifferentiatorParentTagForUnflattenCaseCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.fixDifferentiatorParentTagForUnflattenCase() + fixDifferentiatorParentTagForUnflattenCaseCache = cached + } + return cached + } + override fun fixFindShadowNodeByTagRaceCondition(): Boolean { var cached = fixFindShadowNodeByTagRaceConditionCache 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 2ba162535ac8..6fafd02a222c 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<<8667d7237cea82bb5978cb19582d59c0>> + * @generated SignedSource<> */ /** @@ -138,6 +138,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableVirtualViewDebugFeatures(): Boolean + @DoNotStrip @JvmStatic public external fun fixDifferentiatorParentTagForUnflattenCase(): Boolean + @DoNotStrip @JvmStatic public external fun fixFindShadowNodeByTagRaceCondition(): Boolean @DoNotStrip @JvmStatic public external fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): 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 0bd08cd5665c..ecc3c1543d7f 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<<17abc72a4045c5695818f254be1783b5>> + * @generated SignedSource<<73ce7f74a1fc0843a91d2fe11615b333>> */ /** @@ -133,6 +133,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableVirtualViewDebugFeatures(): Boolean = false + override fun fixDifferentiatorParentTagForUnflattenCase(): Boolean = false + override fun fixFindShadowNodeByTagRaceCondition(): Boolean = false override fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): 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 b2e5d18cd8df..9a1c8375ed0c 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<<77ba6c5db120016e6e1f8af195ab3690>> + * @generated SignedSource<> */ /** @@ -79,6 +79,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableViewRecyclingForViewCache: Boolean? = null private var enableVirtualViewContainerStateExperimentalCache: Boolean? = null private var enableVirtualViewDebugFeaturesCache: Boolean? = null + private var fixDifferentiatorParentTagForUnflattenCaseCache: Boolean? = null private var fixFindShadowNodeByTagRaceConditionCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fixYogaFlexBasisFitContentInMainAxisCache: Boolean? = null @@ -664,6 +665,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun fixDifferentiatorParentTagForUnflattenCase(): Boolean { + var cached = fixDifferentiatorParentTagForUnflattenCaseCache + if (cached == null) { + cached = currentProvider.fixDifferentiatorParentTagForUnflattenCase() + accessedFeatureFlags.add("fixDifferentiatorParentTagForUnflattenCase") + fixDifferentiatorParentTagForUnflattenCaseCache = cached + } + return cached + } + override fun fixFindShadowNodeByTagRaceCondition(): Boolean { var cached = fixFindShadowNodeByTagRaceConditionCache 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 41ce962f044a..f2bf2b7c200e 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<<8496c138ce5493df84149940df0de944>> + * @generated SignedSource<<2de065dd6f726ad92376e2385e134c7e>> */ /** @@ -133,6 +133,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableVirtualViewDebugFeatures(): Boolean + @DoNotStrip public fun fixDifferentiatorParentTagForUnflattenCase(): Boolean + @DoNotStrip public fun fixFindShadowNodeByTagRaceCondition(): Boolean @DoNotStrip public fun fixMappingOfEventPrioritiesBetweenFabricAndReact(): 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 c176fb1b7631..3a6af9c427d0 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<<5bac13bb6faeffdd3c5eca800f25b96a>> + * @generated SignedSource<<2c9c4c2ef57882df723930daa933afd8>> */ /** @@ -369,6 +369,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool fixDifferentiatorParentTagForUnflattenCase() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fixDifferentiatorParentTagForUnflattenCase"); + return method(javaProvider_); + } + bool fixFindShadowNodeByTagRaceCondition() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fixFindShadowNodeByTagRaceCondition"); @@ -852,6 +858,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableVirtualViewDebugFeatures( return ReactNativeFeatureFlags::enableVirtualViewDebugFeatures(); } +bool JReactNativeFeatureFlagsCxxInterop::fixDifferentiatorParentTagForUnflattenCase( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::fixDifferentiatorParentTagForUnflattenCase(); +} + bool JReactNativeFeatureFlagsCxxInterop::fixFindShadowNodeByTagRaceCondition( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::fixFindShadowNodeByTagRaceCondition(); @@ -1218,6 +1229,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableVirtualViewDebugFeatures", JReactNativeFeatureFlagsCxxInterop::enableVirtualViewDebugFeatures), + makeNativeMethod( + "fixDifferentiatorParentTagForUnflattenCase", + JReactNativeFeatureFlagsCxxInterop::fixDifferentiatorParentTagForUnflattenCase), makeNativeMethod( "fixFindShadowNodeByTagRaceCondition", JReactNativeFeatureFlagsCxxInterop::fixFindShadowNodeByTagRaceCondition), 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 d02c0855a993..9b57fdcb8a54 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<> */ /** @@ -195,6 +195,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableVirtualViewDebugFeatures( facebook::jni::alias_ref); + static bool fixDifferentiatorParentTagForUnflattenCase( + facebook::jni::alias_ref); + static bool fixFindShadowNodeByTagRaceCondition( 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 43076de959a4..e70dd6afaa6c 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<> */ /** @@ -246,6 +246,10 @@ bool ReactNativeFeatureFlags::enableVirtualViewDebugFeatures() { return getAccessor().enableVirtualViewDebugFeatures(); } +bool ReactNativeFeatureFlags::fixDifferentiatorParentTagForUnflattenCase() { + return getAccessor().fixDifferentiatorParentTagForUnflattenCase(); +} + bool ReactNativeFeatureFlags::fixFindShadowNodeByTagRaceCondition() { return getAccessor().fixFindShadowNodeByTagRaceCondition(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 8ba43c099206..4942e63cf1f7 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<<86b3267ffa68e0f68280957aa54d5041>> + * @generated SignedSource<> */ /** @@ -314,6 +314,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableVirtualViewDebugFeatures(); + /** + * Fix incorrect parentTag passed as parentTagForUpdate in the unflatten-unflatten branch of calculateShadowViewMutationsFlattener, which causes UPDATE mutations to reference a parent being created in the same batch. + */ + RN_EXPORT static bool fixDifferentiatorParentTagForUnflattenCase(); + /** * Fix a use-after-free race condition in findShadowNodeByTag_DEPRECATED by using getCurrentRevision() instead of tryCommit() with a raw pointer. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 1f40889efc0d..a5e21ab7e27f 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<<218ab046c336961b8220c48eb0426b7f>> + * @generated SignedSource<> */ /** @@ -1019,6 +1019,24 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewDebugFeatures() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::fixDifferentiatorParentTagForUnflattenCase() { + auto flagValue = fixDifferentiatorParentTagForUnflattenCase_.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(55, "fixDifferentiatorParentTagForUnflattenCase"); + + flagValue = currentProvider_->fixDifferentiatorParentTagForUnflattenCase(); + fixDifferentiatorParentTagForUnflattenCase_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::fixFindShadowNodeByTagRaceCondition() { auto flagValue = fixFindShadowNodeByTagRaceCondition_.load(); @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::fixFindShadowNodeByTagRaceCondition() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "fixFindShadowNodeByTagRaceCondition"); + markFlagAsAccessed(56, "fixFindShadowNodeByTagRaceCondition"); flagValue = currentProvider_->fixFindShadowNodeByTagRaceCondition(); fixFindShadowNodeByTagRaceCondition_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(57, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::fixYogaFlexBasisFitContentInMainAxis() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(58, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxAssertSingleHostState() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(59, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxEnabledRelease() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fuseboxEnabledRelease"); + markFlagAsAccessed(60, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1118,7 +1136,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxFrameRecordingEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(60, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(61, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(62, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1154,7 +1172,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxScreenshotCaptureEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(62, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(63, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(64, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::overrideBySynchronousMountPropsAtMountingA // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(65, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1208,7 +1226,7 @@ bool ReactNativeFeatureFlagsAccessor::perfIssuesEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(65, "perfIssuesEnabled"); + markFlagAsAccessed(66, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1226,7 +1244,7 @@ bool ReactNativeFeatureFlagsAccessor::perfMonitorV2Enabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(66, "perfMonitorV2Enabled"); + markFlagAsAccessed(67, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1244,7 +1262,7 @@ double ReactNativeFeatureFlagsAccessor::preparedTextCacheSize() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "preparedTextCacheSize"); + markFlagAsAccessed(68, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::preventShadowTreeCommitExhaustion() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(69, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldPressibilityUseW3CPointerEventsForHo // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(70, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1298,7 +1316,7 @@ bool ReactNativeFeatureFlagsAccessor::shouldTriggerResponderTransferOnScrollAndr // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(70, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(71, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1316,7 +1334,7 @@ bool ReactNativeFeatureFlagsAccessor::skipActivityIdentityAssertionOnHostPause() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(71, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(72, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1334,7 +1352,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipToPaddingWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(72, "syncAndroidClipToPaddingWithOverflow"); + markFlagAsAccessed(73, "syncAndroidClipToPaddingWithOverflow"); flagValue = currentProvider_->syncAndroidClipToPaddingWithOverflow(); syncAndroidClipToPaddingWithOverflow_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::traceTurboModulePromiseRejectionsOnAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(74, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1370,7 +1388,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommit( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(74, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::updateRuntimeShadowNodeReferencesOnCommitT // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(76, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1406,7 +1424,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(76, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(77, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1424,7 +1442,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(77, "useFabricInterop"); + markFlagAsAccessed(78, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1442,7 +1460,7 @@ bool ReactNativeFeatureFlagsAccessor::useLISAlgorithmInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(78, "useLISAlgorithmInDifferentiator"); + markFlagAsAccessed(79, "useLISAlgorithmInDifferentiator"); flagValue = currentProvider_->useLISAlgorithmInDifferentiator(); useLISAlgorithmInDifferentiator_ = flagValue; @@ -1460,7 +1478,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(79, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(80, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1478,7 +1496,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(80, "useNestedScrollViewAndroid"); + markFlagAsAccessed(81, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1496,7 +1514,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(81, "useSharedAnimatedBackend"); + markFlagAsAccessed(82, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1514,7 +1532,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(82, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(83, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1532,7 +1550,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(83, "useTurboModuleInterop"); + markFlagAsAccessed(84, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1550,7 +1568,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(84, "useTurboModules"); + markFlagAsAccessed(85, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1568,7 +1586,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(86, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1586,7 +1604,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(86, "viewCullingOutsetRatio"); + markFlagAsAccessed(87, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1604,7 +1622,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(87, "viewTransitionEnabled"); + markFlagAsAccessed(88, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1622,7 +1640,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(88, "virtualViewPrerenderRatio"); + markFlagAsAccessed(89, "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 7b83ec509d66..569031397016 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<> */ /** @@ -87,6 +87,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableViewRecyclingForView(); bool enableVirtualViewContainerStateExperimental(); bool enableVirtualViewDebugFeatures(); + bool fixDifferentiatorParentTagForUnflattenCase(); bool fixFindShadowNodeByTagRaceCondition(); bool fixMappingOfEventPrioritiesBetweenFabricAndReact(); bool fixYogaFlexBasisFitContentInMainAxis(); @@ -132,7 +133,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 89> accessedFeatureFlags_; + std::array, 90> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -189,6 +190,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableViewRecyclingForView_; std::atomic> enableVirtualViewContainerStateExperimental_; std::atomic> enableVirtualViewDebugFeatures_; + std::atomic> fixDifferentiatorParentTagForUnflattenCase_; std::atomic> fixFindShadowNodeByTagRaceCondition_; std::atomic> fixMappingOfEventPrioritiesBetweenFabricAndReact_; std::atomic> fixYogaFlexBasisFitContentInMainAxis_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index b6f431af8f0e..a3ac7a2570cb 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<<33fd238aafa83c5a42803d3f11d55944>> + * @generated SignedSource<<830f0c43b8031c84208bed9b4bedc607>> */ /** @@ -247,6 +247,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool fixDifferentiatorParentTagForUnflattenCase() override { + return false; + } + bool fixFindShadowNodeByTagRaceCondition() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 0d48c7d05074..b036e93795bb 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<<2a95ea2091c8e73816acf12daf5e2408>> + * @generated SignedSource<<5af61ff2e2372ec457de6b3588d099cc>> */ /** @@ -540,6 +540,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableVirtualViewDebugFeatures(); } + bool fixDifferentiatorParentTagForUnflattenCase() override { + auto value = values_["fixDifferentiatorParentTagForUnflattenCase"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::fixDifferentiatorParentTagForUnflattenCase(); + } + bool fixFindShadowNodeByTagRaceCondition() override { auto value = values_["fixFindShadowNodeByTagRaceCondition"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index a6246efc165a..7799f9fafb91 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<> + * @generated SignedSource<> */ /** @@ -80,6 +80,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableViewRecyclingForView() = 0; virtual bool enableVirtualViewContainerStateExperimental() = 0; virtual bool enableVirtualViewDebugFeatures() = 0; + virtual bool fixDifferentiatorParentTagForUnflattenCase() = 0; virtual bool fixFindShadowNodeByTagRaceCondition() = 0; virtual bool fixMappingOfEventPrioritiesBetweenFabricAndReact() = 0; virtual bool fixYogaFlexBasisFitContentInMainAxis() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 2e9a936b9e1e..53be7cd7de20 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<<3d49e243422f2c220ab36f3e32a78e38>> + * @generated SignedSource<> */ /** @@ -319,6 +319,11 @@ bool NativeReactNativeFeatureFlags::enableVirtualViewDebugFeatures( return ReactNativeFeatureFlags::enableVirtualViewDebugFeatures(); } +bool NativeReactNativeFeatureFlags::fixDifferentiatorParentTagForUnflattenCase( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::fixDifferentiatorParentTagForUnflattenCase(); +} + bool NativeReactNativeFeatureFlags::fixFindShadowNodeByTagRaceCondition( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::fixFindShadowNodeByTagRaceCondition(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 9580cbc99d25..16a4e7a6f0a7 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<<3e07a28d13e142ba3c734ca111eb4974>> + * @generated SignedSource<> */ /** @@ -146,6 +146,8 @@ class NativeReactNativeFeatureFlags bool enableVirtualViewDebugFeatures(jsi::Runtime& runtime); + bool fixDifferentiatorParentTagForUnflattenCase(jsi::Runtime& runtime); + bool fixFindShadowNodeByTagRaceCondition(jsi::Runtime& runtime); bool fixMappingOfEventPrioritiesBetweenFabricAndReact(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp index 10b6e24a12c1..f9767fd0f548 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/Differentiator.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include "internal/CullingContext.h" #include "internal/DiffMap.h" @@ -681,7 +682,10 @@ static void calculateShadowViewMutationsFlattener( treeChildPair, (reparentMode == ReparentMode::Flatten ? oldTreeNodePair.shadowView.tag - : parentTag), + : (ReactNativeFeatureFlags:: + fixDifferentiatorParentTagForUnflattenCase() + ? parentTagForUpdate + : parentTag)), subVisitedNewMap, subVisitedOldMap, cullingContextForUnvisitedOtherNodes, diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/tests/DifferentiatorUnflattenTest.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/tests/DifferentiatorUnflattenTest.cpp new file mode 100644 index 000000000000..45ea4380e986 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/mounting/tests/DifferentiatorUnflattenTest.cpp @@ -0,0 +1,212 @@ +/* + * 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 + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace facebook::react { + +namespace { + +class TestFlagsWithUnflattenFix : public ReactNativeFeatureFlagsDefaults { + public: + bool fixDifferentiatorParentTagForUnflattenCase() override { + return true; + } +}; + +} // namespace + +// Exercises the unflatten-unflatten branch in +// calculateShadowViewMutationsFlattener (Differentiator.cpp), where multiple +// levels of nested views simultaneously transition from flattened to concrete. +// +// The bug requires 3+ levels of nesting to manifest: at the second recursive +// unflatten-unflatten call, parentTag (set to the intermediate node's tag) and +// parentTagForUpdate (which should remain the original parent's tag) diverge. +// +// Tree: +// Root (tag 1) +// A (tag 2) -- transitions from flattened to concrete +// B (tag 3) -- transitions from flattened to concrete +// C (tag 4) -- transitions from flattened to concrete +// D (tag 5) -- always concrete (leaf) +class DifferentiatorUnflattenTest : public ::testing::Test { + protected: + std::unique_ptr builder_; + std::shared_ptr rootShadowNode_; + std::shared_ptr nodeA_; + std::shared_ptr nodeB_; + std::shared_ptr nodeC_; + std::shared_ptr nodeD_; + + std::shared_ptr currentRootShadowNode_; + StubViewTree currentStubViewTree_; + + void SetUp() override { + // clang-format off + auto element = + Element() + .reference(rootShadowNode_) + .tag(1) + .children({ + Element() + .tag(2) + .reference(nodeA_) + .children({ + Element() + .tag(3) + .reference(nodeB_) + .children({ + Element() + .tag(4) + .reference(nodeC_) + .children({ + Element() + .tag(5) + .reference(nodeD_) + }) + }) + }) + }); + // clang-format on + + builder_ = std::make_unique(simpleComponentBuilder()); + builder_->build(element); + + currentRootShadowNode_ = rootShadowNode_; + currentRootShadowNode_->layoutIfNeeded(); + currentStubViewTree_ = + buildStubViewTreeWithoutUsingDifferentiator(*currentRootShadowNode_); + } + + void TearDown() override { + ReactNativeFeatureFlags::dangerouslyReset(); + } + + void mutateViewShadowNodeProps_( + const std::shared_ptr& node, + std::function callback) { + rootShadowNode_ = + std::static_pointer_cast(rootShadowNode_->cloneTree( + node->getFamily(), [&](const ShadowNode& oldShadowNode) { + auto viewProps = std::make_shared(); + callback(*viewProps); + return oldShadowNode.clone( + ShadowNodeFragment{.props = viewProps}); + })); + } + + void testViewTree_( + const std::function& callback) { + rootShadowNode_->layoutIfNeeded(); + + callback(buildStubViewTreeUsingDifferentiator(*rootShadowNode_)); + callback(buildStubViewTreeWithoutUsingDifferentiator(*rootShadowNode_)); + + auto mutations = + calculateShadowViewMutations(*currentRootShadowNode_, *rootShadowNode_); + currentRootShadowNode_ = rootShadowNode_; + currentStubViewTree_.mutate(mutations); + callback(currentStubViewTree_); + } + + ShadowViewMutation::List calculateMutations_() { + rootShadowNode_->layoutIfNeeded(); + auto mutations = + calculateShadowViewMutations(*currentRootShadowNode_, *rootShadowNode_); + currentRootShadowNode_ = rootShadowNode_; + return mutations; + } + + void applyUnflattenSetup_() { + mutateViewShadowNodeProps_( + nodeD_, [](ViewProps& props) { props.backgroundColor = blackColor(); }); + + testViewTree_([](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 2); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 1); + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 5); + }); + + mutateViewShadowNodeProps_( + nodeA_, [](ViewProps& props) { props.nativeId = "a"; }); + mutateViewShadowNodeProps_( + nodeB_, [](ViewProps& props) { props.nativeId = "b"; }); + mutateViewShadowNodeProps_( + nodeC_, [](ViewProps& props) { props.nativeId = "c"; }); + mutateViewShadowNodeProps_( + nodeD_, [](ViewProps& props) { props.backgroundColor = whiteColor(); }); + } +}; + +// Without the fix, the UPDATE mutation for D (tag 5) carries the wrong +// parentTag: an intermediate node's tag instead of Root's tag (1) where D is +// currently mounted. This test verifies the bug by inspecting the mutation list +// directly. +TEST_F( + DifferentiatorUnflattenTest, + withoutFix_updateMutationHasWrongParentTag) { + applyUnflattenSetup_(); + + auto mutations = calculateMutations_(); + + const ShadowViewMutation* updateForD = nullptr; + for (const auto& mutation : mutations) { + if (mutation.type == ShadowViewMutation::Update && + mutation.newChildShadowView.tag == 5) { + updateForD = &mutation; + break; + } + } + + ASSERT_NE(updateForD, nullptr) << "Expected an UPDATE mutation for tag 5 (D)"; + EXPECT_NE(updateForD->parentTag, 1) + << "Without fix, UPDATE for D should carry wrong parentTag (not Root)"; +} + +// With the fix, the UPDATE mutation for D correctly references Root's tag (1) +// as parentTag, and StubViewTree::mutate() succeeds without assertion failure. +TEST_F(DifferentiatorUnflattenTest, withFix_updateMutationHasCorrectParentTag) { + ReactNativeFeatureFlags::dangerouslyForceOverride( + std::make_unique()); + + applyUnflattenSetup_(); + + testViewTree_([](const StubViewTree& viewTree) { + EXPECT_EQ(viewTree.size(), 5); + EXPECT_EQ(viewTree.getRootStubView().children.size(), 1); + EXPECT_EQ(viewTree.getRootStubView().children.at(0)->tag, 2); + + auto& viewA = *viewTree.getRootStubView().children.at(0); + EXPECT_EQ(viewA.children.size(), 1); + EXPECT_EQ(viewA.children.at(0)->tag, 3); + + auto& viewB = *viewA.children.at(0); + EXPECT_EQ(viewB.children.size(), 1); + EXPECT_EQ(viewB.children.at(0)->tag, 4); + + auto& viewC = *viewB.children.at(0); + EXPECT_EQ(viewC.children.size(), 1); + EXPECT_EQ(viewC.children.at(0)->tag, 5); + }); +} + +} // namespace facebook::react diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 4a1ea7a74db7..01fa3b9082e0 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -634,6 +634,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'none', }, + fixDifferentiatorParentTagForUnflattenCase: { + defaultValue: false, + metadata: { + dateAdded: '2026-04-18', + description: + 'Fix incorrect parentTag passed as parentTagForUpdate in the unflatten-unflatten branch of calculateShadowViewMutationsFlattener, which causes UPDATE mutations to reference a parent being created in the same batch.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, fixFindShadowNodeByTagRaceCondition: { defaultValue: false, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index c225edc521d6..8e18b18772c2 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 * @noformat */ @@ -102,6 +102,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableViewRecyclingForView: Getter, enableVirtualViewContainerStateExperimental: Getter, enableVirtualViewDebugFeatures: Getter, + fixDifferentiatorParentTagForUnflattenCase: Getter, fixFindShadowNodeByTagRaceCondition: Getter, fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter, fixYogaFlexBasisFitContentInMainAxis: Getter, @@ -422,6 +423,10 @@ export const enableVirtualViewContainerStateExperimental: Getter = crea * Enables VirtualView debug features such as logging and overlays. */ export const enableVirtualViewDebugFeatures: Getter = createNativeFlagGetter('enableVirtualViewDebugFeatures', false); +/** + * Fix incorrect parentTag passed as parentTagForUpdate in the unflatten-unflatten branch of calculateShadowViewMutationsFlattener, which causes UPDATE mutations to reference a parent being created in the same batch. + */ +export const fixDifferentiatorParentTagForUnflattenCase: Getter = createNativeFlagGetter('fixDifferentiatorParentTagForUnflattenCase', false); /** * Fix a use-after-free race condition in findShadowNodeByTag_DEPRECATED by using getCurrentRevision() instead of tryCommit() with a raw pointer. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 42ebd77f42e4..9fb5965d8171 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<<0e66e4ae4407000706cd243ad17aa605>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -80,6 +80,7 @@ export interface Spec extends TurboModule { +enableViewRecyclingForView?: () => boolean; +enableVirtualViewContainerStateExperimental?: () => boolean; +enableVirtualViewDebugFeatures?: () => boolean; + +fixDifferentiatorParentTagForUnflattenCase?: () => boolean; +fixFindShadowNodeByTagRaceCondition?: () => boolean; +fixMappingOfEventPrioritiesBetweenFabricAndReact?: () => boolean; +fixYogaFlexBasisFitContentInMainAxis?: () => boolean;