mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Integrate IntersectionObserver in Event Loop (#51452)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51452 Changelog: [internal] This implements a long planned refactor of `IntersectionObserver` to have a proper integration with the Event Loop, which unblocks `FragmentRef` in React Native. The existing integration doesn't integrate with the Event Loop, so the intersection determination doesn't happen as a step in the event loop but in 2 different moments: 1. When you start observing a new target, we check if there are any pending transactions for that target, and otherwise determine the intersection immediately and queue the notifications. 2. Using mount hooks, when a transaction for a surfaceId is mounted, we check intersections for all the observers in that surface. This has an important problem: * If you attach a observer on a target before the target is attached to the tree (something that `FragmentRef` will start doing soon), we don't have a pending transaction so we determine intersections immediately. In that case, it's always "not visible" because it's not attached, and then we immediately trigger a transition when mounted in the same tick. To fix this, we can implement a step in the Event Loop the same way that `IntersectionObserver` does on Web. We would still wait for pending transactions to be mounted to determine intersection timing (the same way we do now, but now checking at the end of the current Event Loop tick), but the dispatch of initial notifications for target that won't change is done at the end of the tick instead of synchronously. It also refactors the list of active observers as a list of shared pointers to `IntersectionObserver` objects, instead of as list of `IntersectionObserver` objects directly. This is necessary to build the list of pending observers (with stable references) while making sure the ownership stays in the list of active observers. Reviewed By: javache Differential Revision: D74883214 fbshipit-source-id: 24accf1dba48d13b5773950a5cbf9ea38f4a1745
This commit is contained in:
committed by
Facebook GitHub Bot
parent
50ce8c77a7
commit
3aeba22dda
+7
-1
@@ -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<<e9a109fd77667dd0cb945ef6ef9737f2>>
|
||||
* @generated SignedSource<<43590bc324e803ccf299e2a6ae6c5304>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -144,6 +144,12 @@ public object ReactNativeFeatureFlags {
|
||||
@JvmStatic
|
||||
public fun enableIOSViewClipToPaddingBox(): Boolean = accessor.enableIOSViewClipToPaddingBox()
|
||||
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun enableIntersectionObserverEventLoopIntegration(): Boolean = accessor.enableIntersectionObserverEventLoopIntegration()
|
||||
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+11
-1
@@ -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<<1a5cd689229d4e0c31070123045e84da>>
|
||||
* @generated SignedSource<<5609be38b50c62ff5097b3571940f400>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -39,6 +39,7 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
private var enableFixForParentTagDuringReparentingCache: Boolean? = null
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueModulesOnIOSCache: Boolean? = null
|
||||
@@ -240,6 +241,15 @@ internal class ReactNativeFeatureFlagsCxxAccessor : ReactNativeFeatureFlagsAcces
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = ReactNativeFeatureFlagsCxxInterop.enableIntersectionObserverEventLoopIntegration()
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<c39d0c1834797b3309b4fc5ce814280c>>
|
||||
* @generated SignedSource<<15645e36bbca81dfcec07fc82f8f6264>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -66,6 +66,8 @@ public object ReactNativeFeatureFlagsCxxInterop {
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip @JvmStatic public external fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+3
-1
@@ -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<<7f357475254104729cc7910c14e1c1fb>>
|
||||
* @generated SignedSource<<a8fa2410598d311ea5256aee7658ab3c>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,8 @@ public open class ReactNativeFeatureFlagsDefaults : ReactNativeFeatureFlagsProvi
|
||||
|
||||
override fun enableIOSViewClipToPaddingBox(): Boolean = false
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean = false
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean = false
|
||||
|
||||
override fun enableLayoutAnimationsOnIOS(): Boolean = true
|
||||
|
||||
+12
-1
@@ -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<<14cd1a58bd153dedda045a72c1494caa>>
|
||||
* @generated SignedSource<<efc16fdb113176b932c0005118e7b48c>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -43,6 +43,7 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
private var enableFixForParentTagDuringReparentingCache: Boolean? = null
|
||||
private var enableFontScaleChangesUpdatingLayoutCache: Boolean? = null
|
||||
private var enableIOSViewClipToPaddingBoxCache: Boolean? = null
|
||||
private var enableIntersectionObserverEventLoopIntegrationCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnAndroidCache: Boolean? = null
|
||||
private var enableLayoutAnimationsOnIOSCache: Boolean? = null
|
||||
private var enableMainQueueModulesOnIOSCache: Boolean? = null
|
||||
@@ -263,6 +264,16 @@ internal class ReactNativeFeatureFlagsLocalAccessor : ReactNativeFeatureFlagsAcc
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableIntersectionObserverEventLoopIntegration(): Boolean {
|
||||
var cached = enableIntersectionObserverEventLoopIntegrationCache
|
||||
if (cached == null) {
|
||||
cached = currentProvider.enableIntersectionObserverEventLoopIntegration()
|
||||
accessedFeatureFlags.add("enableIntersectionObserverEventLoopIntegration")
|
||||
enableIntersectionObserverEventLoopIntegrationCache = cached
|
||||
}
|
||||
return cached
|
||||
}
|
||||
|
||||
override fun enableLayoutAnimationsOnAndroid(): Boolean {
|
||||
var cached = enableLayoutAnimationsOnAndroidCache
|
||||
if (cached == null) {
|
||||
|
||||
+3
-1
@@ -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<<593b1d64dc31038140032a6b0a439700>>
|
||||
* @generated SignedSource<<3a61d9b66a7ed6fc9c76548aa72e3ed3>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -61,6 +61,8 @@ public interface ReactNativeFeatureFlagsProvider {
|
||||
|
||||
@DoNotStrip public fun enableIOSViewClipToPaddingBox(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableIntersectionObserverEventLoopIntegration(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnAndroid(): Boolean
|
||||
|
||||
@DoNotStrip public fun enableLayoutAnimationsOnIOS(): Boolean
|
||||
|
||||
+15
-1
@@ -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<<d74dc8182a14fddbd7118149298515bd>>
|
||||
* @generated SignedSource<<93037428475b39d3f2c208e1326b3e86>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -153,6 +153,12 @@ class ReactNativeFeatureFlagsJavaProvider
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableIntersectionObserverEventLoopIntegration");
|
||||
return method(javaProvider_);
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
static const auto method =
|
||||
getReactNativeFeatureFlagsProviderJavaClass()->getMethod<jboolean()>("enableLayoutAnimationsOnAndroid");
|
||||
@@ -426,6 +432,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox(
|
||||
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop> /*unused*/) {
|
||||
return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid();
|
||||
@@ -659,6 +670,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() {
|
||||
makeNativeMethod(
|
||||
"enableIOSViewClipToPaddingBox",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox),
|
||||
makeNativeMethod(
|
||||
"enableIntersectionObserverEventLoopIntegration",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration),
|
||||
makeNativeMethod(
|
||||
"enableLayoutAnimationsOnAndroid",
|
||||
JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid),
|
||||
|
||||
+4
-1
@@ -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<<a5ab0022f6a01bd6e929d36d9d87db10>>
|
||||
* @generated SignedSource<<f5365c0a71e4c7638cec79bebfab55c3>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -87,6 +87,9 @@ class JReactNativeFeatureFlagsCxxInterop
|
||||
static bool enableIOSViewClipToPaddingBox(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enableIntersectionObserverEventLoopIntegration(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
static bool enableLayoutAnimationsOnAndroid(
|
||||
facebook::jni::alias_ref<JReactNativeFeatureFlagsCxxInterop>);
|
||||
|
||||
|
||||
@@ -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<<88fdbea2f97f628187164a47a9737da0>>
|
||||
* @generated SignedSource<<e4946f3e524cd7571fc62c721d611d19>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -102,6 +102,10 @@ bool ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() {
|
||||
return getAccessor().enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration() {
|
||||
return getAccessor().enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid() {
|
||||
return getAccessor().enableLayoutAnimationsOnAndroid();
|
||||
}
|
||||
|
||||
@@ -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<<a4123bc6f44835c022a1a5238908674c>>
|
||||
* @generated SignedSource<<83ef02f26eaf2d847efd7042ae6d1dfc>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -134,6 +134,11 @@ class ReactNativeFeatureFlags {
|
||||
*/
|
||||
RN_EXPORT static bool enableIOSViewClipToPaddingBox();
|
||||
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
RN_EXPORT static bool enableIntersectionObserverEventLoopIntegration();
|
||||
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+48
-30
@@ -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<<0c768a58bd488ed1f3061cb4c13ef78d>>
|
||||
* @generated SignedSource<<0d1e4f0581e3aa90e18dc70ccee8a50a>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -371,6 +371,24 @@ bool ReactNativeFeatureFlagsAccessor::enableIOSViewClipToPaddingBox() {
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enableIntersectionObserverEventLoopIntegration() {
|
||||
auto flagValue = enableIntersectionObserverEventLoopIntegration_.load();
|
||||
|
||||
if (!flagValue.has_value()) {
|
||||
// This block is not exclusive but it is not necessary.
|
||||
// If multiple threads try to initialize the feature flag, we would only
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(19, "enableIntersectionObserverEventLoopIntegration");
|
||||
|
||||
flagValue = currentProvider_->enableIntersectionObserverEventLoopIntegration();
|
||||
enableIntersectionObserverEventLoopIntegration_ = flagValue;
|
||||
}
|
||||
|
||||
return flagValue.value();
|
||||
}
|
||||
|
||||
bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
|
||||
auto flagValue = enableLayoutAnimationsOnAndroid_.load();
|
||||
|
||||
@@ -380,7 +398,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(19, "enableLayoutAnimationsOnAndroid");
|
||||
markFlagAsAccessed(20, "enableLayoutAnimationsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->enableLayoutAnimationsOnAndroid();
|
||||
enableLayoutAnimationsOnAndroid_ = flagValue;
|
||||
@@ -398,7 +416,7 @@ bool ReactNativeFeatureFlagsAccessor::enableLayoutAnimationsOnIOS() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(20, "enableLayoutAnimationsOnIOS");
|
||||
markFlagAsAccessed(21, "enableLayoutAnimationsOnIOS");
|
||||
|
||||
flagValue = currentProvider_->enableLayoutAnimationsOnIOS();
|
||||
enableLayoutAnimationsOnIOS_ = flagValue;
|
||||
@@ -416,7 +434,7 @@ bool ReactNativeFeatureFlagsAccessor::enableMainQueueModulesOnIOS() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(21, "enableMainQueueModulesOnIOS");
|
||||
markFlagAsAccessed(22, "enableMainQueueModulesOnIOS");
|
||||
|
||||
flagValue = currentProvider_->enableMainQueueModulesOnIOS();
|
||||
enableMainQueueModulesOnIOS_ = flagValue;
|
||||
@@ -434,7 +452,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNativeCSSParsing() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(22, "enableNativeCSSParsing");
|
||||
markFlagAsAccessed(23, "enableNativeCSSParsing");
|
||||
|
||||
flagValue = currentProvider_->enableNativeCSSParsing();
|
||||
enableNativeCSSParsing_ = flagValue;
|
||||
@@ -452,7 +470,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNetworkEventReporting() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(23, "enableNetworkEventReporting");
|
||||
markFlagAsAccessed(24, "enableNetworkEventReporting");
|
||||
|
||||
flagValue = currentProvider_->enableNetworkEventReporting();
|
||||
enableNetworkEventReporting_ = flagValue;
|
||||
@@ -470,7 +488,7 @@ bool ReactNativeFeatureFlagsAccessor::enableNewBackgroundAndBorderDrawables() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(24, "enableNewBackgroundAndBorderDrawables");
|
||||
markFlagAsAccessed(25, "enableNewBackgroundAndBorderDrawables");
|
||||
|
||||
flagValue = currentProvider_->enableNewBackgroundAndBorderDrawables();
|
||||
enableNewBackgroundAndBorderDrawables_ = flagValue;
|
||||
@@ -488,7 +506,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePreparedTextLayout() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(25, "enablePreparedTextLayout");
|
||||
markFlagAsAccessed(26, "enablePreparedTextLayout");
|
||||
|
||||
flagValue = currentProvider_->enablePreparedTextLayout();
|
||||
enablePreparedTextLayout_ = flagValue;
|
||||
@@ -506,7 +524,7 @@ bool ReactNativeFeatureFlagsAccessor::enablePropsUpdateReconciliationAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(26, "enablePropsUpdateReconciliationAndroid");
|
||||
markFlagAsAccessed(27, "enablePropsUpdateReconciliationAndroid");
|
||||
|
||||
flagValue = currentProvider_->enablePropsUpdateReconciliationAndroid();
|
||||
enablePropsUpdateReconciliationAndroid_ = flagValue;
|
||||
@@ -524,7 +542,7 @@ bool ReactNativeFeatureFlagsAccessor::enableResourceTimingAPI() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(27, "enableResourceTimingAPI");
|
||||
markFlagAsAccessed(28, "enableResourceTimingAPI");
|
||||
|
||||
flagValue = currentProvider_->enableResourceTimingAPI();
|
||||
enableResourceTimingAPI_ = flagValue;
|
||||
@@ -542,7 +560,7 @@ bool ReactNativeFeatureFlagsAccessor::enableSynchronousStateUpdates() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(28, "enableSynchronousStateUpdates");
|
||||
markFlagAsAccessed(29, "enableSynchronousStateUpdates");
|
||||
|
||||
flagValue = currentProvider_->enableSynchronousStateUpdates();
|
||||
enableSynchronousStateUpdates_ = flagValue;
|
||||
@@ -560,7 +578,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewCulling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(29, "enableViewCulling");
|
||||
markFlagAsAccessed(30, "enableViewCulling");
|
||||
|
||||
flagValue = currentProvider_->enableViewCulling();
|
||||
enableViewCulling_ = flagValue;
|
||||
@@ -578,7 +596,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecycling() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(30, "enableViewRecycling");
|
||||
markFlagAsAccessed(31, "enableViewRecycling");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecycling();
|
||||
enableViewRecycling_ = flagValue;
|
||||
@@ -596,7 +614,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForText() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(31, "enableViewRecyclingForText");
|
||||
markFlagAsAccessed(32, "enableViewRecyclingForText");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForText();
|
||||
enableViewRecyclingForText_ = flagValue;
|
||||
@@ -614,7 +632,7 @@ bool ReactNativeFeatureFlagsAccessor::enableViewRecyclingForView() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(32, "enableViewRecyclingForView");
|
||||
markFlagAsAccessed(33, "enableViewRecyclingForView");
|
||||
|
||||
flagValue = currentProvider_->enableViewRecyclingForView();
|
||||
enableViewRecyclingForView_ = flagValue;
|
||||
@@ -632,7 +650,7 @@ bool ReactNativeFeatureFlagsAccessor::fixMappingOfEventPrioritiesBetweenFabricAn
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(33, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
markFlagAsAccessed(34, "fixMappingOfEventPrioritiesBetweenFabricAndReact");
|
||||
|
||||
flagValue = currentProvider_->fixMappingOfEventPrioritiesBetweenFabricAndReact();
|
||||
fixMappingOfEventPrioritiesBetweenFabricAndReact_ = flagValue;
|
||||
@@ -650,7 +668,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(34, "fuseboxEnabledRelease");
|
||||
markFlagAsAccessed(35, "fuseboxEnabledRelease");
|
||||
|
||||
flagValue = currentProvider_->fuseboxEnabledRelease();
|
||||
fuseboxEnabledRelease_ = flagValue;
|
||||
@@ -668,7 +686,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(35, "fuseboxNetworkInspectionEnabled");
|
||||
markFlagAsAccessed(36, "fuseboxNetworkInspectionEnabled");
|
||||
|
||||
flagValue = currentProvider_->fuseboxNetworkInspectionEnabled();
|
||||
fuseboxNetworkInspectionEnabled_ = flagValue;
|
||||
@@ -686,7 +704,7 @@ bool ReactNativeFeatureFlagsAccessor::incorporateMaxLinesDuringAndroidLayout() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(36, "incorporateMaxLinesDuringAndroidLayout");
|
||||
markFlagAsAccessed(37, "incorporateMaxLinesDuringAndroidLayout");
|
||||
|
||||
flagValue = currentProvider_->incorporateMaxLinesDuringAndroidLayout();
|
||||
incorporateMaxLinesDuringAndroidLayout_ = flagValue;
|
||||
@@ -704,7 +722,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(37, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
markFlagAsAccessed(38, "traceTurboModulePromiseRejectionsOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->traceTurboModulePromiseRejectionsOnAndroid();
|
||||
traceTurboModulePromiseRejectionsOnAndroid_ = flagValue;
|
||||
@@ -722,7 +740,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(38, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
markFlagAsAccessed(39, "updateRuntimeShadowNodeReferencesOnCommit");
|
||||
|
||||
flagValue = currentProvider_->updateRuntimeShadowNodeReferencesOnCommit();
|
||||
updateRuntimeShadowNodeReferencesOnCommit_ = flagValue;
|
||||
@@ -740,7 +758,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(39, "useAlwaysAvailableJSErrorHandling");
|
||||
markFlagAsAccessed(40, "useAlwaysAvailableJSErrorHandling");
|
||||
|
||||
flagValue = currentProvider_->useAlwaysAvailableJSErrorHandling();
|
||||
useAlwaysAvailableJSErrorHandling_ = flagValue;
|
||||
@@ -758,7 +776,7 @@ bool ReactNativeFeatureFlagsAccessor::useAndroidTextLayoutWidthDirectly() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(40, "useAndroidTextLayoutWidthDirectly");
|
||||
markFlagAsAccessed(41, "useAndroidTextLayoutWidthDirectly");
|
||||
|
||||
flagValue = currentProvider_->useAndroidTextLayoutWidthDirectly();
|
||||
useAndroidTextLayoutWidthDirectly_ = flagValue;
|
||||
@@ -776,7 +794,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(41, "useFabricInterop");
|
||||
markFlagAsAccessed(42, "useFabricInterop");
|
||||
|
||||
flagValue = currentProvider_->useFabricInterop();
|
||||
useFabricInterop_ = flagValue;
|
||||
@@ -794,7 +812,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(42, "useNativeViewConfigsInBridgelessMode");
|
||||
markFlagAsAccessed(43, "useNativeViewConfigsInBridgelessMode");
|
||||
|
||||
flagValue = currentProvider_->useNativeViewConfigsInBridgelessMode();
|
||||
useNativeViewConfigsInBridgelessMode_ = flagValue;
|
||||
@@ -812,7 +830,7 @@ bool ReactNativeFeatureFlagsAccessor::useOptimizedEventBatchingOnAndroid() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(43, "useOptimizedEventBatchingOnAndroid");
|
||||
markFlagAsAccessed(44, "useOptimizedEventBatchingOnAndroid");
|
||||
|
||||
flagValue = currentProvider_->useOptimizedEventBatchingOnAndroid();
|
||||
useOptimizedEventBatchingOnAndroid_ = flagValue;
|
||||
@@ -830,7 +848,7 @@ bool ReactNativeFeatureFlagsAccessor::useRawPropsJsiValue() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(44, "useRawPropsJsiValue");
|
||||
markFlagAsAccessed(45, "useRawPropsJsiValue");
|
||||
|
||||
flagValue = currentProvider_->useRawPropsJsiValue();
|
||||
useRawPropsJsiValue_ = flagValue;
|
||||
@@ -848,7 +866,7 @@ bool ReactNativeFeatureFlagsAccessor::useShadowNodeStateOnClone() {
|
||||
// be accessing the provider multiple times but the end state of this
|
||||
// instance and the returned flag value would be the same.
|
||||
|
||||
markFlagAsAccessed(45, "useShadowNodeStateOnClone");
|
||||
markFlagAsAccessed(46, "useShadowNodeStateOnClone");
|
||||
|
||||
flagValue = currentProvider_->useShadowNodeStateOnClone();
|
||||
useShadowNodeStateOnClone_ = flagValue;
|
||||
@@ -866,7 +884,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(46, "useTurboModuleInterop");
|
||||
markFlagAsAccessed(47, "useTurboModuleInterop");
|
||||
|
||||
flagValue = currentProvider_->useTurboModuleInterop();
|
||||
useTurboModuleInterop_ = flagValue;
|
||||
@@ -884,7 +902,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(47, "useTurboModules");
|
||||
markFlagAsAccessed(48, "useTurboModules");
|
||||
|
||||
flagValue = currentProvider_->useTurboModules();
|
||||
useTurboModules_ = flagValue;
|
||||
|
||||
+4
-2
@@ -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<<cf8c08ef77d045a6b0671139c7a003ad>>
|
||||
* @generated SignedSource<<867dd37ee489203791b933b87562fc67>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -51,6 +51,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
bool enableFixForParentTagDuringReparenting();
|
||||
bool enableFontScaleChangesUpdatingLayout();
|
||||
bool enableIOSViewClipToPaddingBox();
|
||||
bool enableIntersectionObserverEventLoopIntegration();
|
||||
bool enableLayoutAnimationsOnAndroid();
|
||||
bool enableLayoutAnimationsOnIOS();
|
||||
bool enableMainQueueModulesOnIOS();
|
||||
@@ -91,7 +92,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::unique_ptr<ReactNativeFeatureFlagsProvider> currentProvider_;
|
||||
bool wasOverridden_;
|
||||
|
||||
std::array<std::atomic<const char*>, 48> accessedFeatureFlags_;
|
||||
std::array<std::atomic<const char*>, 49> accessedFeatureFlags_;
|
||||
|
||||
std::atomic<std::optional<bool>> commonTestFlag_;
|
||||
std::atomic<std::optional<bool>> animatedShouldSignalBatch_;
|
||||
@@ -112,6 +113,7 @@ class ReactNativeFeatureFlagsAccessor {
|
||||
std::atomic<std::optional<bool>> enableFixForParentTagDuringReparenting_;
|
||||
std::atomic<std::optional<bool>> enableFontScaleChangesUpdatingLayout_;
|
||||
std::atomic<std::optional<bool>> enableIOSViewClipToPaddingBox_;
|
||||
std::atomic<std::optional<bool>> enableIntersectionObserverEventLoopIntegration_;
|
||||
std::atomic<std::optional<bool>> enableLayoutAnimationsOnAndroid_;
|
||||
std::atomic<std::optional<bool>> enableLayoutAnimationsOnIOS_;
|
||||
std::atomic<std::optional<bool>> enableMainQueueModulesOnIOS_;
|
||||
|
||||
+5
-1
@@ -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<<829c85b56fbeaf01ece2d1dd84ecde8e>>
|
||||
* @generated SignedSource<<c7ee2c12cbd79f431d5e61a8e5e8e860>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -103,6 +103,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
return false;
|
||||
}
|
||||
|
||||
+10
-1
@@ -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<<d21af3f613aa450f25708c3da0cb6e97>>
|
||||
* @generated SignedSource<<425dc272cba3224430f9948e6773b30f>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -216,6 +216,15 @@ class ReactNativeFeatureFlagsDynamicProvider : public ReactNativeFeatureFlagsDef
|
||||
return ReactNativeFeatureFlagsDefaults::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration() override {
|
||||
auto value = values_["enableIntersectionObserverEventLoopIntegration"];
|
||||
if (!value.isNull()) {
|
||||
return value.getBool();
|
||||
}
|
||||
|
||||
return ReactNativeFeatureFlagsDefaults::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid() override {
|
||||
auto value = values_["enableLayoutAnimationsOnAndroid"];
|
||||
if (!value.isNull()) {
|
||||
|
||||
+2
-1
@@ -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<<25e38ea9e85a704f2f5ab6c7b8cb3062>>
|
||||
* @generated SignedSource<<aae696b94ad35a3cdee45e80ad457a72>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -44,6 +44,7 @@ class ReactNativeFeatureFlagsProvider {
|
||||
virtual bool enableFixForParentTagDuringReparenting() = 0;
|
||||
virtual bool enableFontScaleChangesUpdatingLayout() = 0;
|
||||
virtual bool enableIOSViewClipToPaddingBox() = 0;
|
||||
virtual bool enableIntersectionObserverEventLoopIntegration() = 0;
|
||||
virtual bool enableLayoutAnimationsOnAndroid() = 0;
|
||||
virtual bool enableLayoutAnimationsOnIOS() = 0;
|
||||
virtual bool enableMainQueueModulesOnIOS() = 0;
|
||||
|
||||
+6
-1
@@ -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<<3c85cdc2dc3d2368d09a5c0aff4d083a>>
|
||||
* @generated SignedSource<<fadce2932514be743d6a1175daae39e2>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -139,6 +139,11 @@ bool NativeReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(
|
||||
return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration();
|
||||
}
|
||||
|
||||
bool NativeReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid(
|
||||
jsi::Runtime& /*runtime*/) {
|
||||
return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid();
|
||||
|
||||
+3
-1
@@ -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<<f7fbc7ab177cd84524a544cf3f94ae19>>
|
||||
* @generated SignedSource<<33c4be38d881a6e09e4a58cac6eeca53>>
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -75,6 +75,8 @@ class NativeReactNativeFeatureFlags
|
||||
|
||||
bool enableIOSViewClipToPaddingBox(jsi::Runtime& runtime);
|
||||
|
||||
bool enableIntersectionObserverEventLoopIntegration(jsi::Runtime& runtime);
|
||||
|
||||
bool enableLayoutAnimationsOnAndroid(jsi::Runtime& runtime);
|
||||
|
||||
bool enableLayoutAnimationsOnIOS(jsi::Runtime& runtime);
|
||||
|
||||
+8
-3
@@ -7,6 +7,7 @@
|
||||
|
||||
#include "NativeIntersectionObserver.h"
|
||||
#include <react/renderer/core/ShadowNode.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeSchedulerBinding.h>
|
||||
#include <react/renderer/uimanager/UIManagerBinding.h>
|
||||
#include <react/renderer/uimanager/primitives.h>
|
||||
|
||||
@@ -98,12 +99,16 @@ void NativeIntersectionObserver::connect(
|
||||
AsyncCallback<> notifyIntersectionObserversCallback) {
|
||||
auto& uiManager = getUIManagerFromRuntime(runtime);
|
||||
intersectionObserverManager_.connect(
|
||||
uiManager, notifyIntersectionObserversCallback);
|
||||
*RuntimeSchedulerBinding::getBinding(runtime)->getRuntimeScheduler(),
|
||||
uiManager,
|
||||
std::move(notifyIntersectionObserversCallback));
|
||||
}
|
||||
|
||||
void NativeIntersectionObserver::disconnect(jsi::Runtime& runtime) {
|
||||
auto& uiManager = getUIManagerFromRuntime(runtime);
|
||||
intersectionObserverManager_.disconnect(uiManager);
|
||||
intersectionObserverManager_.disconnect(
|
||||
*RuntimeSchedulerBinding::getBinding(runtime)->getRuntimeScheduler(),
|
||||
uiManager);
|
||||
}
|
||||
|
||||
std::vector<NativeIntersectionObserverEntry>
|
||||
@@ -123,7 +128,7 @@ NativeIntersectionObserver::takeRecords(jsi::Runtime& runtime) {
|
||||
|
||||
NativeIntersectionObserverEntry
|
||||
NativeIntersectionObserver::convertToNativeModuleEntry(
|
||||
IntersectionObserverEntry entry,
|
||||
const IntersectionObserverEntry& entry,
|
||||
jsi::Runtime& runtime) {
|
||||
RectAsTuple targetRect = {
|
||||
entry.targetRect.origin.x,
|
||||
|
||||
+1
-1
@@ -97,7 +97,7 @@ class NativeIntersectionObserver
|
||||
|
||||
static UIManager& getUIManagerFromRuntime(jsi::Runtime& runtime);
|
||||
static NativeIntersectionObserverEntry convertToNativeModuleEntry(
|
||||
IntersectionObserverEntry entry,
|
||||
const IntersectionObserverEntry& entry,
|
||||
jsi::Runtime& runtime);
|
||||
};
|
||||
|
||||
|
||||
+2
-4
@@ -57,10 +57,8 @@ class IntersectionObserver {
|
||||
return intersectionObserverId_;
|
||||
}
|
||||
|
||||
bool isTargetShadowNodeFamily(
|
||||
const ShadowNodeFamily& shadowNodeFamily) const {
|
||||
return std::addressof(*targetShadowNodeFamily_) ==
|
||||
std::addressof(shadowNodeFamily);
|
||||
ShadowNodeFamily::Shared getTargetShadowNodeFamily() const {
|
||||
return targetShadowNodeFamily_;
|
||||
}
|
||||
|
||||
std::vector<Float> getThresholds() const {
|
||||
|
||||
+147
-34
@@ -8,11 +8,33 @@
|
||||
#include "IntersectionObserverManager.h"
|
||||
#include <cxxreact/JSExecutor.h>
|
||||
#include <cxxreact/TraceSection.h>
|
||||
#include <react/debug/react_native_assert.h>
|
||||
#include <utility>
|
||||
#include "IntersectionObserver.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
namespace {
|
||||
RootShadowNode::Shared getRootShadowNode(
|
||||
SurfaceId surfaceId,
|
||||
const ShadowTreeRegistry& shadowTreeRegistry,
|
||||
std::unordered_map<SurfaceId, RootShadowNode::Shared>& cache) {
|
||||
auto it = cache.find(surfaceId);
|
||||
if (it == cache.end()) {
|
||||
RootShadowNode::Shared rootShadowNode = nullptr;
|
||||
|
||||
shadowTreeRegistry.visit(surfaceId, [&](const ShadowTree& shadowTree) {
|
||||
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
||||
});
|
||||
|
||||
cache.insert({surfaceId, rootShadowNode});
|
||||
return rootShadowNode;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
IntersectionObserverManager::IntersectionObserverManager() = default;
|
||||
|
||||
void IntersectionObserverManager::observe(
|
||||
@@ -27,50 +49,60 @@ void IntersectionObserverManager::observe(
|
||||
|
||||
// The actual observer lives in the array, so we need to create it there and
|
||||
// then get a reference. Otherwise we only update its state in a copy.
|
||||
IntersectionObserver* observer;
|
||||
IntersectionObserver* observer = nullptr;
|
||||
|
||||
// Register observer
|
||||
{
|
||||
std::unique_lock lock(observersMutex_);
|
||||
|
||||
auto& observers = observersBySurfaceId_[surfaceId];
|
||||
observers.emplace_back(IntersectionObserver{
|
||||
observers.emplace_back(std::make_unique<IntersectionObserver>(
|
||||
intersectionObserverId,
|
||||
shadowNodeFamily,
|
||||
std::move(thresholds),
|
||||
std::move(rootThresholds)});
|
||||
observer = &observers.back();
|
||||
std::move(rootThresholds)));
|
||||
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
observersPendingInitialization_.emplace_back(observers.back().get());
|
||||
}
|
||||
|
||||
observer = observers.back().get();
|
||||
}
|
||||
|
||||
// Notification of initial state.
|
||||
// Ideally, we'd have well defined event loop step to notify observers
|
||||
// (like on the Web) and we'd send the initial notification there, but as
|
||||
// we don't have it we have to run this check once and manually dispatch.
|
||||
auto& shadowTreeRegistry = uiManager.getShadowTreeRegistry();
|
||||
std::shared_ptr<const MountingCoordinator> mountingCoordinator = nullptr;
|
||||
RootShadowNode::Shared rootShadowNode = nullptr;
|
||||
shadowTreeRegistry.visit(surfaceId, [&](const ShadowTree& shadowTree) {
|
||||
mountingCoordinator = shadowTree.getMountingCoordinator();
|
||||
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
||||
});
|
||||
if (!ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
// Notification of initial state.
|
||||
// Ideally, we'd have well defined event loop step to notify observers
|
||||
// (like on the Web) and we'd send the initial notification there, but as
|
||||
// we don't have it we have to run this check once and manually dispatch.
|
||||
auto& shadowTreeRegistry = uiManager.getShadowTreeRegistry();
|
||||
std::shared_ptr<const MountingCoordinator> mountingCoordinator = nullptr;
|
||||
RootShadowNode::Shared rootShadowNode = nullptr;
|
||||
shadowTreeRegistry.visit(surfaceId, [&](const ShadowTree& shadowTree) {
|
||||
mountingCoordinator = shadowTree.getMountingCoordinator();
|
||||
rootShadowNode = shadowTree.getCurrentRevision().rootShadowNode;
|
||||
});
|
||||
|
||||
// If the surface doesn't exist for some reason, we skip initial notification.
|
||||
if (!rootShadowNode) {
|
||||
return;
|
||||
}
|
||||
// If the surface doesn't exist for some reason, we skip initial
|
||||
// notification.
|
||||
if (!rootShadowNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto hasPendingTransactions = mountingCoordinator != nullptr &&
|
||||
mountingCoordinator->hasPendingTransactions();
|
||||
auto hasPendingTransactions = mountingCoordinator != nullptr &&
|
||||
mountingCoordinator->hasPendingTransactions();
|
||||
|
||||
if (!hasPendingTransactions) {
|
||||
auto entry = observer->updateIntersectionObservation(
|
||||
*rootShadowNode, JSExecutor::performanceNow());
|
||||
if (entry) {
|
||||
{
|
||||
std::unique_lock lock(pendingEntriesMutex_);
|
||||
pendingEntries_.push_back(std::move(entry).value());
|
||||
if (!hasPendingTransactions) {
|
||||
auto entry = observer->updateIntersectionObservation(
|
||||
*rootShadowNode, JSExecutor::performanceNow());
|
||||
if (entry) {
|
||||
{
|
||||
std::unique_lock lock(pendingEntriesMutex_);
|
||||
pendingEntries_.push_back(std::move(entry).value());
|
||||
}
|
||||
notifyObserversIfNecessary();
|
||||
}
|
||||
notifyObserversIfNecessary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,6 +112,23 @@ void IntersectionObserverManager::unobserve(
|
||||
const ShadowNodeFamily::Shared& shadowNodeFamily) {
|
||||
TraceSection s("IntersectionObserverManager::unobserve");
|
||||
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
// This doesn't need to be protected by the mutex because it is only
|
||||
// accessed and modified from the JS thread.
|
||||
observersPendingInitialization_.erase(
|
||||
std::remove_if(
|
||||
observersPendingInitialization_.begin(),
|
||||
observersPendingInitialization_.end(),
|
||||
[intersectionObserverId, &shadowNodeFamily](const auto& observer) {
|
||||
return (
|
||||
observer->getIntersectionObserverId() ==
|
||||
intersectionObserverId &&
|
||||
observer->getTargetShadowNodeFamily() == shadowNodeFamily);
|
||||
}),
|
||||
observersPendingInitialization_.end());
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(observersMutex_);
|
||||
|
||||
@@ -97,9 +146,9 @@ void IntersectionObserverManager::unobserve(
|
||||
observers.begin(),
|
||||
observers.end(),
|
||||
[intersectionObserverId, &shadowNodeFamily](const auto& observer) {
|
||||
return observer.getIntersectionObserverId() ==
|
||||
return observer->getIntersectionObserverId() ==
|
||||
intersectionObserverId &&
|
||||
observer.isTargetShadowNodeFamily(*shadowNodeFamily);
|
||||
observer->getTargetShadowNodeFamily() == shadowNodeFamily;
|
||||
}),
|
||||
observers.end());
|
||||
|
||||
@@ -124,6 +173,7 @@ void IntersectionObserverManager::unobserve(
|
||||
}
|
||||
|
||||
void IntersectionObserverManager::connect(
|
||||
RuntimeScheduler& runtimeScheduler,
|
||||
UIManager& uiManager,
|
||||
std::function<void()> notifyIntersectionObserversCallback) {
|
||||
TraceSection s("IntersectionObserverManager::connect");
|
||||
@@ -135,11 +185,18 @@ void IntersectionObserverManager::connect(
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
runtimeScheduler.setIntersectionObserverDelegate(this);
|
||||
}
|
||||
uiManager.registerMountHook(*this);
|
||||
shadowTreeRegistry_ = &uiManager.getShadowTreeRegistry();
|
||||
mountHookRegistered_ = true;
|
||||
}
|
||||
|
||||
void IntersectionObserverManager::disconnect(UIManager& uiManager) {
|
||||
void IntersectionObserverManager::disconnect(
|
||||
RuntimeScheduler& runtimeScheduler,
|
||||
UIManager& uiManager) {
|
||||
TraceSection s("IntersectionObserverManager::disconnect");
|
||||
|
||||
// Fail-safe in case the caller doesn't guarantee consistency.
|
||||
@@ -147,7 +204,12 @@ void IntersectionObserverManager::disconnect(UIManager& uiManager) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ReactNativeFeatureFlags::
|
||||
enableIntersectionObserverEventLoopIntegration()) {
|
||||
runtimeScheduler.setIntersectionObserverDelegate(nullptr);
|
||||
}
|
||||
uiManager.unregisterMountHook(*this);
|
||||
shadowTreeRegistry_ = nullptr;
|
||||
mountHookRegistered_ = false;
|
||||
notifyIntersectionObserversCallback_ = nullptr;
|
||||
}
|
||||
@@ -163,6 +225,57 @@ IntersectionObserverManager::takeRecords() {
|
||||
return entries;
|
||||
}
|
||||
|
||||
#pragma mark - RuntimeSchedulerIntersectionObserverDelegate
|
||||
|
||||
void IntersectionObserverManager::updateIntersectionObservations(
|
||||
const std::unordered_set<SurfaceId>&
|
||||
surfaceIdsWithPendingRenderingUpdates) {
|
||||
// On Web, this step invoked from the Event Loop computes the intersections
|
||||
// and schedules the notifications.
|
||||
// Doing exactly the same in React Native would exclude the time it takes
|
||||
// to process these transactions and mount the operations in the host
|
||||
// platform, which would provide inaccurate timings for measuring paint time.
|
||||
// Instead, we use mount hooks to compute this.
|
||||
|
||||
// What we can use this step for is to dispatch initial notifications for
|
||||
// observers that were just set up, but for which we do not have any pending
|
||||
// transactions that would trigger the mount hooks.
|
||||
// In those cases it is ok to dispatch the notifications now, because the
|
||||
// current state is already accurate.
|
||||
|
||||
std::unordered_map<SurfaceId, RootShadowNode::Shared> rootShadowNodeCache;
|
||||
|
||||
for (auto observer : observersPendingInitialization_) {
|
||||
auto surfaceId = observer->getTargetShadowNodeFamily()->getSurfaceId();
|
||||
|
||||
// If there are pending updates, we just wait for the mount hook.
|
||||
if (surfaceIdsWithPendingRenderingUpdates.contains(surfaceId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RootShadowNode::Shared rootShadowNode =
|
||||
getRootShadowNode(surfaceId, *shadowTreeRegistry_, rootShadowNodeCache);
|
||||
|
||||
// If the surface doesn't exist for some reason, we skip initial
|
||||
// notification.
|
||||
if (!rootShadowNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto entry = observer->updateIntersectionObservation(
|
||||
*rootShadowNode, JSExecutor::performanceNow());
|
||||
if (entry) {
|
||||
{
|
||||
std::unique_lock lock(pendingEntriesMutex_);
|
||||
pendingEntries_.push_back(std::move(entry).value());
|
||||
}
|
||||
notifyObserversIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
observersPendingInitialization_.clear();
|
||||
}
|
||||
|
||||
#pragma mark - UIManagerMountHook
|
||||
|
||||
void IntersectionObserverManager::shadowTreeDidMount(
|
||||
@@ -202,9 +315,9 @@ void IntersectionObserverManager::updateIntersectionObservations(
|
||||
std::optional<IntersectionObserverEntry> entry;
|
||||
|
||||
if (rootShadowNode != nullptr) {
|
||||
entry = observer.updateIntersectionObservation(*rootShadowNode, time);
|
||||
entry = observer->updateIntersectionObservation(*rootShadowNode, time);
|
||||
} else {
|
||||
entry = observer.updateIntersectionObservationForSurfaceUnmount(time);
|
||||
entry = observer->updateIntersectionObservationForSurfaceUnmount(time);
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
|
||||
+27
-3
@@ -9,14 +9,19 @@
|
||||
|
||||
#include <react/renderer/core/ShadowNode.h>
|
||||
#include <react/renderer/graphics/Float.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeScheduler.h>
|
||||
#include <react/renderer/runtimescheduler/RuntimeSchedulerIntersectionObserverDelegate.h>
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
#include <react/renderer/uimanager/UIManagerMountHook.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include "IntersectionObserver.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class IntersectionObserverManager final : public UIManagerMountHook {
|
||||
class IntersectionObserverManager final
|
||||
: public UIManagerMountHook,
|
||||
public RuntimeSchedulerIntersectionObserverDelegate {
|
||||
public:
|
||||
IntersectionObserverManager();
|
||||
|
||||
@@ -32,13 +37,20 @@ class IntersectionObserverManager final : public UIManagerMountHook {
|
||||
const ShadowNodeFamily::Shared& shadowNode);
|
||||
|
||||
void connect(
|
||||
RuntimeScheduler& runtimeScheduler,
|
||||
UIManager& uiManager,
|
||||
std::function<void()> notifyIntersectionObserversCallback);
|
||||
|
||||
void disconnect(UIManager& uiManager);
|
||||
void disconnect(RuntimeScheduler& runtimeScheduler, UIManager& uiManager);
|
||||
|
||||
std::vector<IntersectionObserverEntry> takeRecords();
|
||||
|
||||
#pragma mark - RuntimeSchedulerIntersectionObserverDelegate
|
||||
|
||||
void updateIntersectionObservations(
|
||||
const std::unordered_set<SurfaceId>&
|
||||
surfaceIdsWithPendingRenderingUpdates) override;
|
||||
|
||||
#pragma mark - UIManagerMountHook
|
||||
|
||||
void shadowTreeDidMount(
|
||||
@@ -48,10 +60,22 @@ class IntersectionObserverManager final : public UIManagerMountHook {
|
||||
void shadowTreeDidUnmount(SurfaceId surfaceId, double time) noexcept override;
|
||||
|
||||
private:
|
||||
mutable std::unordered_map<SurfaceId, std::vector<IntersectionObserver>>
|
||||
mutable std::unordered_map<
|
||||
SurfaceId,
|
||||
std::vector<std::unique_ptr<IntersectionObserver>>>
|
||||
observersBySurfaceId_;
|
||||
mutable std::shared_mutex observersMutex_;
|
||||
|
||||
// This is defined as a list of pointers to keep the ownership of the
|
||||
// observers in the map.
|
||||
std::vector<IntersectionObserver*> observersPendingInitialization_;
|
||||
|
||||
// This is only accessed from the JS thread at the end of the event loop tick,
|
||||
// so it is safe to retain it as a raw pointer.
|
||||
// We need to retain it here because the RuntimeScheduler does not provide
|
||||
// it when calling `updateIntersectionObservations`.
|
||||
const ShadowTreeRegistry* shadowTreeRegistry_{nullptr};
|
||||
|
||||
mutable std::function<void()> notifyIntersectionObserversCallback_;
|
||||
|
||||
mutable std::vector<IntersectionObserverEntry> pendingEntries_;
|
||||
|
||||
@@ -126,4 +126,11 @@ void RuntimeScheduler::setEventTimingDelegate(
|
||||
return runtimeSchedulerImpl_->setEventTimingDelegate(eventTimingDelegate);
|
||||
}
|
||||
|
||||
void RuntimeScheduler::setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) {
|
||||
return runtimeSchedulerImpl_->setIntersectionObserverDelegate(
|
||||
intersectionObserverDelegate);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <react/renderer/runtimescheduler/Task.h>
|
||||
#include <react/timing/primitives.h>
|
||||
#include "RuntimeSchedulerEventTimingDelegate.h"
|
||||
#include "RuntimeSchedulerIntersectionObserverDelegate.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
@@ -60,6 +61,9 @@ class RuntimeSchedulerBase {
|
||||
PerformanceEntryReporter* reporter) = 0;
|
||||
virtual void setEventTimingDelegate(
|
||||
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) = 0;
|
||||
virtual void setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) = 0;
|
||||
};
|
||||
|
||||
// This is a proxy for RuntimeScheduler implementation, which will be selected
|
||||
@@ -172,6 +176,10 @@ class RuntimeScheduler final : RuntimeSchedulerBase {
|
||||
void setEventTimingDelegate(
|
||||
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) override;
|
||||
|
||||
void setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) override;
|
||||
|
||||
private:
|
||||
// Actual implementation, stored as a unique pointer to simplify memory
|
||||
// management.
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
using SurfaceId = int32_t;
|
||||
|
||||
class RuntimeSchedulerIntersectionObserverDelegate {
|
||||
public:
|
||||
virtual ~RuntimeSchedulerIntersectionObserverDelegate() = default;
|
||||
|
||||
virtual void updateIntersectionObservations(
|
||||
const std::unordered_set<SurfaceId>&
|
||||
surfaceIdsWithPendingRenderingUpdates) = 0;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+6
@@ -204,6 +204,12 @@ void RuntimeScheduler_Legacy::setEventTimingDelegate(
|
||||
// No-op in the legacy scheduler
|
||||
}
|
||||
|
||||
void RuntimeScheduler_Legacy::setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
/*intersectionObserverDelegate*/) {
|
||||
// No-op in the legacy scheduler
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
void RuntimeScheduler_Legacy::scheduleWorkLoopIfNecessary() {
|
||||
|
||||
+4
@@ -134,6 +134,10 @@ class RuntimeScheduler_Legacy final : public RuntimeSchedulerBase {
|
||||
void setEventTimingDelegate(
|
||||
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) override;
|
||||
|
||||
void setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) override;
|
||||
|
||||
private:
|
||||
std::priority_queue<
|
||||
std::shared_ptr<Task>,
|
||||
|
||||
+15
@@ -225,6 +225,12 @@ void RuntimeScheduler_Modern::setEventTimingDelegate(
|
||||
eventTimingDelegate_ = eventTimingDelegate;
|
||||
}
|
||||
|
||||
void RuntimeScheduler_Modern::setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) {
|
||||
intersectionObserverDelegate_ = intersectionObserverDelegate;
|
||||
}
|
||||
|
||||
#pragma mark - Private
|
||||
|
||||
void RuntimeScheduler_Modern::scheduleTask(std::shared_ptr<Task> task) {
|
||||
@@ -347,11 +353,20 @@ void RuntimeScheduler_Modern::runEventLoopTick(
|
||||
void RuntimeScheduler_Modern::updateRendering() {
|
||||
TraceSection s("RuntimeScheduler::updateRendering");
|
||||
|
||||
// This is the integration of the Event Timing API in the Event Loop.
|
||||
// See https://w3c.github.io/event-timing/#sec-modifications-HTML
|
||||
if (eventTimingDelegate_ != nullptr) {
|
||||
eventTimingDelegate_->dispatchPendingEventTimingEntries(
|
||||
surfaceIdsWithPendingRenderingUpdates_);
|
||||
}
|
||||
|
||||
// This is the integration of the Intersection Observer API in the Event Loop.
|
||||
// See
|
||||
if (intersectionObserverDelegate_ != nullptr) {
|
||||
intersectionObserverDelegate_->updateIntersectionObservations(
|
||||
surfaceIdsWithPendingRenderingUpdates_);
|
||||
}
|
||||
|
||||
surfaceIdsWithPendingRenderingUpdates_.clear();
|
||||
|
||||
while (!pendingRenderingUpdates_.empty()) {
|
||||
|
||||
+6
@@ -151,6 +151,10 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
|
||||
void setEventTimingDelegate(
|
||||
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) override;
|
||||
|
||||
void setIntersectionObserverDelegate(
|
||||
RuntimeSchedulerIntersectionObserverDelegate*
|
||||
intersectionObserverDelegate) override;
|
||||
|
||||
private:
|
||||
std::atomic<uint_fast8_t> syncTaskRequests_{0};
|
||||
|
||||
@@ -229,6 +233,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase {
|
||||
|
||||
PerformanceEntryReporter* performanceEntryReporter_{nullptr};
|
||||
RuntimeSchedulerEventTimingDelegate* eventTimingDelegate_{nullptr};
|
||||
RuntimeSchedulerIntersectionObserverDelegate* intersectionObserverDelegate_{
|
||||
nullptr};
|
||||
|
||||
RuntimeSchedulerTaskErrorHandler onTaskError_;
|
||||
};
|
||||
|
||||
@@ -247,6 +247,17 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableIntersectionObserverEventLoopIntegration: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
dateAdded: '2025-04-16',
|
||||
description:
|
||||
'Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
enableLayoutAnimationsOnAndroid: {
|
||||
defaultValue: false,
|
||||
metadata: {
|
||||
|
||||
@@ -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<<29691b2062c240377834a01ced24c71c>>
|
||||
* @generated SignedSource<<faa532fc9396b30f13cb55f59fbe7f8e>>
|
||||
* @flow strict
|
||||
* @noformat
|
||||
*/
|
||||
@@ -70,6 +70,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{
|
||||
enableFixForParentTagDuringReparenting: Getter<boolean>,
|
||||
enableFontScaleChangesUpdatingLayout: Getter<boolean>,
|
||||
enableIOSViewClipToPaddingBox: Getter<boolean>,
|
||||
enableIntersectionObserverEventLoopIntegration: Getter<boolean>,
|
||||
enableLayoutAnimationsOnAndroid: Getter<boolean>,
|
||||
enableLayoutAnimationsOnIOS: Getter<boolean>,
|
||||
enableMainQueueModulesOnIOS: Getter<boolean>,
|
||||
@@ -261,6 +262,10 @@ export const enableFontScaleChangesUpdatingLayout: Getter<boolean> = createNativ
|
||||
* iOS Views will clip to their padding box vs border box
|
||||
*/
|
||||
export const enableIOSViewClipToPaddingBox: Getter<boolean> = createNativeFlagGetter('enableIOSViewClipToPaddingBox', false);
|
||||
/**
|
||||
* Integrates IntersectionObserver in the Event Loop in the new architecture, to dispatch the initial notifications for observations in the "Update the rendering" step.
|
||||
*/
|
||||
export const enableIntersectionObserverEventLoopIntegration: Getter<boolean> = createNativeFlagGetter('enableIntersectionObserverEventLoopIntegration', false);
|
||||
/**
|
||||
* When enabled, LayoutAnimations API will animate state changes on Android.
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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<<59ebee6019b99a8c6a29dfc1eb036194>>
|
||||
* @generated SignedSource<<ddb43daeb855838f8d396dfa952e9933>>
|
||||
* @flow strict
|
||||
* @noformat
|
||||
*/
|
||||
@@ -44,6 +44,7 @@ export interface Spec extends TurboModule {
|
||||
+enableFixForParentTagDuringReparenting?: () => boolean;
|
||||
+enableFontScaleChangesUpdatingLayout?: () => boolean;
|
||||
+enableIOSViewClipToPaddingBox?: () => boolean;
|
||||
+enableIntersectionObserverEventLoopIntegration?: () => boolean;
|
||||
+enableLayoutAnimationsOnAndroid?: () => boolean;
|
||||
+enableLayoutAnimationsOnIOS?: () => boolean;
|
||||
+enableMainQueueModulesOnIOS?: () => boolean;
|
||||
|
||||
@@ -27,10 +27,11 @@ surface. If the intersection triggers a state change, the notification is
|
||||
dispatched to the JavaScript observers.
|
||||
|
||||
For the initial intersection notification (which is meant to report the state at
|
||||
the time of observation), this system checks if there are pending transactions
|
||||
for the given surface. If there are, we just wait for the transaction to be
|
||||
mounted and use the mount hook to report the initial notification. If there
|
||||
aren't, we dispatch the notification immediately.
|
||||
the time of observation), we run a step in the Event Loop as specified in the
|
||||
Web spec (via `RuntimeSchedulerIntersectionObserverDelegate`), where we check if
|
||||
there are pending transactions for the given surface. If there are, we just wait
|
||||
for the transaction to be mounted and use the mount hook to report the initial
|
||||
notification. If there aren't, we dispatch the notification immediately.
|
||||
|
||||
## 🔗 Relationship with other systems
|
||||
|
||||
|
||||
+3
-2
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 84 KiB |
+51
-1
@@ -7,6 +7,7 @@
|
||||
* @flow strict-local
|
||||
* @format
|
||||
* @fantom_flags utilizeTokensInIntersectionObserver:true
|
||||
* @fantom_flags enableIntersectionObserverEventLoopIntegration:true
|
||||
*/
|
||||
|
||||
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
|
||||
@@ -18,7 +19,7 @@ import ensureInstance from '../../../__tests__/utilities/ensureInstance';
|
||||
import {createShadowNodeReferenceCountingRef} from '../../../__tests__/utilities/ShadowNodeReferenceCounter';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import * as React from 'react';
|
||||
import {createRef} from 'react';
|
||||
import {createRef, useState} from 'react';
|
||||
import {ScrollView, View} from 'react-native';
|
||||
import setUpIntersectionObserver from 'react-native/src/private/setup/setUpIntersectionObserver';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
@@ -882,6 +883,55 @@ describe('IntersectionObserver', () => {
|
||||
expect(getReferenceCount()).toBe(0);
|
||||
});
|
||||
|
||||
it('should NOT report multiple entries when observing a target that exists and we modify it later in the same tick', () => {
|
||||
const root = Fantom.createRoot({
|
||||
viewportWidth: 1000,
|
||||
viewportHeight: 1000,
|
||||
});
|
||||
|
||||
const nodeRef = createRef<HostInstance>();
|
||||
|
||||
function TestComponent() {
|
||||
const [showView, setShowView] = useState(true);
|
||||
|
||||
return showView ? (
|
||||
<View
|
||||
onClick={() => {
|
||||
observer.observe(ensureReactNativeElement(nodeRef.current));
|
||||
setShowView(false);
|
||||
}}
|
||||
style={{width: 100, height: 100, backgroundColor: 'red'}}
|
||||
ref={nodeRef}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<TestComponent />);
|
||||
});
|
||||
|
||||
const node = ensureReactNativeElement(nodeRef.current);
|
||||
|
||||
expect(node.isConnected).toBe(true);
|
||||
|
||||
const intersectionObserverCallback = jest.fn();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
observer = new IntersectionObserver(intersectionObserverCallback);
|
||||
});
|
||||
|
||||
// We use a discrete event to make sure React processes the update in the
|
||||
// same task.
|
||||
Fantom.dispatchNativeEvent(node, 'click');
|
||||
|
||||
expect(node.isConnected).toBe(false);
|
||||
|
||||
expect(intersectionObserverCallback).toHaveBeenCalledTimes(1);
|
||||
const [entries] = intersectionObserverCallback.mock.lastCall;
|
||||
expect(entries.length).toBe(1);
|
||||
expect(entries[0].isIntersecting).toBe(false);
|
||||
});
|
||||
|
||||
describe('rootThreshold', () => {
|
||||
it('should report partial intersecting initial state correctly', () => {
|
||||
const nodeRef = createRef<HostInstance>();
|
||||
|
||||
Reference in New Issue
Block a user