From ff4537c15ecef319ff3dfc87613e4696d280c643 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 18 Mar 2025 12:12:03 -0700 Subject: [PATCH] Allow listeners in EventEmitter (#49998) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/49998 We have listeners for EventDispatcher, but we don't have any listener capabilities for synchronous hooks from events on the UI thread. This proposal adds an affordance for generic event listeners that can be attached by the host platform mounting manager, e.g., to wire events to NativeAnimated event drivers. ## Changelog [General][Added] - EventEmitter `addListener` and `removeListener` APIs Reviewed By: javache Differential Revision: D71050838 fbshipit-source-id: a7f298c71bd882a573781c2fe1fb5a1ae79f301c --- .../react/renderer/core/EventEmitter.cpp | 33 +++++++++++++ .../react/renderer/core/EventEmitter.h | 14 ++++++ .../react/renderer/core/EventListener.cpp | 44 ----------------- .../react/renderer/core/EventListener.h | 47 ++++++++++++++++--- 4 files changed, 87 insertions(+), 51 deletions(-) delete mode 100644 packages/react-native/ReactCommon/react/renderer/core/EventListener.cpp diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp index b2e5bf04b54..b4af39c6841 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.cpp @@ -94,6 +94,16 @@ void EventEmitter::dispatchEvent( return; } + // Allows the event listener to interrupt default event dispatch + if (payload != nullptr) { + if (eventListeners_.willDispatchEvent( + eventTarget_ != nullptr ? eventTarget_->getTag() : 0, + type, + *payload)) { + return; + } + } + eventDispatcher->dispatchEvent(RawEvent( normalizeEventType(std::move(type)), std::move(payload), @@ -119,6 +129,16 @@ void EventEmitter::dispatchUniqueEvent( return; } + // Allows the event listener to interrupt default event dispatch + if (payload != nullptr) { + if (eventListeners_.willDispatchEvent( + eventTarget_ != nullptr ? eventTarget_->getTag() : 0, + type, + *payload)) { + return; + } + } + eventDispatcher->dispatchUniqueEvent(RawEvent( normalizeEventType(std::move(type)), std::move(payload), @@ -153,4 +173,17 @@ const SharedEventTarget& EventEmitter::getEventTarget() const { return eventTarget_; } +void EventEmitter::addListener( + std::shared_ptr listener) const { + eventListeners_.addListener(std::move(listener)); +} + +/* + * Removes provided event listener to the event dispatcher. + */ +void EventEmitter::removeListener( + const std::shared_ptr& listener) const { + eventListeners_.removeListener(listener); +} + } // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h index 7651b4ccec4..d2b4237526c 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventEmitter.h @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -103,12 +104,25 @@ class EventEmitter { void dispatchUniqueEvent(std::string type, SharedEventPayload payload) const; +#pragma mark - Event listeners + /* + * Adds provided event listener to the event dispatcher. + */ + void addListener(std::shared_ptr listener) const; + + /* + * Removes provided event listener to the event dispatcher. + */ + void removeListener( + const std::shared_ptr& listener) const; + private: friend class UIManagerBinding; mutable SharedEventTarget eventTarget_; EventDispatcher::Weak eventDispatcher_; + mutable EventEmitterListenerContainer eventListeners_{}; mutable int enableCounter_{0}; mutable bool isEnabled_{false}; }; diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventListener.cpp b/packages/react-native/ReactCommon/react/renderer/core/EventListener.cpp deleted file mode 100644 index a64e24fdff7..00000000000 --- a/packages/react-native/ReactCommon/react/renderer/core/EventListener.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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. - */ - -#include "EventListener.h" - -#include - -namespace facebook::react { - -bool EventListenerContainer::willDispatchEvent(const RawEvent& event) { - std::shared_lock lock(mutex_); - - bool handled = false; - for (const auto& listener : eventListeners_) { - handled = (*listener)(event); - if (handled) { - break; - } - } - return handled; -} - -void EventListenerContainer::addListener( - std::shared_ptr listener) { - std::unique_lock lock(mutex_); - - eventListeners_.push_back(std::move(listener)); -} - -void EventListenerContainer::removeListener( - const std::shared_ptr& listener) { - std::unique_lock lock(mutex_); - - auto it = std::find(eventListeners_.begin(), eventListeners_.end(), listener); - if (it != eventListeners_.end()) { - eventListeners_.erase(it); - } -} - -} // namespace facebook::react diff --git a/packages/react-native/ReactCommon/react/renderer/core/EventListener.h b/packages/react-native/ReactCommon/react/renderer/core/EventListener.h index 1d5e72168dd..8f15ebcd92b 100644 --- a/packages/react-native/ReactCommon/react/renderer/core/EventListener.h +++ b/packages/react-native/ReactCommon/react/renderer/core/EventListener.h @@ -7,9 +7,10 @@ #pragma once +#include #include -#include +#include #include namespace facebook::react { @@ -19,23 +20,55 @@ namespace facebook::react { * Return `true` to interrupt default dispatch to JS event emitter, `false` to * pass through to default handlers. */ -using EventListener = std::function; -class EventListenerContainer { +template +using EventListenerT = std::function; + +template +class EventListenerContainerT { public: /* * Invoke listeners in this container with the event. * Returns true if event was handled by the listener, false to continue * default dispatch. */ - bool willDispatchEvent(const RawEvent& event); + bool willDispatchEvent(TArgs... args) { + std::shared_lock lock(mutex_); + bool handled = false; + for (const auto& listener : eventListeners_) { + handled = (*listener)(args...); + if (handled) { + break; + } + } + return handled; + } - void addListener(std::shared_ptr listener); - void removeListener(const std::shared_ptr& listener); + void addListener(std::shared_ptr> listener) { + std::unique_lock lock(mutex_); + eventListeners_.push_back(std::move(listener)); + } + + void removeListener( + const std::shared_ptr>& listener) { + std::unique_lock lock(mutex_); + auto it = + std::find(eventListeners_.begin(), eventListeners_.end(), listener); + if (it != eventListeners_.end()) { + eventListeners_.erase(it); + } + } private: std::shared_mutex mutex_; - std::vector> eventListeners_; + std::vector>> eventListeners_; }; +using EventListener = EventListenerT; +using EventListenerContainer = EventListenerContainerT; +using EventEmitterListener = + EventListenerT; +using EventEmitterListenerContainer = + EventListenerContainerT; + } // namespace facebook::react