diff --git a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp index a5f305525421fd..915512f8feb039 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.cpp @@ -90,7 +90,7 @@ static Rect computeIntersection( std::optional IntersectionObserver::updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double mountTime) { + double time) { const auto layoutableRootShadowNode = dynamic_cast(&rootShadowNode); @@ -121,14 +121,12 @@ IntersectionObserver::updateIntersectionObservation( : intersectionRectArea / targetBoundingRectArea; if (intersectionRatio == 0) { - return setNotIntersectingState( - rootBoundingRect, targetBoundingRect, mountTime); + return setNotIntersectingState(rootBoundingRect, targetBoundingRect, time); } auto highestThresholdCrossed = getHighestThresholdCrossed(intersectionRatio); if (highestThresholdCrossed == -1) { - return setNotIntersectingState( - rootBoundingRect, targetBoundingRect, mountTime); + return setNotIntersectingState(rootBoundingRect, targetBoundingRect, time); } return setIntersectingState( @@ -136,7 +134,13 @@ IntersectionObserver::updateIntersectionObservation( targetBoundingRect, intersectionRect, highestThresholdCrossed, - mountTime); + time); +} + +std::optional +IntersectionObserver::updateIntersectionObservationForSurfaceUnmount( + double time) { + return setNotIntersectingState(Rect{}, Rect{}, time); } Float IntersectionObserver::getHighestThresholdCrossed( @@ -156,7 +160,7 @@ IntersectionObserver::setIntersectingState( const Rect& targetBoundingRect, const Rect& intersectionRect, Float threshold, - double mountTime) { + double time) { auto newState = IntersectionObserverState::Intersecting(threshold); if (state_ != newState) { @@ -168,7 +172,7 @@ IntersectionObserver::setIntersectingState( rootBoundingRect, intersectionRect, true, - mountTime, + time, }; return std::optional{std::move(entry)}; } @@ -180,7 +184,7 @@ std::optional IntersectionObserver::setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, - double mountTime) { + double time) { if (state_ != IntersectionObserverState::NotIntersecting()) { state_ = IntersectionObserverState::NotIntersecting(); IntersectionObserverEntry entry{ @@ -190,7 +194,7 @@ IntersectionObserver::setNotIntersectingState( rootBoundingRect, std::nullopt, false, - mountTime, + time, }; return std::optional(std::move(entry)); } 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 5daba9023ef121..d4e9ac3735fb3b 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserver.h @@ -41,7 +41,10 @@ class IntersectionObserver { // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo std::optional updateIntersectionObservation( const RootShadowNode& rootShadowNode, - double mountTime); + double time); + + std::optional + updateIntersectionObservationForSurfaceUnmount(double time); IntersectionObserverObserverId getIntersectionObserverId() const { return intersectionObserverId_; @@ -63,12 +66,12 @@ class IntersectionObserver { const Rect& targetBoundingRect, const Rect& intersectionRect, Float threshold, - double mountTime); + double time); std::optional setNotIntersectingState( const Rect& rootBoundingRect, const Rect& targetBoundingRect, - double mountTime); + double time); IntersectionObserverObserverId intersectionObserverId_; ShadowNode::Shared targetShadowNode_; 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 2e1fe4eeb9096a..e4d00377388b7f 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.cpp @@ -160,15 +160,27 @@ IntersectionObserverManager::takeRecords() { return entries; } +#pragma mark - UIManagerMountHook + void IntersectionObserverManager::shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept { - updateIntersectionObservations(*rootShadowNode, mountTime); + double time) noexcept { + updateIntersectionObservations( + rootShadowNode->getSurfaceId(), rootShadowNode, time); +} + +void IntersectionObserverManager::shadowTreeDidUnmount( + SurfaceId surfaceId, + double time) noexcept { + updateIntersectionObservations(surfaceId, nullptr, time); } +#pragma mark - Private methods + void IntersectionObserverManager::updateIntersectionObservations( - const RootShadowNode& rootShadowNode, - double mountTime) { + SurfaceId surfaceId, + const RootShadowNode::Shared& rootShadowNode, + double time) { SystraceSection s( "IntersectionObserverManager::updateIntersectionObservations"); @@ -178,8 +190,6 @@ void IntersectionObserverManager::updateIntersectionObservations( { std::shared_lock lock(observersMutex_); - auto surfaceId = rootShadowNode.getSurfaceId(); - auto observersIt = observersBySurfaceId_.find(surfaceId); if (observersIt == observersBySurfaceId_.end()) { return; @@ -187,8 +197,14 @@ void IntersectionObserverManager::updateIntersectionObservations( auto& observers = observersIt->second; for (auto& observer : observers) { - auto entry = - observer.updateIntersectionObservation(rootShadowNode, mountTime); + std::optional entry; + + if (rootShadowNode != nullptr) { + entry = observer.updateIntersectionObservation(*rootShadowNode, time); + } else { + entry = observer.updateIntersectionObservationForSurfaceUnmount(time); + } + if (entry) { entries.push_back(std::move(entry).value()); } 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 480f7f7f22d39e..82ca77d3f4bd3b 100644 --- a/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h +++ b/packages/react-native/ReactCommon/react/renderer/observers/intersection/IntersectionObserverManager.h @@ -42,7 +42,9 @@ class IntersectionObserverManager final : public UIManagerMountHook { void shadowTreeDidMount( const RootShadowNode::Shared& rootShadowNode, - double mountTime) noexcept override; + double time) noexcept override; + + void shadowTreeDidUnmount(SurfaceId surfaceId, double time) noexcept override; private: mutable std::unordered_map> @@ -63,8 +65,9 @@ class IntersectionObserverManager final : public UIManagerMountHook { // Equivalent to // https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo void updateIntersectionObservations( - const RootShadowNode& rootShadowNode, - double mountTime); + SurfaceId surfaceId, + const RootShadowNode::Shared& rootShadowNode, + double time); const IntersectionObserver& getRegisteredIntersectionObserver( SurfaceId surfaceId, diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp index de2bf9c1fa3175..949f367117dd02 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManager.cpp @@ -636,15 +636,15 @@ void UIManager::reportMount(SurfaceId surfaceId) const { shadowTree.getMountingCoordinator()->getBaseRevision().rootShadowNode; }); - if (!rootShadowNode) { - return; - } - { std::shared_lock lock(mountHookMutex_); for (auto* mountHook : mountHooks_) { - mountHook->shadowTreeDidMount(rootShadowNode, time); + if (rootShadowNode) { + mountHook->shadowTreeDidMount(rootShadowNode, time); + } else { + mountHook->shadowTreeDidUnmount(surfaceId, time); + } } } } diff --git a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h index f460a0fd642f4c..3e5627b53eb15f 100644 --- a/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h +++ b/packages/react-native/ReactCommon/react/renderer/uimanager/UIManagerMountHook.h @@ -28,6 +28,12 @@ class UIManagerMountHook { const RootShadowNode::Shared& rootShadowNode, double mountTime) noexcept = 0; + virtual void shadowTreeDidUnmount( + SurfaceId /*surfaceId*/, + double /*unmountTime*/) noexcept { + // Default no-op implementation for backwards compatibility. + } + virtual ~UIManagerMountHook() noexcept = default; };