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 7391a3ee172..7be4cb067bb 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<<039798c50526ff1abf33bc7987aa78a9>> + * @generated SignedSource<<5144fb0350b71394206d614c68ef87f0>> */ /** @@ -390,6 +390,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun fixTextClippingAndroid15useBoundsForWidth(): Boolean = accessor.fixTextClippingAndroid15useBoundsForWidth() + /** + * Fix flex basis computation to not apply FitContent constraint in the main axis for non-measure container nodes, preventing unnecessary re-measurement in scroll containers. + */ + @JvmStatic + public fun fixYogaFlexBasisFitContentInMainAxis(): Boolean = accessor.fixYogaFlexBasisFitContentInMainAxis() + /** * Enable system assertion validating that Fusebox is configured with a single host. When set, the CDP backend will dynamically disable features (Perf and Network) in the event that multiple hosts are registered (undefined behaviour), and broadcast this over `ReactNativeApplication.systemStateChanged`. */ 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 b9bc0ad0024..a77a2ff90fe 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<> */ /** @@ -80,6 +80,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var fixFindShadowNodeByTagRaceConditionCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fixTextClippingAndroid15useBoundsForWidthCache: Boolean? = null + private var fixYogaFlexBasisFitContentInMainAxisCache: Boolean? = null private var fuseboxAssertSingleHostStateCache: Boolean? = null private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxFrameRecordingEnabledCache: Boolean? = null @@ -650,6 +651,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun fixYogaFlexBasisFitContentInMainAxis(): Boolean { + var cached = fixYogaFlexBasisFitContentInMainAxisCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.fixYogaFlexBasisFitContentInMainAxis() + fixYogaFlexBasisFitContentInMainAxisCache = cached + } + return cached + } + override fun fuseboxAssertSingleHostState(): Boolean { var cached = fuseboxAssertSingleHostStateCache 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 db739decb43..fa8758c0901 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<<3d472893c05bdee6e09f834f52d3d92f>> + * @generated SignedSource<> */ /** @@ -148,6 +148,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun fixTextClippingAndroid15useBoundsForWidth(): Boolean + @DoNotStrip @JvmStatic public external fun fixYogaFlexBasisFitContentInMainAxis(): Boolean + @DoNotStrip @JvmStatic public external fun fuseboxAssertSingleHostState(): Boolean @DoNotStrip @JvmStatic public external fun fuseboxEnabledRelease(): 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 bebba929b4f..e467f8cd732 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<<29f7031f91527f41f2f184596c542a5b>> + * @generated SignedSource<> */ /** @@ -143,6 +143,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun fixTextClippingAndroid15useBoundsForWidth(): Boolean = false + override fun fixYogaFlexBasisFitContentInMainAxis(): Boolean = false + override fun fuseboxAssertSingleHostState(): Boolean = true override fun fuseboxEnabledRelease(): Boolean = false diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsLocalAccessor.kt index 671ea69b550..99d211c64a3 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<<014ee4afdf58a61c9974df198068d81a>> + * @generated SignedSource<<7b87f5541ecf881d8ce51c5edd5b99b0>> */ /** @@ -84,6 +84,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var fixFindShadowNodeByTagRaceConditionCache: Boolean? = null private var fixMappingOfEventPrioritiesBetweenFabricAndReactCache: Boolean? = null private var fixTextClippingAndroid15useBoundsForWidthCache: Boolean? = null + private var fixYogaFlexBasisFitContentInMainAxisCache: Boolean? = null private var fuseboxAssertSingleHostStateCache: Boolean? = null private var fuseboxEnabledReleaseCache: Boolean? = null private var fuseboxFrameRecordingEnabledCache: Boolean? = null @@ -714,6 +715,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun fixYogaFlexBasisFitContentInMainAxis(): Boolean { + var cached = fixYogaFlexBasisFitContentInMainAxisCache + if (cached == null) { + cached = currentProvider.fixYogaFlexBasisFitContentInMainAxis() + accessedFeatureFlags.add("fixYogaFlexBasisFitContentInMainAxis") + fixYogaFlexBasisFitContentInMainAxisCache = cached + } + return cached + } + override fun fuseboxAssertSingleHostState(): Boolean { var cached = fuseboxAssertSingleHostStateCache if (cached == null) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlagsProvider.kt index 09e3822c039..de1d05f86ef 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<> */ /** @@ -143,6 +143,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun fixTextClippingAndroid15useBoundsForWidth(): Boolean + @DoNotStrip public fun fixYogaFlexBasisFitContentInMainAxis(): Boolean + @DoNotStrip public fun fuseboxAssertSingleHostState(): Boolean @DoNotStrip public fun fuseboxEnabledRelease(): Boolean diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaExperimentalFeature.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaExperimentalFeature.java index 3fabbb91729..32e643439e3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaExperimentalFeature.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/yoga/YogaExperimentalFeature.java @@ -10,7 +10,8 @@ package com.facebook.yoga; public enum YogaExperimentalFeature { - WEB_FLEX_BASIS(0); + WEB_FLEX_BASIS(0), + FIX_FLEX_BASIS_FIT_CONTENT(1); private final int mIntValue; @@ -25,6 +26,7 @@ public int intValue() { public static YogaExperimentalFeature fromInt(int value) { switch (value) { case 0: return WEB_FLEX_BASIS; + case 1: return FIX_FLEX_BASIS_FIT_CONTENT; default: throw new IllegalArgumentException("Unknown enum value: " + value); } } 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 e01b7e0f1ea..fe397f8b1e4 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<> + * @generated SignedSource<> */ /** @@ -399,6 +399,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool fixYogaFlexBasisFitContentInMainAxis() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fixYogaFlexBasisFitContentInMainAxis"); + return method(javaProvider_); + } + bool fuseboxAssertSingleHostState() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("fuseboxAssertSingleHostState"); @@ -877,6 +883,11 @@ bool JReactNativeFeatureFlagsCxxInterop::fixTextClippingAndroid15useBoundsForWid return ReactNativeFeatureFlags::fixTextClippingAndroid15useBoundsForWidth(); } +bool JReactNativeFeatureFlagsCxxInterop::fixYogaFlexBasisFitContentInMainAxis( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::fixYogaFlexBasisFitContentInMainAxis(); +} + bool JReactNativeFeatureFlagsCxxInterop::fuseboxAssertSingleHostState( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::fuseboxAssertSingleHostState(); @@ -1233,6 +1244,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "fixTextClippingAndroid15useBoundsForWidth", JReactNativeFeatureFlagsCxxInterop::fixTextClippingAndroid15useBoundsForWidth), + makeNativeMethod( + "fixYogaFlexBasisFitContentInMainAxis", + JReactNativeFeatureFlagsCxxInterop::fixYogaFlexBasisFitContentInMainAxis), makeNativeMethod( "fuseboxAssertSingleHostState", JReactNativeFeatureFlagsCxxInterop::fuseboxAssertSingleHostState), 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 3bc818cc1fb..08276eab5ed 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<<6ff305bf95e95cd8a65c138fc24b43f8>> + * @generated SignedSource<<5433b4a2f4a0574591a38017422edac8>> */ /** @@ -210,6 +210,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool fixTextClippingAndroid15useBoundsForWidth( facebook::jni::alias_ref); + static bool fixYogaFlexBasisFitContentInMainAxis( + facebook::jni::alias_ref); + static bool fuseboxAssertSingleHostState( facebook::jni::alias_ref); diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp index 30369eb9cda..4f058038abb 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<<903b6404deb3339d2cfeced5be354134>> + * @generated SignedSource<> */ /** @@ -266,6 +266,10 @@ bool ReactNativeFeatureFlags::fixTextClippingAndroid15useBoundsForWidth() { return getAccessor().fixTextClippingAndroid15useBoundsForWidth(); } +bool ReactNativeFeatureFlags::fixYogaFlexBasisFitContentInMainAxis() { + return getAccessor().fixYogaFlexBasisFitContentInMainAxis(); +} + bool ReactNativeFeatureFlags::fuseboxAssertSingleHostState() { return getAccessor().fuseboxAssertSingleHostState(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 69bc02bbdf1..7185d625c25 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<<5ae89f16fbc942bd1c857c85b0520a03>> + * @generated SignedSource<<4811a81c7839f2be5c8a127e6c8e310b>> */ /** @@ -339,6 +339,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool fixTextClippingAndroid15useBoundsForWidth(); + /** + * Fix flex basis computation to not apply FitContent constraint in the main axis for non-measure container nodes, preventing unnecessary re-measurement in scroll containers. + */ + RN_EXPORT static bool fixYogaFlexBasisFitContentInMainAxis(); + /** * Enable system assertion validating that Fusebox is configured with a single host. When set, the CDP backend will dynamically disable features (Perf and Network) in the event that multiple hosts are registered (undefined behaviour), and broadcast this over `ReactNativeApplication.systemStateChanged`. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index f50d3e9e26a..936e3c590d5 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<<803777ba8fd4207f5e466a546639a058>> + * @generated SignedSource<> */ /** @@ -1109,6 +1109,24 @@ bool ReactNativeFeatureFlagsAccessor::fixTextClippingAndroid15useBoundsForWidth( return flagValue.value(); } +bool ReactNativeFeatureFlagsAccessor::fixYogaFlexBasisFitContentInMainAxis() { + auto flagValue = fixYogaFlexBasisFitContentInMainAxis_.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(60, "fixYogaFlexBasisFitContentInMainAxis"); + + flagValue = currentProvider_->fixYogaFlexBasisFitContentInMainAxis(); + fixYogaFlexBasisFitContentInMainAxis_ = flagValue; + } + + return flagValue.value(); +} + bool ReactNativeFeatureFlagsAccessor::fuseboxAssertSingleHostState() { auto flagValue = fuseboxAssertSingleHostState_.load(); @@ -1118,7 +1136,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(60, "fuseboxAssertSingleHostState"); + markFlagAsAccessed(61, "fuseboxAssertSingleHostState"); flagValue = currentProvider_->fuseboxAssertSingleHostState(); fuseboxAssertSingleHostState_ = flagValue; @@ -1136,7 +1154,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(61, "fuseboxEnabledRelease"); + markFlagAsAccessed(62, "fuseboxEnabledRelease"); flagValue = currentProvider_->fuseboxEnabledRelease(); fuseboxEnabledRelease_ = flagValue; @@ -1154,7 +1172,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(62, "fuseboxFrameRecordingEnabled"); + markFlagAsAccessed(63, "fuseboxFrameRecordingEnabled"); flagValue = currentProvider_->fuseboxFrameRecordingEnabled(); fuseboxFrameRecordingEnabled_ = flagValue; @@ -1172,7 +1190,7 @@ bool ReactNativeFeatureFlagsAccessor::fuseboxNetworkInspectionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(63, "fuseboxNetworkInspectionEnabled"); + markFlagAsAccessed(64, "fuseboxNetworkInspectionEnabled"); flagValue = currentProvider_->fuseboxNetworkInspectionEnabled(); fuseboxNetworkInspectionEnabled_ = flagValue; @@ -1190,7 +1208,7 @@ bool ReactNativeFeatureFlagsAccessor::hideOffscreenVirtualViewsOnIOS() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(64, "hideOffscreenVirtualViewsOnIOS"); + markFlagAsAccessed(65, "hideOffscreenVirtualViewsOnIOS"); flagValue = currentProvider_->hideOffscreenVirtualViewsOnIOS(); hideOffscreenVirtualViewsOnIOS_ = flagValue; @@ -1208,7 +1226,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(65, "overrideBySynchronousMountPropsAtMountingAndroid"); + markFlagAsAccessed(66, "overrideBySynchronousMountPropsAtMountingAndroid"); flagValue = currentProvider_->overrideBySynchronousMountPropsAtMountingAndroid(); overrideBySynchronousMountPropsAtMountingAndroid_ = flagValue; @@ -1226,7 +1244,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(66, "perfIssuesEnabled"); + markFlagAsAccessed(67, "perfIssuesEnabled"); flagValue = currentProvider_->perfIssuesEnabled(); perfIssuesEnabled_ = flagValue; @@ -1244,7 +1262,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(67, "perfMonitorV2Enabled"); + markFlagAsAccessed(68, "perfMonitorV2Enabled"); flagValue = currentProvider_->perfMonitorV2Enabled(); perfMonitorV2Enabled_ = flagValue; @@ -1262,7 +1280,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(68, "preparedTextCacheSize"); + markFlagAsAccessed(69, "preparedTextCacheSize"); flagValue = currentProvider_->preparedTextCacheSize(); preparedTextCacheSize_ = flagValue; @@ -1280,7 +1298,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(69, "preventShadowTreeCommitExhaustion"); + markFlagAsAccessed(70, "preventShadowTreeCommitExhaustion"); flagValue = currentProvider_->preventShadowTreeCommitExhaustion(); preventShadowTreeCommitExhaustion_ = flagValue; @@ -1298,7 +1316,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(70, "shouldPressibilityUseW3CPointerEventsForHover"); + markFlagAsAccessed(71, "shouldPressibilityUseW3CPointerEventsForHover"); flagValue = currentProvider_->shouldPressibilityUseW3CPointerEventsForHover(); shouldPressibilityUseW3CPointerEventsForHover_ = flagValue; @@ -1316,7 +1334,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(71, "shouldTriggerResponderTransferOnScrollAndroid"); + markFlagAsAccessed(72, "shouldTriggerResponderTransferOnScrollAndroid"); flagValue = currentProvider_->shouldTriggerResponderTransferOnScrollAndroid(); shouldTriggerResponderTransferOnScrollAndroid_ = flagValue; @@ -1334,7 +1352,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(72, "skipActivityIdentityAssertionOnHostPause"); + markFlagAsAccessed(73, "skipActivityIdentityAssertionOnHostPause"); flagValue = currentProvider_->skipActivityIdentityAssertionOnHostPause(); skipActivityIdentityAssertionOnHostPause_ = flagValue; @@ -1352,7 +1370,7 @@ bool ReactNativeFeatureFlagsAccessor::syncAndroidClipToPaddingWithOverflow() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "syncAndroidClipToPaddingWithOverflow"); + markFlagAsAccessed(74, "syncAndroidClipToPaddingWithOverflow"); flagValue = currentProvider_->syncAndroidClipToPaddingWithOverflow(); syncAndroidClipToPaddingWithOverflow_ = flagValue; @@ -1370,7 +1388,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(74, "traceTurboModulePromiseRejectionsOnAndroid"); + markFlagAsAccessed(75, "traceTurboModulePromiseRejectionsOnAndroid"); flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid(); traceTurboModulePromiseRejectionsOnAndroid_ = flagValue; @@ -1388,7 +1406,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(75, "updateRuntimeShadowNodeReferencesOnCommit"); + markFlagAsAccessed(76, "updateRuntimeShadowNodeReferencesOnCommit"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit(); updateRuntimeShadowNodeReferencesOnCommit_ = flagValue; @@ -1406,7 +1424,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(76, "updateRuntimeShadowNodeReferencesOnCommitThread"); + markFlagAsAccessed(77, "updateRuntimeShadowNodeReferencesOnCommitThread"); flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommitThread(); updateRuntimeShadowNodeReferencesOnCommitThread_ = flagValue; @@ -1424,7 +1442,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(77, "useAlwaysAvailableJSErrorHandling"); + markFlagAsAccessed(78, "useAlwaysAvailableJSErrorHandling"); flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling(); useAlwaysAvailableJSErrorHandling_ = flagValue; @@ -1442,7 +1460,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(78, "useFabricInterop"); + markFlagAsAccessed(79, "useFabricInterop"); flagValue = currentProvider_->useFabricInterop(); useFabricInterop_ = flagValue; @@ -1460,7 +1478,7 @@ bool ReactNativeFeatureFlagsAccessor::useNativeViewConfigsInBridgelessMode() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(79, "useNativeViewConfigsInBridgelessMode"); + markFlagAsAccessed(80, "useNativeViewConfigsInBridgelessMode"); flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode(); useNativeViewConfigsInBridgelessMode_ = flagValue; @@ -1478,7 +1496,7 @@ bool ReactNativeFeatureFlagsAccessor::useNestedScrollViewAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(80, "useNestedScrollViewAndroid"); + markFlagAsAccessed(81, "useNestedScrollViewAndroid"); flagValue = currentProvider_->useNestedScrollViewAndroid(); useNestedScrollViewAndroid_ = flagValue; @@ -1496,7 +1514,7 @@ bool ReactNativeFeatureFlagsAccessor::useSharedAnimatedBackend() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(81, "useSharedAnimatedBackend"); + markFlagAsAccessed(82, "useSharedAnimatedBackend"); flagValue = currentProvider_->useSharedAnimatedBackend(); useSharedAnimatedBackend_ = flagValue; @@ -1514,7 +1532,7 @@ bool ReactNativeFeatureFlagsAccessor::useTraitHiddenOnAndroid() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(82, "useTraitHiddenOnAndroid"); + markFlagAsAccessed(83, "useTraitHiddenOnAndroid"); flagValue = currentProvider_->useTraitHiddenOnAndroid(); useTraitHiddenOnAndroid_ = flagValue; @@ -1532,7 +1550,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModuleInterop() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(83, "useTurboModuleInterop"); + markFlagAsAccessed(84, "useTurboModuleInterop"); flagValue = currentProvider_->useTurboModuleInterop(); useTurboModuleInterop_ = flagValue; @@ -1550,7 +1568,7 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(84, "useTurboModules"); + markFlagAsAccessed(85, "useTurboModules"); flagValue = currentProvider_->useTurboModules(); useTurboModules_ = flagValue; @@ -1568,7 +1586,7 @@ bool ReactNativeFeatureFlagsAccessor::useUnorderedMapInDifferentiator() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(85, "useUnorderedMapInDifferentiator"); + markFlagAsAccessed(86, "useUnorderedMapInDifferentiator"); flagValue = currentProvider_->useUnorderedMapInDifferentiator(); useUnorderedMapInDifferentiator_ = flagValue; @@ -1586,7 +1604,7 @@ double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(86, "viewCullingOutsetRatio"); + markFlagAsAccessed(87, "viewCullingOutsetRatio"); flagValue = currentProvider_->viewCullingOutsetRatio(); viewCullingOutsetRatio_ = flagValue; @@ -1604,7 +1622,7 @@ bool ReactNativeFeatureFlagsAccessor::viewTransitionEnabled() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(87, "viewTransitionEnabled"); + markFlagAsAccessed(88, "viewTransitionEnabled"); flagValue = currentProvider_->viewTransitionEnabled(); viewTransitionEnabled_ = flagValue; @@ -1622,7 +1640,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(88, "virtualViewPrerenderRatio"); + markFlagAsAccessed(89, "virtualViewPrerenderRatio"); flagValue = currentProvider_->virtualViewPrerenderRatio(); virtualViewPrerenderRatio_ = flagValue; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index aa8d4677c75..e76f1322f58 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<<1f2d789f0d84636a86e3cc194411cf3d>> + * @generated SignedSource<<06dba77b9b76d06d5c338ed8a97e33f5>> */ /** @@ -92,6 +92,7 @@ class ReactNativeFeatureFlagsAccessor { bool fixFindShadowNodeByTagRaceCondition(); bool fixMappingOfEventPrioritiesBetweenFabricAndReact(); bool fixTextClippingAndroid15useBoundsForWidth(); + bool fixYogaFlexBasisFitContentInMainAxis(); bool fuseboxAssertSingleHostState(); bool fuseboxEnabledRelease(); bool fuseboxFrameRecordingEnabled(); @@ -132,7 +133,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 89> accessedFeatureFlags_; + std::array, 90> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -194,6 +195,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> fixFindShadowNodeByTagRaceCondition_; std::atomic> fixMappingOfEventPrioritiesBetweenFabricAndReact_; std::atomic> fixTextClippingAndroid15useBoundsForWidth_; + std::atomic> fixYogaFlexBasisFitContentInMainAxis_; std::atomic> fuseboxAssertSingleHostState_; std::atomic> fuseboxEnabledRelease_; std::atomic> fuseboxFrameRecordingEnabled_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 82b98c44455..1a6289888d1 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<<29c29b34ee3aefc5d904a1e26ed6f137>> + * @generated SignedSource<<0934d867533630904fc69e30e7a929b3>> */ /** @@ -267,6 +267,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool fixYogaFlexBasisFitContentInMainAxis() override { + return false; + } + bool fuseboxAssertSingleHostState() override { return true; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index a72a4ddce90..5c939775920 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<<25d1f9cb509dbd8274e3a00237d2ea62>> */ /** @@ -585,6 +585,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::fixTextClippingAndroid15useBoundsForWidth(); } + bool fixYogaFlexBasisFitContentInMainAxis() override { + auto value = values_["fixYogaFlexBasisFitContentInMainAxis"]; + if (!value.isNull()) { + return value.getBool(); + } + + return ReactNativeFeatureFlagsDefaults::fixYogaFlexBasisFitContentInMainAxis(); + } + bool fuseboxAssertSingleHostState() override { auto value = values_["fuseboxAssertSingleHostState"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 54e5ef383ef..cc3fbc19b88 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -85,6 +85,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool fixFindShadowNodeByTagRaceCondition() = 0; virtual bool fixMappingOfEventPrioritiesBetweenFabricAndReact() = 0; virtual bool fixTextClippingAndroid15useBoundsForWidth() = 0; + virtual bool fixYogaFlexBasisFitContentInMainAxis() = 0; virtual bool fuseboxAssertSingleHostState() = 0; virtual bool fuseboxEnabledRelease() = 0; virtual bool fuseboxFrameRecordingEnabled() = 0; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 4aced87344f..1fbd4198e5d 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<<0b3534a570416860aa1ffc7e1d808090>> */ /** @@ -344,6 +344,11 @@ bool NativeReactNativeFeatureFlags::fixTextClippingAndroid15useBoundsForWidth( return ReactNativeFeatureFlags::fixTextClippingAndroid15useBoundsForWidth(); } +bool NativeReactNativeFeatureFlags::fixYogaFlexBasisFitContentInMainAxis( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::fixYogaFlexBasisFitContentInMainAxis(); +} + bool NativeReactNativeFeatureFlags::fuseboxAssertSingleHostState( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::fuseboxAssertSingleHostState(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index f0309222e82..081db38ca2f 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<<7db04fd2e9b9ea200fe1ef894ab33727>> + * @generated SignedSource<<0aeea7c4fa2a8aa4180c83bbd0746250>> */ /** @@ -156,6 +156,8 @@ class NativeReactNativeFeatureFlags bool fixTextClippingAndroid15useBoundsForWidth(jsi::Runtime& runtime); + bool fixYogaFlexBasisFitContentInMainAxis(jsi::Runtime& runtime); + bool fuseboxAssertSingleHostState(jsi::Runtime& runtime); bool fuseboxEnabledRelease(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp index bd327efc351..c630cb42018 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/view/YogaLayoutableShadowNode.cpp @@ -899,6 +899,11 @@ yoga::Config& YogaLayoutableShadowNode::initializeYogaConfig( YGConfigSetErrata(&config, YGConfigGetErrata(previousConfig)); } + if (ReactNativeFeatureFlags::fixYogaFlexBasisFitContentInMainAxis()) { + YGConfigSetExperimentalFeatureEnabled( + &config, YGExperimentalFeatureFixFlexBasisFitContent, true); + } + return config; } diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp index 9fc4a83a82d..538b0b0847e 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.cpp @@ -129,6 +129,8 @@ const char* YGExperimentalFeatureToString(const YGExperimentalFeature value) { switch (value) { case YGExperimentalFeatureWebFlexBasis: return "web-flex-basis"; + case YGExperimentalFeatureFixFlexBasisFitContent: + return "fix-flex-basis-fit-content"; } return "unknown"; } diff --git a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h index 1b69f093186..aa0b1d4350d 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h +++ b/packages/react-native/ReactCommon/yoga/yoga/YGEnums.h @@ -73,7 +73,8 @@ YG_DEFINE_ENUM_FLAG_OPERATORS(YGErrata) YG_ENUM_DECL( YGExperimentalFeature, - YGExperimentalFeatureWebFlexBasis) + YGExperimentalFeatureWebFlexBasis, + YGExperimentalFeatureFixFlexBasisFitContent) YG_ENUM_DECL( YGFlexDirection, diff --git a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp index 0a7224cc0b5..d25435071bf 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp +++ b/packages/react-native/ReactCommon/yoga/yoga/algorithm/CalculateLayout.cpp @@ -94,7 +94,18 @@ static void computeFlexBasisForChild( const bool isColumnStyleDimDefined = child->hasDefiniteLength(Dimension::Height, ownerHeight); - if (resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize)) { + const bool fixFlexBasisFitContent = + node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent); + + bool useResolvedFlexBasis = + resolvedFlexBasis.isDefined() && yoga::isDefined(mainAxisSize); + if (fixFlexBasisFitContent && resolvedFlexBasis.isDefined() && + resolvedFlexBasis.unwrap() > 0) { + useResolvedFlexBasis = true; + } + + if (useResolvedFlexBasis) { if (child->getLayout().computedFlexBasis.isUndefined() || (child->getConfig()->isExperimentalFeatureEnabled( ExperimentalFeature::WebFlexBasis) && @@ -164,12 +175,27 @@ static void computeFlexBasisForChild( } } - if ((isMainAxisRow && node->style().overflow() == Overflow::Scroll) || - node->style().overflow() != Overflow::Scroll) { - if (yoga::isUndefined(childHeight) && yoga::isDefined(height)) { - childHeight = height; - childHeightSizingMode = SizingMode::FitContent; - } + // For height in the main axis (column direction): when the + // FixFlexBasisFitContent feature is enabled, skip FitContent for + // non-measure container children. This makes the flex basis independent + // of the parent's content-determined height, preventing unnecessary + // re-measurement cascades when a sibling changes size in a ScrollView. + // + // We only optimize the height (column) axis because text wrapping depends + // on width constraints propagating through container nodes. Removing + // FitContent from the width axis would cause text inside nested + // containers to stop wrapping. + bool applyHeightFitContent = + isMainAxisRow || node->style().overflow() != Overflow::Scroll; + if (fixFlexBasisFitContent) { + applyHeightFitContent = isMainAxisRow || + (child->hasMeasureFunc() && + node->style().overflow() != Overflow::Scroll); + } + if (applyHeightFitContent && yoga::isUndefined(childHeight) && + yoga::isDefined(height)) { + childHeight = height; + childHeightSizingMode = SizingMode::FitContent; } const auto& childStyle = child->style(); @@ -537,6 +563,8 @@ static float computeFlexBasisForChildren( yoga::Node* const node, const float availableInnerWidth, const float availableInnerHeight, + const float ownerWidth, + const float ownerHeight, SizingMode widthSizingMode, SizingMode heightSizingMode, Direction direction, @@ -598,8 +626,8 @@ static float computeFlexBasisForChildren( availableInnerWidth, widthSizingMode, availableInnerHeight, - availableInnerWidth, - availableInnerHeight, + ownerWidth, + ownerHeight, heightSizingMode, direction, layoutMarkerData, @@ -1429,12 +1457,53 @@ static void calculateLayoutImpl( // STEP 3: DETERMINE FLEX BASIS FOR EACH ITEM + // When this node is measured with MaxContent (FixFlexBasisFitContent + // behavior), availableInnerHeight is NaN. + // To preserve percentage resolution for descendants, derive a definite + // owner-size from the parent-provided ownerHeight. + float ownerWidthForChildren = availableInnerWidth; + float ownerHeightForChildren = availableInnerHeight; + + if (node->getConfig()->isExperimentalFeatureEnabled( + ExperimentalFeature::FixFlexBasisFitContent)) { + const auto* owner = node->getOwner(); + const bool isChildOfScrollContainer = + owner != nullptr && owner->style().overflow() == Overflow::Scroll; + + if (!isChildOfScrollContainer) { + if (yoga::isUndefined(ownerWidthForChildren) && + yoga::isDefined(ownerWidth)) { + ownerWidthForChildren = calculateAvailableInnerDimension( + node, + direction, + Dimension::Width, + ownerWidth - marginAxisRow, + paddingAndBorderAxisRow, + ownerWidth, + ownerWidth); + } + if (yoga::isUndefined(ownerHeightForChildren) && + yoga::isDefined(ownerHeight)) { + ownerHeightForChildren = calculateAvailableInnerDimension( + node, + direction, + Dimension::Height, + ownerHeight - marginAxisColumn, + paddingAndBorderAxisColumn, + ownerHeight, + ownerWidth); + } + } + } + // Computed basis + margins + gap float totalMainDim = 0; totalMainDim += computeFlexBasisForChildren( node, availableInnerWidth, availableInnerHeight, + ownerWidthForChildren, + ownerHeightForChildren, widthSizingMode, heightSizingMode, direction, diff --git a/packages/react-native/ReactCommon/yoga/yoga/enums/ExperimentalFeature.h b/packages/react-native/ReactCommon/yoga/yoga/enums/ExperimentalFeature.h index bbbf9cda431..7e6c5eb8b6c 100644 --- a/packages/react-native/ReactCommon/yoga/yoga/enums/ExperimentalFeature.h +++ b/packages/react-native/ReactCommon/yoga/yoga/enums/ExperimentalFeature.h @@ -17,11 +17,12 @@ namespace facebook::yoga { enum class ExperimentalFeature : uint8_t { WebFlexBasis = YGExperimentalFeatureWebFlexBasis, + FixFlexBasisFitContent = YGExperimentalFeatureFixFlexBasisFitContent, }; template <> constexpr int32_t ordinalCount() { - return 1; + return 2; } constexpr ExperimentalFeature scopedEnum(YGExperimentalFeature unscoped) { diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index c77e270bd3c..1d6ffadb4af 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -689,6 +689,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'experimental', }, + fixYogaFlexBasisFitContentInMainAxis: { + defaultValue: false, + metadata: { + dateAdded: '2026-03-09', + description: + 'Fix flex basis computation to not apply FitContent constraint in the main axis for non-measure container nodes, preventing unnecessary re-measurement in scroll containers.', + expectedReleaseValue: true, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, fuseboxAssertSingleHostState: { defaultValue: true, metadata: { diff --git a/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeRevisionGetter-itest.js b/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeRevisionGetter-itest.js index f5bd1e3bfbd..7d2e6aa2d76 100644 --- a/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeRevisionGetter-itest.js +++ b/packages/react-native/src/private/__tests__/utilities/__tests__/ShadowNodeRevisionGetter-itest.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. * - * @fantom_flags enableFabricCommitBranching:* + * @fantom_flags enableFabricCommitBranching:* fixYogaFlexBasisFitContentInMainAxis:* * @flow strict-local * @format */ @@ -15,6 +15,7 @@ import {createShadowNodeReferenceGetterRef} from '../ShadowNodeRevisionGetter'; import * as Fantom from '@react-native/fantom'; import * as React from 'react'; import {ScrollView, View} from 'react-native'; +import * as ReactNativeFeatureFlags from 'react-native/src/private/featureflags/ReactNativeFeatureFlags'; test('base case when cloning results in revision +1', () => { const root = Fantom.createRoot(); @@ -34,7 +35,7 @@ test('base case when cloning results in revision +1', () => { expect(getRevision()).toBe(2); }); -test('changing height of the top item in ScrollView results in excessive cloning', () => { +test('changing height of sibling in ScrollView does not clone unrelated descendants', () => { const root = Fantom.createRoot(); const [getRevision, ref] = createShadowNodeReferenceGetterRef(); @@ -70,6 +71,9 @@ test('changing height of the top item in ScrollView results in excessive cloning ); }); - // TODO(T225268793): the below assertion should be: `expect(getRevision()).toBe(1);` - expect(getRevision()).toBe(2); + if (ReactNativeFeatureFlags.fixYogaFlexBasisFitContentInMainAxis()) { + expect(getRevision()).toBe(1); + } else { + expect(getRevision()).toBe(2); + } }); diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 35e658eb926..3deb4d23822 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<<44a2abb1927d4db27052329f6544fefc>> + * @generated SignedSource<<5966ef11ee71a38059decda1c529fd6f>> * @flow strict * @noformat */ @@ -108,6 +108,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ fixFindShadowNodeByTagRaceCondition: Getter, fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter, fixTextClippingAndroid15useBoundsForWidth: Getter, + fixYogaFlexBasisFitContentInMainAxis: Getter, fuseboxAssertSingleHostState: Getter, fuseboxEnabledRelease: Getter, fuseboxFrameRecordingEnabled: Getter, @@ -448,6 +449,10 @@ export const fixMappingOfEventPrioritiesBetweenFabricAndReact: Getter = * Fix text clipping starting in Android 15 due to usage of useBoundsForWidth */ export const fixTextClippingAndroid15useBoundsForWidth: Getter = createNativeFlagGetter('fixTextClippingAndroid15useBoundsForWidth', false); +/** + * Fix flex basis computation to not apply FitContent constraint in the main axis for non-measure container nodes, preventing unnecessary re-measurement in scroll containers. + */ +export const fixYogaFlexBasisFitContentInMainAxis: Getter = createNativeFlagGetter('fixYogaFlexBasisFitContentInMainAxis', false); /** * Enable system assertion validating that Fusebox is configured with a single host. When set, the CDP backend will dynamically disable features (Perf and Network) in the event that multiple hosts are registered (undefined behaviour), and broadcast this over `ReactNativeApplication.systemStateChanged`. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 434ade599e7..ea68b2cf9eb 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<<58c114ad13dad3296a563952cc6365f8>> + * @generated SignedSource<<85ddaee522fc3be8546aef21cf236e9a>> * @flow strict * @noformat */ @@ -85,6 +85,7 @@ export interface Spec extends TurboModule { +fixFindShadowNodeByTagRaceCondition?: () => boolean; +fixMappingOfEventPrioritiesBetweenFabricAndReact?: () => boolean; +fixTextClippingAndroid15useBoundsForWidth?: () => boolean; + +fixYogaFlexBasisFitContentInMainAxis?: () => boolean; +fuseboxAssertSingleHostState?: () => boolean; +fuseboxEnabledRelease?: () => boolean; +fuseboxFrameRecordingEnabled?: () => boolean;