From 2f40224faf73bda13cbd16ecbaebca71b6e1169d Mon Sep 17 00:00:00 2001 From: Nick Lefever Date: Sun, 28 Sep 2025 15:13:07 -0700 Subject: [PATCH] Add support for global view culling outset ratio setting (#53845) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53845 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 fbshipit-source-id: 3d1aaf680c5896fd4bb992b272b650ab2c097905 --- .../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 eb07acd5df2..abfbf15920c 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 91a6990138d..65796a5915e 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 98d05d177e3..339184e78d6 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 d08bd81b443..21f217aa1a0 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 eb575e5159b..b3a0d648332 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 06e0872a916..48c2b385d8c 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 8170bb1fcd3..a7c8a8f100b 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 9670480569e..829762419d1 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 4473f6df66a..58db55d5ea4 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 2a8d97a0b9e..581252fd33e 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 8263cb0cf18..1b108ff32fc 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 ede7746b6dc..1dd1f037dd3 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 2b407600c30..832f87dfcc7 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 126f07e6bd4..e02953c8afa 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 b8c7ca07366..de16d29eac4 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 761cb2a2ea4..beb3ea906ae 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 5a064f078cc..59c6518e663 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 8294edb254b..609fc268e12 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 31caaddc0a5..dbec8f14627 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 8db688c63c6..6636a2647e2 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 0a72c27c6a7..659f86c0428 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; }