From 0277c563a05979a09744c9742f1ac397827cb6a2 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 21 Apr 2022 08:04:14 -0700 Subject: [PATCH 1/4] Add event listeners to Scheduler Summary: Minimal set of changes to intercept events in external modules. Current intended use-case is for Reanimated to handle events for the Animated properties. Changelog: [Added] Add listeners to allow intercepting events in C++ core. Differential Revision: D35312534 fbshipit-source-id: 17ea760d016e68ee203b0106a113006f39fc159b --- .../react/renderer/core/EventDispatcher.cpp | 21 +++++++++ .../react/renderer/core/EventDispatcher.h | 15 +++++++ .../react/renderer/core/EventListener.cpp | 39 ++++++++++++++++ .../react/renderer/core/EventListener.h | 44 +++++++++++++++++++ .../react/renderer/scheduler/Scheduler.cpp | 14 ++++++ .../react/renderer/scheduler/Scheduler.h | 6 +++ 6 files changed, 139 insertions(+) create mode 100644 ReactCommon/react/renderer/core/EventListener.cpp create mode 100644 ReactCommon/react/renderer/core/EventListener.h diff --git a/ReactCommon/react/renderer/core/EventDispatcher.cpp b/ReactCommon/react/renderer/core/EventDispatcher.cpp index 615390b04d6a0c..34f68f4bf3b88f 100644 --- a/ReactCommon/react/renderer/core/EventDispatcher.cpp +++ b/ReactCommon/react/renderer/core/EventDispatcher.cpp @@ -36,6 +36,10 @@ EventDispatcher::EventDispatcher( void EventDispatcher::dispatchEvent(RawEvent &&rawEvent, EventPriority priority) const { + // Allows the event listener to interrupt default event dispatch + if (eventListeners_.willDispatchEvent(rawEvent)) { + return; + } getEventQueue(priority).enqueueEvent(std::move(rawEvent)); } @@ -46,6 +50,10 @@ void EventDispatcher::dispatchStateUpdate( } void EventDispatcher::dispatchUniqueEvent(RawEvent &&rawEvent) const { + // Allows the event listener to interrupt default event dispatch + if (eventListeners_.willDispatchEvent(rawEvent)) { + return; + } asynchronousBatchedQueue_->enqueueUniqueEvent(std::move(rawEvent)); } @@ -62,5 +70,18 @@ const EventQueue &EventDispatcher::getEventQueue(EventPriority priority) const { } } +void EventDispatcher::addListener( + const std::shared_ptr &listener) const { + eventListeners_.addListener(listener); +} + +/* + * Removes provided event listener to the event dispatcher. + */ +void EventDispatcher::removeListener( + const std::shared_ptr &listener) const { + eventListeners_.removeListener(listener); +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/core/EventDispatcher.h b/ReactCommon/react/renderer/core/EventDispatcher.h index 9b630b3bf2a9be..19f1fabb7e7b70 100644 --- a/ReactCommon/react/renderer/core/EventDispatcher.h +++ b/ReactCommon/react/renderer/core/EventDispatcher.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -52,6 +53,18 @@ class EventDispatcher { void dispatchStateUpdate(StateUpdate &&stateUpdate, EventPriority priority) const; +#pragma mark - Event listeners + /* + * Adds provided event listener to the event dispatcher. + */ + void addListener(const std::shared_ptr &listener) const; + + /* + * Removes provided event listener to the event dispatcher. + */ + void removeListener( + const std::shared_ptr &listener) const; + private: EventQueue const &getEventQueue(EventPriority priority) const; @@ -59,6 +72,8 @@ class EventDispatcher { std::unique_ptr synchronousBatchedQueue_; std::unique_ptr asynchronousUnbatchedQueue_; std::unique_ptr asynchronousBatchedQueue_; + + mutable EventListenerContainer eventListeners_; }; } // namespace react diff --git a/ReactCommon/react/renderer/core/EventListener.cpp b/ReactCommon/react/renderer/core/EventListener.cpp new file mode 100644 index 00000000000000..48f5793292dc7b --- /dev/null +++ b/ReactCommon/react/renderer/core/EventListener.cpp @@ -0,0 +1,39 @@ +/* + * 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" + +namespace facebook::react { + +bool EventListenerContainer::willDispatchEvent(const RawEvent &event) { + std::shared_lock lock(mutex_); + + bool handled = false; + for (auto const &listener : eventListeners_) { + handled = handled || listener->operator()(event); + } + return handled; +} + +void EventListenerContainer::addListener( + const std::shared_ptr &listener) { + std::unique_lock lock(mutex_); + + eventListeners_.push_back(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/ReactCommon/react/renderer/core/EventListener.h b/ReactCommon/react/renderer/core/EventListener.h new file mode 100644 index 00000000000000..f9176b514cbfa0 --- /dev/null +++ b/ReactCommon/react/renderer/core/EventListener.h @@ -0,0 +1,44 @@ +/* + * 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 + +#include + +#include + +namespace facebook { +namespace react { + +/** + * Listener for events dispatched to JS runtime. + * Return `true` to interrupt default dispatch to JS event emitter, `false` to + * pass through to default handlers. + */ +using EventListener = std::function; + +class EventListenerContainer { + 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); + + void addListener(const std::shared_ptr &listener); + void removeListener(const std::shared_ptr &listener); + + private: + butter::shared_mutex mutex_; + std::vector> eventListeners_; +}; + +} // namespace react +} // namespace facebook diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index f33cf522d4aa3b..5161f5c9fca18c 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -368,5 +368,19 @@ ContextContainer::Shared Scheduler::getContextContainer() const { return contextContainer_; } +void Scheduler::addEventListener( + const std::shared_ptr &listener) { + if (eventDispatcher_->has_value()) { + eventDispatcher_->value().addListener(listener); + } +} + +void Scheduler::removeEventListener( + const std::shared_ptr &listener) { + if (eventDispatcher_->has_value()) { + eventDispatcher_->value().removeListener(listener); + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.h b/ReactCommon/react/renderer/scheduler/Scheduler.h index 30dad1f75adb04..f06adbaadf14ca 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -107,6 +108,11 @@ class Scheduler final : public UIManagerDelegate { #pragma mark - ContextContainer ContextContainer::Shared getContextContainer() const; +#pragma mark - Event listeners + void addEventListener(const std::shared_ptr &listener); + void removeEventListener( + const std::shared_ptr &listener); + private: friend class SurfaceHandler; From f15406c4fe811f95b8715c380e1971909dfb222f Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 21 Apr 2022 06:28:27 -0700 Subject: [PATCH 2/4] Expose scheduler through FabricUIManager Differential Revision: D35313399 fbshipit-source-id: d53d9642ae351360b5825d280f4244ce701eebcd --- .../com/facebook/react/fabric/jni/Binding.h | 3 ++- .../react/fabric/jni/JFabricUIManager.cpp | 18 +++++++++++++ .../react/fabric/jni/JFabricUIManager.h | 25 +++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.cpp create mode 100644 ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.h diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h index b7df029f3d2ed7..9b0a1fd49cca02 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/Binding.h @@ -43,6 +43,8 @@ class Binding : public jni::HybridClass, static void registerNatives(); + std::shared_ptr getScheduler(); + private: void setConstraints( jint surfaceId, @@ -133,7 +135,6 @@ class Binding : public jni::HybridClass, std::shared_ptr mountingManager_; std::shared_ptr scheduler_; - std::shared_ptr getScheduler(); std::shared_ptr verifyMountingManager( std::string const &locationHint); diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.cpp b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.cpp new file mode 100644 index 00000000000000..595cccd6c7adc6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.cpp @@ -0,0 +1,18 @@ +/* + * 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 "JFabricUIManager.h" + +namespace facebook::react { + +Binding *JFabricUIManager::getBinding() { + static const auto bindingField = + javaClassStatic()->getField("mBinding"); + + return getFieldValue(bindingField)->cthis(); +} +} // namespace facebook::react diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.h b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.h new file mode 100644 index 00000000000000..dffb09905ccae6 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/jni/JFabricUIManager.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 +#include "Binding.h" + +using namespace facebook::jni; + +namespace facebook::react { + +class JFabricUIManager : public JavaClass { + public: + static constexpr auto kJavaDescriptor = + "Lcom/facebook/react/fabric/FabricUIManager;"; + + Binding *getBinding(); +}; + +} // namespace facebook::react From 16698ea2ddc24122bf89fe127d062c13530de0e4 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 21 Apr 2022 06:28:27 -0700 Subject: [PATCH 3/4] Expose scheduler through RCTSurfacePresenter Summary: Exposes event listener through iOS scheduler wrapper (`RCTScheduler`) and exposes scheduler itself through `RCTSurfacePresenter`. Changelog: [Changed] Exposed `RCTScheduler` to allow setting event listeners. Differential Revision: D35313398 fbshipit-source-id: 67d27969d7a64f021810292f84ddb52484c11b68 --- React/Fabric/RCTScheduler.h | 5 +++++ React/Fabric/RCTScheduler.mm | 10 ++++++++++ React/Fabric/RCTSurfacePresenter.h | 2 ++ React/Fabric/RCTSurfacePresenter.mm | 10 +++++----- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index b201c0a75f5694..c9ee613b8219c7 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -10,6 +10,7 @@ #import #import +#import #import #import #import @@ -64,6 +65,10 @@ NS_ASSUME_NONNULL_BEGIN - (void)animationTick; +- (void)addEventListener:(std::shared_ptr const &)listener; + +- (void)removeEventListener:(std::shared_ptr const &)listener; + @end NS_ASSUME_NONNULL_END diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index 4bc19fd9c8c7ff..b8921fdabe2f2a 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -193,4 +193,14 @@ - (void)onAllAnimationsComplete } } +- (void)addEventListener:(std::shared_ptr const &)listener +{ + return _scheduler->addEventListener(listener); +} + +- (void)removeEventListener:(std::shared_ptr const &)listener +{ + return _scheduler->removeEventListener(listener); +} + @end diff --git a/React/Fabric/RCTSurfacePresenter.h b/React/Fabric/RCTSurfacePresenter.h index d9a56ac31cc836..8e645be4c413c5 100644 --- a/React/Fabric/RCTSurfacePresenter.h +++ b/React/Fabric/RCTSurfacePresenter.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @class RCTFabricSurface; @class RCTImageLoader; @class RCTMountingManager; +@class RCTScheduler; /** * Coordinates presenting of React Native Surfaces and represents application @@ -53,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)unregisterSurface:(RCTFabricSurface *)surface; @property (readonly) RCTMountingManager *mountingManager; +@property (readonly, nullable) RCTScheduler *scheduler; /* * Allow callers to initialize a new fabric surface without adding Fabric as a Buck dependency. diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index 8ccc496c3dea8f..75e6b2c9833add 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -115,7 +115,7 @@ - (RCTMountingManager *)mountingManager return _mountingManager; } -- (RCTScheduler *_Nullable)_scheduler +- (RCTScheduler *_Nullable)scheduler { std::lock_guard lock(_schedulerAccessMutex); return _scheduler; @@ -151,7 +151,7 @@ - (void)setRuntimeExecutor:(RuntimeExecutor)runtimeExecutor - (void)registerSurface:(RCTFabricSurface *)surface { [_surfaceRegistry registerSurface:surface]; - RCTScheduler *scheduler = [self _scheduler]; + RCTScheduler *scheduler = [self scheduler]; if (scheduler) { [scheduler registerSurface:surface.surfaceHandler]; } @@ -159,7 +159,7 @@ - (void)registerSurface:(RCTFabricSurface *)surface - (void)unregisterSurface:(RCTFabricSurface *)surface { - RCTScheduler *scheduler = [self _scheduler]; + RCTScheduler *scheduler = [self scheduler]; if (scheduler) { [scheduler unregisterSurface:surface.surfaceHandler]; } @@ -188,7 +188,7 @@ - (UIView *)findComponentViewWithTag_DO_NOT_USE_DEPRECATED:(NSInteger)tag - (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictionary *)props { - RCTScheduler *scheduler = [self _scheduler]; + RCTScheduler *scheduler = [self scheduler]; if (!scheduler) { return NO; } @@ -212,7 +212,7 @@ - (BOOL)synchronouslyUpdateViewOnUIThread:(NSNumber *)reactTag props:(NSDictiona - (void)setupAnimationDriverWithSurfaceHandler:(facebook::react::SurfaceHandler const &)surfaceHandler { - [[self _scheduler] setupAnimationDriver:surfaceHandler]; + [[self scheduler] setupAnimationDriver:surfaceHandler]; } - (BOOL)suspend From 2c6211c2dd426e47e1044a7784a1fb7a97414d38 Mon Sep 17 00:00:00 2001 From: Andrei Shikov Date: Thu, 21 Apr 2022 08:45:01 -0700 Subject: [PATCH 4/4] Expose UIManager from Scheduler (#33545) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/33545 Exposes `UIManager` instance for access from third-party modules. Changelog: [Changed] Exposes `UIManager` instance for third-party access. Reviewed By: javache Differential Revision: D35314058 fbshipit-source-id: 8640911c24a19c48fe6a4141cfec26c9427fb9c3 --- React/Fabric/RCTScheduler.h | 2 ++ React/Fabric/RCTScheduler.mm | 5 +++++ ReactCommon/react/renderer/scheduler/Scheduler.cpp | 4 ++++ ReactCommon/react/renderer/scheduler/Scheduler.h | 3 +++ 4 files changed, 14 insertions(+) diff --git a/React/Fabric/RCTScheduler.h b/React/Fabric/RCTScheduler.h index c9ee613b8219c7..29d0f7038cf8a0 100644 --- a/React/Fabric/RCTScheduler.h +++ b/React/Fabric/RCTScheduler.h @@ -16,6 +16,7 @@ #import #import #import +#import #import NS_ASSUME_NONNULL_BEGIN @@ -48,6 +49,7 @@ NS_ASSUME_NONNULL_BEGIN @interface RCTScheduler : NSObject @property (atomic, weak, nullable) id delegate; +@property (readonly, nullable) facebook::react::UIManager const *uiManager; - (instancetype)initWithToolbox:(facebook::react::SchedulerToolbox)toolbox; diff --git a/React/Fabric/RCTScheduler.mm b/React/Fabric/RCTScheduler.mm index b8921fdabe2f2a..0c42e2157877af 100644 --- a/React/Fabric/RCTScheduler.mm +++ b/React/Fabric/RCTScheduler.mm @@ -203,4 +203,9 @@ - (void)removeEventListener:(std::shared_ptr const &)listener return _scheduler->removeEventListener(listener); } +- (facebook::react::UIManager const *)uiManager +{ + return _scheduler->getUIManager().get(); +} + @end diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.cpp b/ReactCommon/react/renderer/scheduler/Scheduler.cpp index 5161f5c9fca18c..01cf24e7c1fa57 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.cpp +++ b/ReactCommon/react/renderer/scheduler/Scheduler.cpp @@ -368,6 +368,10 @@ ContextContainer::Shared Scheduler::getContextContainer() const { return contextContainer_; } +std::shared_ptr Scheduler::getUIManager() const { + return uiManager_; +} + void Scheduler::addEventListener( const std::shared_ptr &listener) { if (eventDispatcher_->has_value()) { diff --git a/ReactCommon/react/renderer/scheduler/Scheduler.h b/ReactCommon/react/renderer/scheduler/Scheduler.h index f06adbaadf14ca..626572a073f23f 100644 --- a/ReactCommon/react/renderer/scheduler/Scheduler.h +++ b/ReactCommon/react/renderer/scheduler/Scheduler.h @@ -108,6 +108,9 @@ class Scheduler final : public UIManagerDelegate { #pragma mark - ContextContainer ContextContainer::Shared getContextContainer() const; +#pragma mark - UIManager + std::shared_ptr getUIManager() const; + #pragma mark - Event listeners void addEventListener(const std::shared_ptr &listener); void removeEventListener(