From 3aeba22ddabd7ae5d8242fb91edfd8012b85a1da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rub=C3=A9n=20Norte?= Date: Tue, 20 May 2025 05:40:00 -0700 Subject: [PATCH] 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 --- .../featureflags/ReactNativeFeatureFlags.kt | 8 +- .../ReactNativeFeatureFlagsCxxAccessor.kt | 12 +- .../ReactNativeFeatureFlagsCxxInterop.kt | 4 +- .../ReactNativeFeatureFlagsDefaults.kt | 4 +- .../ReactNativeFeatureFlagsLocalAccessor.kt | 13 +- .../ReactNativeFeatureFlagsProvider.kt | 4 +- .../JReactNativeFeatureFlagsCxxInterop.cpp | 16 +- .../JReactNativeFeatureFlagsCxxInterop.h | 5 +- .../featureflags/ReactNativeFeatureFlags.cpp | 6 +- .../featureflags/ReactNativeFeatureFlags.h | 7 +- .../ReactNativeFeatureFlagsAccessor.cpp | 78 +++++--- .../ReactNativeFeatureFlagsAccessor.h | 6 +- .../ReactNativeFeatureFlagsDefaults.h | 6 +- .../ReactNativeFeatureFlagsDynamicProvider.h | 11 +- .../ReactNativeFeatureFlagsProvider.h | 3 +- .../NativeReactNativeFeatureFlags.cpp | 7 +- .../NativeReactNativeFeatureFlags.h | 4 +- .../NativeIntersectionObserver.cpp | 11 +- .../NativeIntersectionObserver.h | 2 +- .../intersection/IntersectionObserver.h | 6 +- .../IntersectionObserverManager.cpp | 181 ++++++++++++++---- .../IntersectionObserverManager.h | 30 ++- .../runtimescheduler/RuntimeScheduler.cpp | 7 + .../runtimescheduler/RuntimeScheduler.h | 8 + ...imeSchedulerIntersectionObserverDelegate.h | 25 +++ .../RuntimeScheduler_Legacy.cpp | 6 + .../RuntimeScheduler_Legacy.h | 4 + .../RuntimeScheduler_Modern.cpp | 15 ++ .../RuntimeScheduler_Modern.h | 6 + .../ReactNativeFeatureFlags.config.js | 11 ++ .../featureflags/ReactNativeFeatureFlags.js | 7 +- .../specs/NativeReactNativeFeatureFlags.js | 3 +- .../intersectionobserver/__docs__/README.md | 9 +- .../__docs__/architecture.excalidraw.svg | 5 +- .../__tests__/IntersectionObserver-itest.js | 52 ++++- 35 files changed, 481 insertions(+), 101 deletions(-) create mode 100644 packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerIntersectionObserverDelegate.h 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 5fc4a09e63d..96b3f40988a 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<> + * @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. */ 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 b93b0ad580d..9e8b53f411a 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<<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) { 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 2ec9c166d88..6b8f3a264f8 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<> + * @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 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 86d3e2ace7d..b9ed606f27a 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<<7f357475254104729cc7910c14e1c1fb>> + * @generated SignedSource<> */ /** @@ -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 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 0ef3d9aae20..99153fc2adb 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<<14cd1a58bd153dedda045a72c1494caa>> + * @generated SignedSource<> */ /** @@ -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) { 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 8de12fbab66..508199f083b 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<<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 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 a3da5669db5..2875ddf0278 100644 --- a/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp +++ b/packages/react-native/ReactAndroid/src/main/jni/react/featureflags/JReactNativeFeatureFlagsCxxInterop.cpp @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<93037428475b39d3f2c208e1326b3e86>> */ /** @@ -153,6 +153,12 @@ class ReactNativeFeatureFlagsJavaProvider return method(javaProvider_); } + bool enableIntersectionObserverEventLoopIntegration() override { + static const auto method = + getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableIntersectionObserverEventLoopIntegration"); + return method(javaProvider_); + } + bool enableLayoutAnimationsOnAndroid() override { static const auto method = getReactNativeFeatureFlagsProviderJavaClass()->getMethod("enableLayoutAnimationsOnAndroid"); @@ -426,6 +432,11 @@ bool JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox( return ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox(); } +bool JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration( + facebook::jni::alias_ref /*unused*/) { + return ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration(); +} + bool JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid( facebook::jni::alias_ref /*unused*/) { return ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid(); @@ -659,6 +670,9 @@ void JReactNativeFeatureFlagsCxxInterop::registerNatives() { makeNativeMethod( "enableIOSViewClipToPaddingBox", JReactNativeFeatureFlagsCxxInterop::enableIOSViewClipToPaddingBox), + makeNativeMethod( + "enableIntersectionObserverEventLoopIntegration", + JReactNativeFeatureFlagsCxxInterop::enableIntersectionObserverEventLoopIntegration), makeNativeMethod( "enableLayoutAnimationsOnAndroid", JReactNativeFeatureFlagsCxxInterop::enableLayoutAnimationsOnAndroid), 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 5bf284c848e..8eac16b566b 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<> + * @generated SignedSource<> */ /** @@ -87,6 +87,9 @@ class JReactNativeFeatureFlagsCxxInterop static bool enableIOSViewClipToPaddingBox( facebook::jni::alias_ref); + static bool enableIntersectionObserverEventLoopIntegration( + facebook::jni::alias_ref); + static bool enableLayoutAnimationsOnAndroid( 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 2e1048c0193..550ab56014d 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<<88fdbea2f97f628187164a47a9737da0>> + * @generated SignedSource<> */ /** @@ -102,6 +102,10 @@ bool ReactNativeFeatureFlags::enableIOSViewClipToPaddingBox() { return getAccessor().enableIOSViewClipToPaddingBox(); } +bool ReactNativeFeatureFlags::enableIntersectionObserverEventLoopIntegration() { + return getAccessor().enableIntersectionObserverEventLoopIntegration(); +} + bool ReactNativeFeatureFlags::enableLayoutAnimationsOnAndroid() { return getAccessor().enableLayoutAnimationsOnAndroid(); } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h index 2426a0f371c..bccadbae54f 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlags.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<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. */ diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.cpp index d57e49cc52f..606fe8c01a6 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<<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; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsAccessor.h index bfabf21872d..8f1f9f0311c 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<<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 currentProvider_; bool wasOverridden_; - std::array, 48> accessedFeatureFlags_; + std::array, 49> accessedFeatureFlags_; std::atomic> commonTestFlag_; std::atomic> animatedShouldSignalBatch_; @@ -112,6 +113,7 @@ class ReactNativeFeatureFlagsAccessor { std::atomic> enableFixForParentTagDuringReparenting_; std::atomic> enableFontScaleChangesUpdatingLayout_; std::atomic> enableIOSViewClipToPaddingBox_; + std::atomic> enableIntersectionObserverEventLoopIntegration_; std::atomic> enableLayoutAnimationsOnAndroid_; std::atomic> enableLayoutAnimationsOnIOS_; std::atomic> enableMainQueueModulesOnIOS_; diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDefaults.h index 2e631fa15b0..f14b3f91fc1 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<<829c85b56fbeaf01ece2d1dd84ecde8e>> + * @generated SignedSource<> */ /** @@ -103,6 +103,10 @@ class ReactNativeFeatureFlagsDefaults : public ReactNativeFeatureFlagsProvider { return false; } + bool enableIntersectionObserverEventLoopIntegration() override { + return false; + } + bool enableLayoutAnimationsOnAndroid() override { return false; } diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h index 032e8980c65..dca5dd84636 100644 --- a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h +++ b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsDynamicProvider.h @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<> + * @generated SignedSource<<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()) { diff --git a/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h b/packages/react-native/ReactCommon/react/featureflags/ReactNativeFeatureFlagsProvider.h index 6ebab81a832..d287951bd93 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<<25e38ea9e85a704f2f5ab6c7b8cb3062>> + * @generated SignedSource<> */ /** @@ -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; diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.cpp index 1da8bd98487..6bf86650527 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<<3c85cdc2dc3d2368d09a5c0aff4d083a>> + * @generated SignedSource<> */ /** @@ -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(); diff --git a/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h b/packages/react-native/ReactCommon/react/nativemodule/featureflags/NativeReactNativeFeatureFlags.h index 3870d136f33..ff5cf3f2c45 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<> + * @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); diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp index a24ea429097..9b54c3a5abd 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.cpp @@ -7,6 +7,7 @@ #include "NativeIntersectionObserver.h" #include +#include #include #include @@ -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 @@ -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, diff --git a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h index 711ebd1584e..9ea6487b9f7 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/nativemodule/intersectionobserver/NativeIntersectionObserver.h @@ -97,7 +97,7 @@ class NativeIntersectionObserver static UIManager& getUIManagerFromRuntime(jsi::Runtime& runtime); static NativeIntersectionObserverEntry convertToNativeModuleEntry( - IntersectionObserverEntry entry, + const IntersectionObserverEntry& entry, jsi::Runtime& runtime); }; diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h index 89b45d83b65..d5dd24174af 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -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 getThresholds() const { diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp index e2ac61ae1d2..4725628b898 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -8,11 +8,33 @@ #include "IntersectionObserverManager.h" #include #include +#include #include #include "IntersectionObserver.h" namespace facebook::react { +namespace { +RootShadowNode::Shared getRootShadowNode( + SurfaceId surfaceId, + const ShadowTreeRegistry& shadowTreeRegistry, + std::unordered_map& 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( 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 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 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 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& + 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 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 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) { diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h index af65a599203..9331bcfef67 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -9,14 +9,19 @@ #include #include +#include +#include #include #include +#include #include #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 notifyIntersectionObserversCallback); - void disconnect(UIManager& uiManager); + void disconnect(RuntimeScheduler& runtimeScheduler, UIManager& uiManager); std::vector takeRecords(); +#pragma mark - RuntimeSchedulerIntersectionObserverDelegate + + void updateIntersectionObservations( + const std::unordered_set& + 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> + mutable std::unordered_map< + SurfaceId, + std::vector>> 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 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 notifyIntersectionObserversCallback_; mutable std::vector pendingEntries_; diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp index 528ddf026c6..f7393dbdd52 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.cpp @@ -126,4 +126,11 @@ void RuntimeScheduler::setEventTimingDelegate( return runtimeSchedulerImpl_->setEventTimingDelegate(eventTimingDelegate); } +void RuntimeScheduler::setIntersectionObserverDelegate( + RuntimeSchedulerIntersectionObserverDelegate* + intersectionObserverDelegate) { + return runtimeSchedulerImpl_->setIntersectionObserverDelegate( + intersectionObserverDelegate); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h index 6ddd496ef00..813537ec7ae 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler.h @@ -14,6 +14,7 @@ #include #include #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. diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerIntersectionObserverDelegate.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerIntersectionObserverDelegate.h new file mode 100644 index 00000000000..be58f754aaa --- /dev/null +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeSchedulerIntersectionObserverDelegate.h @@ -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 + +namespace facebook::react { + +using SurfaceId = int32_t; + +class RuntimeSchedulerIntersectionObserverDelegate { + public: + virtual ~RuntimeSchedulerIntersectionObserverDelegate() = default; + + virtual void updateIntersectionObservations( + const std::unordered_set& + surfaceIdsWithPendingRenderingUpdates) = 0; +}; + +} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp index 24b2ae0ae9f..f9f2f223236 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.cpp @@ -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() { diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h index 15e7fb74e73..3856a0645ba 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Legacy.h @@ -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, diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp index 9f90a6bb21f..10fef3e9831 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.cpp @@ -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) { @@ -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()) { diff --git a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h index e26eb7e38a2..b0616200351 100644 --- a/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h +++ b/packages/react-native/ReactCommon/react/renderer/runtimescheduler/RuntimeScheduler_Modern.h @@ -151,6 +151,10 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase { void setEventTimingDelegate( RuntimeSchedulerEventTimingDelegate* eventTimingDelegate) override; + void setIntersectionObserverDelegate( + RuntimeSchedulerIntersectionObserverDelegate* + intersectionObserverDelegate) override; + private: std::atomic syncTaskRequests_{0}; @@ -229,6 +233,8 @@ class RuntimeScheduler_Modern final : public RuntimeSchedulerBase { PerformanceEntryReporter* performanceEntryReporter_{nullptr}; RuntimeSchedulerEventTimingDelegate* eventTimingDelegate_{nullptr}; + RuntimeSchedulerIntersectionObserverDelegate* intersectionObserverDelegate_{ + nullptr}; RuntimeSchedulerTaskErrorHandler onTaskError_; }; diff --git a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js index 36b17fbcf43..19c3e1eafef 100644 --- a/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js +++ b/packages/react-native/scripts/featureflags/ReactNativeFeatureFlags.config.js @@ -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: { diff --git a/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/ReactNativeFeatureFlags.js index 5bc924dec8f..38fc614a8a2 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<<29691b2062c240377834a01ced24c71c>> + * @generated SignedSource<> * @flow strict * @noformat */ @@ -70,6 +70,7 @@ export type ReactNativeFeatureFlags = $ReadOnly<{ enableFixForParentTagDuringReparenting: Getter, enableFontScaleChangesUpdatingLayout: Getter, enableIOSViewClipToPaddingBox: Getter, + enableIntersectionObserverEventLoopIntegration: Getter, enableLayoutAnimationsOnAndroid: Getter, enableLayoutAnimationsOnIOS: Getter, enableMainQueueModulesOnIOS: Getter, @@ -261,6 +262,10 @@ export const enableFontScaleChangesUpdatingLayout: Getter = createNativ * iOS Views will clip to their padding box vs border box */ export const enableIOSViewClipToPaddingBox: Getter = 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 = createNativeFlagGetter('enableIntersectionObserverEventLoopIntegration', false); /** * When enabled, LayoutAnimations API will animate state changes on Android. */ diff --git a/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js b/packages/react-native/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js index 48f06fd308b..394143bc10e 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<<59ebee6019b99a8c6a29dfc1eb036194>> + * @generated SignedSource<> * @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; diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__docs__/README.md b/packages/react-native/src/private/webapis/intersectionobserver/__docs__/README.md index cf3e27dc8ae..73428ea06b7 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__docs__/README.md +++ b/packages/react-native/src/private/webapis/intersectionobserver/__docs__/README.md @@ -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 diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__docs__/architecture.excalidraw.svg b/packages/react-native/src/private/webapis/intersectionobserver/__docs__/architecture.excalidraw.svg index 3f2d15a1ee2..4e926c7676a 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__docs__/architecture.excalidraw.svg +++ b/packages/react-native/src/private/webapis/intersectionobserver/__docs__/architecture.excalidraw.svg @@ -1,4 +1,5 @@ -eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO19WXPiWrL1e/9cbkfdx9vo7J177ojvwbPxPE9f3HBgwFx1MDAwNpvJgMdcdTAwMWL932+m7DJcdTAwMDJcclx1MDAxNlx1MDAwMmxxqn26qstcYqSNtFfmyvl//7Gw8Kv/2qn++tfCr+pLudSoV7ql51//pNefqt1evd3CQ+D/3ms/dsv+O2v9fqf3r7/+XHUwMDFhfMIrt5vvn6o2qs1qq9/D9/1//H1h4X/9v1x1MDAwM9fpVsv9Uuu2UfU/4Fx1MDAxZlx1MDAxYVxcSls1+upuu+Vf1kiwwjkrP99Q763g5frVXG5cdTAwMWW9KTV61cFcdTAwMTF66ddcdTAwMWQv3+9cdTAwMWZtLK1cdTAwMTZg5aBx1b58uN98XHUwMDFlXFz1pt5oXHUwMDFj9V9cdTAwMWLv36lUrj12XHUwMDAza+r1u+376lm90q/hcT7y+ufnem28XHUwMDAzg09124+3tVa111x1MDAxYvpMu1Mq1/uv9Fx1MDAxYWOfr77fhH8tXGZeecHflDOe5NYozfBv4IOz0OdcdTAwMGJae1YxXHUwMDBihlx1MDAxYot3RMHIwpbbjXaXXHUwMDE29l/M/1x1MDAxOSztulS+v8X1tSqf7+l3S61ep9TFXHUwMDA3Nnjf88dXXHUwMDA2ozzLXHUwMDA0Z5Iry6VcdTAwMTOf76hV67e1Pn1cdTAwMWLJPK6NlZJzXHUwMDA2xunBw+tV/cfCLVx1MDAxZXJWm8E6aVxynWLF3yH/M3hcdTAwMTjdUrNapI+0XHUwMDFlXHUwMDFijeD9bFU+7ufQgWs6sDq62YJcdTAwMWIusFx1MDAxMdjp0259ZXGttLt0a1xmq2yZ+7vG5zdcdTAwMWXanaVut/386/PIv/+ZdN6DRXF+/bDcPH89f1xc66/1VqvQfp7CeZfM0cnrUb3GmvdcdTAwMTcn7vSt27l+sFM472pr5e6O9zs3y/v7rYO3zdfHndOTKZz36ab0elqr7O5Vdjd22aWuvTztXHUwMDE3p3DeSrXZaCy/Xdi9fqfQ3tk4XHUwMDEwNzVId96Pf1xyNthjp1J6XHUwMDE3XHUwMDE13EjJJDCH/9Ofx1x1MDAxYvXW/egma7TL91HSpVWpvvhcdTAwMTdl71f8+Fx1MDAxNiFR16++9KOknHBs9NXfUo5ra1xmR+xcdTAwMGZW9pWY2zraOrrZt8XDXHUwMDBi17u8Xu08tW43X/Iu5rRcdTAwMTWeXHUwMDE0WjLNnERcdTAwMTEykFx1MDAwZr6Yc1x1MDAxZWjtmOIgNEpcdTAwMTbQI1x1MDAwYpuemONMe8w4J/EvZVG9XGb0y6ecg7BcXMNVXHUwMDBiZ1x1MDAxOLOzlmsz2cE37Vb/qP5Gj1x1MDAwNdjQq2ulZr3xOvRY/V2Mt/GkuFNqlW6r3V9Dh1x1MDAxNlx1MDAxYvVb2tS/XHUwMDFh1Zvh3d6vIz/4PNxvd1x1MDAwNkfLeKlSvVXthm9Nu1u/rbdKjeP4y+I3rW58KiEv8HiuS70qXHUwMDFk9VVPXHUwMDA0YPngJKXHfvuw2nu/XHUwMDBi/e5jNVx1MDAxMcrv8iWKsTg++upvLON+MsC0XHIs5Fx1MDAwYignK6qcQtlcYoSyXHUwMDAzay1IXHJCXHUwMDBlnlx1MDAwNn3eWs9cdTAwMTmpOGJYKFByZFnTXHUwMDAzsvA4XHUwMDEyXHUwMDE1w4EhY9GBVVxmYFxm0pNKc6tcdTAwMDRcIp2zXHUwMDEwqi1jXFxcdTAwMTg7p6Du9Uvd/lx1MDAxMu71eutcdTAwMTZcdTAwMGZcdTAwMGX06W9SXkzBin0xUH6kVTKPXHUwMDAxPjYnXHUwMDFk3lDplHBCXHUwMDA33nZb6tCqlaeVUYxcdIE8z+JcdTAwMDc+3vGp2X9VW5WvV/V6sc1cdTAwMGVPN5abu7tn9/tm63n/afs2ZlW4XHUwMDE4hk9JoP5AfmqVXG6vKrSIRqnXX243m/U+3u39dr3VXHUwMDFmvav+7VskmNeqpdCzxS9cdTAwMTE8NipcdTAwMGY6dMZhXHUwMDFlOvjXwlx1MDAwMDD+L5///p9/Rr47bifTT2hcdTAwMGZcdTAwMGZOXHUwMDE2ojxcdTAwMDOxXHUwMDA3yTwl0SQzXHUwMDAx5j9KVtBQcfgsdHpcdJf8nIcl3Iik+jGLzIAnpNFcbo1cdTAwMTlDJGFIvlx0LjztkKJcdTAwMTgpmLDMTFwi4P7rxv+JXHUwMDEwbqA8J5XgXHUwMDEwIdc4XHUwMDFhhUI5YcI0RVxiXCKTcuZcdTAwMDItmsVH8uDAZjjbWi8/di5Xr29cdTAwMWF3WzeFt9dcciFP01x1MDAxYVx1MDAwN0W+7czKXHUwMDAxu1675J2d7WLharO3NVx1MDAwNaNjVsaiXFw/aj6KXHUwMDFlY0tHNVx1MDAxMCeFTvHgvvknXHUwMDE4SVwim5Fk4olcdTAwMTVwLlx1MDAwMe0kN7BcdTAwMWG+kjvRWy3nzEo75Vx0gapXo7SHkOSx0lx1MDAxM1x1MDAwNkg3Ty55XHUwMDEyqZVxyOGYYdHiJ2xcdTAwMWTh81x1MDAwMclcdTAwMWPXP06k3LdZR0flWrXy2Ii1jsq45ODBkH3UrFcqQVxyPGxcIn2lOEetpojlpLOaXHUwMDA2d2yAYJnVakokXHUwMDE2XFxJXHUwMDE4ffmTWZA/UXFcdTAwMTe4/18hfH/RbZli+fRi82L9fKlWWW9V7lrzzCxcZnOeJoaruXJaXG4xsq5cdTAwMDG+VdlJVvleZmGEQZD9lF/3K2bRvTw6X+ssXVwiXFy2d1x1MDAxZe6fXHUwMDFle9fnVz/NLEqXldL10uvjW29nSYvl25OH8uVDjpnF7ulxr31cdTAwMDdH56eX65VGqd5cXFur7OeJWahszFx1MDAwMlx1MDAwNY9cdTAwMWR9+VPwXHUwMDE4XHUwMDE0PdqNwSyit1rOmVx1MDAwNepyjyGupVIuQvJYhnJcdCmD0JNLnkRmocmaTUsrOGhmpFx1MDAxMVx1MDAwMVx1MDAxN+2P0Irg7Z01rfjtOZlccqn4QmeOkorQYrJTXG49dUcsXGKjR1/+jWrpODJoJdJcdTAwMDdVknVALtmEXHUwMDExqKyFNtxxUs2D7/rq31x1MDAwMeVZgZLNWMGdYm5cIkdsPJvwLFx1MDAxYSvGOim4QrkyWMRcdTAwMDDXjHvaXHUwMDFhXHUwMDAwNFx1MDAwZjh3Ulx1MDAwN+7QXHUwMDA3zrXGT1x1MDAwM/y9/bCpPZ5cdTAwMDVyeVx1MDAxYW2kM1IzvH145yDwvk9HrDFWOSVcdTAwMTneeVx1MDAwNvy3Z3BMR2yySFx1MDAxOFlcdTAwMTZYNE8tbjy0RVx1MDAwNZdShJdcdTAwMDVcdTAwMWXa6sJpLTV+XHUwMDAxbURoWfPkmi3EbnD6XHRv7cH5QoxoIFx1MDAwYs0k3llcdTAwMWXrnZXaWdTwY4Sfrl21UemaVmFz7f5ccm5hvXR6sZd3KoOswHnWOiGUXHUwMDE2YCRcdTAwMWb4Qd4zZvCRMcJcdTAwMDOAU3i7xOxiyUB0hqIlllx1MDAxYudMpEUlpMe01oAr4kjAXHUwMDAy+TtcdTAwMWbCTyFcdTAwMWLCv9SPZszMKrOloFdX96uLXHUwMDFiZ6rUuX3YXls8PLkv/GzGTFximDMxVWw2U0Wx2Hw4jlx1MDAxMsjKoG//K3g/r97K2zosrW5cdTAwMWaLSmtjsdN56z/mXHUwMDFl3kJcblx1MDAwZsHENEJcXCqph3nNd2aKcO2hyLfInpBBpfaFMmeZRktqPslMNl9orVRpP1x1MDAxZner1Vx1MDAxOLtlRqlcIlHXzZ4r4qZuonCI93hKtINcdTAwMDVwmT69NVlG51x1MDAxNM7WgodsXHUwMDExXHUwMDEwz4hmJLRDaFx1MDAwNuEhadJcbkFcdTAwMGaozqWcXUxDXHUwMDE466GZpFx1MDAxNFhOeStRXHQjzPN5rbPacaRcdTAwMTmCXHUwMDA1jKbfeWDIf1x1MDAxOaFsLtE9/ZRcdTAwMTFAXHUwMDEzXHUwMDA0XHUwMDFjcl/pkONcZpK1P1xyXHUwMDAy51FOXHUwMDAzxztvXGalN4TMgVRWSjJTXHUwMDFktlJcdTAwMDRzjEtjtXCSg1x1MDAwYtso1kO1glxuhFs0nHD/ydCa5slEid/Y/sdDW3pwvlx1MDAxMFx1MDAxM1x1MDAxYcjCxYye1lx1MDAwMGJC9IW2h1x1MDAwNpXeJ7Or2q2L+ubaXuHkxJb03vrl+c3lVH0ylVKvVp0ufUHN72niL9Ip/L6BXHUwMDFi4sdwjWdBg7BKXHUwMDE5pVxcQFx1MDAwZk3TK6PRXGLnXHUwMDBl6Vx1MDAxM7OQOogruNZMqzmVbJl4S9mH+PdyltFrZucrS1n5Slx1MDAxY3rBxbJcdTAwMTVcdTAwMTSS0ipt04dnr29fas/LR52l88eDxf1cdTAwMWLXXmusxIVJcuJQJYdcdTAwMTaSXHUwMDE1ym4kblx1MDAxNqTw/lx0NPOMQJhYRXtVsfgwySTgldxzyjmptLGGXHUwMDA3XHUwMDFkt0mhXHUwMDEyiVJeXHUwMDE5XGJ8x789ejuPvdr3Ynf4itmRuzz9YFxiXHUwMDEzbvTl39hVTFx1MDAwMTlsU0M32WmTT+hcIlg9YzRqPMGQ5IhhO8Nyj0vOhWXMIFx1MDAwM1x04me6wVx1MDAxMFx1MDAwNlx1MDAxY8knMjHErVx1MDAxZFx1MDAxODOBlHS0eJRTQuNTUVK4XHUwMDEwko2jhVx1MDAwNonUPFx1MDAwMTmlgZGazqOBoVx1MDAxOVx1MDAwM0VxXHUwMDA3bYxkPPCmj4hcdTAwMDPzLEWJXHUwMDA1XHUwMDAzaVx1MDAxZNKvbFx1MDAxNlx1MDAwNqVTlG86XHUwMDA129pu75zeb++9wJWLsTC4QzWA+0xcdTAwMTLF0lKFXHUwMDE3pT2utLJo21xuXFw/WiTzbWLEbWz6XHRv6VRcdTAwMTbGylx1MDAwNEFcdTAwMTC06szoy7+lnVx1MDAwMMMtcya9mZH85HPqV0FSbzxlXHJD1s65XHUwMDFhUJH3XHUwMDFjdWQqyFx1MDAwNpDPk0hkXHUwMDEzibvkXGKIdVx1MDAxZXP44DXgPnAqQuQhNfFcdTAwMTQukmluwIgga/pcdTAwMWRcdTAwMDFBY5yKjmcu8j435jdGKpr99lLrrceu6tu90v5D8bioz8/zXHUwMDE0qVjNWNMq48tEcD9K7fRcdTAwMTg1rc3V0s2zvXSnO+pcdTAwMTGO9s/2XHUwMDFm+NlOzimHJDNcdTAwMWJcdTAwMTCHXHUwMDFhyZeUfJhyoMrykGygXHUwMDEyQFx1MDAxZGFcdTAwMDRcdTAwMWZd2HQoXHUwMDA3l85DkWtcdTAwMTFBXHUwMDFjkPdEUY4wxaD6fjWvXHUwMDE0I6Ot0GhcdTAwMWOTICuV+7R/v9lsiLl4dlx1MDAwYmJt+rFcbkRsLKCt1czhI0tcdTAwMWZ7TFx1MDAwZdDmXHUwMDEz0GhcdTAwMTl44JjUXHUwMDE2XHUwMDAxK8SIXHJRXHUwMDAw5lx0XHUwMDAxyOkkpW8zLmdkRVx1MDAxOO1cdKmUYMpYXHQyKqVcbjFPiUHCWNTxilx1MDAwNWKgv+vAQIBcdTAwMDOQ8+lcdTAwMGWYvlx1MDAxNYFPXHUwMDEzkCfhvVx1MDAwNaZcIqJcdTAwMTRcXHjK4S4npyzakEyH05ZSWVx1MDAxMakjJ1x1MDAxNKdAU1x1MDAwZtfFOGVcdTAwMGbhrlx1MDAwYq9Ke1x1MDAwNoW6Qe1hmbDKhGtt58mKKMTubP9oaFOnMiPWs7FcdTAwMTe0XHUwMDE044RcdTAwMWQwrYFiWmOkhNfOb7bbt/XTa7iq1p5WLteK57v5XHUwMDE2do6TXHUwMDE5XHUwMDFiXHUwMDE3pig4quJcdTAwMDfpJPFcdTAwMTfuJsuhilx1MDAxN3VcXHpa4YVcdTAwMWPayI7pdIFcblx1MDAwN1xuNZGCPyjBoveZ6LBSr6zVW/Ve7cfYTNq1ZCc3XHUwMDFiWclNstNAQ3xw0iltJIrY9Fx1MDAxNkvhoHy9s7WytHd1xo6P5cHl2r3Mu5NcdTAwMTQtdI9rZHPIX8DAXHUwMDFjlZ9cdTAwMDGXXHUwMDA2l1x1MDAxZqxcdTAwMDDOVf3ZxumTW1ut7bnyxlVzb6daK1x1MDAxY1x1MDAxNY9+uk5sXHUwMDAy10PieU/7L8f9wvXZ5VZX1I944ab3dvOUJ5dGMWP2glbxfbrI+lx1MDAxOW6t8JWEiN5cdTAwMTPRXHUwMDEyXCI/fkVBOe1cZpBy4reFUVx1MDAxOfFthWKUWs+0XHUwMDFjp1iMOWHBzV5C5KdYbK103a2Xd3CBfTRLkrt1TVo69oW+XHUwMDFiZVx0XywteyHZ5vRjpyq+81x1MDAwNFdGKlx1MDAxYlx1MDAxNEhfoT5ZmOeSXHUwMDE3kC3ghJJcXDhcdTAwMDDqIDOMeccp4EXBXHUwMDA0YZlcbrRYmK4p4DylXHUwMDE0+VXAUiZDRPtR4YFcdTAwMTOCKnFcdTAwMTj4WFx1MDAwYvk9XHUwMDA0UJRwXrMgUro9xqrYXCLfM65cbqUoXHUwMDE3SkUkQ3LwNNriaFx1MDAwNuJOxy1g7cc7xvR8JMuH0KpcdTAwMDBccnxw5FVcdTAwMDRcdTAwMDVcdTAwMTGuXHUwMDBm62mDO1dKXHUwMDAwJnBvhlc1T66P+N3tXHUwMDFmXHLt68H5QoRpIFx0tzKWmPDYYniqdZA62Lz3yyyvyuvzM1enO6u9q82Hc/580FmPy0nPibTDdyS5PoxEdcRRiFBZltNmNk5eXHUwMDA0o4dmltWaPIDMXGZcdTAwMWVJUtyGasXmNvc8XHUwMDEzw6m+VMuP/arPI77XzVx1MDAxMX3l7E6N7anzXHUwMDE2wXR8sVx1MDAxOPUhXHUwMDA1pccwV5KtxXxCWTLtXHRccsww0MpcYjtcdTAwMWOx0c56TFGDZU0+3cl6kcZD2UO5Tk1EtTOo0CRExWBcdTAwMDXzgCmOX1x0UMYqXGIlQeB30JwpXGJga57AnZK6pCZcdH4vUlRGaFx1MDAwMGpkXHUwMDAwXHUwMDFj/4voRUqWqiC3nUKr1ViXsVx1MDAwNH6c1C9KLaQsJ27xf85E9FwitZ5cdTAwMDIrKdHQUlx1MDAwYtWIRc1cdTAwMTNzid/d9FNcYm/sVNRlJ2t1bGzaXHUwMDE3KnSJhJGP0cjnuPl4Xzk7Xax14GV18UrD2YVay7nAXHUwMDEzTCZcdTAwMTTHXG5cdTAwMDP4uJjw45xGKz2bsFxyXHUwMDAyk1xilEVjXHUwMDAx98NQf57Ewliqx7K4ieZSwmWiL7/9XHUwMDFmy+12XHUwMDE3hVCp3/7mZuqJXHUwMDBiyE5mdrOSmThku1ibXHUwMDA0t421aFx1MDAxOI3R1qJ5dfaqWHNn/YA/MWn2ls7dNuRcdTAwMWLYxkqPWdJ01LWIs+F8TjRHPDKWjWGUTub4bJpcdTAwMGXjJTyUoMao9MFY5C3WiqFS2b89qlx1MDAxZutcdTAwMWb+zJ9cdTAwMGbGplxcSnak701cdTAwMWLpMr5cdTAwMTdcdTAwMWY9RztEgb9C+uFDvd3tV1mzvH9cdTAwMDJ29Xy7Ubqp5Vx1MDAxZOmUt1x1MDAxZId0ZbRnkcloZLVMoEUzXHUwMDFioFvraVx1MDAxYWeCV0LDKSXUkWRcdTAwMWLH1Z+E9N7vrro/j/SUS8mO9P2sSE/OurAqNrhcIlx1MDAxY2hcdTAwMGI8QFxiv8J7ZatV4U87XHUwMDE3x2snjXb96NbePNdWsuB91Fx1MDAxNTBDyu6Y9VCfXHUwMDFhyVx1MDAwMU0kXHUwMDEwc5R1oSWijuqqZo34j1x1MDAwM2MmXexeNFx1MDAxZLTbz+JiZ71x/7LTaF31XG7fkMSQeN7cN9E9yJhcdTAwMWNh41Mm0e6WXHUwMDE2WfpcdTAwMTgpk9HPLlx1MDAxYcn5SY5wznmGLGtkKdTm8KeyI7hcdTAwMTRcdTAwMWVcdTAwMTi/6Nuky46g5oB66Fx0/SHZXHUwMDExX00xmzQv4lx1MDAwYpVcdTAwMTSdXHUwMDE3kXnGWVRGxOH0I1x1MDAwYtLEO9pAKUlNL9Nr7WRRm0uWzlx1MDAxNZrcTpCLSyhtXHUwMDA1XGbTdOOYZ6wgqFx1MDAxYk5cdTAwMGXhmdB0wZQnLVx1MDAxYVx1MDAwMlx1MDAwZai0XVx1MDAwZVx1MDAxZf4n0iXyeMWEXHUwMDEzXG40oiw4TvezutJcdTAwMTK14LOvrpyLwFx1MDAwMrnwlaJcdLfknUdcdTAwMDNLiIBHe5BcdTAwMTOBXHUwMDBm15H9RZ2rhMxcdTAwMThYSJZcciOr0kKgTlx1MDAxMci3rNNKhsNcdTAwMWQgPYfAw2ftpJRaz3dgIXZz0094W1x1MDAwZk5cdTAwMTdiSVx1MDAwM0l4lDGsIGNcdTAwMTPDnTZcZmCcgWd7q7eVSntl/a55f19cdTAwMTSHXHUwMDE3e439ymrOhZ2W1ElcXDLFXGZJXG41klx1MDAxMSGMJ5hUVlFccqAxs2l7g1x1MDAwMsqzylluXHUwMDE0QzJcdOkyXCJQtKD4/ZNCXG6//Vx1MDAwML5nv9ivNmN4zYzdXHUwMDEwXHUwMDExl8/uejieietcdTAwMDFYfIW6XHUwMDA1p7RcdTAwMGImXG5+heuLcr9z2eRnnVtzWz5ZLzxcdTAwMTSutzdzjmswzFx1MDAwM0kpQ1aCXHUwMDBlVrS9n8BwT1JRiFx1MDAxMc5STHFcdTAwMTJ7JfSewDAxjoZcdTAwMDd1XHUwMDA0MChNVVx1MDAwNIvR1Fx1MDAxNY9rwpNFlSDC9axSoVx1MDAwNcOnkthcdTAwMTlQ1lx1MDAxZtsoXFzr+T34XHUwMDFmXHUwMDAw4CSb4krY4ZoyYWGM9J/Sod7benrcOrtoXtaKz49Fu3Gd87xlLlxmXHUwMDEyXHUwMDA1S73gjfWz6Ub2t0XFXHUwMDA1+J/Uflx1MDAxN8T4qFma/W2h7Hg1QnGB9UBcdTAwMWJcdTAwMDeOXHUwMDE53ySPiIaHdzQwbnBNdvajOmeuuuzQq1x0quv/LSy2Kt12cFx1MDAxM3yHyoq47KiqssrQhMmPv21gXHUwMDFmfCqugDpcdTAwMWLg9nQmisu6+DIkXHUwMDAwbcfSW2eNrXvdqdyvdi/P5Pr53cPW3kFcXDA8N462XHUwMDAyl4Z6Tjlqrlx1MDAwMmKo35Nfn8y582g4XG5cdTAwMTlcdTAwMGWW+sDH666yg1x1MDAxMpSy+9qEUlx1MDAxZbVqVdRQwbnIVF2pPGG14ZZ6R+JPXHUwMDE47tZJq4M9Yn6gxdFqa+Xujvc7N8v7+62Dt83Xx53Tkym4vC87R4WLff2wcbBUXHUwMDEwJ69na41KdVx1MDAxYfWLTzel19NaZXevsruxyy517eVpvziF83b1/nVr+3zn+qDdbLf3XHUwMDBmn9qFtUq68378a8Z04CxcdTAwMWJcdTAwMWTQNim0Tl1cdTAwMTfG6Iqmblx1MDAwZk/h+GmT79jt2uPK/ePRc/0m/3KDXHUwMDBiXHUwMDBmXHUwMDA1hkDCiVxckv5cZsuN9NMjJlx1MDAxNVx1MDAxYqjaPecoQ9RcdTAwMGXH1pK89IZcdTAwMWKnlNbz6avLZM9cdTAwMTbJ/d6r+mHsvetetYs7N9ljPyOakGoh2W3c86xUIWG6RIDwjvJcdTAwMDThUHtyLdJH5JK1Qy5cclx1MDAwMESKh5zbcK2tUMGRXHUwMDBl7yTBekwwIbSVTjObkFxym1x1MDAwMuzxXnrkXHUwMDA3NHlL+51lISBiXHUwMDA3XHUwMDExdmp746xFO1x1MDAxYs1cdTAwMTRcdTAwMWGfXHUwMDE2Qj71XHRcdTAwMTV/97av48yVcECeXHSUnlx1MDAxYaWoXGJ4hz+84YJ5Wlx1MDAxYfJcdTAwMTFcdTAwMGKq0/pcdTAwMWO9NqaLPplcdTAwMWUvXGa56KlxXHUwMDA2KEudvyVcdTAwMDCPmH4nPIG7UUnONHkrxLw3bIrb2v7R8K5cdTAwMWWcMMSTXHUwMDA2gvBcIiO5MbHkhmunKbtljPZ0hbPCeWNrpWFOrjdK2+rxXHUwMDFh3laW8y3sUJzTOCr8orT/5Igzr6BcdTAwMTX58jTD52UkuFx1MDAwNGfHJMJcdTAwMGWYQpnqLGfc4lx1MDAxZZcsQtpF8Fx1MDAxYedL4W+YXHUwMDFlnlx1MDAxZl4z1CbpXHUwMDA36lx1MDAxN5Oun53FXFxmZTHJSYLcxFYyolxuMGDGXHUwMDE55fu0uLaits36euV56Wm31t/e2TrYnlx1MDAwM8vFXHUwMDBmelpnNFomSrBcdTAwMTF8S1x1MDAwNVx1MDAxZZpcdTAwMTKaXHUwMDAyxEbJwLjI6VsuaCWRklx1MDAxM1R6YUyEw54751FNpUHLxjFcdTAwMDbB0MFvU8ZQhlx1MDAxNPxU7uD7VoCNnWu99fZ6297YbJ6fvj5cdTAwMWb3ltbzZOmXss5ZSkjGXHUwMDEzoDjNfE5P/eu2fnpyVWxcdTAwMWZcdTAwMWQ8LT1el7udje5lNfeIoWK3XHUwMDA0U58mQjjKWEfm4FAt6vgknYlcdTAwMTHDpMfQ1ncgLbWPTWnta8uQ9zv4g6LXUUb2z5v509CM11O376WOZ7xo3lO1uE4/1CVZXHUwMDE05pLxXG5/XHUwMDEwOkgnlOZWhWNcdTAwMDD+MDWGllx0hd5sQov1SSgv81x1MDAxNMJcdTAwMDRcdTAwMTUulbs6tDUjOlx1MDAxM6HM9VDZaSNo2p6NiGBLlD98Op69cFx1MDAwMFx1MDAxYj5e+e5cdTAwMDD2iNk/aqh+bX0nU7WFoX5cdTAwMDBcZtlcdTAwMTHamYozMviNikiQ84ygtlx1MDAwMciehNFMzvfQlUL8xvNcdTAwMGaH99zgjCHyMpBT5azp/7G+Rufw6kjQ08ckz6uHe92X6vXK6dr26s3TRe2SLVx1MDAxZORbXHUwMDE2aeNJ5qTfbIRSXHUwMDEzR8hcdTAwMDZcdTAwMTFioYxcdTAwMTSUXHUwMDFmZ4HNyNXIwOPGpSZcdTAwMTjSXHUwMDE5TpOK5rOnSLbaXFz/+mElX/qB2tx0S8lOOSpZKcdcdTAwMTfGuIhP/jdGWjM0V+ArrN9s1kXt8rG4454vrqu7XHUwMDE3qnd1XHUwMDEwXHUwMDE3VsiPbUH5XHUwMDA3isZ305wvp1x1MDAwNVxmc1x1MDAwZnD+cFx1MDAxN8FcdTAwMTVcdTAwMDBcdTAwMGZ2gcln9lx1MDAwMVJFpaj/9cwtjU9cdTAwMTX0jWlcdTAwMDJ3O9Av3ZX78ni/v1I7ed7brjePp3DeXHTSXHUwMDA0XHUwMDEyz1tbNK50tlm8q1/d9c9cdTAwMWY6x62j0m2enFx1MDAxMtVsXHUwMDE0wbr4cCRozqRxY1xmodwsrsvak73aPVuzJ8dL28XS2t5i/uVcdTAwMDZ5XHUwMDAy4p1cdTAwMTKSulx1MDAxYStOnXso08zO0ImHhEWgvOBUcUyTwdONVnDUK43ZP8gjsYvq+ClCS3+3X1wizTqyU4WbrFQh3jvB4r1cdTAwMTPOaWNhnFHvybohl1x1MDAxNoGx1Fx1MDAxYU2B0JLhzpXDXHUwMDE1gmCk54TWaFx1MDAxOEmujODxzVx1MDAwNyeKx1E0ziFcdTAwMTUhh72ULir5QEhPXHUwMDE4TtkgmqPpXHUwMDEw4avXlCT9d08+SCaiXHUwMDBiQ8lcdTAwMDeWUVx1MDAwNTe1RkGrW4aTXHUwMDBmuPW4UFx1MDAxYZy11MtYRLQnnnr2XHUwMDAxSCO5tFx1MDAxNG1R1jlcdTAwMTf2f/hcdTAwMTEk64STlFduQYZcdTAwMGJcdTAwMWLmylx1MDAwMVx1MDAxMru5/aPhfT04YYgnXHIk4e30x11SNoiV44yc7T+dmfXlN15uPDY3O6uXR7CzfpxvccdB4Fx1MDAxNiRcdTAwMTdcdTAwMDfza+7scJd4rqzHtLVcYlx1MDAwNWXshMwmofMgJXw5y5U277QmXHKr4VxcXHUwMDFiwzT/6dFcdTAwMTDfSWva7+Rh4a+Fx9bHv7+XzyQuIDuRqWUlMrHFvzq+hooxybmF9EZLp7FkK63ns/Xlu9ZemTXE8el5L++4XHUwMDA2z1x1MDAxYSWssuCMsnrY04H6z6PsSi1cdTAwMTFu0vCZ5Vx1MDAxNTFcdTAwMWbYaFx1MDAxNznq6pxy5othliZT2PkvokpccmyEYFx1MDAwYu1cdTAwMDPEVaXe+/jle5GdvILs0K7PKrcoVnFcdTAwMDMl1Jhxpr6JbuVk6Uq/rq7slI4uzGbxdKls8lxy8IKjXG5cXKA0WUE1IIPEXHUwMDExv2Uwc2inXHUwMDEwYeRWXHUwMDBiZlx1MDAxMrIkqqRFs7YyXHUwMDAx41x0i5dA6mBcdTAwMDSeJ6plMPXz1kDNplx1MDAwMZdcdTAwMWEoXHUwMDE0/j3dXHUwMDA15Vx1MDAwM9jZd0j/5JFjdSN7O5WyuXJUPFo8XHUwMDEx7db+XHUwMDE1bixb+2lcdTAwMWZmf+eo79hcdTAwMTU/Pr89Z9v7or+4cbo1hfO+XtSfb2/YSrn23LpcdTAwMTA3+826KIo/wTd6lzF8XG6xXVx1MDAxMJ1cdTAwMDNFldpcdTAwMDNcdTAwMWHylVx1MDAxMIreaTlcdTAwMTdCivopUVxig2SMXHUwMDE5kUKSoXGhaVx1MDAwNiea3cHY/lSFUKaSLFxcrGLqJyOolPBcdTAwMTCYUJa/gqxJW6h9oVRnWKhcdTAwMTXVTu1+JjRcdTAwMDRcdTAwMTJGS1wiu8ZHrcbonngv2fNK67pWXHUwMDAxW9q7XHUwMDEwq1x1MDAwZrBfzrn/IJmGXHUwMDE0nEMrXHUwMDA0mVx1MDAwMbVcdTAwMTmiOXQ5piH8nSx9gzshXHUwMDFiXHUwMDExMafLL/bAXHUwMDFlb+1srG5dy/uzV3b/MseE4XtcdTAwMTR7I2NcdTAwMGKW+MlcdTAwMDVonUpqXHUwMDFmXHUwMDE3sKi/wnX0o8s5rrnxpFx1MDAwNcGNsIZcdTAwMDdbzrzXJXmKqsQkPSrD43uHTaTYM+VfW2tAy5/twMJcdTAwMTmwgDGco/TrSZX6XHUwMDE3KmpcdTAwMTZp2VHavJlVm2dJy5acXHUwMDFi4I6ld1x1MDAxOCbbfflcdTAwMDS8lsqTjFHgXHUwMDEznFJ8OPAprfY0447qmaVcdTAwMTMzSsqWhryWiCDH8I+0XHUwMDExM9dcdTAwMDT1Yta4XHUwMDE0lD2ksUNq3LdGZDBcdTAwMWU6T87DlFHPZG69XHUwMDEwjHpqRVO+OKf4IVxiXHUwMDEzji9qvKNcdTAwMWNQnFx1MDAwYqEpXHUwMDA1NmNX1NSB2Fx1MDAwMq6JpnWideiU5VxcR6R8K4/RtCvciULjwue74Dp2V9PP6H5cdTAwMWWcLESQXHUwMDA24q+VzGpihZxcdTAwMDJcdTAwMTh99ZPW4CoswmaM2pNkUplPKWc1oJSTVHUgnNPDLVE5MM8pXHJWXHUwMDBiakGgxWxcdTAwMWNcdTAwMTbMQ1x1MDAxY9JcdTAwMTBams6tpVRcdTAwMTHmXG431qNuwJxqXHUwMDAwmFx1MDAwZXSf+8xcdTAwMDKnrsJOzGn0M6WcS6ZcdTAwMWJcdTAwMGLD2Vx1MDAxZEZTXoGwXHUwMDA0s4jWXHUwMDEyaKdcdTAwMWGGN5OjuPOH7WWTc6lFLyV3WCk4PiSrSClccvjN55o485SgbuRcdTAwMTItU6Qg813bXHUwMDEyv7HpJ7SlU4m6dkZcdTAwMDMuQdIpTVx1MDAxM6LFXHUwMDE4fK7z8uZcdTAwMGVOKt3W5S27kc1V57qVVs4lXHUwMDFk51ag8kchpt8n1Iy00VSAT4vm+NAjXHUwMDAzo2dkw1x1MDAxOedcdTAwMTlcdTAwMTSlVrEx3LPackp0+oPyVbvV23pcdTAwMGZtpZ/PWE23kuzx4E5W0y1cdTAwMTbq8V0mOFxuV+2sXHUwMDFkg9Q87b7unknYXFxtVmr9842G3XH3eTfdnNFJSHfgaa0tXHIgMDThZjasxlxiP69cdTAwMGbvt1x1MDAwM57SVyNcdTAwMDTSsHlcdTAwMWSOPUlcdTAwMDLXjyRtTVx1MDAwM71cdTAwMGZTR298V1xc4MwpRqVcdTAwMGKp0du4uny8gFx1MDAxN6hVjpfqbP+xslQr5rzbXVx1MDAwMbloXHUwMDAyevE1zzjJXGayKmWMSChKm1x1MDAwNL1OkJq2/jQp6v+UKlx1MDAwM1x1MDAxM4EuKHlsPk2QbIWoP5N2OdVky25WXGYnhkKFjc24VFJcdTAwMTnKqFx1MDAxZqO9dWvzpSDMXHUwMDBib3ZcdTAwMGavNm/7a+qweJ5zXHUwMDFja0f2XHUwMDBmp3aCXG7/dsNtK8dcdTAwMTjLkFx1MDAwNsej75nmWFx1MDAwNpRcdTAwMDCOUsJn0tTi56cy9DKWXG7YeJqJtjtcdTAwMDMrxuhmVtmtXWzWutf1y5PzxY23qzvgp+2cb3Dlz/JcdTAwMDGryXli5Mg8oTHmMqRSVFx1MDAxNVPlMrzBtaZSS2e4XHUwMDE0/sS6iMrp8I5cdTAwMTZcdTAwMWGcNH+Dhlx0Y1xmZdg8yqaiklx1MDAwM4BfjGRcYl50WtNcdTAwMTj6s1FXOr7Vmlx1MDAwNaVcdTAwMTjZMen1VfF0fd3WXHUwMDFmdttvu5evt1f8Tize51x1MDAxY86cRrVpLpFkU5RcdTAwMWZt5axcbitNyG/0PdNUWMoxJqxmU1xi+eVRYT1mU1iSx+emccWctXJcZr9Iq6aXrW5cdTAwMWM3NH/my0tcdTAwMTe2Wd/bz/lcdTAwMGWnaZ5MXGLyfFBXS8FHdzhqLGYpYiyoXCIo3lx1MDAwM5pmg1e1dDaCkYFWnqYgJu6GmFx1MDAxZT9cdTAwMTGTsYTi0oL8s+ZcYu1Wn1x1MDAxN+qtheX//u+/NktPpWz6K/s8odjLT0uTPWXVZPFZK1wiPlx1MDAwMV2D1oqP02E3cTBLLiFcdTAwMGWAXHUwMDE4V5KG9lLAiY2kqYHwnO+TXHUwMDE0SFx1MDAxN0FDfLV+mlx1MDAxOZdxo/CEwstQr1x1MDAwMC6lo5BfXHUwMDE44WhcdTAwMTlcdTAwMWGEM6pcXNS0mvGhgpxcdTAwMGbIW050VkyDpea2l+DEXHUwMDE188xDXCIgXHUwMDE0WVx1MDAxOKjEXFzA6bBcdTAwMTBcdTAwMTjz61x1MDAxY8LSXHUwMDE4tEGU0jLjXGaB1GNcclDPXGLGXGbNelx1MDAwMItPmGyj0KpcdTAwMDA8bYlp+aFgXHImXHUwMDFjfp6nUG/snvc/XHUwMDFk3u2D8/1j5LxcdTAwMDH5+Jwx1itiLXNQtE9cdTAwMWNPn6v79nJdPdxaOyiu3VRuXHUwMDBlqsd3hVLea32pf6Ci2n3kXHUwMDBiIFx1MDAxOVx1MDAxZnYgK4SDNMbSXFxzPFdcdTAwMDKNn2jOr9GeU0iArKCpLE5EdTSLmGJOk+CkXHUwMDE0P+lB5pr+TEpzxo70+s37N9rt+++lOVx0l8/uUX6ZOrFcdFx1MDAwZX9cdTAwMWXBtKFcdTAwMDRcdTAwMTOrx2hWmNzyLSeYXHUwMDE2XHUwMDFll6jR0Fxc8MdcdTAwMDVcdTAwMDT6XHUwMDExvyeuedqg5uDG4lx1MDAxZsfcXGLRMdRWliP+qNaGWzOrvkRCXHUwMDFhSf1cdTAwMDRAglx1MDAxNlx1MDAxMqIy14T0XHUwMDFjk8JcdTAwMTi0qIxcblx1MDAxYV2fTEeBNnh8PuNG06c01HFSOsqOMTRgaFx1MDAxME/7ZFx1MDAwZtZjYDhyXHUwMDE5h9pUonjNRmnGydHl1IJBaq5wtyE3ZVx1MDAxMVm60qNxSWDQrkVLg4VcdTAwMTOH54nRXHUwMDE04je3fzi8r1NxmteMlcVcdTAwMDFcdTAwMDSPXHUwMDFhdoBcdTAwMTRcdTAwMTOVVvqxSO1y0zRcdTAwMGZf23dvp/Xivek99drwNFX5Vyn1atXpOm/wXHUwMDA2e1x1MDAxYW+3tVx1MDAxY+WeXHUwMDBlJqi9Z7U4xFx1MDAwNMo6Y7nWZsJcdTAwMDYmcaZcdTAwMWTJVZqCXHUwMDA15GF0aVx1MDAxYphYoVx1MDAxOFfBvIV5km+ZWE2r3a/fvEbljPW+l9+kWkh2pvM2daajWaz1QkqSXHUwMDEymtN7aZPrMeeD6SDiPVSCZCNTgG94YFx1MDAxMlx1MDAwN2q16rjftMjS0OxY3E+Wos+AaW6RSFx1MDAxOVx1MDAwNJNcdTAwMGV2Plx1MDAxYiNHXyklqcfdfHpxp1+L5CdcdTAwMTYoJDs0OFtF5cPj01x1MDAwNbDCT1x1MDAxN1x1MDAwNstcIrwkqYhO6rqBgr8oTr1cdTAwMTdcciVB0zi7XGL2hVx1MDAxNodcdTAwMDRqXHUwMDE4XHUwMDAxRF7nmuckbG36yZSlf404y0RzhI1t4ySQW7JxXCJUJXg4aa62blx1MDAxYfuH92+7z+Xi3XXsgLiceG5cbkZz3FuSWlx1MDAxMjCmjMjKcSaRdVxc+15yi1x1MDAxNJ6DYDKi6DJMcVxmciFpjZ3P3rKZKE71pVp+7FdcdTAwMTeQpTTo/n4vr4m/emYyc8349Fx1MDAwM1I6NiCFfNhcIpHn6Vx1MDAwM1LJbaPyiWhunIeMTaLN7Fx1MDAxMLtmmL5cYo3Wvlx1MDAxMtJaavbLXHUwMDEy3DSTIFx1MDAxYaWmp6m6VnGpybdcdTAwMTDRP1p4wnFUZkagXHUwMDA1b1x1MDAxOVx1MDAwZvVDcahcdTAwMTZJXHUwMDA3/K25y1x1MDAxON2jpVCCMt9cdTAwMWOlnVx1MDAxOYiYXHUwMDEyzT1ruDPWILe1aC1mLKRcdTAwMWWnwFx1MDAxMFx0XHSSXHUwMDEy1NfIlVx1MDAxNVx1MDAxZsTaP1x1MDAxN2U831UhkG1rfODhhtbzRF1cbvFcdTAwMWLbP1x1MDAxY9rT6bhcdTAwMGJk4y5Kx7poXHUwMDE0oFVnxnHRvFx1MDAxY91uq4PGzenx68o2tO6ajS5TOVx1MDAxN3VKXHUwMDE5XHUwMDBmKOqO3FgqKYdbzFxuqvxTXHUwMDAwQlx1MDAxYmZm1lwiJpN/xk/RXHUwMDA2J+dUtv3HQZPEacTUOVxyQGwsXG5cdTAwMTiaSHysqbzntWZLPDXvXHUwMDE22+aiL3ZcdTAwMGW2XHUwMDFliju5c9GMIFx1MDAxZJk+wsxKSqngQ1x1MDAxZZv3sVk0XHUwMDE0g3ItXHUwMDFjjPRkmaIr1lwir1x1MDAxMtxRrlx1MDAwZmhcdTAwMTGsg1xuZNlYSjynNlx1MDAxNlx1MDAxY1x1MDAxNXCQan52TbDAXGb1pJxcdTAwMWP5uU2yiVx1MDAxZtg5fLI50vyxXHUwMDBm3/906LGn0/sy40CsWH+tkjSvU0F6tf9Q4Mc3J6/N+6ebu9P7bXnWP5L9vFx1MDAwYlx1MDAwM+eQXGZcdTAwMGJq0q4k6n8+XFzohI/Jc34lIyjkzJzHXHUwMDE3LE5cIlx1MDAwZchcdTAwMTFMreVcdTAwMWT1mzDI+KJ6PoY0PyBvXHUwMDA0h/bZXHUwMDFmXHUwMDE0mumX7quH1XK7W/lmTVx1MDAxZnnhXHQ0u5q6ZlexPd84zb41XHUwMDEwnJn2XHUwMDE1luX6UfNR9Fx1MDAxOFs6qoE4KXSKXHUwMDA39818Y1lb7VlF2WGCM448flxiyVx1MDAxYbU+kmSEOFwixjgzXHUwMDFivY4wplk/oJHCU9VYVKDFXHRcdTAwMGbf4pBpaaqvXGb3bkU2j8JfTCPimlu1XHUwMDFl5y/YX3Rbplg+vdi8WD9fqlXWW5W7QXOahZE8Ve5cdTAwMDC1qNZkPIfdXHUwMDA1JFQ5MMWR81pcdTAwMTSVWZskvV5ss8PTjeXm7u7Z/b7Zet5/2r6NWlx1MDAxM6WKOomKhFx1MDAxObTdfUMytCguPUNzUqnYXHUwMDFjwCg2316M2FxyTz+F0F5Px2V0Rlx1MDAxZkZ8TSun2eRDscuvXHUwMDA04M7e6dlFpd1dal6uL665t/rDgd7Jt1x1MDAwMERcdTAwMTB4THBK3Fx1MDAwNm2UXHUwMDFlSZ2lSLRGbmktXCJGssmmX8VcbkBufSZD5W/W0dD3NExGWYQwQuFcdTAwMGZyYXSrnXa37yeufnfObMSFJyAyZupERrDY5lx1MDAwYn67c2nG6Ha2e3rca9/B0fnp5XqlUao319YqOS/1Q23FPdJcIohcbkHFfEM4RvR6giODMVJcdTAwMGJcdTAwMWJcdTAwMWOIOVVcdTAwMWNcdTAwMGJcdJ7VoNBcdTAwMTRl3HBcdTAwMWRVyoqqXGaxjmRLcyDH6CisUeA7v4XEXHUwMDFmSGQqW61cbn/auTheO2m060e39ua5tlx1MDAxMkNk6FkrjnebUV0/RMzyRGbhLLXyZJJTYXE2XCKTmlxc4ZKcoOBcdTAwMTDgg1x1MDAxNbhccsPkXG55XGZcdTAwMThpOD5+XHUwMDE081x1MDAwZeZ8jqemzcxcdTAwMDXgLubEXHUwMDE5h0qAQlx1MDAxYv3L08XDh36o2kj7UVx1MDAxZKVRolx1MDAxOZfSx2Oz8aL4plSG1jbO8MCVdqX7dsSb9Y6tP79s3XXvXHUwMDBmT1byLU2pXHUwMDFh32NcdTAwMTKNXHUwMDA1pEVcdTAwMGW4XHUwMDFh9vA4ozxHLT40Ja4rN1ljgOnSXCJirdbwn1x1MDAxZO3zXHUwMDFmXpSNXHUwMDE3uanzXCJcdTAwMWXflFxuhYlgwbFzX7bsqTZcdTAwMWKN5bdcdTAwMGK71+9cdTAwMTTaO1x1MDAxYlx1MDAwN+KmXHUwMDA2+Vx1MDAwNjJcbis0nYVGM0cwcHy4aFxi8e1RwpdcdTAwMDNtqTIwoTx6Mlx1MDAwN1x1MDAwZrXkV1x1MDAwNuW2RDhbXHUwMDFllWDGjEdcckdcdTAwMDRKf6VcdTAwMDWEPDz0YSbkbFp85JxcdTAwMTeldqZQx2lG8lorQa1UpFxu57Jy8KSmhlx1MDAxN0Y6gV/AZpxxnro6XHUwMDFh11x1MDAwNI5Z5Gm4XHUwMDAzXHUwMDE5XVmGXHUwMDE2XHUwMDA1zLNMXHUwMDBiapFP/V34fNdGx294+imE9no6JrOYsZBIxPY5XHUwMDAymjk/Xn1cdTAwMDE/WTu/vl5brb0uXHUwMDFk84376+blViHnXHUwMDE5eUojNWe4/Vx1MDAxY5faieFcdTAwMDFlNHncoeBcdTAwMDPm5+pJN7KsXHUwMDFmpTLKIZuZTluj/1CZb6YyS1mpTFx1MDAxY461iI9VKcpmsmxcZi5z9nxSOD68apqH1t7Z3UH98r61nPO4M1x1MDAwN4k2ibWMSussWijDRlx0XHUwMDEyXHUwMDE5TyqEMaJcdTAwMTn37mQ2SWyqfKagM1x1MDAwN+CWWVx1MDAwM/9cdDrPX9B5edpAVjLWt8CdVVxc0jzs1EDeu3iAbnHtiVx1MDAxNU8qzm2vVLmo6nxcdTAwMDO5gOTUU5Y8XHUwMDBiaJFQv+khIONcdTAwMWTwqG7OUlx1MDAwMjrjbrKocyySUfFcdTAwMTOSkStcdTAwMWJhwfJUXHKv0VRihopcdTAwMGb/XHUwMDFjIH/0nV74a+GHWl8nLmBcdTAwMDJgr0xcdTAwMWLYJr5lXHTuXHUwMDE4yjviOn0wdW+pza6aZXPefumvbT7fXHUwMDFjXlVeqnNcdTAwMDBsYqyKckkgXHUwMDE4xPCBrShXXHUwMDFijzmtKeI6o1x1MDAxNiWM+cCWXHUwMDFhRYdMm1x1MDAxMVx1MDAwZYxcdTAwMWJcdTAwMWGQ9ye5XHJcdTAwMTGDrWq5j8Cq1HtcdTAwMWa/fC+0k1cwXHUwMDAxtlenjW0hYutUOY34MsZcdTAwMDbK+L/C9mLdbSyePKvi7sby2u7azdO5dtf5xjZY6n5iJVx1MDAxN8ZqqrRcdTAwMTiCtuOURYFKVFx1MDAxOCpFsvFcdTAwMTNmJukxXHUwMDA2YDzKQ0NBy5gzwSTuxFx1MDAxOTPINairzlx1MDAwZlwiXHUwMDFi/1x1MDAwZlx1MDAwMrJ/1si+rfaPaqVK+/m4W0UyTFxyv7qv31x1MDAwYu0vljBcdTAwMDG2177E9j8+PGi/Sp3OUVx1MDAxZp/Er99uzF9P9erzUvyW+8eHZCBcYlZ9X/G///Hv/1x1MDAwM+s/ZCwifQ==UIManagerSchedulerBindingShadowTreecommitpushpullTransactionshadowTreeDidFinishTransactionFabricMountingManagerexecuteMountMountingCoordinatoruiManagerDidFinishTransactionschedulerDidFinishTransactionFabricUIManagerscheduleMountItem= AndroidIntersectionObserverManagershadowTreeDidMountIntersectionObserverupdateIntersectionObservationNativeIntersectionObserverobserve / unobserveconnect / disconnectIntersectionObserverManagerIntersectionObserverregisterIntersectionObserverobserveunobserve= JS= New in C++/JavaregisterMountHooknotifyIntersectionObserversexecute callbacknotifyIntersectionObserverstakeRecordsreportMountreportMountreportMounttakeRecordsobserve / unobserveconnect / disconnectgetShadowTreeRegistry \ No newline at end of file +eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO19WXPiWrL1e/9cbkfdx9vo7Hnv7IjvwfM8z/7ihlx1MDAwMzNcdTAwMTjMaMDjjf7vN1N2XHUwMDE5gVx1MDAwNlx1MDAwYlx1MDAwMVx1MDAwNrpcdTAwMGXVp9qFsLSR9spcXDn/7z+Wln713tqlX/9a+lV6LeTr1WIn//Lrn/T+c6nTrbaaeEj4/+62njpcdTAwMDX/k5Ver939119/9X/DK7RcdTAwMWFcdTAwMWa/VaqXXHUwMDFhpWavi5/7//jvpaX/9f9cdTAwMGVcXKdTKvTyzft6yf9cdTAwMDX/UP9SXHUwMDA22PC7XHUwMDA3raZ/WcGsVU6Akl+fqHbX8Hq9Ulx1MDAxMVx1MDAwZpfz9W6pf4Te+vXAXHUwMDBitaPTrZX1nFg7rt+2blx1MDAxZWs7L/3Llqv1+mnvrf7xpfKFylMnsKhur9OqlS6rxV5cdTAwMDWP86H3v36v28Jb0P+tTuvpvtIsdbtcdTAwMDO/02rnXHUwMDBi1d5cdTAwMWK9x/pf8OMu/Gup/84r/kuD87RyjClcdTAwMDHOMem+jtLv54z1XHUwMDAwXHUwMDFjXHUwMDAzLa3jzunhha226q1cdTAwMGUt7L+Y/+ov7S5fqN3j+prFr8/0Ovlmt53v4Fx1MDAxM+t/7uXzK1x1MDAwYqs9vD5nimvHXHUwMDE19O97pVS9r/To2yjmcWOdUpwzYcHo/mpK/mPhXHUwMDBlXHUwMDBmgTNWfFx1MDAxZKE1tLeL/lx1MDAxNvmf/sPo5Fx1MDAxYqVt+pXmU71cdTAwMWW8n83i5/1cdTAwMWM4cEdcdTAwMDfWh3dbcMdcdTAwMDU2XHUwMDAyu3g+qK4tb+RcdTAwMGZW7q1lxV1be6h/feOB7ZnvdFovv76O/PufSec9XpZXd4+rjau3q6eN3kZ3vSRaL1x1MDAxMzjvij09fzutVlijdn1cdTAwMGVcdTAwMTfvnfbdo5vAedeba1x1MDAwZlx1MDAwZrzXLq9cdTAwMWVcdTAwMWQ1j9933p72L84ncN7ncv7tolI8OCxcdTAwMWVsXHUwMDFksFx1MDAxYlN5fT7ansB5i6VGvb76fu1cdTAwMGV77Vxca3/rWJYrXCLdeT9/6m+wp3Yx/yEquFXWKGuNXHUwMDE0XHUwMDFhvo7Xq83a8Cart1xutSjp0iyWXv2Lso8rfn6LkKzrlV57UWJOxos5bpy13DBuUou53dPd0/KR2z65hu7N3Xr7uXm/8zrvYs446SlpXHUwMDE0M1xmXHUwMDE0ipC+fPDFXHUwMDFjeMKgMtBcXEiDkkWYoYVNTsxxZjxmXHUwMDAxXHUwMDE0/qVcdTAwMWSgLFx1MDAwYss5XHUwMDExlmu4alx0lrG+eJ6SXFyL2cFcbvVcdTAwMDNcdTAwMDP8n8myg8utZu+0+v6hV1x1MDAwN97dyDeq9beBx+rvYryN59v7+Wb+vtT5NXBouV69p039q14qXHUwMDBm7vZeXHUwMDE1XHTC1+Feq90/WsBL5avNUid8a1qd6n21ma+fxV9cdTAwMTa/aWnrS1x0eYHHc5fvluior3pcIlx1MDAwMMv7J8k/9Vonpe7HXeh1nkqJUP6QL5GUxVxmv/uFZcS5dJLZ/r38XHUwMDBly8maak6xbKXyJOp5/LpcdTAwMTa5iVx1MDAxOMSys55DXHUwMDA0K2CGMCbV0LomyFg8aVx1MDAwNVFcdTAwMTVcXFx1MDAwM1jObH9f9KEslOeMVtZJYyTjMlx1MDAwNG2kXlx1MDAxY8nVzJA9nm7q9vKd3lxubvhq81x1MDAxZVx1MDAwZvaV6m9qvp2CXHUwMDFh+7Kg8ESrZFx1MDAxZVx1MDAxM9xcIu1cdTAwMDbkgVxuaSdIXHUwMDEz+Nh9vk2r1p7RVjMmJZI9h7/w+Ykv9f6r1Cx+v6q36z12crG12jg4uKxcdTAwMWTZ3Zej5737mFXhYlx1MDAxOD4liUpcdTAwMDRJqtM6vKrQXCLq+W5vtdVoVHt4t49a1WZv+K76t2+ZsF4p5UPPXHUwMDE2v0Tw2LBQaNNcdTAwMTlcdTAwMDfJaP+npT5o/H98/fw//4z8dPxm9o+GtnH/fCHq01x1MDAxN39cIpmvJNpmNmBcdTAwMDFcZlx1MDAwYjrFXHUwMDFk4OMwXHUwMDAxkfuNoEt+1IOCbkhgzcwys/hMXHUwMDEwnlx1MDAxYY1cdTAwMWFLZGFAzEkuPVx1MDAwMyjorJIo9ZlcdTAwMWRHzP1X2X+FRVx1MDAxY4pcdTAwMDVcdTAwMGaUllxcRMg2bownNUhcdTAwMWKmK1JcdTAwMTKpVFOXadFsPpJcdTAwMGZcdTAwMDc2w+XuZuGpfbN+V64/7JZz729bUl2kNVx1MDAxMrb5XHUwMDFl2LVjdrdxw9v7e9u5253u7lx1MDAwNIyPaVx1MDAxOY1q87TxJLuMrZxWhDzPtbePa415NpbGo5p94SOzXHUwMDE5S1x1MDAxNvjwu18+Ic6VQHtcdPqM4zu5XHUwMDEzvdXmnGBcdTAwMTnQnpSofY2WWoQkj1OkK0g9jy95XHUwMDEyXHSWXHUwMDA1XHUwMDBmbVx1MDAxZcuixU/YSsLnI1x1MDAxNFx1MDAwM25mbiVl4lKZrKTTQqVUfKrHWklcdTAwMDVcXHLwYMhOalSLxaBcdTAwMDZcdTAwMWU0lb5TnMPWU8Ry0llP/TvWR7DKaj0lXHUwMDEyXHUwMDBirpVcdTAwMTh++4tZkF9Rc1xi3P/vXHUwMDEwfrRcZrt2u3BxvXO9ebVSKW42i1x1MDAwZs1FZlx1MDAxNpaBZ4jkXHUwMDFhrsEoKYfW1ce3LoBixZ9lXHUwMDE2VlpcdTAwMDTZrPy73zGLzs3p1UZ75Vx1MDAwNuGyt/9Ye37q3l3dzppZ5G+K+buVt6f37v6Kkav354+Fm8c5Zlx1MDAxNlx1MDAwN1x1MDAxN2fd1oM4vbq42SzW89XGxkbxaJ6Yhc7GLFDwuOG3v1x1MDAwNI9F0WNgXHUwMDA0Zlx1MDAxMb3V5pxZoC73XHUwMDE44lppXHJcdTAwMTGSxzGUS0hcdTAwMTmkXHUwMDE5X/IkMlx1MDAwYqO8ONlcdTAwMTPhfFx1MDAxNYZZZWXAVTtcdTAwMTNaXHUwMDExvL3Tplx1MDAxNb+dJ9MhXHUwMDE13+jMYVJcdTAwMTFaTHZKYSbukFx1MDAxNdLGemRcdTAwMTVwZNBaplx1MDAwZq4k64C5ZFx1MDAxM1aispbGcuCkmvvf9c2/XHUwMDAz2nNcdTAwMTIlm3WSg2Ywljs2nk14XHUwMDBljVx1MDAxNetASa5RrvRcdTAwMTfRxzXjnnFWXGI0XHUwMDBmOFx1MDAwN2VcdTAwMDJ36Fx1MDAxM+fG4G9cdTAwMGKxoEGWlK7Y1E7PXHUwMDFjeT2tsVxurDJcZm9cdTAwMWbeOVx1MDAxMfjcly/WWqdBK4Z3nlx0/tszOKIvNlkkXGYtSzg0T1x1MDAxZG48tEUlV/1cdTAwMWON/rKEh7a6XHUwMDA0Y5TBL2CsXGYta5G8s7nYXHJOr/DW7p8vxIj6stCO453lsd5ZZcChhnfpnbN3UKpcdTAwMTc7tpnb2ai9i3uxmb+4Ppx3KoOsXHUwMDAwPOdASm2ksIr3/SBcdTAwMWaZM/jIXHUwMDE44UFcYtB4u+T0YsqC6FxmXHUwMDA1TFx1MDAxY7dcdTAwMDA20qKSymPGXHUwMDE4gSviSMD6PPO38NPIhvAvPdPMmWlluOTM+vpRaXnrUufb9497XHUwMDFiyyfntdxsM2dCwJyKqeKymSqa6Th4c5RATlx1MDAwNX3738H7Zf1e3VfFyvremSw2t5bb7ffe09zDWyrpIZiYQYgrrcwgr/nJjFx1MDAxMW48XHUwMDE0+Vx1MDAwZdlcdTAwMTMyqNS+UFx1MDAwNo5cdTAwMTm0pFx1MDAxNpPMZPOFVvLF1stZp1SKsVumlDJcdTAwMTJ13ew5IzBxXHUwMDEzhVx1MDAwYmWH3/6Cs9aCIb/rf+A7OCfL6DmFs3PSQ1x1MDAxNqi5XHUwMDAzrpBIXHLmjFxi4Vx1MDAxOUmKk/iVRK09vaRcdTAwMTFpLaJZS6a0RNNcdTAwMTFcXFx1MDAwNJqZXHUwMDA3Wlx1MDAxYkSOo9xbIXVEPlx1MDAxOPJfRiibXHUwMDExuucta0SgXHRcIlx1MDAwMLmvXHUwMDAy5DhOXHUwMDA1PvRhXHUwMDEwgKe4UVx1MDAxY2+7RSuVm5A5kMpKSWaqg1aKZMC4slx1MDAwZXeW4lx1MDAwMsI2XG4lXlx1MDAxYlQg3KHhhKRdhda0SCZK/Mb2fz20pfvnXHUwMDBiMaG+LFxczuhpZWr43T59oe1hhE7vkznQreZ1dWfjMHd+7vLmcPPmqnwzUZ9MMd+tlCZLX1Dze4b4i1x1MDAwMo3fN3BD/Fx1MDAxOC4lyVx1MDAxOSGdxkemIaCHJumVMWiEc0D6xJxIXHUwMDFkxJXcXHUwMDE4ZvSsna0/yVtcbj7Ef5azXGZfMztfWcnKV+LQKyA2PotCUjltXFz68Ozd/WvlZfW0vXL1dLx8VIbWRn0tLkwyJ1x1MDAwZVVyaCFcdTAwMWahXHUwMDA0Ry1RlprBKFx0N8yzXHUwMDEyYeI07VXN4sMk44BXcVx1MDAxNNlcdTAwMDBKXHUwMDFi6yxcdTAwMGY6bpNCJcopiewq8Fx1MDAxZP/j0dt+6lZ+XHUwMDE2u4NXzI7c1clcdTAwMDdDmITht39jVzMtyGGbXHUwMDFhuslOm/mELoLVQ2KMXHUwMDFhTzKkQnLQa+C4xylx3TGGxoZcdOJnssFcdTAwMTAmOJJP4Vxit65v2lx1MDAwNdLSpac0aGnwqWglIYRkXHUwMDBitNAgkVokIKc0MFLTeTQwXGaahZriXHUwMDBlxlrFeOBDn1x1MDAxMVx1MDAwN+Y5ilx1MDAxMku0XHUwMDFlXHUwMDFkIP3KZmFQOkWh3M655l5r/6K2d/gqbiHGwuCAalx1MDAwMPeZXCKKZZRcdTAwMGUvynhcXFx1MDAxYu1cZjdcdTAwMTLXj1x1MDAxNslim1x1MDAxOHFcdTAwMWKbXuEtncrCWFx1MDAxYiNcYoJWXaxfRVxuy1x1MDAxZFx1MDAwM5vezEh+8nPqV0FSj2afs1xmWTvnuk9FPnLUkakgXHUwMDFiQD5PXCKRjSXukiMgXHUwMDBlPFx1MDAwNvjgjcB9XHUwMDAwOkLkITXxNC6SXHUwMDE5boWVQdb0O1x1MDAwMoLGOFx1MDAxNVx1MDAxZk9d5H1tzFx1MDAxZoxUNHqtleZ7l91W97r5o8fts21zdTVPkYr1jLWtKr5MXHUwMDA096MyYEaobW2s58sv7lx1MDAwNi729ZM4Pbo8euSX+3NOOVx1MDAxNJnZXHUwMDAycWiQfCnFXHUwMDA3KVx1MDAwN6osXHUwMDBmyVx1MDAwNipcdTAwMDHUXHUwMDExVvKxXG74YylcdTAwMDdX4KHIdYggLpD3RFGOMMWgOn+9qFx1MDAxNCOjrVCvn5Egy1x1MDAxN3q0f3/YbIi5eHZcdTAwMGJiY/KxXG5l4313flqyMlx1MDAwMX/4d4BOXHUwMDBl0M4noNEy8Fx1MDAwNDBlXHUwMDFjXHUwMDAyXHUwMDE2X4Ouu5xAosm1dlx1MDAwMslcdTAwMGVYkZQmOZZcdTAwMTVhtUdcdTAwMTk/TlxiQNnBI6tbPUBcbkSFg0A+XHUwMDAyXHUwMDE3wP1XJZiQXHUwMDAyhFCzclxi/EigYlx1MDAwNDtcdTAwMDKfp0CmJK1cdTAwMTFMR8QpuPQ0OGfILYtWJDPhxKVUdkTq2Fx0RSoshb0k45Q/hPsuvCrjWVx1MDAxNOtcdTAwMTb1XHUwMDA3tYjRNlxccLtIdkQufm/7h8PbOpUpsZmNwaC1XHUwMDE4J/BcdTAwMDQzRlBcXGuEtPDKVXmvdV+9uFx1MDAxM7elyvPazcb21cF8XHUwMDBiPOBkysaFKnLgPFx1MDAxMnOgiMNwXHUwMDE4L48qXtxx5Vx1MDAxOZKngHYyMJMuWFx1MDAwMShXhNPiXHUwMDBmSrLofiU7rFWLXHUwMDFi1Wa1W5lcdTAwMTmjSbuW7Fx1MDAwNGcrK8FJdlx1MDAxY1x1MDAxOFx1MDAxMU9yQFx1MDAxYqtQyKa3WnLHhbv93bWVw9tLdnamjm82amreXHUwMDFkpWile9xw51C6XG4rXHUwMDE2qFx1MDAwNE0gReVcdTAwMTThntNcdTAwMWG0rYtn2FivXHUwMDFjQmHrtnG4X6rkTrdPZ10rNob7IfG8XHUwMDE3vdezXu7u8ma3I6unPFfuvpef58mtsZ0xg8Ho+J5duC432F7hO1x0XHUwMDExvSeiJcT8+Fx1MDAxNiXltTOBpFx1MDAxM7+tXHUwMDE4llx1MDAxMT9WLEbp9cyoUVxuxlx1MDAxOEgnYPpcdTAwMTJiflxuxjbyd51qYVx1MDAxZlx1MDAxN9hDwyS5c9e45WPf6LthlvDN0rJcdTAwMTeT7Uw+fqrju09wbZV2QYH0XHUwMDFk6pOF+Vxc8lx1MDAwMrJcdTAwMDVAasUlXGJBXWRcdTAwMDYxXHUwMDBmnIJeXHUwMDE0UJCO6UCbhcmaXHUwMDAy4GmtlaVAXHUwMDEzZTNEtFwilZ5cdTAwMDApqVx1MDAxYYdcdFx1MDAxZmtcdTAwMDGr5DOPSVCkcFEzIVI6Pkaq2lwi/zOuXG6lKJdaRyREcuFcdTAwMTklXHUwMDE0moG403FcdTAwMGI49/mJXHUwMDExfVx1MDAxZsnyIbQqodHcXHUwMDAzw1x1MDAwMH9cdTAwMTJcdTAwMTHOXHUwMDBm51x1MDAxOYs7VykhmMS9XHUwMDE5XtVcIjk/4ne3fzS0r/vnXHUwMDBiXHUwMDExpr4k3M1YZsJjXHUwMDBi4qneQZlgI99vM72Kby8vXFxf7K93b3dcdTAwMWWv+MtxezMuL31OpFx1MDAxZH5cIsn1YVx1MDAxNaojjkKESrPA2OmkiyBcdTAwMTg9NLOcMeRcdTAwMDNktv9IkmI3VC82w/zzXHUwMDE5OD5Kr6XCU6/k84ifdXNEXzm7U2Nv4rxFMlx1MDAxM18wJphxQptcdTAwMTHMlWRrcT6hrJjxpFx1MDAxMcwyYbSVbjBcZmvAeUxTs2VDPl09nTJ47qFcXKdeolx1MDAwNiwqNCWi4rCSeYJpjl9JoIzVXCKUXGKB38FwpkVcdTAwMDBbi1x1MDAwNO6U1CU1SfBbkqIyQlx1MDAwM9AgXHUwMDAz4PgnoiUpWaqS3HZcdTAwMWGtVusgY1x1MDAxOfwo6V+UXkiZTtzh/8BGtCR1nlx1MDAxNk5RsqGjTqpcdTAwMTGLWiTmXHUwMDEyv7vplVx1MDAwYm/sVNRlP2uFbHxJXHUwMDFkgELCyEdo5nPWeKpcdTAwMTUvL5YrbfG6vnxrxOW13phzgSeZSiiQlVbg42LSj3Rao810wjZcYkxcIlBcdTAwMGWNXHUwMDA13Fx1MDAwZlx1MDAwMz16XHUwMDEyi2OpJsvhJlpIXHSXib789n+stlpcdTAwMWRcdTAwMTRC+V7rh1x1MDAxYqsnLiA7mTnISmbikFxysTZcdG5cdTAwMWLn0DBcdTAwMWGhtUXj9vJNs8b+5jF/ZspcdTAwMWWuXFzBnphvYFunPOZI01HnXCLOXHUwMDA2czrRXHUwMDFj8chYtpZRSlx1MDAxOfDpNFx1MDAxZcZLeChBrdXpg7HIW5yTRv5JXHRlT9VPf+bsg7Epl5JcdTAwMWTph5NGuorvx0fP0VxyUODvkH7yWG11eiXWKFx1MDAxY51cdTAwMGK3frVXz5cr8450yt2OQ7q2xnNAXHRU0jFp5XRqRFGY+rX3XGavhIZTSqgjybbA9Z+E9O7vzrqzR3rKpWRH+lFWpCdnXVx1MDAwNMeXXHKXa4AwTvBcdTAwMDAh/Fx1MDAwZe/F3WaRP+9fn22c11vV03tXfqmsZcH7sCtgipRcdTAwMWSY82hqiuJcdTAwMDJNJCFcdTAwMTco68IoRJ1cZnbmmKuki4PrXHUwMDA2iFbrRV7vb9Zrr/v15m0391x1MDAwM0lcZonnnftGusdcdTAwMTmTI1xcfMok2t3KIUtcdTAwMWYhZTL62UUjeX6SI1x1MDAwMMCzZFkjS6FWh7PKjuBKesL6hd+RKeJcdTAwMTE14lx1MDAwNpxcdTAwMTl4Qn9IdsR3XHUwMDEzzcbNi/hGJUXnRWSed1x1MDAxNpVcdTAwMTFxMvnIgrLxjjahtaLGl+m1drKonUuWzjWa3CDJxSW1cVJcZtJ0XHUwMDBizLNOXHUwMDEy1C2XwdqYieZEMO0ph4ZcdTAwMDBcYipvV/2H/4V0hTxeM1x0Ulx1MDAwYoMoMy6MfKpEpm4zi0nbJ1x1MDAxZFggXHUwMDE3vtY07Za882hgSVx1MDAxOfBo93NcIvDhXHUwMDAy2V/UvUqqjIGFZNkwtCojJepcdTAwMTSJfMuBQYiFViWUXHUwMDA3XGI8fNaglDJmsVx1MDAwM1x1MDAwYrGbm17hbd0/XYgl9SXhacawgopNXGZcdTAwMDdjmVx1MDAxMKNcZj07XFy/L1x1MDAxNltrm1x1MDAwZo1abVueXFxcdTAwMWbWj4rrcy7sjKJu4oppZklS6KGMXGJpPeow5jRcdTAwMDNcdTAwMDbWTqf0XHJcdTAwMDWU5zQ4bjVDMinSZUSgaEHx+yeFXHUwMDE0fvtcdTAwMDF8z/52r9SI4TVTdkNEXFw+u+vhbCquXHUwMDA3weKr1J1cdTAwMDBtIJgo+Fx1MDAxZK6vXHUwMDBivfZNg1+27+194Xwz95i729uZc1xcXHUwMDBiyzyhKGXIKWGCXHUwMDE1bVx1MDAxZiew3FNUXHUwMDE0YiU4iimOY6+EPlx1MDAxM1x1MDAxOCjG0fCgrlx1MDAwMFx1MDAxNqWpjmAxhjrjcUN4cqhcdTAwMTJkKK+TStaV4Vx1MDAxM0nsXGYo689tXHUwMDE0rvb8XHUwMDE5/PdcdTAwMDFwnk1xJexwQ5mwYoT0n/yJOdx9ftq9vG7cVLZfnrbd1t2c5y1zaZEoOOpcdTAwMDdvnZ9NN7S/XHUwMDFkKi6Bf5TxOyHGR83S7G8nXG7AS1x1MDAxMYpLOE9cdTAwMThcdTAwMGJcdTAwMDKY9U3yiGh4eEdcdTAwMGLGLa7JTX9cXOfUVZdcdTAwMWJ4N0F1/b+l5Wax01xuboKfUFlcdTAwMTGXXHUwMDFkVlVOW5oy+fm3XHUwMDBi7IMvxVx1MDAxNVBnfdxeTEVxOYgvVNTC0dhcdTAwMDObvnX0ZX23ZtrF2nrn5lJtXj087lx1MDAxZVx1MDAxZcdFw+fG05bjylLjKaBcdTAwMGUrQlx1MDAwZTR98lx1MDAwYpQ5XHUwMDA3jyakkOXgqFx1MDAxOXy88iqAyIt8dmeb1Nqjfq2aeipcdTAwMDBE5uoq7UlnLHfUQFx1MDAxMl9hvONDdSbYKGZcdTAwMDZ9jtaba1x1MDAwZlx1MDAwZrzXLq9cdTAwMWVcdTAwMWQ1j9933p72L84n4PO+aZ/mro/M49bxSk6ev11u1IulSVx1MDAxNDA+l/NvXHUwMDE3leLBYfFg64DdmMrr89H2XHUwMDA0ztsxR3fNvav9u+NWo9U6Onlu5TaKXHUwMDEzOO9cdTAwMTW7vL0r5litsr5yfndcdTAwMDLH753CxVx1MDAwNM57XHK35bNlaJZP1474Vaf6vFG5vJxETIFcdTAwMWF1OKWlzOSV7cvBy2z8xbikXFxcdTAwMDDJtFx1MDAxZKGVm74/uVx1MDAxMGfPO3zf7VWe1mpPpy/V8vzLOS49XHUwMDE0cFx1MDAxMlx1MDAxOTKSX/pvUM6lXHUwMDFmeTGumEMu4lx1MDAwMVBKq1x1MDAxYlxmXHUwMDA2JoVcdTAwMTUst9Rj3CymczGTXHUwMDAxvk3xgm7Jj7tcdTAwMWbedUtcdTAwMWTcuckhhinxmlRcdTAwMGLJbpRfZeU2XHQjMeKbTHPn0Fx1MDAwMDTpaU2yLptLe0WjnVx1MDAwYlxm96pcdTAwMDJqXHI6NFx1MDAwZiPHnecsVS9cItDBcUjoMZ1cdTAwMDLr8VFcdTAwMDVt0Nq2xs8/ksFcdTAwMDSkwCxcZiv9pnJcdTAwMDbQiFx1MDAwNK3CwKcvIGfYqtbXXFzIhVx1MDAwM0HW2c7CXHUwMDAwQZ5cdTAwMTRcdTAwMTSeXHUwMDA2hahcZnizP733knm4ZPJpS6or+1x1MDAxYVx1MDAxNzdiSCGZzS9ccoRcdTAwMTSo0YegnmVMKiF4xMQ+6UlcdTAwMTThWnFmyLtcIlx1MDAxN73FVNzW9o+Gd3X/hCGa1JeD11x1MDAxObmNjeU2XHUwMDFjXHUwMDE3QNk4XCK9dyZ3mbuq767V7fndVn5PP92J97XV+ZZ2XHUwMDAyOI3Qwi9K+09ccjlcdTAwMWZzRpPv0TBcdTAwMDNo0FxuSHDOjCPsXHUwMDA001x1MDAxZWLOccZcdTAwMWTucVx1MDAxNTDMkmhccspeadxcdTAwMGZMPJ9cdTAwMWZaM9DWaVx1MDAwNvWWSdfPTmJuspKY5KTGYL/iYcOFU5t5qVx1MDAwMj35v1x1MDAwM/fz8saa3rObm8WXleeDSm9vf/d4b1x1MDAwMSxcdTAwMTflUUNFaVx1MDAxOaJcdTAwMTfUXHUwMDEwvpWjsnarXHUwMDFkNZaA4DC0yVx1MDAxYi5oJJGSk1QqYm1EgIFcdTAwMDN4VFx1MDAwM2rRsEFcdTAwMDYmgqGO35aMpYwuMatcXMePnSC29u/M7vvbfWtrp3F18fZy1l3ZXHUwMDFjy4HwXVx1MDAwZaW7qLVXV8orq9297dvNw21uN9dcdTAwMGZG81x1MDAxZjjB+q35s/lcdTAwMGby0To2aYl94Vx1MDAxNKmI/S1qJdJcdTAwMDFQqPA18ulhXHKEbFxyXHJeJaiAVChcdTAwMTFgkn3tYSkz1knkXHUwMDE0NGzbJVx1MDAwN6XDQFx1MDAxOd7qJarN46Nv9dnq95GAXHUwMDEweKhcdTAwMTe/vofHV8taiv4pXHUwMDE1rminW5xQ524p7EOML4W0XHUwMDFkhuLAQia4r5M8XHUwMDA1gWf6vZbWXHUwMDAzp5xYyuI3+iaNnyFBrcZr7USFXHUwMDFi6zVwibmIaLnoYE+i73RtsoSdS1wiLf2Z8EJcdTAwMDGaN9zpcCTEnyvHjPAjkC6h2/w4TFx1MDAxYc1X45ig7thgmVA8wm/AjfQo0C9cdTAwMDQu1KJcdTAwMDVcdTAwMTRSs1xujOaT8Vx1MDAxN4bj+OLzndHj+OPhfcibMGz/fm/UJ1x1MDAwM3JpoC1cdTAwMDIzaMRqwI1Pflx1MDAwNKsj8lx1MDAwNNHOpe5cdMhCpTVMLfb8mVxc/MbzXHUwMDBmh/dcXP+MIfLSV06FjJMvIKFcYlx1MDAwMlx1MDAxY03h5OmF0VXp5LDzWrpbu9jYWy8/X1du2MrxfFx1MDAwYqNcdTAwMWN1IFx1MDAxMMLX15pmRurhTvlcXHvIxYWzWlAqq4rPjVx1MDAxZcuLyYTHLeXDpqx/XHUwMDAwy2lw06zaq3xIXHUwMDE4yVx1MDAwM1x1MDAxZe6plyn711x1MDAwZmvv/FxmypTTLSW7nV9MICSJQE+287mM51x1MDAxZdYqZ1x1MDAwN4YsfFx1MDAwN/fyTlVWbp629+Hl+q50cK27t8dxIYs5svOV9TQnXHUwMDAziUtcdTAwMDZGikH+IcCfdSO5RqlcdTAwMTBsiDOfeVx1MDAxOJxpralcdTAwMTX41FOGv9TQXHUwMDBmJkw87Ite/qHQU2dHvbXK+cvhXrVxNoHzTithorJsIX+5s/1QvX3oXT22z5qn+ftJJDZMKjGzlI0muMB1QzaL4UxZXHUwMDE4YSbnzvamqjy724PLXHJ3frayt53fOFxcnn+5gUQhIbNBUYNnzamJXHUwMDExJd05MbSyXHT6XHUwMDA3jfWQt1lOxdfCpewnjb+A5Jm5qcuJ+Vx0XHUwMDAxXHUwMDFjoDp+jtDSP53YkGZcdTAwMWTZqUI5K1WI9VAoXHUwMDE2b1x1MDAxNNDwQ7TUXHUwMDAyTVx1MDAxML5De7JumEujwDrqXHUwMDEyh3zfKEZuvEFHq7DKXHUwMDAziUacloprK6fjn1x1MDAxMFx1MDAxNOdcdTAwMDNkXCJcdTAwMTRcblAqIE/67lx0qTxpuVx1MDAwMHoqZDmE2YGhdPH/9LSGZFx1MDAxZbo0kNbgXHUwMDE41bJTk1x1MDAxOFx1MDAwZVKF01x1MDAxYbhD61tcdTAwMWJcdTAwMDHOUVdnXHUwMDE50ah54nlcckJZxZWjOI52XHUwMDAwXHUwMDEw0Vx1MDAxOZJcblxiXHUwMDFkSFCUYe+EXG6XeCyUXHUwMDBmJHZz+0fD+7p/wlx1MDAxME3qXHUwMDBiwvvJXHUwMDBm/6Q8XHUwMDEzp0ZcdTAwMTnA23u+tJur77xQf2rstNdvTsX+5tl8SzsuJG5BbakzXG5VXHUwMDFmusF++VxcO49cdTAwMTm09ZXT1o1JbFx1MDAxMnowXHUwMDFhz1wii+Ta2Fx1MDAwZlaThtRwbqxlhs96SMZPsprWXHUwMDA3d1j6a+mp+fnzz9KZxFx1MDAwNWTnMZWsPCa2XGbaxFeTMaY4d1wivc3Srq+4YvPlcnP1oXlYYHV5dnHVnXdcXFx1MDAwYlx1MDAwZoWndNr5iVx1MDAwYmbQ0YH6jzzRyHFcdTAwMTBuyvKpZSwxXHUwMDFm2GhcdTAwMTZcdTAwMDH1t045/cYyRzM63OKXk6VcdTAwMDY2QrCJ5lx1MDAwMeKqWO1+/uNnkZ28guzQrk7Jm5lcdTAwMTRIdahIRpl/JzvF85Vb87a+tp8/vbY721x1MDAxNytcdTAwMDU731x1MDAwMM9cdTAwMDHVXCJcdTAwMGJKwJVUXFzSt9r85slcZtBMIcLInZHMmnhPZom0aNamLsJ60uElkDpYieeJap5Mnc2NoLbbXHUwMDAyl1x1MDAxYSiZ/j3nXHUwMDA25YNw0+9cdTAwMTX/xSNHyil6v1CqsXa6fbp8LlvNo1tcbspVZu3C7O2f9oDd8rOr+yu2dyR7y1tcdTAwMTe7XHUwMDEzOO/bdfXlvszWXG6Vl+a1LFx1MDAxZjWqclv+XHSu0YeMfeREbD9IXHUwMDAwoalmvU9Dvlx1MDAxM0LRO23OhZCmzlJcdTAwMTTBIFx1MDAxOWOHpJBiaFxcXHUwMDE4mkaKZjdcZrgoJiiEMtV64WI107NcbqDStqWcXHUwMDA3yJSi8UOVXuNmZn2jVKdYXHUwMDAxXHUwMDE21ViuNlx1MDAxNVx1MDAxYVwiXHUwMDEyhmxcIrvGR61H6CNZU+xlrXlXKVxulz+8luuP4qgw5/6DZFx1MDAxYZJcdTAwMDOgMjBJc1x1MDAwNzVN5JtjXHUwMDFhwj/I0lx1MDAwZrhcdTAwMTOyXHUwMDExXHUwMDExe7H66o7d2e7+1vrunapdvrHa61x1MDAwMlx1MDAxM4afUez1jM1o4mc4oHWqqJHeXGLlndGPbs5xza2nnJDcSmd5sPnOR8WTp1GpI2LwUVlcdTAwMWVf3DmWYqegKyp2wKszlTY7yjkrjJptL1x1MDAxYc5cdTAwMDRcdTAwMGJcdTAwMTjDs9DtU1Lq36io0dKts2vzRlZtXHUwMDFlXHUwMDFm9zSxiFecW8GBpXdcdTAwMTgm233zXHR4o7SnXHUwMDE4o7inXHUwMDAwrflgk1jljGdcdTAwMThcdTAwMDeqlFYgp5SXrSx5LVx1MDAxMUHA8D/lXCKmz0nqSm1wKSh7SGOH1Lhvjahgl8tFclx1MDAxZaaMeiZz66Vg1NNomnfGOcVcdTAwMGaFtOH4osE7yoWmzin4Weky9odNXHUwMDFkiKUkaJpbitYhlTJxXHUwMDEzkfWtPUZzv3AnSoNcdTAwMGJf7FLu2F1Nr+H93D9ZiCD1xV8zmdXEXG45LeJ7VuAqXHUwMDFjwsakl3LJpHI+pZwzXHUwMDAypZyiwlx1MDAwM1x0YFx1MDAwNvO9uWBcdTAwMWVoI5yR1NzAyOk4LJiHOKRxvDSn3CilI8xcdTAwMTVunUd9kbmSzjFcdTAwMTPow/eVXHUwMDA0Tv2VQS5o9DOlnEumXHUwMDFiS4PZXHUwMDFk1lBegXRcdTAwMDSziKZcdTAwMTVop1qGN5OjuPPHXHUwMDBlZpNzqUUvJXc4JTk+JKdJKfX5zdeaOPO0pL7sXG4tU6Qgi13eXHUwMDEyv7HpXHUwMDE12tKpRF0ro1x1MDAwMZcg6TSV0Co5XHUwMDAyn2u/vsPxebHTvLlnZdVYXHUwMDA36Fx1MDAxNJtzLuk4d1x1MDAxMpU/XG4x8zGrZ6ihqFx1MDAxNvi0aKJcdTAwMTE9MqpcdJ6OXHJnwbMoSp1mI7hnjeOU6PRcdTAwMDelq3ZK99Uu2kqzT1hNt5Ls8eB2VtMtXHUwMDE26ja+olx1MDAxYYWrXHUwMDAx50YgNc9cdTAwMDdvXHUwMDA3l0rsrDeKld7VVt3tQ23eTTewJlx06SA8Q2WHXHUwMDBlJTPN+plcdTAwMGWrsdLP68P7XHKCp/TVSIk0bFHHhI+TwDWTpK1JoPdx4uiF2FxiiuBcZjSjyoXU6K3f3jxdi1dRKZ6tVNnRU3Glsj3njfRyyEVcdTAwMTPQi+95XHUwMDE2XHUwMDE0s8iqtLUyoSZtXHUwMDFj9IIkNe38uVrUWSpVXHUwMDA2Jlx1MDAwMl1S8thimiDZ6lBnk3Y50WTLpIZcdTAwMTeJXHUwMDE4TlxmhUpcdTAwMTebcamVtpRRn1x1MDAxZceXzZ3XnLSvvNE5ud25723ok+2rOcexXHUwMDAxsn84NSrU+Df0XHUwMDFmyIhcdTAwMDMq0uB4+DOTXHUwMDFjUIFcdTAwMTJcdTAwMDAoJXwqfS1mP5+im7FUwMXTTLTdmXBSps84LFx1MDAxZVSudyqdu+rN+dXy1vvtg+BcdTAwMTetOd/g2p9qJJwh54lVQ81cdTAwMTJGmFCRSlFcdTAwMTVtiavwXHUwMDA2N4YqLcFyJf3ZfVx1MDAxMYXT4Vx1MDAxZC2NXHUwMDAwZWfWL2Em4yl2TrOpqORcdTAwMDDgN8Mpglx1MDAxN53UXFyK3nTUlYmvc3RCa0Z2THp9tX2xuemqj1x1MDAwN633g5u3+1v+IJdrc1x1MDAwZWdOQ+tcZldIsinKj7ZyVoWVJuQ3/JlJKixccoxJZ9hcdTAwMDRCfvOosJ6yKSzF43PTuGbgnFx1MDAxYcEv0qyYVWfqZ3XDX/jqyrVrVFx1MDAwZo/mfIfTXFxTJiV5PqhfpuTDO1x1MDAxYzVcdTAwMTZzXHUwMDE0MZZUXHUwMDExNF6L8pJR4FwiXHUwMDE4mTDaM1x1MDAxNMTE3Vx1MDAxMNPiJ2JGmNRcXDmh/qyJSlx1MDAwN6WXpWpzafW///uvnfxzPpv+yj5ZKfbyk9Jkz1k1WXzWSnxPXHUwMDFmgSxNXHUwMDBiXGKkXHUwMDE4f+v6TJxQM5dcdTAwMTBcdTAwMTdcdTAwMDIxrlx1MDAxNY0vpoBcdTAwMTNTg0aXkJ5BVm5cdTAwMTnQX1x0+edphn3GzVx1MDAwNKRWPpxJ48BSuoKJ0GDMk1x1MDAxMvBpIKhRq1widbbhjHTHic3KSZDUiXdcdTAwMTOcdlx1MDAxMX/qgnnmIVx1MDAwZpCaXGZcZtRhXHUwMDEw8DksXHUwMDA15lx1MDAxZFx1MDAwMyAqrUVcdTAwMTNEa6MyXHUwMDBlJ0g9L1x1MDAwMdWMZIya6qJccoi2XHSZRqFVXHTh4Vx1MDAwNtF+ZylcdTAwMDAjbDj6vEiR3tg9T69cXMR275/wXHUwMDFmQydcdTAwMGXIx5eMsV5cdTAwMTlrmVx1MDAwYk1cdTAwMWJcdTAwMDV4+lxc3ffXu9LJ7sbx9ka5WD4unT3k8vNe60vtXHUwMDAzNdXuI19cdTAwMTCK8UFcdTAwMDeyRjwoS/36XHUwMDE4w3Ml0PixJlx1MDAxZVvjgUZcdTAwMDLkpFx1MDAwMoPyNtXIY65pJJ5ScpZcdTAwMWVkbui/cWnOyJFefyzAVqtV+1mak3D57Fx1MDAxZeXXiVx1MDAxM1x1MDAxYm1iK+soPdVJPkKf5OSOb3OCaelxhSpccs1cdTAwMDWpUaxCX99+JK555EBcdTAwMTeMktd858Qg0THOQz6Kdjp1ykU5oIZWOqm+RCjR0VRcIntdg1xy5kxcdTAwMGY0Jlx1MDAwMobE1KJFZbVkXHUwMDAxP+BvqqOFsXh8VqifN05DXHInXHUwMDE1UHaMpdFFOtBcdTAwMTHnkz7g01x1MDAxN5YjmVx1MDAwMYbrR/GajdOMkqPLqVx1MDAwNYMyXHUwMDFjd1x1MDAxNUNyylwisnSVR4OYXHUwMDA0Ulx1MDAwMGM0Z+HE4UWiNLn4ze1cdTAwMWZcdTAwMGXv61Sc5i1jZbGNXHUwMDFkyoJcYudcdTAwMDaVVvqBS61CwzZO3lpcdTAwMGbvXHUwMDE31e2a7T53W+J5ovKvmO9WSlx1MDAxM+7NzNF4w9vtXHUwMDFjR8psglx0alx1MDAxZlktgJhQmlvHjbFjNjCJs+1o6DxcbmXc4YwhpU3ZwMRJzbhcdTAwMGXmLSyq8yY1q2m2etXyW1TOWPdn+U2qhWRnOu9cdTAwMTNnOobFWi+kJCmhOb2XNrlcdTAwMWVzMZhcdTAwMGVcIt5DJUhGMlx1MDAwNfhcdTAwMDb7L3JBnVaB+02LXHUwMDFj2p/xXHUwMDFkycZL0UeqZbhzSFx1MDAxNlx1MDAxMEzGRbZg/DZHX2ut/Fx1MDAxZXdcdTAwMGIpXGImX4vkJ1x1MDAxNmgkOzRBXFxH5cPj01x1MDAxNcJJP11YOFx1MDAxNuEmSUV0UtdccuT8RXHqvWj9wUhO2NCqnGdoJlx1MDAxODWMXHUwMDEwRF5cdTAwMTea5yRsbXplytK/Q5xlojnSxfqvpSA6NkKEKi9cdTAwMWXPXHUwMDFi681y/eik9n7wUth+uIudPDcnnpucNVx1MDAxY/eWopZcdTAwMDSM6aBBMlx1MDAxYcdcdTAwMTlH1tHQXHUwMDExkOCMP0+UqYiiyzDFsciFlLPuXHUwMDBmmpVdei1cdTAwMTWeeqUlZCl1ur8/y2vir56ZzNwxPnE2o+L9NsiHXHUwMDFkXHUwMDEyeZ4+SSq5bdR8XCKaW/CQsSm0mVx1MDAwMbE7NFx1MDAxN1tcdTAwMWG09rVUzlGzX2bj3TTjIFx1MDAxYaWmZ6i6VnNlyLdcdTAwMTAxKFZ6XHUwMDEyOCozK9GCd4yH+qFcdTAwMDCqRdJcdTAwMDH/0dxlhO7RSmpJmW9AaWdWRMyf5p6zXHUwMDFjrLPIbVx1MDAxZFx1MDAwNUCycZdRXG5cZpGQIClBfY1cXFlz4UKLsp6lXHUwMDAyQ4ls2+BcdTAwMDNcdTAwMGY3tF4k6pKL39j+4dCeTsddRDbuok2si0ZcdTAwMGK06uwoLprX0/s9fVxcL1+cva3tieZDo95hes5FndbWXHUwMDEzXHUwMDE0dae5o1qpwVx1MDAxNrOSKv+0XHUwMDEwXHUwMDE0ebdTa1x1MDAxMZPJP+OnaFx1MDAwYlBcdTAwMGIq2/520CRxXHUwMDFhOXFOI0RsXCKdYGhcInFcdTAwMGU6faroVaXRlM+Nh+WWve7J/ePdx+39uXPRXGYhXHUwMDFkmb5Hc8Ipp4JcdTAwMGZ4bD6mZtFMXGZKtlx1MDAwMDHUk2WCrliHvEpyoFxcXHUwMDFmYWSwXHUwMDBlKpBm4yjxnNpYcFTAQar51TXBXHRmqSfl+MifZJbNRMlO/MzOwZMtkOaPffj+b4dcdTAwMWV7Or2vMs7DivXXatQo0miRXu0/5vhZ+fytUXsuP1xc1PbUZe9Uzc2o7DhhXHUwMDAwgGRYUpN2rVD/88GcO3xMXHUwMDFl+JWMQiNn5jy+YHFcdTAwMWNxQI5gai1cdTAwMGbUb8JcIuOL6vlcdTAwMTjS/Fx1MDAwMnmjXHUwMDAwtM/+oNBML18rnZRcbq1O8Yc1feSFx9DseuKaXcf2fEOKqKVcdTAwMTXBkWnfYVltnjaeZJexldOKkOe59vZxrTHfWDbOeE5TdpjkjCOPXHUwMDFmQLJBrY8kXHUwMDE5IY6IsWCno9dcdTAwMTHGNOtHXHUwMDE4pPBUNVx1MDAxNlx1MDAxNWhcdTAwMDHp4UdcdTAwMDCZlqH6ynDvVmTzKPzlJFwirnOr1uP8XHUwMDA1R8uwa7dcdTAwMGJcdTAwMTfXO9ebVyuV4maz+NBvTrM0lKjKQaBcdTAwMTY1hoznsLuAhCpcdTAwMTdMc+S8XHUwMDBlRWXWJklv13vs5GJrtXFwcFk7srsvR89791FrYp6woFCRMIu2u29Ihlx1MDAxNsWVZ2lMKlx1MDAxNZtcdTAwMGJhNVtsL0bshqdXLrTX03FcdTAwMTmT0YdcdTAwMTFf04pGXHL1vFx1MDAxY8GHsX94cXldbHVWXHUwMDFhN5vLXHUwMDFi8F59PDb78y1cdTAwMDBcdTAwMTFcdTAwMDRcdTAwMWWTnDK3hbHaXGalzlIk2iC3dFx1MDAwZVx1MDAxMaPYeNOvYlx1MDAwNSB3PpOh8jdcdTAwMDdOpUud1Vx1MDAwZSGMUPiDXFxcdTAwMTidUrvV6fmJqz+dM1x1MDAxYnHhMYiMnTiRkSy2+YLf7lxc2Vx1MDAxMbqdXHUwMDFkXFycdVtcdTAwMGbi9OriZrNYz1dcdTAwMWJcdTAwMWJcdTAwMWLFOS/1Q23FPdJcIohcbknFfFx1MDAwMzhG9HqSI4OxykhcdTAwMTdcdTAwMWOIOVFcdTAwMWNLJTxnhEZTlHHLo1xugVxmqjLEOpItw1x1MDAwNTlGh2GNXHUwMDAyXHUwMDFm/Fx1MDAxNlx1MDAxMn8gkSnuNov8ef/6bOO83qqe3rvyS2UthsjQs9ZcdTAwMWPvNqO6flx1MDAxMTHLXHUwMDEzmVx1MDAwNThq5clcdTAwMTSn4q9sRCY1ucIlgaTgkMBcdTAwMDcrcVx1MDAxYobJXHUwMDE18lx1MDAxOGGV5fj4UcyDWPA5noY2M5dcdTAwMDJ3MSfOKIO/XHUwMDFl2ujfni5cdTAwMWU+9JJcdTAwMWVcdTAwMTJ8P6qjXHJKNFx1MDAwYil9PC5cdTAwMWIvim9KZWlto1xmXHUwMDBmXFxrXHUwMDE1O++nvFFtu+rL6+5Dp3Zyvjbf0pSq8T2m0FhAWlx1MDAwNILrQVx1MDAwZlx1MDAwZljtXHUwMDAxtfgwlLiuYbzGXHUwMDAwk6VFxFqd5bNcdTAwMWTt8zcvysaLYOK8iLt4XHUwMDBmjzA07Jnr9HVExVKjXl99v3aHvXautb91LMtcdTAwMTUx31BGceU5IaWk8lx1MDAxMTXcs1x1MDAwN1x1MDAxMe5Rylx1MDAxN1xi46g2UEyHXHUwMDE5XHSPMU0p/Fo5rqjZfFx1MDAwNJSZw72C8JFcbs1cdTAwMWHrIJRNa6gjrFTT6fIxXoG0NVJcdTAwMDT20eSpUWp/XG41nWYkso2W1E1F6XA6K1x1MDAxN54y1PPCKpAosVxcxjHnqSukcU1cdTAwMDKYQ6rG8P/pyiq0KME8x4ykLvnU4oUvdn10/I6nVy682dOxmeWMxUQytteRoLnzo9VcdTAwMTjw842ru7uN9crbylx1MDAxOd+q3TVudnNznpWnXHLSc4b7XHUwMDBmuDIgXHUwMDA3aydp+jig6Fx1MDAxM8zP11MwtKyZ0lx1MDAxOVxyyGgm09robzrzw3RmJSudicOxkfFsRlNGk1x1MDAwYj6nb3uWvZznzk5uXHUwMDFi9rF5ePlwXFy9qTVX5zz2zIVCu8Q5RuV1XHUwMDBlrZThdi/MU1x1MDAxYWGMaMa9O55dXHUwMDEymy6fKfDMheCOOSv+XHUwMDBlPC9e4Hl10kDWKta/wMFprmgmdmogXHUwMDFmXj+KzvbGM9s+L1x1MDAwMuytlbgsmflcdTAwMDZyXHUwMDBl2amnXHUwMDFkeVx1MDAxN4D7PadcdTAwMDeAjHfAo9o5R0nojMN4kedYJKPiJyQjWbbSXHTHUzW9RmOJWSpA/HOA/Nl7eumvpVx1MDAxObW/TlxcwFx1MDAxOMBemzSwgzXfoYxcdTAwMTJK01x1MDAwN27SXHUwMDA3VFx1MDAwZlda7LZRsFet197Gzkv55Lb4WlpcdTAwMDBgXHUwMDEzY9WUT1wigoFcZlx1MDAxZtia8rXxXHUwMDE4XHUwMDE4Q1HXKbUpYcxcdTAwMDe2Mig6VNqscMG4pSF5f5LrXHUwMDEwMdgsXHUwMDE1elxirGK1+/mPn4V28lxuxsD2+qSxLeN7LXJcdTAwMWHzZa1cdTAwMGKU8n+H7eUqbC2fv+jtg63VjYON8vOVgbv5xrZw1Fx1MDAwMcUpLq0zVG0xXHUwMDAwbeCUSYFKVFoqR3LjNVuMhbawXHUwMDFl5aKhoGVcZmwwkTtxzlxmclxyXHUwMDEz9DvOoM2YpWLrXHUwMDFmQ/Z9qXdayVx1MDAxN1svZ51cdTAwMTKSYWr61Xn7WWh/s4QxsL2RXHUwMDE124ktwYHHZkMph+hcdTAwMTaI8tT43t1rl2Cztity6zd3/HJ3r1evqlx1MDAxOHxX8oXKU6c0e4RcdTAwMWKGtq1cdTAwMTSAZip+V6tcdTAwMDZcdTAwMWRlOaVpJrhD21dJXHUwMDBiMql8lZfoT1wixnudfLPbzndKQXdLv7BLe1x1MDAwZeVcdFNcXDtcdTAwMWGhXHUwMDE4hjlqdupcdTAwMDaKtIpzJiyYXGKj2zglRbCX3JRw/3lgKeArj9MogT1iX8/ObvSlbpZcdTAwMWV4cf1d7V/WKpu/vj73739Gn/azjohd3t5cdTAwMTVzrFZZXzm/O4Hj907hYvAqg+GxlOe9htvy2TI0y6drR/yqU33eqFxcXqY77+dPSV3XrHJKXHUwMDA0RcDI7dDv2Fa0XHUwMDE3POme9uVepJL3977WXHUwMDFlgNIgJViu+dDel1x1MDAwMKj8NPNNRVx1MDAwYlxmQltcdTAwMTb3XCIls6KeUURwdcSWXHLIuDBcdTAwMDAnXHUwMDA1odlSh5FcdTAwMDBcdTAwMTZ4qNu/vofdV3N0tC40KtRcdTAwMTBcdTAwMWb75ftPYllcdTAwMWEya+RcdTAwMDFSp5Hiw1x1MDAxMFx1MDAxZljIXHUwMDA09/VvfX3y1OxVXHUwMDFipdNCpVR8qlx1MDAwN0ctplD/euB0X4q7UKIqy1x1MDAwNM2ePOvjO1x1MDAxZDas7+O/QkhJx5OAXHUwMDA0aCdLvLhkXHUwMDAwn7ly7nGkf9oohjtHXHUwMDBlUdecQ2WDqkJpp5GlsX5xf7+lXHUwMDAyXHUwMDE3XHUwMDFl484oMP4434jcPsG1p0Fz+oM0OVx1MDAxOEz/XHUwMDFl7CXUdZL/KWDfjVx1MDAwNnuasPxvXHUwMDE5wCm2XG424Fx1MDAxYVxmXHUwMDE07PLQu1/xTqeNk9ZkXHUwMDExXHUwMDAxSVx1MDAxOVx1MDAwMMCscTy4rdNcboDJjpqP26b0yoV3aP98Id2dMtw+0UaojHJcdTAwMTKcn1BhXHUwMDA1XHKEXHR86rPJhidpVFwiZ6hoUVx1MDAxMWuesVx1MDAxM2qyaFtcdTAwMWFIqOBWXHUwMDFiXHUwMDA2gDdcdTAwMGXJKFx1MDAwYvTr+1pcdTAwMTXeV5RcdTAwMWNcdTAwMWG3geac3HzhbNOxclx1MDAxM0r1u9ZLf+8kycib9Y7cXFytlODo9mL9Wp7YM9NKQ3+UpUQh5lx1MDAxODJcdTAwMWOGd1ZcdTAwMGV2c8jRXHUwMDE0WYVcdTAwMGZGXHRJ/b1cdTAwMDNM5mtcdTAwMGWCMLj3gFx1MDAxMkosjWOIXHUwMDEykaPwnz9LJO6PwH9cdTAwMDAlXHUwMDFmdVx1MDAxN1xu5fzTU4ivaVx1MDAxNFxiXHUwMDE31H1cIo2TakT6XHUwMDAzVOs7XHUwMDBl/emWelEtIWh593itSbChKXk5Uq98MiQo2TxLXCJBVM1kUHZcIn5p9lx1MDAxM1x1MDAxYkxcdTAwMDXModnvWcPIr2Yo1bpPkfpcdTAwMWNIe1xmlGNKgbDcRFx1MDAxOTiUUGioXHUwMDAxXHUwMDBmilxia1kgpPY34Fx1MDAwN1x1MDAwMX8wPlx1MDAwN5JCcsesiOJAXFzFOrOcQzFOXHIgJy9cdTAwMDd839DMWVAudqfSK7xHf4JcdTAwMDWlJlx1MDAxY8g3uNQgKdVVU82rjJhxIz2GXHUwMDA0j2Zt4mH8klx1MDAxOTM4U1Mzn1x1MDAwNVnmOOWUXG68plx0L1xuiL5cdTAwMTnGcSdQTUhEectcdTAwMGZxIFu4WS7tXT1cdTAwMTdl42T1Yfn0qlaC5zRcdTAwMWNcYlx1MDAxNaSnrVx1MDAwMe7QQmRcdTAwMDNNfz9EpJZcdTAwMWVFXHUwMDE0XHUwMDA1INdjyoZFpMSNZyU1j2cgo0a9/c2AYlx1MDAwNeJhelx1MDAwNiSMoFxudVx1MDAxYkprpWNcdTAwMTBbXG5pONqLXHUwMDAzvcgmR4A452P5fz5OXHUwMDE5Zlx1MDAxMnn6sTvH/CftwkemP//4lMO/8u32aY+o1G9B9uu5WnpZiY9cdTAwMWH+41MyXHUwMDEwXHUwMDEwSr5S/fc//v1/m+f+4SJ9UIManagerSchedulerBindingShadowTreecommitpushpullTransactionshadowTreeDidFinishTransactionFabricMountingManagerexecuteMountMountingCoordinatoruiManagerDidFinishTransactionschedulerDidFinishTransactionFabricUIManagerscheduleMountItem= AndroidIntersectionObserverManagershadowTreeDidMountIntersectionObserverupdateIntersectionObservationNativeIntersectionObserverobserve / unobserveconnect / disconnectIntersectionObserverManagerIntersectionObserverregisterIntersectionObserverobserveunobserve= JS= New in C++/JavaregisterMountHooknotifyIntersectionObserversexecute callbacknotifyIntersectionObserverstakeRecordsreportMountreportMountreportMounttakeRecordsobserve / unobserveconnect / disconnectgetShadowTreeRegistryRuntimeSchedulersetIntersectionObserverDelegateupdateIntersectionObservations \ No newline at end of file diff --git a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js index c05e3c4cbfe..125343172a5 100644 --- a/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js +++ b/packages/react-native/src/private/webapis/intersectionobserver/__tests__/IntersectionObserver-itest.js @@ -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(); + + function TestComponent() { + const [showView, setShowView] = useState(true); + + return showView ? ( + { + observer.observe(ensureReactNativeElement(nodeRef.current)); + setShowView(false); + }} + style={{width: 100, height: 100, backgroundColor: 'red'}} + ref={nodeRef} + /> + ) : null; + } + + Fantom.runTask(() => { + root.render(); + }); + + 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();