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 7fcdaaeec16d..c15c65ba4677 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<<4004d8650ddb3d1bc9afb9c5c72fa191>> */ /** @@ -144,6 +144,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun enableBridgelessArchitecture(): Boolean = accessor.enableBridgelessArchitecture() + /** + * When enabled, Android receives prop updates based on props diffing only for core components with hand-written props (View, Text, Image, ScrollView, TextInput). Other components keep receiving raw props unless `enablePropsUpdateReconciliationAndroid` is enabled. + */ + @JvmStatic + public fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean = accessor.enableCoreComponentsPropsUpdateReconciliationAndroid() + /** * Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java). */ 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 6e0da4d9d0e4..508f7ce8f96b 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<> */ /** @@ -39,6 +39,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null private var enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null + private var enableCoreComponentsPropsUpdateReconciliationAndroidCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null @@ -289,6 +290,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean { + var cached = enableCoreComponentsPropsUpdateReconciliationAndroidCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.enableCoreComponentsPropsUpdateReconciliationAndroid() + enableCoreComponentsPropsUpdateReconciliationAndroidCache = cached + } + return cached + } + override fun enableCppPropsIteratorSetter(): Boolean { var cached = enableCppPropsIteratorSetterCache 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 2268d03f3674..06ccc040a0e2 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<<6d86d4adbc261c9271b5bed72bd12dcf>> + * @generated SignedSource<<8e143d9668e06e97c1a4e04542e35295>> */ /** @@ -66,6 +66,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun enableBridgelessArchitecture(): Boolean + @DoNotStrip @JvmStatic public external fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean + @DoNotStrip @JvmStatic public external fun enableCppPropsIteratorSetter(): Boolean @DoNotStrip @JvmStatic public external fun enableCustomFocusSearchOnClippedElementsAndroid(): 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 defafe46f6b4..7335882be3eb 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<<328892d289851df4d865c737199e8ee0>> */ /** @@ -61,6 +61,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun enableBridgelessArchitecture(): Boolean = true + override fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean = false + override fun enableCppPropsIteratorSetter(): Boolean = false override fun enableCustomFocusSearchOnClippedElementsAndroid(): 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 53af4b4edab0..83d2302aa943 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<> + * @generated SignedSource<<19c3e5eaeb8f71fff3433783360a2a5f>> */ /** @@ -43,6 +43,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var enableAndroidTextMeasurementOptimizationsCache: Boolean? = null private var enableAsyncDiskCacheCheckInMultiSourceImageAndroidCache: Boolean? = null private var enableBridgelessArchitectureCache: Boolean? = null + private var enableCoreComponentsPropsUpdateReconciliationAndroidCache: Boolean? = null private var enableCppPropsIteratorSetterCache: Boolean? = null private var enableCustomFocusSearchOnClippedElementsAndroidCache: Boolean? = null private var enableDestroyShadowTreeRevisionAsyncCache: Boolean? = null @@ -312,6 +313,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean { + var cached = enableCoreComponentsPropsUpdateReconciliationAndroidCache + if (cached == null) { + cached = currentProvider.enableCoreComponentsPropsUpdateReconciliationAndroid() + accessedFeatureFlags.add("enableCoreComponentsPropsUpdateReconciliationAndroid") + enableCoreComponentsPropsUpdateReconciliationAndroidCache = cached + } + return cached + } + override fun enableCppPropsIteratorSetter(): Boolean { var cached = enableCppPropsIteratorSetterCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt index 850fcaa9ff55..c16af1df175e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android.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<> */ /** @@ -27,6 +27,8 @@ public open class ReactNativeFeatureFlagsOverrides_RNOSS_Experimental_Android : override fun disableIdleMountItemFrameCallbackRearmAndroid(): Boolean = true + override fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean = true + override fun enableFlexboxAutoMinSizeInStrictMode(): Boolean = true override fun preventShadowTreeCommitExhaustion(): Boolean = true 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 8fa2f4269464..38e868007dbe 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<<12a19d59f2a9d8242a0ae3d3d4b1d825>> */ /** @@ -61,6 +61,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun enableBridgelessArchitecture(): Boolean + @DoNotStrip public fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean + @DoNotStrip public fun enableCppPropsIteratorSetter(): Boolean @DoNotStrip public fun enableCustomFocusSearchOnClippedElementsAndroid(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt index 8714a01fa84a..becfbed38ebe 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactHorizontalScrollView.kt @@ -156,9 +156,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : // Private state private var activelyScrolling = false private var clippingRect: Rect? = null - private var _overflow: Overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) Overflow.VISIBLE - else Overflow.SCROLL + private var _overflow: Overflow = ReactScrollViewHelper.getDefaultOverflow() private var dragging = false private var pagingEnabled = false private var postTouchRunnable: Runnable? = null @@ -262,9 +260,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : _virtualViewContainerState = null activelyScrolling = false clippingRect = null - _overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) Overflow.VISIBLE - else Overflow.SCROLL + _overflow = ReactScrollViewHelper.getDefaultOverflow() dragging = false pagingEnabled = false postTouchRunnable = null @@ -425,11 +421,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : if (overflow == null) { Overflow.SCROLL } else { - Overflow.fromString( - overflow, - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) Overflow.VISIBLE - else Overflow.SCROLL, - ) + Overflow.fromString(overflow, ReactScrollViewHelper.getDefaultOverflow()) } ImportantForInteractionHelper.setImportantForInteraction(this, pointerEvents, _overflow) invalidate() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.kt index 01cb197ff7b5..d93f15d7ef73 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactNestedScrollView.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<<5d7adbc00208fc9995b1c3e69c677a38>> + * @generated SignedSource<<5774837740e2ec0d6337db8b2c5e9ab9>> */ /** @@ -187,12 +187,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : private var activelyScrolling = false private var clippingRect: Rect? = null - private var _overflow: Overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) { - Overflow.VISIBLE - } else { - Overflow.SCROLL - } + private var _overflow: Overflow = ReactScrollViewHelper.getDefaultOverflow() private var dragging = false private var pagingEnabled = false private var postTouchRunnable: Runnable? = null @@ -234,12 +229,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : _virtualViewContainerState = null activelyScrolling = false clippingRect = null - _overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) { - Overflow.VISIBLE - } else { - Overflow.SCROLL - } + _overflow = ReactScrollViewHelper.getDefaultOverflow() dragging = false pagingEnabled = false postTouchRunnable = null @@ -392,11 +382,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : if (overflow == null) { Overflow.SCROLL } else { - Overflow.fromString( - overflow, - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) Overflow.VISIBLE - else Overflow.SCROLL, - ) + Overflow.fromString(overflow, ReactScrollViewHelper.getDefaultOverflow()) } ImportantForInteractionHelper.setImportantForInteraction(this, pointerEvents, _overflow) invalidate() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt index fca6538d0774..2f5cfa049b83 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.kt @@ -179,12 +179,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : private var activelyScrolling = false private var clippingRect: Rect? = null - private var _overflow: Overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) { - Overflow.VISIBLE - } else { - Overflow.SCROLL - } + private var _overflow: Overflow = ReactScrollViewHelper.getDefaultOverflow() private var dragging = false private var pagingEnabled = false private var postTouchRunnable: Runnable? = null @@ -226,12 +221,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : _virtualViewContainerState = null activelyScrolling = false clippingRect = null - _overflow = - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) { - Overflow.VISIBLE - } else { - Overflow.SCROLL - } + _overflow = ReactScrollViewHelper.getDefaultOverflow() dragging = false pagingEnabled = false postTouchRunnable = null @@ -384,11 +374,7 @@ constructor(context: Context, private val fpsListener: FpsListener? = null) : if (overflow == null) { Overflow.SCROLL } else { - Overflow.fromString( - overflow, - if (ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid()) Overflow.VISIBLE - else Overflow.SCROLL, - ) + Overflow.fromString(overflow, ReactScrollViewHelper.getDefaultOverflow()) } ImportantForInteractionHelper.setImportantForInteraction(this, pointerEvents, _overflow) invalidate() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt index e6e0660d174b..1cbc5148ed89 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollViewHelper.kt @@ -24,12 +24,14 @@ import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap import com.facebook.react.common.ReactConstants import com.facebook.react.fabric.FabricUIManager +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags import com.facebook.react.uimanager.PixelUtil.toDIPFromPixel import com.facebook.react.uimanager.PixelUtil.toPixelFromDIP import com.facebook.react.uimanager.ReactClippingViewGroup import com.facebook.react.uimanager.StateWrapper import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.common.UIManagerType +import com.facebook.react.uimanager.style.Overflow import java.lang.ref.WeakReference import java.util.concurrent.CopyOnWriteArrayList import kotlin.math.abs @@ -64,6 +66,18 @@ public object ReactScrollViewHelper { private var SMOOTH_SCROLL_DURATION = 250 private var smoothScrollDurationInitialized = false + // With props diffing, props equal to the C++ default (`visible`) are not sent, so the native + // default has to match it. + internal fun getDefaultOverflow(): Overflow = + if ( + ReactNativeFeatureFlags.enablePropsUpdateReconciliationAndroid() || + ReactNativeFeatureFlags.enableCoreComponentsPropsUpdateReconciliationAndroid() + ) { + Overflow.VISIBLE + } else { + Overflow.SCROLL + } + /** Shared by [ReactScrollView] and [ReactHorizontalScrollView]. */ @JvmStatic public fun emitScrollEvent(scrollView: T, xVelocity: Float, yVelocity: Float) diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp index b5f1eb569b2e..ab4605824b57 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/FabricMountingManager.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -325,42 +326,45 @@ bool shouldDiffInsertPropsAgainstPreallocatedProps() { ReactNativeFeatureFlags::enablePreallocatedPropsDiffOnInsertAndroid(); } -jni::local_ref getProps( +// Diffs the props of the last mounted ShadowTree against the props of the +// latest committed ShadowTree. Only valid when `isPropsDiffingEnabled`. +folly::dynamic computeDiffProps( const ShadowView& oldShadowView, const ShadowView& newShadowView) { - // We calculate the diffing between the props of the last mounted ShadowTree - // and the Props of the latest commited ShadowTree). ONLY for - // components when the "enablePropsUpdateReconciliationAndroid" feature flag - // is enabled. auto* oldProps = oldShadowView.props.get(); auto* newProps = newShadowView.props.get(); - if ((ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid()) && - strcmp( - newShadowView.componentName, - newProps->getDiffPropsImplementationTarget()) == 0) { - auto diff = newProps->getDiffProps(oldProps); + auto diff = newProps->getDiffProps(oldProps); #ifdef REACT_NATIVE_DEBUG - if (oldProps != nullptr) { - auto controlDiff = - diffDynamicProps(oldProps->rawProps, newProps->rawProps); - - for (const auto& [prop, value] : controlDiff.items()) { - if (diff.count(prop) == 0) { - // Skip layout-only props since they are not included in Props 2.0 - if (!isLayoutOnlyProp(prop.asString())) { - LOG(ERROR) << "Props diff validation failed: Props 1.5 has prop '" - << prop.asString() - << "' = " << (value != nullptr ? value : "NULL") - << " that Props 2.0 doesn't have for component " - << newShadowView.componentName; - } + if (oldProps != nullptr) { + auto controlDiff = diffDynamicProps(oldProps->rawProps, newProps->rawProps); + + for (const auto& [prop, value] : controlDiff.items()) { + if (diff.count(prop) == 0) { + // Skip layout-only props since they are not included in Props 2.0 + if (!isLayoutOnlyProp(prop.asString())) { + LOG(ERROR) << "Props diff validation failed: Props 1.5 has prop '" + << prop.asString() + << "' = " << (value != nullptr ? value : "NULL") + << " that Props 2.0 doesn't have for component " + << newShadowView.componentName; } } } + } #endif - return ReadableNativeMap::newObjectCxxArgs(std::move(diff)); + return diff; +} + +jni::local_ref getProps( + const ShadowView& oldShadowView, + const ShadowView& newShadowView) { + auto* oldProps = oldShadowView.props.get(); + auto* newProps = newShadowView.props.get(); + if (isPropsDiffingEnabled(newShadowView.componentName, *newProps)) { + return ReadableNativeMap::newObjectCxxArgs( + computeDiffProps(oldShadowView, newShadowView)); } if (ReactNativeFeatureFlags::enableAccumulatedUpdatesInRawPropsAndroid()) { if (oldProps == nullptr) { @@ -373,6 +377,24 @@ jni::local_ref getProps( return ReadableNativeMap::newObjectCxxArgs(newProps->rawProps); } +// With props diffing, returns no item when there is nothing to update (e.g. +// only layout props changed) so it is not sent to Java and applied as a no-op. +std::optional makeUpdatePropsMountItem( + const ShadowView& oldShadowView, + const ShadowView& newShadowView) { + auto mountItem = + CppMountItem::UpdatePropsMountItem(oldShadowView, newShadowView); + if (isPropsDiffingEnabled( + newShadowView.componentName, *newShadowView.props)) { + auto diff = computeDiffProps(oldShadowView, newShadowView); + if (diff.empty()) { + return std::nullopt; + } + mountItem.diffProps = std::make_unique(std::move(diff)); + } + return mountItem; +} + struct InstructionBuffer { InstructionBuffer(int intsSize, int objectsSize) : objects(jni::JArrayClass::newArray(objectsSize)) { @@ -484,11 +506,17 @@ inline void writeRemoveMountItem( inline void writeUpdatePropsMountItem( InstructionBuffer& buffer, - const CppMountItem& mountItem) { + CppMountItem& mountItem) { buffer.writeInt(mountItem.newChildShadowView.tag); - buffer.writeObject( - getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView) - .get()); + if (mountItem.diffProps != nullptr) { + buffer.writeObject( + ReadableNativeMap::newObjectCxxArgs(std::move(*mountItem.diffProps)) + .get()); + } else { + buffer.writeObject( + getProps(mountItem.oldChildShadowView, mountItem.newChildShadowView) + .get()); + } } inline void writeUpdateStateMountItem( @@ -690,11 +718,12 @@ void FabricMountingManager::executeMount( } if (oldChildShadowView.props != newChildShadowView.props) { - (maintainMutationOrder ? cppCommonMountItems - : cppUpdatePropsMountItems) - .push_back( - CppMountItem::UpdatePropsMountItem( - oldChildShadowView, newChildShadowView)); + if (auto mountItem = makeUpdatePropsMountItem( + oldChildShadowView, newChildShadowView)) { + (maintainMutationOrder ? cppCommonMountItems + : cppUpdatePropsMountItems) + .push_back(std::move(*mountItem)); + } } if (oldChildShadowView.state != newChildShadowView.state) { (maintainMutationOrder ? cppCommonMountItems @@ -775,22 +804,24 @@ void FabricMountingManager::executeMount( } if (shouldCreateView || !shouldDiffInsertPropsAgainstPreallocatedProps()) { - (maintainMutationOrder ? cppCommonMountItems - : cppUpdatePropsMountItems) - .push_back( - CppMountItem::UpdatePropsMountItem( - {}, newChildShadowView)); + if (auto mountItem = + makeUpdatePropsMountItem({}, newChildShadowView)) { + (maintainMutationOrder ? cppCommonMountItems + : cppUpdatePropsMountItems) + .push_back(std::move(*mountItem)); + } } else if (auto& preallocatedProps = allocatedView->second.preallocatedProps; preallocatedProps != nullptr) { if (preallocatedProps != newChildShadowView.props) { auto preallocatedShadowView = newChildShadowView; preallocatedShadowView.props = preallocatedProps; - (maintainMutationOrder ? cppCommonMountItems - : cppUpdatePropsMountItems) - .push_back( - CppMountItem::UpdatePropsMountItem( - preallocatedShadowView, newChildShadowView)); + if (auto mountItem = makeUpdatePropsMountItem( + preallocatedShadowView, newChildShadowView)) { + (maintainMutationOrder ? cppCommonMountItems + : cppUpdatePropsMountItems) + .push_back(std::move(*mountItem)); + } } preallocatedProps = nullptr; } @@ -798,11 +829,12 @@ void FabricMountingManager::executeMount( if (shouldCreateView) { LOG(ERROR) << "Emitting insert for unallocated view " << newChildShadowView.tag; - (maintainMutationOrder ? cppCommonMountItems - : cppUpdatePropsMountItems) - .push_back( - CppMountItem::UpdatePropsMountItem( - {}, newChildShadowView)); + if (auto mountItem = + makeUpdatePropsMountItem({}, newChildShadowView)) { + (maintainMutationOrder ? cppCommonMountItems + : cppUpdatePropsMountItems) + .push_back(std::move(*mountItem)); + } } } @@ -918,7 +950,7 @@ void FabricMountingManager::executeMount( // Fill in CREATE instructions. for (int i = 0; i < cppCommonMountItems.size(); i++) { - const auto& mountItem = cppCommonMountItems[i]; + auto& mountItem = cppCommonMountItems[i]; const auto& mountItemType = mountItem.type; // Get type here, and count forward how many items of this type are in a @@ -976,7 +1008,7 @@ void FabricMountingManager::executeMount( buffer, CppMountItem::Type::UpdateProps, cppUpdatePropsMountItems.size()); - for (const auto& mountItem : cppUpdatePropsMountItems) { + for (auto& mountItem : cppUpdatePropsMountItems) { writeUpdatePropsMountItem(buffer, mountItem); } } diff --git a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h index 0901de24f001..d6015d59b5c8 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h +++ b/packages/react-native/ReactAndroid/src/main/jni/react/fabric/MountItem.h @@ -8,8 +8,11 @@ #pragma once #include +#include #include +#include + namespace facebook::react { struct JMountItem : public jni::JavaClass { @@ -63,6 +66,9 @@ struct CppMountItem final { ShadowView oldChildShadowView = {}; ShadowView newChildShadowView = {}; int index = {}; + + // For UpdateProps items, the props diff when it was already computed. + std::unique_ptr diffProps = {}; }; } // namespace facebook::react 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 23fd0978a766..d0725c39879f 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<<944781e7a547e601f0637869a3a3ec5e>> + * @generated SignedSource<> */ /** @@ -153,6 +153,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableCoreComponentsPropsUpdateReconciliationAndroid() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableCoreComponentsPropsUpdateReconciliationAndroid"); + return method(javaProvider_); + } + bool enableCppPropsIteratorSetter() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableCppPropsIteratorSetter"); @@ -720,6 +726,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture( return ReactNativeFeatureFlags::enableBridgelessArchitecture(); } +bool JReactNativeFeatureFlagsCxxInterop::enableCoreComponentsPropsUpdateReconciliationAndroid( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableCoreComponentsPropsUpdateReconciliationAndroid(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableCppPropsIteratorSetter( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableCppPropsIteratorSetter(); @@ -1198,6 +1209,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableBridgelessArchitecture", JReactNativeFeatureFlagsCxxInterop::enableBridgelessArchitecture), + makeNativeMethod( + "enableCoreComponentsPropsUpdateReconciliationAndroid", + JReactNativeFeatureFlagsCxxInterop::enableCoreComponentsPropsUpdateReconciliationAndroid), makeNativeMethod( "enableCppPropsIteratorSetter", JReactNativeFeatureFlagsCxxInterop::enableCppPropsIteratorSetter), 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 ae774f80c82b..799b58768c34 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<<23f91198ea1e19abb3eb7004de696505>> */ /** @@ -87,6 +87,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableBridgelessArchitecture( facebook::jni::alias_ref); + static bool enableCoreComponentsPropsUpdateReconciliationAndroid( + facebook::jni::alias_ref); + static bool enableCppPropsIteratorSetter( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/scroll/ReactScrollViewDefaultOverflowTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/scroll/ReactScrollViewDefaultOverflowTest.kt new file mode 100644 index 000000000000..184c16ce9ef3 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/scroll/ReactScrollViewDefaultOverflowTest.kt @@ -0,0 +1,74 @@ +/* + * 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. + */ + +@file:Suppress("DEPRECATION") + +package com.facebook.react.views.scroll + +import android.annotation.SuppressLint +import com.facebook.react.bridge.ReactTestHelper.createTestReactApplicationContext +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsDefaults +import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.testutils.shadows.ShadowSoLoader +import org.assertj.core.api.Assertions.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +/** Verifies the default overflow of the vertical and horizontal ScrollViews under props diffing. */ +@SuppressLint("DeprecatedClass", "DeprecatedMethod") +@RunWith(RobolectricTestRunner::class) +@Config(shadows = [ShadowSoLoader::class]) +class ReactScrollViewDefaultOverflowTest { + + @Before + fun setUp() { + ReactNativeFeatureFlagsForTests.setUp() + } + + @Test + fun defaultOverflowIsScrollWithoutPropsDiffing() { + overrideFlags(allComponents = false, coreComponents = false) + assertDefaultOverflow("scroll") + } + + @Test + fun defaultOverflowIsVisibleWithPropsDiffingForAllComponents() { + overrideFlags(allComponents = true, coreComponents = false) + assertDefaultOverflow("visible") + } + + @Test + fun defaultOverflowIsVisibleWithPropsDiffingForCoreComponents() { + overrideFlags(allComponents = false, coreComponents = true) + assertDefaultOverflow("visible") + } + + private fun assertDefaultOverflow(expected: String) { + val context = createTestReactApplicationContext() + val themedContext = ThemedReactContext(context, context, null, -1) + assertThat(ReactScrollViewManager().createViewInstance(themedContext).overflow) + .isEqualTo(expected) + assertThat(ReactHorizontalScrollViewManager().createViewInstance(themedContext).overflow) + .isEqualTo(expected) + } + + private fun overrideFlags(allComponents: Boolean, coreComponents: Boolean) { + ReactNativeFeatureFlags.dangerouslyForceOverride( + object : ReactNativeFeatureFlagsDefaults() { + override fun enablePropsUpdateReconciliationAndroid(): Boolean = allComponents + + override fun enableCoreComponentsPropsUpdateReconciliationAndroid(): Boolean = + coreComponents + }, + ) + } +} diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index ac8ddb96a112..2e4907a5748d 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<<56e127471f63e47798ec619e6fa56738>> + * @generated SignedSource<<764aeebfb1e2fc17dae032247e5e69cf>> */ /** @@ -102,6 +102,10 @@ bool ReactNativeFeatureFlags::enableBridgelessArchitecture() { return getAccessor().enableBridgelessArchitecture(); } +bool ReactNativeFeatureFlags::enableCoreComponentsPropsUpdateReconciliationAndroid() { + return getAccessor().enableCoreComponentsPropsUpdateReconciliationAndroid(); +} + bool ReactNativeFeatureFlags::enableCppPropsIteratorSetter() { return getAccessor().enableCppPropsIteratorSetter(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 0e5b3f2013fb..652e976c8850 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<<6887b671526bc8a3b0678e3e45b4e3b4>> + * @generated SignedSource<<8fc79697c78a91d401a1aec0b5e44d1b>> */ /** @@ -136,6 +136,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool enableBridgelessArchitecture(); + /** + * When enabled, Android receives prop updates based on props diffing only for core components with hand-written props (View, Text, Image, ScrollView, TextInput). Other components keep receiving raw props unless `enablePropsUpdateReconciliationAndroid` is enabled. + */ + RN_EXPORT static bool enableCoreComponentsPropsUpdateReconciliationAndroid(); + /** * Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java). */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 7640cea6418a..55db7d64ea83 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<<5d9712bd7e62ba3a29b084ba4c60bec5>> + * @generated SignedSource<<4b3db41e42b1dced34353a26a3c266c2>> */ /** @@ -371,6 +371,24 @@ bool ReactNativeFeatureFlagsAccessor::enableBridgelessArchitecture() { return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::enableCoreComponentsPropsUpdateReconciliationAndroid() { + auto flagValue = enableCoreComponentsPropsUpdateReconciliationAndroid_.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(19, "enableCoreComponentsPropsUpdateReconciliationAndroid"); + + flagValue = currentProvider_->enableCoreComponentsPropsUpdateReconciliationAndroid(); + enableCoreComponentsPropsUpdateReconciliationAndroid_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::enableCppPropsIteratorSetter() { auto flagValue = enableCppPropsIteratorSetter_.load(); @@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCppPropsIteratorSetter() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(19, "enableCppPropsIteratorSetter"); + markFlagAsAccessed(20, "enableCppPropsIteratorSetter"); flagValue = currentProvider_->enableCppPropsIteratorSetter(); enableCppPropsIteratorSetter_ = flagValue; @@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableCustomFocusSearchOnClippedElementsAn // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(20, "enableCustomFocusSearchOnClippedElementsAndroid"); + markFlagAsAccessed(21, "enableCustomFocusSearchOnClippedElementsAndroid"); flagValue = currentProvider_->enableCustomFocusSearchOnClippedElementsAndroid(); enableCustomFocusSearchOnClippedElementsAndroid_ = flagValue; @@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDestroyShadowTreeRevisionAsync() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(21, "enableDestroyShadowTreeRevisionAsync"); + markFlagAsAccessed(22, "enableDestroyShadowTreeRevisionAsync"); flagValue = currentProvider_->enableDestroyShadowTreeRevisionAsync(); enableDestroyShadowTreeRevisionAsync_ = flagValue; @@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableDoubleMeasurementFixAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(22, "enableDoubleMeasurementFixAndroid"); + markFlagAsAccessed(23, "enableDoubleMeasurementFixAndroid"); flagValue = currentProvider_->enableDoubleMeasurementFixAndroid(); enableDoubleMeasurementFixAndroid_ = flagValue; @@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableEagerRootViewAttachment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(23, "enableEagerRootViewAttachment"); + markFlagAsAccessed(24, "enableEagerRootViewAttachment"); flagValue = currentProvider_->enableEagerRootViewAttachment(); enableEagerRootViewAttachment_ = flagValue; @@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableExclusivePropsUpdateAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(24, "enableExclusivePropsUpdateAndroid"); + markFlagAsAccessed(25, "enableExclusivePropsUpdateAndroid"); flagValue = currentProvider_->enableExclusivePropsUpdateAndroid(); enableExclusivePropsUpdateAndroid_ = flagValue; @@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranching() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(25, "enableFabricCommitBranching"); + markFlagAsAccessed(26, "enableFabricCommitBranching"); flagValue = currentProvider_->enableFabricCommitBranching(); enableFabricCommitBranching_ = flagValue; @@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricCommitBranchingMergeOnMainThre // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(26, "enableFabricCommitBranchingMergeOnMainThread"); + markFlagAsAccessed(27, "enableFabricCommitBranchingMergeOnMainThread"); flagValue = currentProvider_->enableFabricCommitBranchingMergeOnMainThread(); enableFabricCommitBranchingMergeOnMainThread_ = flagValue; @@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFabricLogs() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(27, "enableFabricLogs"); + markFlagAsAccessed(28, "enableFabricLogs"); flagValue = currentProvider_->enableFabricLogs(); enableFabricLogs_ = flagValue; @@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFlexboxAutoMinSizeInStrictMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(28, "enableFlexboxAutoMinSizeInStrictMode"); + markFlagAsAccessed(29, "enableFlexboxAutoMinSizeInStrictMode"); flagValue = currentProvider_->enableFlexboxAutoMinSizeInStrictMode(); enableFlexboxAutoMinSizeInStrictMode_ = flagValue; @@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableFollowUpMountItemDispatchAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(29, "enableFollowUpMountItemDispatchAndroid"); + markFlagAsAccessed(30, "enableFollowUpMountItemDispatchAndroid"); flagValue = currentProvider_->enableFollowUpMountItemDispatchAndroid(); enableFollowUpMountItemDispatchAndroid_ = flagValue; @@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSCompressedTextFrameAdjustment() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(30, "enableIOSCompressedTextFrameAdjustment"); + markFlagAsAccessed(31, "enableIOSCompressedTextFrameAdjustment"); flagValue = currentProvider_->enableIOSCompressedTextFrameAdjustment(); enableIOSCompressedTextFrameAdjustment_ = flagValue; @@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSTextBaselineOffsetPerLine() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(31, "enableIOSTextBaselineOffsetPerLine"); + markFlagAsAccessed(32, "enableIOSTextBaselineOffsetPerLine"); flagValue = currentProvider_->enableIOSTextBaselineOffsetPerLine(); enableIOSTextBaselineOffsetPerLine_ = flagValue; @@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(32, "enableIOSViewClipToPaddingBox"); + markFlagAsAccessed(33, "enableIOSViewClipToPaddingBox"); flagValue = currentProvider_->enableIOSViewClipToPaddingBox(); enableIOSViewClipToPaddingBox_ = flagValue; @@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImagePrefetchingAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(33, "enableImagePrefetchingAndroid"); + markFlagAsAccessed(34, "enableImagePrefetchingAndroid"); flagValue = currentProvider_->enableImagePrefetchingAndroid(); enableImagePrefetchingAndroid_ = flagValue; @@ -650,7 +668,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImageTransparentTintColor() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(34, "enableImageTransparentTintColor"); + markFlagAsAccessed(35, "enableImageTransparentTintColor"); flagValue = currentProvider_->enableImageTransparentTintColor(); enableImageTransparentTintColor_ = flagValue; @@ -668,7 +686,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImmediateUpdateModeForContentOffsetC // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(35, "enableImmediateUpdateModeForContentOffsetChanges"); + markFlagAsAccessed(36, "enableImmediateUpdateModeForContentOffsetChanges"); flagValue = currentProvider_->enableImmediateUpdateModeForContentOffsetChanges(); enableImmediateUpdateModeForContentOffsetChanges_ = flagValue; @@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(36, "enableImperativeEvents"); + markFlagAsAccessed(37, "enableImperativeEvents"); flagValue = currentProvider_->enableImperativeEvents(); enableImperativeEvents_ = flagValue; @@ -704,7 +722,7 @@ bool ReactNativeFeatureFlagsAccessor::enableImperativeFocus() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(37, "enableImperativeFocus"); + markFlagAsAccessed(38, "enableImperativeFocus"); flagValue = currentProvider_->enableImperativeFocus(); enableImperativeFocus_ = flagValue; @@ -722,7 +740,7 @@ bool ReactNativeFeatureFlagsAccessor::enableInteropViewManagerClassLookUpOptimiz // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(38, "enableInteropViewManagerClassLookUpOptimizationIOS"); + markFlagAsAccessed(39, "enableInteropViewManagerClassLookUpOptimizationIOS"); flagValue = currentProvider_->enableInteropViewManagerClassLookUpOptimizationIOS(); enableInteropViewManagerClassLookUpOptimizationIOS_ = flagValue; @@ -740,7 +758,7 @@ bool ReactNativeFeatureFlagsAccessor::enableIntersectionObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(39, "enableIntersectionObserverByDefault"); + markFlagAsAccessed(40, "enableIntersectionObserverByDefault"); flagValue = currentProvider_->enableIntersectionObserverByDefault(); enableIntersectionObserverByDefault_ = flagValue; @@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::enableKeyEvents() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(40, "enableKeyEvents"); + markFlagAsAccessed(41, "enableKeyEvents"); flagValue = currentProvider_->enableKeyEvents(); enableKeyEvents_ = flagValue; @@ -776,7 +794,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(41, "enableLayoutAnimationsOnAndroid"); + markFlagAsAccessed(42, "enableLayoutAnimationsOnAndroid"); flagValue = currentProvider_->enableLayoutAnimationsOnAndroid(); enableLayoutAnimationsOnAndroid_ = flagValue; @@ -794,7 +812,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(42, "enableLayoutAnimationsOnIOS"); + markFlagAsAccessed(43, "enableLayoutAnimationsOnIOS"); flagValue = currentProvider_->enableLayoutAnimationsOnIOS(); enableLayoutAnimationsOnIOS_ = flagValue; @@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::enableModuleArgumentNSNullConversionIOS() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(43, "enableModuleArgumentNSNullConversionIOS"); + markFlagAsAccessed(44, "enableModuleArgumentNSNullConversionIOS"); flagValue = currentProvider_->enableModuleArgumentNSNullConversionIOS(); enableModuleArgumentNSNullConversionIOS_ = flagValue; @@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMountingCoordinatorPullModelAndroid( // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(44, "enableMountingCoordinatorPullModelAndroid"); + markFlagAsAccessed(45, "enableMountingCoordinatorPullModelAndroid"); flagValue = currentProvider_->enableMountingCoordinatorPullModelAndroid(); enableMountingCoordinatorPullModelAndroid_ = flagValue; @@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMutationObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(45, "enableMutationObserverByDefault"); + markFlagAsAccessed(46, "enableMutationObserverByDefault"); flagValue = currentProvider_->enableMutationObserverByDefault(); enableMutationObserverByDefault_ = flagValue; @@ -866,7 +884,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(46, "enableNativeCSSParsing"); + markFlagAsAccessed(47, "enableNativeCSSParsing"); flagValue = currentProvider_->enableNativeCSSParsing(); enableNativeCSSParsing_ = flagValue; @@ -884,7 +902,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreallocatedPropsDiffOnInsertAndroid // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(47, "enablePreallocatedPropsDiffOnInsertAndroid"); + markFlagAsAccessed(48, "enablePreallocatedPropsDiffOnInsertAndroid"); flagValue = currentProvider_->enablePreallocatedPropsDiffOnInsertAndroid(); enablePreallocatedPropsDiffOnInsertAndroid_ = flagValue; @@ -902,7 +920,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(48, "enablePreparedTextLayout"); + markFlagAsAccessed(49, "enablePreparedTextLayout"); flagValue = currentProvider_->enablePreparedTextLayout(); enablePreparedTextLayout_ = flagValue; @@ -920,7 +938,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(49, "enablePropsUpdateReconciliationAndroid"); + markFlagAsAccessed(50, "enablePropsUpdateReconciliationAndroid"); flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid(); enablePropsUpdateReconciliationAndroid_ = flagValue; @@ -938,7 +956,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResizeObserverByDefault() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(50, "enableResizeObserverByDefault"); + markFlagAsAccessed(51, "enableResizeObserverByDefault"); flagValue = currentProvider_->enableResizeObserverByDefault(); enableResizeObserverByDefault_ = flagValue; @@ -956,7 +974,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSwiftUIBasedFilters() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(51, "enableSwiftUIBasedFilters"); + markFlagAsAccessed(52, "enableSwiftUIBasedFilters"); flagValue = currentProvider_->enableSwiftUIBasedFilters(); enableSwiftUIBasedFilters_ = flagValue; @@ -974,7 +992,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(52, "enableViewCulling"); + markFlagAsAccessed(53, "enableViewCulling"); flagValue = currentProvider_->enableViewCulling(); enableViewCulling_ = flagValue; @@ -992,7 +1010,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(53, "enableViewRecycling"); + markFlagAsAccessed(54, "enableViewRecycling"); flagValue = currentProvider_->enableViewRecycling(); enableViewRecycling_ = flagValue; @@ -1010,7 +1028,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForImage() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(54, "enableViewRecyclingForImage"); + markFlagAsAccessed(55, "enableViewRecyclingForImage"); flagValue = currentProvider_->enableViewRecyclingForImage(); enableViewRecyclingForImage_ = flagValue; @@ -1028,7 +1046,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForScrollView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(55, "enableViewRecyclingForScrollView"); + markFlagAsAccessed(56, "enableViewRecyclingForScrollView"); flagValue = currentProvider_->enableViewRecyclingForScrollView(); enableViewRecyclingForScrollView_ = flagValue; @@ -1046,7 +1064,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(56, "enableViewRecyclingForText"); + markFlagAsAccessed(57, "enableViewRecyclingForText"); flagValue = currentProvider_->enableViewRecyclingForText(); enableViewRecyclingForText_ = flagValue; @@ -1064,7 +1082,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(57, "enableViewRecyclingForView"); + markFlagAsAccessed(58, "enableViewRecyclingForView"); flagValue = currentProvider_->enableViewRecyclingForView(); enableViewRecyclingForView_ = flagValue; @@ -1082,7 +1100,7 @@ bool ReactNativeFeatureFlagsAccessor::enableVirtualViewContainerStateExperimenta // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(58, "enableVirtualViewContainerStateExperimental"); + markFlagAsAccessed(59, "enableVirtualViewContainerStateExperimental"); flagValue = currentProvider_->enableVirtualViewContainerStateExperimental(); enableVirtualViewContainerStateExperimental_ = flagValue; @@ -1100,7 +1118,7 @@ bool ReactNativeFeatureFlagsAccessor::fixBorderlessRippleAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(59, "fixBorderlessRippleAndroid"); + markFlagAsAccessed(60, "fixBorderlessRippleAndroid"); flagValue = currentProvider_->fixBorderlessRippleAndroid(); fixBorderlessRippleAndroid_ = flagValue; @@ -1118,7 +1136,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(60, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); + markFlagAsAccessed(61, "fixMappingOfEventPrioritiesBetweenFabricAndReact"); flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact(); fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue; @@ -1136,7 +1154,7 @@ bool ReactNativeFeatureFlagsAccessor::fixTextLayoutOnPixelDensityChange() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(61, "fixTextLayoutOnPixelDensityChange"); + markFlagAsAccessed(62, "fixTextLayoutOnPixelDensityChange"); flagValue = currentProvider_->fixTextLayoutOnPixelDensityChange(); fixTextLayoutOnPixelDensityChange_ = flagValue; @@ -1154,7 +1172,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(62, "fixYogaFlexBasisFitContentInMainAxis"); + markFlagAsAccessed(63, "fixYogaFlexBasisFitContentInMainAxis"); flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); fixYogaFlexBasisFitContentInMainAxis_ = flagValue; @@ -1172,7 +1190,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(63, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(64, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1190,7 +1208,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(64, "fuseboxEnabledRelease"); + markFlagAsAccessed(65, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1208,7 +1226,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(65, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(66, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1226,7 +1244,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(66, "fuseboxScreenshotCaptureEnabled"); + markFlagAsAccessed(67, "fuseboxScreenshotCaptureEnabled"); flagValue = currentProvider_->fuseboxScreenshotCaptureEnabled(); fuseboxScreenshotCaptureEnabled_ = flagValue; @@ -1244,7 +1262,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxWebSocketEventsEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(67, "fuseboxWebSocketEventsEnabled"); + markFlagAsAccessed(68, "fuseboxWebSocketEventsEnabled"); flagValue = currentProvider_->fuseboxWebSocketEventsEnabled(); fuseboxWebSocketEventsEnabled_ = flagValue; @@ -1262,7 +1280,7 @@ bool ReactNativeFeatureFlagsAccessor::keepTimersRunningWhileHostPausedAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(68, "keepTimersRunningWhileHostPausedAndroid"); + markFlagAsAccessed(69, "keepTimersRunningWhileHostPausedAndroid"); flagValue = currentProvider_->keepTimersRunningWhileHostPausedAndroid(); keepTimersRunningWhileHostPausedAndroid_ = flagValue; @@ -1280,7 +1298,7 @@ bool ReactNativeFeatureFlagsAccessor::optimizedAnimatedPropUpdates() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(69, "optimizedAnimatedPropUpdates"); + markFlagAsAccessed(70, "optimizedAnimatedPropUpdates"); flagValue = currentProvider_->optimizedAnimatedPropUpdates(); optimizedAnimatedPropUpdates_ = flagValue; @@ -1298,7 +1316,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(70, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(71, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1316,7 +1334,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(71, "perfIssuesEnabled"); + markFlagAsAccessed(72, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1334,7 +1352,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(72, "perfMonitorV2Enabled"); + markFlagAsAccessed(73, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1352,7 +1370,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(73, "preparedTextCacheSize"); + markFlagAsAccessed(74, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1370,7 +1388,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(74, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(75, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1388,7 +1406,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2Android() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(75, "redBoxV2Android"); + markFlagAsAccessed(76, "redBoxV2Android"); flagValue = currentProvider_->redBoxV2Android(); redBoxV2Android_ = flagValue; @@ -1406,7 +1424,7 @@ bool ReactNativeFeatureFlagsAccessor::redBoxV2IOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(76, "redBoxV2IOS"); + markFlagAsAccessed(77, "redBoxV2IOS"); flagValue = currentProvider_->redBoxV2IOS(); redBoxV2IOS_ = flagValue; @@ -1424,7 +1442,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(77, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(78, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1442,7 +1460,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(78, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(79, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1460,7 +1478,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(79, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(80, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::skipUnchangedChildrenInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "skipUnchangedChildrenInDifferentiator"); + markFlagAsAccessed(81, "skipUnchangedChildrenInDifferentiator"); flagValue = currentProvider_->skipUnchangedChildrenInDifferentiator(); skipUnchangedChildrenInDifferentiator_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipBoundsWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "syncAndroidClipBoundsWithOverflow"); + markFlagAsAccessed(82, "syncAndroidClipBoundsWithOverflow"); flagValue = currentProvider_->syncAndroidClipBoundsWithOverflow(); syncAndroidClipBoundsWithOverflow_ = flagValue; @@ -1514,7 +1532,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(82, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(83, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1532,7 +1550,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(83, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(84, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1550,7 +1568,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(84, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(85, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1568,7 +1586,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(85, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(86, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1586,7 +1604,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(86, "useFabricInterop"); + markFlagAsAccessed(87, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1604,7 +1622,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(87, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(88, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1622,7 +1640,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(88, "useNestedScrollViewAndroid"); + markFlagAsAccessed(89, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1640,7 +1658,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(89, "useSharedAnimatedBackend"); + markFlagAsAccessed(90, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1658,7 +1676,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(90, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(91, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1676,7 +1694,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(91, "useTurboModuleInterop"); + markFlagAsAccessed(92, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1694,7 +1712,7 @@ bool ReactNativeFeatureFlagsAccessor::useWindowDensityForDisplayMetricsAndroid() // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(92, "useWindowDensityForDisplayMetricsAndroid"); + markFlagAsAccessed(93, "useWindowDensityForDisplayMetricsAndroid"); flagValue = currentProvider_->useWindowDensityForDisplayMetricsAndroid(); useWindowDensityForDisplayMetricsAndroid_ = flagValue; @@ -1712,7 +1730,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(93, "viewCullingOutsetRatio"); + markFlagAsAccessed(94, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1730,7 +1748,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(94, "viewTransitionEnabled"); + markFlagAsAccessed(95, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1748,7 +1766,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionUseHardwareBitmapAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(95, "viewTransitionUseHardwareBitmapAndroid"); + markFlagAsAccessed(96, "viewTransitionUseHardwareBitmapAndroid"); flagValue = currentProvider_->viewTransitionUseHardwareBitmapAndroid(); viewTransitionUseHardwareBitmapAndroid_ = flagValue; @@ -1766,7 +1784,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(96, "virtualViewPrerenderRatio"); + markFlagAsAccessed(97, "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 7a9bc59bc57e..0ef554bf15e3 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<<17f41335dce7e5b45e7af2878c47d69b>> + * @generated SignedSource<<917c49b8668e677e8e85ca97c8cb91db>> */ /** @@ -53,6 +53,7 @@ class ReactNativeFeatureFlagsAccessor { bool enableAndroidTextMeasurementOptimizations(); bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid(); bool enableBridgelessArchitecture(); + bool enableCoreComponentsPropsUpdateReconciliationAndroid(); bool enableCppPropsIteratorSetter(); bool enableCustomFocusSearchOnClippedElementsAndroid(); bool enableDestroyShadowTreeRevisionAsync(); @@ -142,7 +143,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 97> accessedFeatureFlags_; + std::array, 98> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -163,6 +164,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableAndroidTextMeasurementOptimizations_; std::atomic> enableAsyncDiskCacheCheckInMultiSourceImageAndroid_; std::atomic> enableBridgelessArchitecture_; + std::atomic> enableCoreComponentsPropsUpdateReconciliationAndroid_; std::atomic> enableCppPropsIteratorSetter_; std::atomic> enableCustomFocusSearchOnClippedElementsAndroid_; std::atomic> enableDestroyShadowTreeRevisionAsync_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 600aab1882cc..8bcd29a48816 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<<35e8175277baa31d135d802ef5a5e6f1>> + * @generated SignedSource<<2eaa9084184e082b495516961dff4c08>> */ /** @@ -105,6 +105,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return true; } + bool enableCoreComponentsPropsUpdateReconciliationAndroid() override { + return false; + } + bool enableCppPropsIteratorSetter() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 88878807cfec..de4693a32c91 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<> + * @generated SignedSource<> */ /** @@ -218,6 +218,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::enableBridgelessArchitecture(); } + bool enableCoreComponentsPropsUpdateReconciliationAndroid() override { + auto value = values_["enableCoreComponentsPropsUpdateReconciliationAndroid"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::enableCoreComponentsPropsUpdateReconciliationAndroid(); + } + bool enableCppPropsIteratorSetter() override { auto value = values_["enableCppPropsIteratorSetter"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h index 47292fd47cc4..33f32de8279a 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsOverridesOSSExperimental.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<<02c8f99ea2314241417752f2efbbefa3>> */ /** @@ -37,6 +37,10 @@ class ReactNativeFeatureFlagsOverridesOSSExperimental : public ReactNativeFeatur return true; } + bool enableCoreComponentsPropsUpdateReconciliationAndroid() override { + return true; + } + bool enableFlexboxAutoMinSizeInStrictMode() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 664922287009..ca81245c47bc 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<<7413e958441d0502dc1266ff87e9da85>> + * @generated SignedSource<<4134897f2b8e7692103f86614f7ad284>> */ /** @@ -46,6 +46,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool enableAndroidTextMeasurementOptimizations() = 0; virtual bool enableAsyncDiskCacheCheckInMultiSourceImageAndroid() = 0; virtual bool enableBridgelessArchitecture() = 0; + virtual bool enableCoreComponentsPropsUpdateReconciliationAndroid() = 0; virtual bool enableCppPropsIteratorSetter() = 0; virtual bool enableCustomFocusSearchOnClippedElementsAndroid() = 0; virtual bool enableDestroyShadowTreeRevisionAsync() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index ca8a80f1adbf..64d965c296dd 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<> + * @generated SignedSource<<65e9161ec3769f2c776bd50686c7edad>> */ /** @@ -139,6 +139,11 @@ bool NativeReactNativeFeatureFlags::enableBridgelessArchitecture( return ReactNativeFeatureFlags::enableBridgelessArchitecture(); } +bool NativeReactNativeFeatureFlags::enableCoreComponentsPropsUpdateReconciliationAndroid( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::enableCoreComponentsPropsUpdateReconciliationAndroid(); +} + bool NativeReactNativeFeatureFlags::enableCppPropsIteratorSetter( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::enableCppPropsIteratorSetter(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 7413766e285a..66b31a57ffdb 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<<90f271ea47f91e6a84979209b7b3d816>> + * @generated SignedSource<> */ /** @@ -76,6 +76,8 @@ class NativeReactNativeFeatureFlags bool enableBridgelessArchitecture(jsi::Runtime& runtime); + bool enableCoreComponentsPropsUpdateReconciliationAndroid(jsi::Runtime& runtime); + bool enableCppPropsIteratorSetter(jsi::Runtime& runtime); bool enableCustomFocusSearchOnClippedElementsAndroid(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp index ce2a3ac666e6..4106efbe43e5 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp @@ -75,15 +75,15 @@ AndroidTextInputProps::AndroidTextInputProps( showSoftInputOnFocus(convertRawProp(context, rawProps, "showSoftInputOnFocus", sourceProps.showSoftInputOnFocus, - {false})), + {true})), autoCorrect(convertRawProp(context, rawProps, "autoCorrect", sourceProps.autoCorrect, - {false})), + {})), allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, - {false})), + {true})), maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, @@ -290,7 +290,8 @@ folly::dynamic AndroidTextInputProps::getDynamic() const { props["importantForAutofill"] = importantForAutofill; props["showSoftInputOnFocus"] = showSoftInputOnFocus; props["autoCapitalize"] = autoCapitalize; - props["autoCorrect"] = autoCorrect; + props["autoCorrect"] = + autoCorrect.has_value() ? folly::dynamic(*autoCorrect) : nullptr; props["autoFocus"] = autoFocus; props["allowFontScaling"] = allowFontScaling; props["maxFontSizeMultiplier"] = maxFontSizeMultiplier; @@ -354,7 +355,7 @@ SharedDebugStringConvertibleList AndroidTextInputProps::getDebugProps() const { #endif ComponentName AndroidTextInputProps::getDiffPropsImplementationTarget() const { - return "TextInput"; + return "AndroidTextInput"; } folly::dynamic AndroidTextInputProps::getDiffProps( @@ -500,18 +501,10 @@ folly::dynamic AndroidTextInputProps::getDiffProps( result["returnKeyLabel"] = returnKeyLabel; } - if (numberOfLines != oldProps->numberOfLines) { - result["numberOfLines"] = numberOfLines; - } - if (disableFullscreenUI != oldProps->disableFullscreenUI) { result["disableFullscreenUI"] = disableFullscreenUI; } - if (textBreakStrategy != oldProps->textBreakStrategy) { - result["textBreakStrategy"] = textBreakStrategy; - } - if (inlineImageLeft != oldProps->inlineImageLeft) { result["inlineImageLeft"] = inlineImageLeft; } @@ -529,7 +522,8 @@ folly::dynamic AndroidTextInputProps::getDiffProps( } if (autoCorrect != oldProps->autoCorrect) { - result["autoCorrect"] = autoCorrect; + result["autoCorrect"] = + autoCorrect.has_value() ? folly::dynamic(*autoCorrect) : nullptr; } if (allowFontScaling != oldProps->allowFontScaling) { @@ -608,10 +602,6 @@ folly::dynamic AndroidTextInputProps::getDiffProps( result["textAlign"] = textAlign; } - if (includeFontPadding != oldProps->includeFontPadding) { - result["includeFontPadding"] = includeFontPadding; - } - if (fontWeight != oldProps->fontWeight) { result["fontWeight"] = fontWeight; } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.h index 8bfc984e2529..78d8778c04df 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.h +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/platform/android/react/renderer/components/androidtextinput/AndroidTextInputProps.h @@ -16,6 +16,7 @@ #include #include #include +#include #include namespace facebook::react { @@ -83,9 +84,9 @@ class AndroidTextInputProps final : public BaseTextInputProps { std::string inlineImageLeft{}; int inlineImagePadding{0}; std::string importantForAutofill{}; - bool showSoftInputOnFocus{false}; - bool autoCorrect{false}; - bool allowFontScaling{false}; + bool showSoftInputOnFocus{true}; + std::optional autoCorrect{}; + bool allowFontScaling{true}; Float maxFontSizeMultiplier{0.0}; std::string keyboardType{}; std::string returnKeyType{}; diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/tests/AndroidTextInputPropsTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/tests/AndroidTextInputPropsTest.cpp new file mode 100644 index 000000000000..40df56cfde0b --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/tests/AndroidTextInputPropsTest.cpp @@ -0,0 +1,65 @@ +/* + * 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 + +#if defined(ANDROID) && defined(RN_SERIALIZABLE_STATE) + +#include + +namespace facebook::react { + +// Props diffing is only used when the props target matches the component name. +TEST(AndroidTextInputPropsTest, diffPropsTargetMatchesComponentName) { + EXPECT_STREQ( + AndroidTextInputProps().getDiffPropsImplementationTarget(), + AndroidTextInputShadowNode::Name()); +} + +// Values equal to the C++ default are not sent on creation, so the C++ default +// has to match the state of a newly created native view. +TEST(AndroidTextInputPropsTest, diffSendsValuesThatDifferFromNativeDefaults) { + AndroidTextInputProps props; + props.showSoftInputOnFocus = false; + props.allowFontScaling = false; + props.autoCorrect = false; + + auto diff = props.getDiffProps(nullptr); + + EXPECT_EQ(diff["showSoftInputOnFocus"], false); + EXPECT_EQ(diff["allowFontScaling"], false); + EXPECT_EQ(diff["autoCorrect"], false); +} + +TEST(AndroidTextInputPropsTest, diffOfDefaultPropsIsEmpty) { + EXPECT_TRUE(AndroidTextInputProps().getDiffProps(nullptr).empty()); +} + +TEST(AndroidTextInputPropsTest, removedAutoCorrectIsSentAsNull) { + AndroidTextInputProps oldProps; + oldProps.autoCorrect = false; + + auto diff = AndroidTextInputProps().getDiffProps(&oldProps); + + ASSERT_EQ(diff.count("autoCorrect"), 1); + EXPECT_TRUE(diff["autoCorrect"].isNull()); +} + +// `includeFontPadding` is sent from the paragraph attributes only, whose +// default matches the native default. +TEST(AndroidTextInputPropsTest, removedIncludeFontPaddingIsNotSentAsFalse) { + AndroidTextInputProps oldProps; + oldProps.includeFontPadding = true; + + auto diff = AndroidTextInputProps().getDiffProps(&oldProps); + + EXPECT_EQ(diff.count("includeFontPadding"), 0); +} + +} // namespace facebook::react + +#endif diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp index 2f2820422161..246cb9db6e16 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/platform/android/react/renderer/components/view/HostPlatformViewProps.cpp @@ -143,7 +143,7 @@ inline static void updateEventProp( const ViewEvents& newEvents, const ViewEvents& oldEvents, const ViewEvents::Offset& offset, - const std::string& name) { + const char* name) { if (newEvents[offset] != oldEvents[offset]) { auto value = newEvents[offset]; result[name] = value; @@ -152,7 +152,7 @@ inline static void updateEventProp( static void updateBorderWidthPropValue( folly::dynamic& result, - const std::string& propName, + const char* propName, const std::optional& newValue, const std::optional& oldValue) { if (newValue != oldValue) { @@ -189,7 +189,7 @@ static void updateBorderWidthProps( static void updateBorderRadiusPropValue( folly::dynamic& result, - const std::string& propName, + const char* propName, const std::optional& newValue, const std::optional& oldValue) { if (newValue != oldValue) { @@ -298,7 +298,7 @@ static void updateBorderStyleProps( static void updateBorderColorPropValue( folly::dynamic& result, - const std::string& propName, + const char* propName, const std::optional& newColor, const std::optional& oldColor) { if (newColor != oldColor) { @@ -344,7 +344,7 @@ static void updateBorderColorsProps( inline static void updateNativeDrawableProp( folly::dynamic& result, - const std::string& propName, + const char* propName, const std::optional& nativeDrawable) { folly::dynamic nativeDrawableResult; if (nativeDrawable.has_value()) { @@ -392,7 +392,7 @@ inline static void updateNativeDrawableProp( nativeDrawableResult = folly::dynamic(nullptr); } - result[propName] = nativeDrawableResult; + result[propName] = std::move(nativeDrawableResult); } static folly::dynamic toDynamic(const AccessibilityState& state) { @@ -651,7 +651,6 @@ folly::dynamic HostPlatformViewProps::getDiffProps( if (getClipsContentToBounds() != oldProps->getClipsContentToBounds()) { result["overflow"] = getClipsContentToBounds() ? "hidden" : "visible"; - result["scroll"] = result["overflow"]; } if (backfaceVisibility != oldProps->backfaceVisibility) { @@ -873,8 +872,8 @@ folly::dynamic HostPlatformViewProps::getDiffProps( } // Transforms - if (transform != oldProps->transform || - transformOrigin != oldProps->transformOrigin) { + // Java re-applies the last transform when only the origin changes. + if (transform != oldProps->transform) { folly::dynamic resultTranslateArray = folly::dynamic::array(); for (const auto& operation : transform.operations) { updateTransformProps(transform, operation, resultTranslateArray); @@ -904,7 +903,8 @@ folly::dynamic HostPlatformViewProps::getDiffProps( accessibilityLabelledBy.value) { accessibilityLabelledByValues.push_back(accessibilityLabelledByValue); } - result["accessibilityLabelledBy"] = accessibilityLabelledByValues; + result["accessibilityLabelledBy"] = + std::move(accessibilityLabelledByValues); } if (accessibilityOrder != oldProps->accessibilityOrder) { @@ -912,7 +912,8 @@ folly::dynamic HostPlatformViewProps::getDiffProps( for (const auto& accessibilityChildId : accessibilityOrder) { accessibilityChildrenIds.push_back(accessibilityChildId); } - result["experimental_accessibilityOrder"] = accessibilityChildrenIds; + result["experimental_accessibilityOrder"] = + std::move(accessibilityChildrenIds); } if (accessibilityLiveRegion != oldProps->accessibilityLiveRegion) { @@ -949,7 +950,7 @@ folly::dynamic HostPlatformViewProps::getDiffProps( } result["accessibilityValue"] = accessibilityValueObject.empty() ? folly::dynamic(nullptr) - : accessibilityValueObject; + : std::move(accessibilityValueObject); } if (accessibilityActions != oldProps->accessibilityActions) { @@ -960,9 +961,9 @@ folly::dynamic HostPlatformViewProps::getDiffProps( if (accessibilityAction.label.has_value()) { accessibilityActionObject["label"] = accessibilityAction.label.value(); } - accessibilityActionsArray.push_back(accessibilityActionObject); + accessibilityActionsArray.push_back(std::move(accessibilityActionObject)); } - result["accessibilityActions"] = accessibilityActionsArray; + result["accessibilityActions"] = std::move(accessibilityActionsArray); } if (accessibilityViewIsModal != oldProps->accessibilityViewIsModal) { diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/PropsDiffingGateTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/PropsDiffingGateTest.cpp new file mode 100644 index 000000000000..db48270aea40 --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/PropsDiffingGateTest.cpp @@ -0,0 +1,99 @@ +/* + * 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 + +#ifdef RN_SERIALIZABLE_STATE + +namespace facebook::react { + +namespace { + +class PropsDiffingFlags : public ReactNativeFeatureFlagsDefaults { + public: + PropsDiffingFlags(bool allComponents, bool coreComponents) + : allComponents_(allComponents), coreComponents_(coreComponents) {} + + bool enablePropsUpdateReconciliationAndroid() override { + return allComponents_; + } + + bool enableCoreComponentsPropsUpdateReconciliationAndroid() override { + return coreComponents_; + } + + private: + bool allComponents_; + bool coreComponents_; +}; + +// Stands in for codegen props, which return their own component name. +class CodegenLikeProps final : public ViewProps { + public: + ComponentName getDiffPropsImplementationTarget() const override { + return "Switch"; + } +}; + +} // namespace + +class PropsDiffingGateTest : public ::testing::Test { + protected: + void TearDown() override { + ReactNativeFeatureFlags::dangerouslyReset(); + } + + static void setFlags(bool allComponents, bool coreComponents) { + ReactNativeFeatureFlags::dangerouslyReset(); + ReactNativeFeatureFlags::override( + std::make_unique(allComponents, coreComponents)); + } + + const ViewProps viewProps_{}; + const CodegenLikeProps codegenProps_{}; +}; + +TEST_F(PropsDiffingGateTest, disabledWhenNoFlagIsOn) { + setFlags(/*allComponents*/ false, /*coreComponents*/ false); + EXPECT_FALSE(isPropsDiffingEnabled("View", viewProps_)); + EXPECT_FALSE(isPropsDiffingEnabled("Switch", codegenProps_)); +} + +TEST_F(PropsDiffingGateTest, allComponentsFlagEnablesCoreAndCodegen) { + setFlags(/*allComponents*/ true, /*coreComponents*/ false); + EXPECT_TRUE(isPropsDiffingEnabled("View", viewProps_)); + EXPECT_TRUE(isPropsDiffingEnabled("Switch", codegenProps_)); +} + +TEST_F(PropsDiffingGateTest, coreComponentsFlagEnablesOnlyCoreComponents) { + setFlags(/*allComponents*/ false, /*coreComponents*/ true); + EXPECT_TRUE(isPropsDiffingEnabled("View", viewProps_)); + EXPECT_FALSE(isPropsDiffingEnabled("Switch", codegenProps_)); +} + +TEST_F(PropsDiffingGateTest, disabledWhenComponentDoesNotImplementDiffing) { + // A component whose props inherit the View implementation without + // overriding the target falls back to raw props. + setFlags(/*allComponents*/ true, /*coreComponents*/ true); + EXPECT_FALSE(isPropsDiffingEnabled("SafeAreaView", viewProps_)); +} + +TEST_F(PropsDiffingGateTest, viewPropsTargetIsACoreComponent) { + setFlags(/*allComponents*/ false, /*coreComponents*/ true); + EXPECT_TRUE(isPropsDiffingEnabled( + viewProps_.getDiffPropsImplementationTarget(), viewProps_)); +} + +} // namespace facebook::react + +#endif diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp index 7fe5a6dfdab3..186ebe6c8fd2 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/tests/ViewPropsDiffTest.cpp @@ -152,6 +152,37 @@ TEST(ViewPropsDiffTest, removingAccessibilityValueEmitsNull) { }); } +// BaseViewManager re-applies the stored transform when only the origin changes. +TEST(ViewPropsDiffTest, changingOnlyTransformOriginDoesNotResendTransform) { + auto origin = [](float x) { + TransformOrigin transformOrigin; + transformOrigin.xy[0] = ValueUnit(x, UnitType::Point); + return transformOrigin; + }; + ViewProps oldProps; + oldProps.transform = Transform::Translate(10, 0, 0); + oldProps.transformOrigin = origin(1); + ViewProps newProps; + newProps.transform = oldProps.transform; + newProps.transformOrigin = origin(2); + + auto diff = newProps.getDiffProps(&oldProps); + + EXPECT_EQ(diff.count("transformOrigin"), 1); + EXPECT_EQ(diff.count("transform"), 0); +} + +TEST(ViewPropsDiffTest, emitsOnlyTheChangedBorderRadiusCorner) { + ViewProps oldProps; + ViewProps newProps; + newProps.borderRadii.bottomRight = ValueUnit(4, UnitType::Point); + + auto diff = newProps.getDiffProps(&oldProps); + + ASSERT_EQ(diff.size(), 1); + EXPECT_DOUBLE_EQ(diff["borderBottomRightRadius"].asDouble(), 4); +} + } // namespace facebook::react #endif diff --git a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h index e11bf20fb3a1..7248441a536d 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h +++ b/packages/react-native/ReactCommon/react/renderer/core/ConcreteComponentDescriptor.h @@ -156,13 +156,9 @@ class ConcreteComponentDescriptor : public ComponentDescriptor { ReactNativeFeatureFlags_DO_NOT_USE::enableAccumulatedUpdatesInRawPropsAndroid()) { // When exclusive props update is enabled, we only apply Props 1.5 processing // (raw props merging) when Props 2.0 is not available. - if (ReactNativeFeatureFlags_DO_NOT_USE::enablePropsUpdateReconciliationAndroid()) { - // Cast to base Props reference to safely call virtual method - const auto &baseProps = static_cast(*shadowNodeProps); - if (strcmp(ShadowNodeT::Name(), baseProps.getDiffPropsImplementationTarget()) == 0) { - // Props 2.0 supported for this component, Props 1.5 processing can be skipped - fallbackToDynamicRawPropsAccumulation = false; - } + if (isPropsDiffingEnabled(ShadowNodeT::Name(), static_cast(*shadowNodeProps))) { + // Props 2.0 supported for this component, Props 1.5 processing can be skipped + fallbackToDynamicRawPropsAccumulation = false; } } if (fallbackToDynamicRawPropsAccumulation) { diff --git a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp index 3e8845e9c31d..f9680e04e0a1 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/Props.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/Props.cpp @@ -13,6 +13,11 @@ #include #include +#include +#include +#include +#include + namespace facebook::react { Props::Props( @@ -59,6 +64,39 @@ void Props::initializeDynamicProps( ComponentName Props::getDiffPropsImplementationTarget() const { return ""; } + +namespace { + +// Components whose hand-written props implement `getDiffProps`. Codegen +// components are excluded: their native defaults are not guaranteed to match +// the C++ defaults that props diffing relies on. +bool isCoreComponentWithPropsDiffing(std::string_view componentName) { + static constexpr std::array kCoreComponents = { + "View", + "Paragraph", + "Text", + "RawText", + "TextEffect", + "Image", + "ScrollView", + "AndroidTextInput", + }; + return std::find( + kCoreComponents.begin(), kCoreComponents.end(), componentName) != + kCoreComponents.end(); +} + +} // namespace + +bool isPropsDiffingEnabled(ComponentName componentName, const Props& props) { + bool isEnabledForComponent = + ReactNativeFeatureFlags::enablePropsUpdateReconciliationAndroid() || + (ReactNativeFeatureFlags:: + enableCoreComponentsPropsUpdateReconciliationAndroid() && + isCoreComponentWithPropsDiffing(componentName)); + return isEnabledForComponent && + std::strcmp(componentName, props.getDiffPropsImplementationTarget()) == 0; +} #endif #pragma mark - DebugStringConvertible diff --git a/packages/react-native/ReactCommon/react/renderer/core/Props.h b/packages/react-native/ReactCommon/react/renderer/core/Props.h index c717e3182a05..c0e354ba60a4 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/Props.h +++ b/packages/react-native/ReactCommon/react/renderer/core/Props.h @@ -86,6 +86,14 @@ class Props : public virtual Sealable, public virtual DebugStringConvertible { #endif }; +#ifdef RN_SERIALIZABLE_STATE +/* + * Whether mount instructions for `componentName` are built with + * `Props::getDiffProps` instead of raw props, given the enabled feature flags. + */ +bool isPropsDiffingEnabled(ComponentName componentName, const Props &props); +#endif + namespace detail { /* diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 612c196ff22d..e896a8cb0f23 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -256,6 +256,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'stable', }, + enableCoreComponentsPropsUpdateReconciliationAndroid: { + defaultValue: false, + metadata: { + dateAdded: '2026-10-26', + description: + 'When enabled, Android receives prop updates based on props diffing only for core components with hand-written props (View, Text, Image, ScrollView, TextInput). Other components keep receiving raw props unless `enablePropsUpdateReconciliationAndroid` is enabled.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'experimental', + }, enableCppPropsIteratorSetter: { dangerouslyExposeInPublicCppHeaders: true, defaultValue: false, diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index e5a8120cff62..13ca4b621462 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<<674f975884ae746e067e614523f2bfc3>> + * @generated SignedSource<<5a20b81ad6800e3a83cb4e9681e0f903>> * @flow strict * @noformat */ @@ -68,6 +68,7 @@ export type ReactNativeFeatureFlags = Readonly<{ enableAndroidTextMeasurementOptimizations: Getter, enableAsyncDiskCacheCheckInMultiSourceImageAndroid: Getter, enableBridgelessArchitecture: Getter, + enableCoreComponentsPropsUpdateReconciliationAndroid: Getter, enableCppPropsIteratorSetter: Getter, enableCustomFocusSearchOnClippedElementsAndroid: Getter, enableDestroyShadowTreeRevisionAsync: Getter, @@ -298,6 +299,10 @@ export const enableAsyncDiskCacheCheckInMultiSourceImageAndroid: Getter * Feature flag to enable the new bridgeless architecture. */ export const enableBridgelessArchitecture: Getter = createNativeFlagGetter('enableBridgelessArchitecture', true); +/** + * When enabled, Android receives prop updates based on props diffing only for core components with hand-written props (View, Text, Image, ScrollView, TextInput). Other components keep receiving raw props unless `enablePropsUpdateReconciliationAndroid` is enabled. + */ +export const enableCoreComponentsPropsUpdateReconciliationAndroid: Getter = createNativeFlagGetter('enableCoreComponentsPropsUpdateReconciliationAndroid', false); /** * Enable prop iterator setter-style construction of Props in C++ (this flag is not used in Java). */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 6fc752bba2a9..07c69a14ab94 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<<2eb3f4a3dca013c51ad2913944855010>> + * @generated SignedSource<<8a6f75a66050ab8cf760cb8b4920cb0d>> * @flow strict * @noformat */ @@ -44,6 +44,7 @@ export interface Spec extends TurboModule { readonly enableAndroidTextMeasurementOptimizations?: () => boolean; readonly enableAsyncDiskCacheCheckInMultiSourceImageAndroid?: () => boolean; readonly enableBridgelessArchitecture?: () => boolean; + readonly enableCoreComponentsPropsUpdateReconciliationAndroid?: () => boolean; readonly enableCppPropsIteratorSetter?: () => boolean; readonly enableCustomFocusSearchOnClippedElementsAndroid?: () => boolean; readonly enableDestroyShadowTreeRevisionAsync?: () => boolean; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 675923cec7c0..5ca1d3de5784 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -490,6 +490,7 @@ YGDirection facebook::react::yogaDirectionFromLayoutDirection(facebook::react::L bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; +bool facebook::react::isPropsDiffingEnabled(facebook::react::ComponentName componentName, const facebook::react::Props& props); bool facebook::react::isZero(facebook::react::Float n); constexpr auto facebook::react::to_underlying(auto e) noexcept; constexpr char facebook::react::toLower(char c); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api index 0d1f0fd94a91..86407003bb5a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidFrameworksCxx.api @@ -704,6 +704,7 @@ bool facebook::react::areTextAttributesEquivalentLayoutWise(const facebook::reac bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; bool facebook::react::isHermesBytecodeBundle(const facebook::react::BundleHeader& header); +bool facebook::react::isPropsDiffingEnabled(facebook::react::ComponentName componentName, const facebook::react::Props& props); bool facebook::react::isZero(facebook::react::Float n); const char* facebook::react::stringForScriptTag(const facebook::react::ScriptTag& tag); constexpr auto facebook::react::to_underlying(auto e) noexcept; diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index 4b15d66312e8..6605ed509dc0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -490,6 +490,7 @@ YGDirection facebook::react::yogaDirectionFromLayoutDirection(facebook::react::L bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; +bool facebook::react::isPropsDiffingEnabled(facebook::react::ComponentName componentName, const facebook::react::Props& props); bool facebook::react::isZero(facebook::react::Float n); constexpr auto facebook::react::to_underlying(auto e) noexcept; constexpr char facebook::react::toLower(char c); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 4b15d66312e8..6605ed509dc0 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -490,6 +490,7 @@ YGDirection facebook::react::yogaDirectionFromLayoutDirection(facebook::react::L bool facebook::react::areBorderRadiiCircular(const facebook::react::BorderRadii& borderRadii); bool facebook::react::hostPlatformColorIsColorMeaningful(facebook::react::Color color) noexcept; bool facebook::react::isColorMeaningful(const facebook::react::SharedColor& color) noexcept; +bool facebook::react::isPropsDiffingEnabled(facebook::react::ComponentName componentName, const facebook::react::Props& props); bool facebook::react::isZero(facebook::react::Float n); constexpr auto facebook::react::to_underlying(auto e) noexcept; constexpr char facebook::react::toLower(char c);