From 792e450affb6509beb59b1c18968cd42b1cc9006 Mon Sep 17 00:00:00 2001 From: Luna Wei Date: Mon, 25 Aug 2025 12:58:22 -0700 Subject: [PATCH] Introduce hysteresis window (#53345) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/53345 Changelog: [Internal] - Introduce hysteresis window that is nested between the prerender and hidden window sizes. Currently set to enlargening the prerender window by hysteresis ratio When a VirtualView intersects with the hysteresis window, it mode remains unchanged. This prevents us dispatch mode changes for things like overscroll. I put the hysteresis between prerender and hidden because we already avoid dispatching mode changes from visible -> prerender. For prerender -> visible, we use renderState Reviewed By: yungsters Differential Revision: D80511627 fbshipit-source-id: cd14256abc898e7120705e277147d52a06c865a9 --- .../RCTVirtualViewComponentView.mm | 21 ++++++++++++--- .../featureflags/ReactNativeFeatureFlags.kt | 8 +++++- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 ++++++++- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 ++- .../ReactNativeFeatureFlagsDefaults.kt | 4 ++- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 ++++++++- .../ReactNativeFeatureFlagsProvider.kt | 4 ++- .../views/virtual/view/ReactVirtualView.kt | 27 ++++++++++++++++--- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 ++++++++++- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +++- .../featureflags/ReactNativeFeatureFlags.cpp | 6 ++++- .../featureflags/ReactNativeFeatureFlags.h | 7 ++++- .../ReactNativeFeatureFlagsAccessor.cpp | 22 +++++++++++++-- .../ReactNativeFeatureFlagsAccessor.h | 6 +++-- .../ReactNativeFeatureFlagsDefaults.h | 6 ++++- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +++++++- .../ReactNativeFeatureFlagsProvider.h | 3 ++- .../NativeReactNativeFeatureFlags.cpp | 7 ++++- .../NativeReactNativeFeatureFlags.h | 4 ++- .../ReactNativeFeatureFlags.config.js | 11 ++++++++ .../featureflags/ReactNativeFeatureFlags.js | 7 ++++- .../specs/NativeReactNativeFeatureFlags.js | 3 ++- 22 files changed, 178 insertions(+), 29 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm index c0d326b508e..5cd9ce171ec 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/VirtualView/RCTVirtualViewComponentView.mm @@ -230,21 +230,34 @@ static BOOL sIsAccessibilityUsed = NO; scrollView.contentOffset.y, scrollView.frame.size.width, scrollView.frame.size.height); + const CGFloat visibleWidth = thresholdRect.size.width; + const CGFloat visibleHeight = thresholdRect.size.height; + if (CGRectOverlaps(targetRect, thresholdRect)) { newMode = RCTVirtualViewModeVisible; } else { auto prerender = false; const CGFloat prerenderRatio = ReactNativeFeatureFlags::virtualViewPrerenderRatio(); if (prerenderRatio > 0) { - thresholdRect = CGRectInset( - thresholdRect, -thresholdRect.size.width * prerenderRatio, -thresholdRect.size.height * prerenderRatio); + thresholdRect = CGRectInset(thresholdRect, -visibleWidth * prerenderRatio, -visibleHeight * prerenderRatio); prerender = CGRectOverlaps(targetRect, thresholdRect); } if (prerender) { newMode = RCTVirtualViewModePrerender; } else { - newMode = RCTVirtualViewModeHidden; - thresholdRect = CGRectZero; + const CGFloat hysteresisRatio = ReactNativeFeatureFlags::virtualViewHysteresisRatio(); + if (_mode.has_value() && hysteresisRatio > 0) { + thresholdRect = CGRectInset(thresholdRect, -visibleWidth * hysteresisRatio, -visibleHeight * hysteresisRatio); + if (CGRectOverlaps(targetRect, thresholdRect)) { + newMode = _mode.value(); + } else { + newMode = RCTVirtualViewModeHidden; + thresholdRect = CGRectZero; + } + } else { + newMode = RCTVirtualViewModeHidden; + thresholdRect = CGRectZero; + } } } 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 7995a1db8c2..feea7276baf 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<<1e8a84a53072fa0c8665aead80d0199f>> + * @generated SignedSource<<34c12f5a31aab5bfb874953f1beefef1>> */ /** @@ -432,6 +432,12 @@ public object ReactNativeFeatureFlags { @JvmStatic public fun useTurboModules(): Boolean = accessor.useTurboModules() + /** + * Sets a hysteresis window for transition between prerender and hidden modes. + */ + @JvmStatic + public fun virtualViewHysteresisRatio(): Double = accessor.virtualViewHysteresisRatio() + /** * Initial prerender ratio for VirtualView. */ 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 b1c01a45d71..9fadb1e7416 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<<392da016e0bf4193b72c44a508811e10>> */ /** @@ -87,6 +87,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces private var useShadowNodeStateOnCloneCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null + private var virtualViewHysteresisRatioCache: Double? = null private var virtualViewPrerenderRatioCache: Double? = null override fun commonTestFlag(): Boolean { @@ -692,6 +693,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces return cached } + override fun virtualViewHysteresisRatio(): Double { + var cached = virtualViewHysteresisRatioCache + if (cached == null) { + cached = ReactNativeFeatureFlagsCxxInterop.virtualViewHysteresisRatio() + virtualViewHysteresisRatioCache = cached + } + return cached + } + override fun virtualViewPrerenderRatio(): Double { var cached = virtualViewPrerenderRatioCache 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 6ce331fd73f..03e35b7436f 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<<6e384a07f0e7bc237f72b49a1268e16b>> + * @generated SignedSource<> */ /** @@ -162,6 +162,8 @@ public object ReactNativeFeatureFlagsCxxInterop { @DoNotStrip @JvmStatic public external fun useTurboModules(): Boolean + @DoNotStrip @JvmStatic public external fun virtualViewHysteresisRatio(): Double + @DoNotStrip @JvmStatic public external fun virtualViewPrerenderRatio(): Double @DoNotStrip @JvmStatic public external fun override(provider: Any) 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 13b2b2b766e..fbb9c291afb 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<<2daac10f81c205728db8127da4dd36cc>> + * @generated SignedSource<<719706a983a073b6c286c49d993f7f80>> */ /** @@ -157,5 +157,7 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi override fun useTurboModules(): Boolean = false + 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 b0b54e83625..d591e22bf5b 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<<1a19e0569371a038ba8d3849fb26eb5c>> + * @generated SignedSource<<594815ba6a984c460ab8bddd91c5cae2>> */ /** @@ -91,6 +91,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc private var useShadowNodeStateOnCloneCache: Boolean? = null private var useTurboModuleInteropCache: Boolean? = null private var useTurboModulesCache: Boolean? = null + private var virtualViewHysteresisRatioCache: Double? = null private var virtualViewPrerenderRatioCache: Double? = null override fun commonTestFlag(): Boolean { @@ -763,6 +764,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc return cached } + override fun virtualViewHysteresisRatio(): Double { + var cached = virtualViewHysteresisRatioCache + if (cached == null) { + cached = currentProvider.virtualViewHysteresisRatio() + accessedFeatureFlags.add("virtualViewHysteresisRatio") + virtualViewHysteresisRatioCache = cached + } + return cached + } + override fun virtualViewPrerenderRatio(): Double { var cached = virtualViewPrerenderRatioCache 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 5c50635fb9e..91114a5074c 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<<5f84a9a17209b9ecb95e13e6567e7c3c>> + * @generated SignedSource<> */ /** @@ -157,5 +157,7 @@ public interface ReactNativeFeatureFlagsProvider { @DoNotStrip public fun useTurboModules(): Boolean + @DoNotStrip public fun virtualViewHysteresisRatio(): Double + @DoNotStrip public fun virtualViewPrerenderRatio(): Double } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualView.kt index f78c5226883..c47195f80af 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/virtual/view/ReactVirtualView.kt @@ -41,6 +41,7 @@ public class ReactVirtualView(context: Context) : internal var modeChangeEmitter: VirtualViewModeChangeEmitter? = null internal var prerenderRatio: Double = ReactNativeFeatureFlags.virtualViewPrerenderRatio() internal val debugLogEnabled: Boolean = ReactNativeFeatureFlags.enableVirtualViewDebugFeatures() + private val hysteresisRatio: Double = ReactNativeFeatureFlags.virtualViewHysteresisRatio() private val onWindowFocusChangeListener = if (ReactNativeFeatureFlags.enableVirtualViewWindowFocusDetection()) { @@ -222,6 +223,8 @@ public class ReactVirtualView(context: Context) : bottom + offsetY, ) scrollView.getDrawingRect(thresholdRect) + val visibleHeight = thresholdRect.height() + val visibleWidth = thresholdRect.width() // TODO: Validate whether this is still the case and whether these checks are still needed. // updateRects will initially get called before the targetRect has any dimensions set, so if @@ -260,16 +263,32 @@ public class ReactVirtualView(context: Context) : var prerender = false if (prerenderRatio > 0.0) { thresholdRect.inset( - (-thresholdRect.width() * prerenderRatio).toInt(), - (-thresholdRect.height() * prerenderRatio).toInt(), + (-visibleWidth * prerenderRatio).toInt(), + (-visibleHeight * prerenderRatio).toInt(), ) prerender = rectsOverlap(targetRect, thresholdRect) } if (prerender) { newMode = VirtualViewMode.Prerender } else { - newMode = VirtualViewMode.Hidden - thresholdRect.setEmpty() + val _mode = mode // local variable so Kotlin knows its not nullable + if (_mode != null && hysteresisRatio > 0.0) { + thresholdRect.inset( + (-visibleWidth * hysteresisRatio).toInt(), + (-visibleHeight * hysteresisRatio).toInt(), + ) + if (rectsOverlap(targetRect, thresholdRect)) { + // In hysteresis window, no change to mode + newMode = _mode + debugLog("dispatchOnModeChangeIfNeeded") { "hysteresis, mode=$newMode" } + } else { + newMode = VirtualViewMode.Hidden + thresholdRect.setEmpty() + } + } else { + newMode = VirtualViewMode.Hidden + thresholdRect.setEmpty() + } } } debugLog("dispatchOnModeChangeIfNeeded") { 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 f2d24b7de7e..d34c49a3ce8 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<<1eff5bade524e3ad8e827ad4adb37f1a>> + * @generated SignedSource<<16b12024bb363358ef09b9a42cb2fc97>> */ /** @@ -441,6 +441,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + double virtualViewHysteresisRatio() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("virtualViewHysteresisRatio"); + return method(javaProvider_); + } + double virtualViewPrerenderRatio() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("virtualViewPrerenderRatio"); @@ -786,6 +792,11 @@ bool JReactNativeFeatureFlagsCxxInterop::useTurboModules( return ReactNativeFeatureFlags::useTurboModules(); } +double JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::virtualViewHysteresisRatio(); +} + double JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::virtualViewPrerenderRatio(); @@ -1023,6 +1034,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "useTurboModules", JReactNativeFeatureFlagsCxxInterop::useTurboModules), + makeNativeMethod( + "virtualViewHysteresisRatio", + JReactNativeFeatureFlagsCxxInterop::virtualViewHysteresisRatio), makeNativeMethod( "virtualViewPrerenderRatio", JReactNativeFeatureFlagsCxxInterop::virtualViewPrerenderRatio), 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 3941c2e6f52..d3961e399de 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<<05dba4cd49bd4f490e1ea943dd02cae0>> + * @generated SignedSource<<54118ccd475a8bf1d7db83304b1f17d0>> */ /** @@ -231,6 +231,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool useTurboModules( facebook::jni::alias_ref); + static double virtualViewHysteresisRatio( + facebook::jni::alias_ref); + static double virtualViewPrerenderRatio( 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 87020a8702c..a57a919cf57 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<<12a06ea04fc09c34f1fbdcbdf6046d81>> */ /** @@ -294,6 +294,10 @@ bool ReactNativeFeatureFlags::useTurboModules() { return getAccessor().useTurboModules(); } +double ReactNativeFeatureFlags::virtualViewHysteresisRatio() { + return getAccessor().virtualViewHysteresisRatio(); +} + double ReactNativeFeatureFlags::virtualViewPrerenderRatio() { return getAccessor().virtualViewPrerenderRatio(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index b14e3be19c2..1a725829332 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<<4831f7b871223d8347db93f9a0b3a1f9>> + * @generated SignedSource<> */ /** @@ -374,6 +374,11 @@ class ReactNativeFeatureFlags { */ RN_EXPORT static bool useTurboModules(); + /** + * Sets a hysteresis window for transition between prerender and hidden modes. + */ + RN_EXPORT static double virtualViewHysteresisRatio(); + /** * Initial prerender ratio for VirtualView. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index 6f0a805e093..77c7804d667 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<> + * @generated SignedSource<<3c5588a851e6cdefaba22236c5ebb828>> */ /** @@ -1235,6 +1235,24 @@ bool ReactNativeFeatureFlagsAccessor::useTurboModules() { return flagValue.value(); } +double ReactNativeFeatureFlagsAccessor::virtualViewHysteresisRatio() { + auto flagValue = virtualViewHysteresisRatio_.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(67, "virtualViewHysteresisRatio"); + + flagValue = currentProvider_->virtualViewHysteresisRatio(); + virtualViewHysteresisRatio_ = flagValue; + } + + return flagValue.value(); +} + double ReactNativeFeatureFlagsAccessor::virtualViewPrerenderRatio() { auto flagValue = virtualViewPrerenderRatio_.load(); @@ -1244,7 +1262,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(67, "virtualViewPrerenderRatio"); + markFlagAsAccessed(68, "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 8e1c7b5c439..00cfe5ac235 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<> */ /** @@ -99,6 +99,7 @@ class ReactNativeFeatureFlagsAccessor { bool useShadowNodeStateOnClone(); bool useTurboModuleInterop(); bool useTurboModules(); + double virtualViewHysteresisRatio(); double virtualViewPrerenderRatio(); void override(std::unique_ptr provider); @@ -111,7 +112,7 @@ class ReactNativeFeatureFlagsAccessor { std::unique_ptr currentProvider_; bool wasOverridden_; - std::array, 68> accessedFeatureFlags_; + std::array, 69> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> cdpInteractionMetricsEnabled_; @@ -180,6 +181,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> useShadowNodeStateOnClone_; std::atomic> useTurboModuleInterop_; std::atomic> useTurboModules_; + 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 09af98d0741..2342fe6526b 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<<47b7dd2d84a9ea6c2e89677ac5b5554e>> + * @generated SignedSource<> */ /** @@ -295,6 +295,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + double virtualViewHysteresisRatio() override { + return 0.0; + } + double virtualViewPrerenderRatio() override { return 5.0; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index f55df4907ae..61cab1e2ef2 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<<6030ed59832efdf42e23c13b689da64f>> + * @generated SignedSource<> */ /** @@ -648,6 +648,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef return ReactNativeFeatureFlagsDefaults::useTurboModules(); } + double virtualViewHysteresisRatio() override { + auto value = values_["virtualViewHysteresisRatio"]; + if (!value.isNull()) { + return value.getDouble(); + } + + return ReactNativeFeatureFlagsDefaults::virtualViewHysteresisRatio(); + } + double virtualViewPrerenderRatio() override { auto value = values_["virtualViewPrerenderRatio"]; if (!value.isNull()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index edfeaf7643a..5880a958bd8 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<<95940b74b0055760be69c501a97a6f45>> + * @generated SignedSource<> */ /** @@ -92,6 +92,7 @@ class ReactNativeFeatureFlagsProvider { virtual bool useShadowNodeStateOnClone() = 0; virtual bool useTurboModuleInterop() = 0; virtual bool useTurboModules() = 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 a4fa0e554d9..ba23b4f6acc 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<<998bdf037d58ca747c22ada099d236f0>> + * @generated SignedSource<> */ /** @@ -379,6 +379,11 @@ bool NativeReactNativeFeatureFlags::useTurboModules( return ReactNativeFeatureFlags::useTurboModules(); } +double NativeReactNativeFeatureFlags::virtualViewHysteresisRatio( + jsi::Runtime& /*runtime*/) { + return ReactNativeFeatureFlags::virtualViewHysteresisRatio(); +} + double NativeReactNativeFeatureFlags::virtualViewPrerenderRatio( jsi::Runtime& /*runtime*/) { return ReactNativeFeatureFlags::virtualViewPrerenderRatio(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 3fdaeda4acd..cf3082dbe14 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<<89c5d22cd228da57d82843f7e660f262>> + * @generated SignedSource<<320e69fa54228a352fad210e3a43b947>> */ /** @@ -170,6 +170,8 @@ class NativeReactNativeFeatureFlags bool useTurboModules(jsi::Runtime& runtime); + double virtualViewHysteresisRatio(jsi::Runtime& runtime); + double virtualViewPrerenderRatio(jsi::Runtime& runtime); }; diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 55d0b8a0b9b..afe6110129f 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -760,6 +760,17 @@ const definitions: FeatureFlagDefinitions = { }, ossReleaseStage: 'canary', }, + virtualViewHysteresisRatio: { + defaultValue: 0, + metadata: { + dateAdded: '2025-08-22', + description: + 'Sets a hysteresis window for transition between prerender and hidden modes.', + expectedReleaseValue: 1, + purpose: 'experimentation', + }, + ossReleaseStage: 'none', + }, virtualViewPrerenderRatio: { defaultValue: 5, metadata: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index f00404e33f7..0e2941d0e64 100644 --- a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js +++ b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -114,6 +114,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ useShadowNodeStateOnClone: Getter, useTurboModuleInterop: Getter, useTurboModules: Getter, + virtualViewHysteresisRatio: Getter, virtualViewPrerenderRatio: Getter, }>; @@ -449,6 +450,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); +/** + * Sets a hysteresis window for transition between prerender and hidden modes. + */ +export const virtualViewHysteresisRatio: Getter = createNativeFlagGetter('virtualViewHysteresisRatio', 0); /** * Initial prerender ratio for VirtualView. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 30a467f2224..2ed27daeb81 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<<2b0679c42d8e1cb021612807d94d9f4c>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -92,6 +92,7 @@ export interface Spec extends TurboModule { +useShadowNodeStateOnClone?: () => boolean; +useTurboModuleInterop?: () => boolean; +useTurboModules?: () => boolean; + +virtualViewHysteresisRatio?: () => number; +virtualViewPrerenderRatio?: () => number; }