From 54bfd5c98e5b0dcd6f633d5947bcea90209a252e Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sun, 28 Sep 2025 13:15:57 -0700 Subject: [PATCH] Add support for global view culling outset ratio setting (#53845) Summary: Adding a feature flag controlling the outset ratio set on the culling context frame. This feature flag value will add an outset on the width and height calculated by multiplying these with the provided ratio value. This will offset each side of the frame, maintaining the center of the frame at the same location. This outset ratio helps mitigate async scroll blanking and focus handling on host platforms. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D82684027 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 ++++++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +++++++++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +++- .../ReactNativeFeatureFlagsDefaults.kt | 4 +++- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +++++++++- .../ReactNativeFeatureFlagsProvider.kt | 4 +++- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++++++++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +++- .../featureflags/ReactNativeFeatureFlags.cpp | 6 ++++- .../featureflags/ReactNativeFeatureFlags.h | 7 +++++- .../ReactNativeFeatureFlagsAccessor.cpp | 24 ++++++++++++++++--- .../ReactNativeFeatureFlagsAccessor.h | 6 +++-- .../ReactNativeFeatureFlagsDefaults.h | 6 ++++- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 ++++++++- .../ReactNativeFeatureFlagsProvider.h | 3 ++- .../NativeReactNativeFeatureFlags.cpp | 7 +++++- .../NativeReactNativeFeatureFlags.h | 4 +++- .../mounting/internal/CullingContext.cpp | 14 +++++++++++ .../ReactNativeFeatureFlags.config.js | 11 +++++++++ .../featureflags/ReactNativeFeatureFlags.js | 7 +++++- .../specs/NativeReactNativeFeatureFlags.js | 3 ++- 21 files changed, 153 insertions(+), 22 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/featureflags/ReactNativeFeatureFlags.kt index eb07acd5df2386..abfbf15920c3bb 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<<6b632ac7553ae149fa42a67efde5acfa>> + * @generated SignedSource<> */ /** @@ -468,6 +468,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useTurboModules(): Boolean = accessor.useTurboModules() + /** + * Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom. + */ + @JvmStatic + public fun viewCullingOutsetRatio(): Double = accessor.viewCullingOutsetRatio() + /** * Sets a hysteresis window for transition between prerender and hidden modes. */ 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 91a6990138d11f..65796a5915ee1d 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<> */ /** @@ -93,6 +93,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useShadowNodeStateOnCloneCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null + private var viewCullingOutsetRatioCache: Double? = null private var virtualViewHysteresisRatioCache: Double? = null private var virtualViewPrerenderRatioCache: Double? = null @@ -753,6 +754,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun viewCullingOutsetRatio(): Double { + var cached = viewCullingOutsetRatioCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.viewCullingOutsetRatio() + viewCullingOutsetRatioCache = cached + } + return cached + } + override fun virtualViewHysteresisRatio(): Double { var cached = virtualViewHysteresisRatioCache 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 98d05d177e36dd..339184e78d6825 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<<26dce1945df5641ab84cbca9eaf2b10f>> + * @generated SignedSource<<3254fef626b10ef21d8d9ee1bdbb1880>> */ /** @@ -174,6 +174,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useTurboModules(): Boolean + @DoNotStrip @JvmStatic public external fun viewCullingOutsetRatio(): Double + @DoNotStrip @JvmStatic public external fun virtualViewHysteresisRatio(): Double @DoNotStrip @JvmStatic public external fun virtualViewPrerenderRatio(): Double 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 d08bd81b443420..21f217aa1a0cf7 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<<11051ece1b61fd4bf4ca003a3b7fc4f9>> + * @generated SignedSource<<9f7eb1bb5e24fd8ee87accf60209cce3>> */ /** @@ -169,6 +169,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useTurboModules(): Boolean = false + override fun viewCullingOutsetRatio(): Double = 0.0 + override fun virtualViewHysteresisRatio(): Double = 0.0 override fun virtualViewPrerenderRatio(): Double = 5.0 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 eb575e5159bc20..b3a0d648332bf5 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<<66fa583c37021750123a483ab0ccb030>> + * @generated SignedSource<<9e6e04ca37edd1ad9265b74e251ff4de>> */ /** @@ -97,6 +97,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useShadowNodeStateOnCloneCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null + private var viewCullingOutsetRatioCache: Double? = null private var virtualViewHysteresisRatioCache: Double? = null private var virtualViewPrerenderRatioCache: Double? = null @@ -830,6 +831,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun viewCullingOutsetRatio(): Double { + var cached = viewCullingOutsetRatioCache + if (cached == null) { + cached = currentProvider.viewCullingOutsetRatio() + accessedFeatureFlags.add("viewCullingOutsetRatio") + viewCullingOutsetRatioCache = cached + } + return cached + } + override fun virtualViewHysteresisRatio(): Double { var cached = virtualViewHysteresisRatioCache 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 06e0872a916529..48c2b385d8c5df 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<<65e895433fc609bd7c2b5d7faa507f46>> */ /** @@ -169,6 +169,8 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useTurboModules(): Boolean + @DoNotStrip public fun viewCullingOutsetRatio(): Double + @DoNotStrip public fun virtualViewHysteresisRatio(): Double @DoNotStrip public fun virtualViewPrerenderRatio(): Double 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 8170bb1fcd34c6..a7c8a8f100b105 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<<8e1821e9a153a4b725890cb4a7f262ee>> + * @generated SignedSource<<7a0a26494846d6b4881bea01beabb9d6>> */ /** @@ -477,6 +477,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + double viewCullingOutsetRatio() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("viewCullingOutsetRatio"); + return method(javaProvider_); + } + double virtualViewHysteresisRatio() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("virtualViewHysteresisRatio"); @@ -858,6 +864,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useTurboModules( return ReactNativeFeatureFlags::useTurboModules(); } +double JReactNativeFeatureFlagsCxxInterop::viewCullingOutsetRatio( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::viewCullingOutsetRatio(); +} + double JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::virtualViewHysteresisRatio(); @@ -1118,6 +1129,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useTurboModules", JReactNativeFeatureFlagsCxxInterop::useTurboModules), + makeNativeMethod( + "viewCullingOutsetRatio", + JReactNativeFeatureFlagsCxxInterop::viewCullingOutsetRatio), makeNativeMethod( "virtualViewHysteresisRatio", JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio), 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 9670480569e55f..829762419d1732 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<<5be2ec52bda638a1eac837c402149b9f>> + * @generated SignedSource<> */ /** @@ -249,6 +249,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useTurboModules( facebook::jni::alias_ref); + static double viewCullingOutsetRatio( + facebook::jni::alias_ref); + static double virtualViewHysteresisRatio( 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 4473f6df66a974..58db55d5ea4890 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -318,6 +318,10 @@ bool ReactNativeFeatureFlags::useTurboModules() { return getAccessor().useTurboModules(); } +double ReactNativeFeatureFlags::viewCullingOutsetRatio() { + return getAccessor().viewCullingOutsetRatio(); +} + double ReactNativeFeatureFlags::virtualViewHysteresisRatio() { return getAccessor().virtualViewHysteresisRatio(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 2a8d97a0b9e6db..581252fd33e94f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<559a8be87c24238e70fceded8ac962a0>> */ /** @@ -404,6 +404,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useTurboModules(); + /** + * Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom. + */ + RN_EXPORT static double viewCullingOutsetRatio(); + /** * Sets a hysteresis window for transition between prerender and hidden modes. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 8263cb0cf18306..1b108ff32fc673 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<<908baa1dbc4608be4b6407ffd670c066>> + * @generated SignedSource<<379732a049a8539a1cde814996ff4791>> */ /** @@ -1343,6 +1343,24 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { return flagValue.value(); } +double ReactNativeFeatureFlagsAccessor::viewCullingOutsetRatio() { + auto flagValue = viewCullingOutsetRatio_.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(73, "viewCullingOutsetRatio"); + + flagValue = currentProvider_->viewCullingOutsetRatio(); + viewCullingOutsetRatio_ = flagValue; + } + + return flagValue.value(); +} + double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { auto flagValue = virtualViewHysteresisRatio_.load(); @@ -1352,7 +1370,7 @@ double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { // be accessing the provider multiple times but the end state of this // instance and the returned flag value would be the same. - markFlagAsAccessed(73, "virtualViewHysteresisRatio"); + markFlagAsAccessed(74, "virtualViewHysteresisRatio"); flagValue = currentProvider_->virtualViewHysteresisRatio(); virtualViewHysteresisRatio_ = flagValue; @@ -1370,7 +1388,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(74, "virtualViewPrerenderRatio"); + markFlagAsAccessed(75, "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 ede7746b6dc2b0..1dd1f037dd38af 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> */ /** @@ -105,6 +105,7 @@ class ReactNativeFeatureFlagsAccessor { bool useShadowNodeStateOnClone(); bool useTurboModuleInterop(); bool useTurboModules(); + double viewCullingOutsetRatio(); double virtualViewHysteresisRatio(); double virtualViewPrerenderRatio(); @@ -118,7 +119,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 75> accessedFeatureFlags_; + std::array, 76> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -193,6 +194,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useShadowNodeStateOnClone_; std::atomic> useTurboModuleInterop_; std::atomic> useTurboModules_; + std::atomic> viewCullingOutsetRatio_; std::atomic> virtualViewHysteresisRatio_; std::atomic> virtualViewPrerenderRatio_; }; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2b407600c30d4b..832f87dfcc7ad6 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<<5e6ee2118acc15edb9da813bb43978f3>> + * @generated SignedSource<> */ /** @@ -319,6 +319,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + double viewCullingOutsetRatio() override { + return 0.0; + } + double virtualViewHysteresisRatio() override { return 0.0; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 126f07e6bd44c4..e02953c8afa28c 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<<00b4d80631374e0714c8aa9f65060220>> */ /** @@ -702,6 +702,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useTurboModules(); } + double viewCullingOutsetRatio() override { + auto value = values_["viewCullingOutsetRatio"]; + if (!value.isNull()) { + return value.getDouble(); + } + + return ReactNativeFeatureFlagsDefaults::viewCullingOutsetRatio(); + } + double virtualViewHysteresisRatio() override { auto value = values_["virtualViewHysteresisRatio"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index b8c7ca07366f3b..de16d29eac4260 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<<4d141c52bc66225656194eeb8786f475>> + * @generated SignedSource<> */ /** @@ -98,6 +98,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useShadowNodeStateOnClone() = 0; virtual bool useTurboModuleInterop() = 0; virtual bool useTurboModules() = 0; + virtual double viewCullingOutsetRatio() = 0; virtual double virtualViewHysteresisRatio() = 0; virtual double virtualViewPrerenderRatio() = 0; }; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 761cb2a2ea44da..beb3ea906aecd3 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<<27c7bc7a528da06ffe2bcea48211f6bd>> */ /** @@ -409,6 +409,11 @@ bool NativeReactNativeFeatureFlags::useTurboModules( return ReactNativeFeatureFlags::useTurboModules(); } +double NativeReactNativeFeatureFlags::viewCullingOutsetRatio( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::viewCullingOutsetRatio(); +} + double NativeReactNativeFeatureFlags::virtualViewHysteresisRatio( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::virtualViewHysteresisRatio(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 5a064f078cc5f8..59c6518e663620 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<<2dcd8b3add6f1fdc1bf378714deb41d7>> + * @generated SignedSource<<39140c882a6faa0403d3a59806c6bea5>> */ /** @@ -182,6 +182,8 @@ class NativeReactNativeFeatureFlags bool useTurboModules(jsi::Runtime& runtime); + double viewCullingOutsetRatio(jsi::Runtime& runtime); + double virtualViewHysteresisRatio(jsi::Runtime& runtime); double virtualViewPrerenderRatio(jsi::Runtime& runtime); diff --git a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp index 8294edb254b157..609fc268e122d2 100644 --- a/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mounting/internal/CullingContext.cpp @@ -32,6 +32,20 @@ CullingContext CullingContext::adjustCullingContextIfNeeded( /* includeTransform */ true); cullingContext.frame.size = scrollViewShadowNode->getLayoutMetrics().frame.size; + + // Enlarge the frame if an outset ratio is defined + auto outsetRatio = ReactNativeFeatureFlags::viewCullingOutsetRatio(); + if (outsetRatio > 0) { + auto xOutset = static_cast( + floor(cullingContext.frame.size.width * outsetRatio)); + auto yOutset = static_cast( + floor(cullingContext.frame.size.height * outsetRatio)); + cullingContext.frame.origin.x -= xOutset; + cullingContext.frame.origin.y -= yOutset; + cullingContext.frame.size.width += 2.0f * xOutset; + cullingContext.frame.size.height += 2.0f * yOutset; + } + cullingContext.transform = Transform::Identity(); if (layoutMetrics.layoutDirection == LayoutDirection::RightToLeft) { diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 31caaddc0a5fde..dbec8f146278a3 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -825,6 +825,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'canary', }, + viewCullingOutsetRatio: { + defaultValue: 0, + metadata: { + dateAdded: '2025-09-18', + description: + 'Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom.', + expectedReleaseValue: 0, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, virtualViewHysteresisRatio: { defaultValue: 0, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 8db688c63c65a9..6636a2647e2a62 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<<44c491f4078e75b8b77ee346ac802a70>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -123,6 +123,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useShadowNodeStateOnClone: Getter, useTurboModuleInterop: Getter, useTurboModules: Getter, + viewCullingOutsetRatio: Getter, virtualViewHysteresisRatio: Getter, virtualViewPrerenderRatio: Getter, }>; @@ -498,6 +499,10 @@ export const useTurboModuleInterop: Getter = createNativeFlagGetter('us * When enabled, NativeModules will be executed by using the TurboModule system */ export const useTurboModules: Getter = createNativeFlagGetter('useTurboModules', false); +/** + * Outset the culling context frame with the provided ratio. The culling context frame size will be outset by width * ratio on the left and right, and height * ratio on the top and bottom. + */ +export const viewCullingOutsetRatio: Getter = createNativeFlagGetter('viewCullingOutsetRatio', 0); /** * Sets a hysteresis window for transition between prerender and hidden modes. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 0a72c27c6a7570..659f86c042876f 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<<18ae10cff4e5611d4774252917f57c13>> + * @generated SignedSource<<765a2f99602c00c38047e25ce805af86>> * @flow strict * @noformat */ @@ -98,6 +98,7 @@ export interface Spec extends TurboModule { +useShadowNodeStateOnClone?: () => boolean; +useTurboModuleInterop?: () => boolean; +useTurboModules?: () => boolean; + +viewCullingOutsetRatio?: () => number; +virtualViewHysteresisRatio?: () => number; +virtualViewPrerenderRatio?: () => number; }