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();