mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
8dded42b68
Summary:
# Context
ScrollView sticky headers rely on this bit of code to work:
```
AnimatedImplementation.attachNativeEvent(
this._scrollViewRef,
'onScroll',
[{nativeEvent: {contentOffset: {y: this._scrollAnimatedValue}}}],
);
```
What this code means:
When the ScrollView host component receives the "onScroll" event, assign event.nativeEvent.contentOffSet.y to the this._scrollAnimatedValue AnimatedValue.
How this subscription mechanism is set up:
NativeAnimatedTurboModule subscribes to events dispatched by RCTEventDispatcher sendEvent. Then, whenever RCTEventEmitter sendEvent executes, NativeAnimatedTurboModule also updates the AnimatedValue for that event.
# Problem
Previously, in bridgeless, we started dispatching RCTScrollView via the RCTEventDispatcher sendEvent to update the AnimatedValue for ScrollView. This meant that we started dispatching the onScroll event to JavaScript via RCTEventEmitter.receiveEvent JSModule, which isn't supported in the Fabric renderer.
With this diff, we dialed back that solution. Instead of (1) notifying NativeAnimatedTurboModule and (2) sending the onScroll event to JavaScript, we're only doing (1) (i.e: notifying NativeAnimatedTurboModule).
Changelog: [Internal]
Reviewed By: fkgozali
Differential Revision: D37257719
fbshipit-source-id: 7dea3cf8b9ae78f6b0fd40414b8f224d43367a5a
236 lines
6.5 KiB
Plaintext
236 lines
6.5 KiB
Plaintext
/*
|
|
* 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.
|
|
*/
|
|
|
|
#import "RCTEventDispatcher.h"
|
|
|
|
#import <React/RCTAssert.h>
|
|
#import <React/RCTBridge+Private.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTComponentEvent.h>
|
|
#import <React/RCTProfile.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <ReactCommon/RCTTurboModule.h>
|
|
|
|
#import "CoreModulesPlugins.h"
|
|
|
|
static NSNumber *RCTGetEventID(NSNumber *viewTag, NSString *eventName, uint16_t coalescingKey)
|
|
{
|
|
return @(viewTag.intValue | (((uint64_t)eventName.hash & 0xFFFF) << 32) | (((uint64_t)coalescingKey) << 48));
|
|
}
|
|
|
|
static uint16_t RCTUniqueCoalescingKeyGenerator = 0;
|
|
|
|
@interface RCTEventDispatcher () <RCTTurboModule>
|
|
@end
|
|
|
|
@implementation RCTEventDispatcher {
|
|
// We need this lock to protect access to _events, _eventQueue and _eventsDispatchScheduled. It's filled in on main
|
|
// thread and consumed on js thread.
|
|
NSLock *_eventQueueLock;
|
|
// We have this id -> event mapping so we coalesce effectively.
|
|
NSMutableDictionary<NSNumber *, id<RCTEvent>> *_events;
|
|
// This array contains ids of events in order they come in, so we can emit them to JS in the exact same order.
|
|
NSMutableArray<NSNumber *> *_eventQueue;
|
|
BOOL _eventsDispatchScheduled;
|
|
NSHashTable<id<RCTEventDispatcherObserver>> *_observers;
|
|
NSRecursiveLock *_observersLock;
|
|
}
|
|
|
|
@synthesize bridge = _bridge;
|
|
@synthesize dispatchToJSThread = _dispatchToJSThread;
|
|
@synthesize callableJSModules = _callableJSModules;
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
- (void)initialize
|
|
{
|
|
_events = [NSMutableDictionary new];
|
|
_eventQueue = [NSMutableArray new];
|
|
_eventQueueLock = [NSLock new];
|
|
_eventsDispatchScheduled = NO;
|
|
_observers = [NSHashTable weakObjectsHashTable];
|
|
_observersLock = [NSRecursiveLock new];
|
|
}
|
|
|
|
- (void)sendViewEventWithName:(NSString *)name reactTag:(NSNumber *)reactTag
|
|
{
|
|
[_callableJSModules invokeModule:@"RCTViewEventEmitter" method:@"emit" withArgs:@[ name, RCTNullIfNil(reactTag) ]];
|
|
}
|
|
|
|
- (void)sendAppEventWithName:(NSString *)name body:(id)body
|
|
{
|
|
[_callableJSModules invokeModule:@"RCTNativeAppEventEmitter"
|
|
method:@"emit"
|
|
withArgs:body ? @[ name, body ] : @[ name ]];
|
|
}
|
|
|
|
- (void)sendDeviceEventWithName:(NSString *)name body:(id)body
|
|
{
|
|
[_callableJSModules invokeModule:@"RCTDeviceEventEmitter" method:@"emit" withArgs:body ? @[ name, body ] : @[ name ]];
|
|
}
|
|
|
|
- (void)sendTextEventWithType:(RCTTextEventType)type
|
|
reactTag:(NSNumber *)reactTag
|
|
text:(NSString *)text
|
|
key:(NSString *)key
|
|
eventCount:(NSInteger)eventCount
|
|
{
|
|
static NSString *events[] = {@"focus", @"blur", @"change", @"submitEditing", @"endEditing", @"keyPress"};
|
|
|
|
NSMutableDictionary *body = [[NSMutableDictionary alloc] initWithDictionary:@{
|
|
@"eventCount" : @(eventCount),
|
|
}];
|
|
|
|
if (text) {
|
|
body[@"text"] = text;
|
|
}
|
|
|
|
if (key) {
|
|
if (key.length == 0) {
|
|
key = @"Backspace"; // backspace
|
|
} else {
|
|
switch ([key characterAtIndex:0]) {
|
|
case '\t':
|
|
key = @"Tab";
|
|
break;
|
|
case '\n':
|
|
key = @"Enter";
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
body[@"key"] = key;
|
|
}
|
|
|
|
RCTComponentEvent *event = [[RCTComponentEvent alloc] initWithName:events[type] viewTag:reactTag body:body];
|
|
[self sendEvent:event];
|
|
}
|
|
|
|
- (void)notifyObserversOfEvent:(id<RCTEvent>)event
|
|
{
|
|
[_observersLock lock];
|
|
|
|
for (id<RCTEventDispatcherObserver> observer in _observers) {
|
|
[observer eventDispatcherWillDispatchEvent:event];
|
|
}
|
|
|
|
[_observersLock unlock];
|
|
}
|
|
|
|
- (void)sendEvent:(id<RCTEvent>)event
|
|
{
|
|
[self notifyObserversOfEvent:event];
|
|
|
|
[_eventQueueLock lock];
|
|
|
|
NSNumber *eventID;
|
|
if (event.canCoalesce) {
|
|
eventID = RCTGetEventID(event.viewTag, event.eventName, event.coalescingKey);
|
|
id<RCTEvent> previousEvent = _events[eventID];
|
|
if (previousEvent) {
|
|
event = [previousEvent coalesceWithEvent:event];
|
|
} else {
|
|
[_eventQueue addObject:eventID];
|
|
}
|
|
} else {
|
|
id<RCTEvent> previousEvent = _events[eventID];
|
|
eventID = RCTGetEventID(event.viewTag, event.eventName, RCTUniqueCoalescingKeyGenerator++);
|
|
RCTAssert(
|
|
previousEvent == nil,
|
|
@"Got event %@ which cannot be coalesced, but has the same eventID %@ as the previous event %@",
|
|
event,
|
|
eventID,
|
|
previousEvent);
|
|
[_eventQueue addObject:eventID];
|
|
}
|
|
|
|
_events[eventID] = event;
|
|
|
|
BOOL scheduleEventsDispatch = NO;
|
|
if (!_eventsDispatchScheduled) {
|
|
_eventsDispatchScheduled = YES;
|
|
scheduleEventsDispatch = YES;
|
|
}
|
|
|
|
// We have to release the lock before dispatching block with events,
|
|
// since dispatchBlock: can be executed synchronously on the same queue.
|
|
// (This is happening when chrome debugging is turned on.)
|
|
[_eventQueueLock unlock];
|
|
|
|
if (scheduleEventsDispatch) {
|
|
if (_bridge) {
|
|
[_bridge
|
|
dispatchBlock:^{
|
|
[self flushEventsQueue];
|
|
}
|
|
queue:RCTJSThread];
|
|
} else if (_dispatchToJSThread) {
|
|
_dispatchToJSThread(^{
|
|
[self flushEventsQueue];
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)addDispatchObserver:(id<RCTEventDispatcherObserver>)observer
|
|
{
|
|
[_observersLock lock];
|
|
[_observers addObject:observer];
|
|
[_observersLock unlock];
|
|
}
|
|
|
|
- (void)removeDispatchObserver:(id<RCTEventDispatcherObserver>)observer
|
|
{
|
|
[_observersLock lock];
|
|
[_observers removeObject:observer];
|
|
[_observersLock unlock];
|
|
}
|
|
|
|
- (void)dispatchEvent:(id<RCTEvent>)event
|
|
{
|
|
NSString *moduleDotMethod = [[event class] moduleDotMethod];
|
|
NSArray<NSString *> *const components = [moduleDotMethod componentsSeparatedByString:@"."];
|
|
NSString *const moduleName = components[0];
|
|
NSString *const methodName = components[1];
|
|
|
|
[_callableJSModules invokeModule:moduleName method:methodName withArgs:[event arguments]];
|
|
}
|
|
|
|
- (dispatch_queue_t)methodQueue
|
|
{
|
|
return RCTJSThread;
|
|
}
|
|
|
|
// js thread only (which surprisingly can be the main thread, depends on used JS executor)
|
|
- (void)flushEventsQueue
|
|
{
|
|
[_eventQueueLock lock];
|
|
NSDictionary *events = _events;
|
|
_events = [NSMutableDictionary new];
|
|
NSMutableArray *eventQueue = _eventQueue;
|
|
_eventQueue = [NSMutableArray new];
|
|
_eventsDispatchScheduled = NO;
|
|
[_eventQueueLock unlock];
|
|
|
|
for (NSNumber *eventId in eventQueue) {
|
|
[self dispatchEvent:events[eventId]];
|
|
}
|
|
}
|
|
|
|
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
@end
|
|
|
|
Class RCTEventDispatcherCls(void)
|
|
{
|
|
return RCTEventDispatcher.class;
|
|
}
|