Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/react-native/React/Fabric/RCTConversions.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,28 @@
#import <react/renderer/graphics/Color.h>
#import <react/renderer/graphics/RCTPlatformColorUtils.h>
#import <react/renderer/graphics/Transform.h>
#import <react/timing/primitives.h>

NS_ASSUME_NONNULL_BEGIN

/*
* Converts an iOS timestamp (seconds since boot, NOT including sleep time, from
* NSProcessInfo.processInfo.systemUptime or UITouch.timestamp) to a HighResTimeStamp.
*
* iOS timestamps use mach_absolute_time() which doesn't account for sleep time,
* while std::chrono::steady_clock uses mach_continuous_time() which does.
* To handle this correctly, we compute the relative offset from the current time
* and apply it to HighResTimeStamp::now().
*/
inline facebook::react::HighResTimeStamp RCTHighResTimeStampFromSeconds(NSTimeInterval seconds)
{
NSTimeInterval nowSystemUptime = NSProcessInfo.processInfo.systemUptime;
NSTimeInterval delta = nowSystemUptime - seconds;
auto deltaDuration =
std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(delta));
return facebook::react::HighResTimeStamp::now() - facebook::react::HighResDuration::fromChrono(deltaDuration);
}

inline NSString *RCTNSStringFromString(
const std::string &string,
const NSStringEncoding &encoding = NSUTF8StringEncoding)
Expand Down
10 changes: 8 additions & 2 deletions packages/react-native/React/Fabric/RCTSurfacePointerHandler.mm
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,7 @@ static PointerEvent CreatePointerEventFromActivePointer(
PointerEvent event = {};
event.pointerId = activePointer.identifier;
event.pointerType = PointerTypeCStringFromUITouchType(activePointer.touchType);
event.timeStamp = RCTHighResTimeStampFromSeconds(activePointer.timestamp);

if (eventType == RCTPointerEventTypeCancel) {
event.clientPoint = RCTPointFromCGPoint(CGPointZero);
Expand Down Expand Up @@ -345,7 +346,8 @@ static PointerEvent CreatePointerEventFromIncompleteHoverData(
CGPoint clientLocation,
CGPoint screenLocation,
CGPoint offsetLocation,
UIKeyModifierFlags modifierFlags)
UIKeyModifierFlags modifierFlags,
HighResTimeStamp timeStamp)
{
PointerEvent event = {};
event.pointerId = pointerId;
Expand All @@ -365,6 +367,7 @@ static PointerEvent CreatePointerEventFromIncompleteHoverData(
event.tangentialPressure = 0.0;
event.twist = 0;
event.isPrimary = true;
event.timeStamp = timeStamp;

return event;
}
Expand Down Expand Up @@ -760,8 +763,11 @@ - (void)hovering:(UIHoverGestureRecognizer *)recognizer

modifierFlags = recognizer.modifierFlags;

// For hover events, use the current time as we don't have a precise timestamp
HighResTimeStamp eventTimestamp = HighResTimeStamp::now();

PointerEvent event = CreatePointerEventFromIncompleteHoverData(
pointerId, pointerType, clientLocation, screenLocation, offsetLocation, modifierFlags);
pointerId, pointerType, clientLocation, screenLocation, offsetLocation, modifierFlags, eventTimestamp);

SharedTouchEventEmitter eventEmitter = GetTouchEmitterFromView(targetView, offsetLocation);
if (eventEmitter != nil) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ static void UpdateActiveTouchWithUITouch(
activeTouch.touch.pagePoint = RCTPointFromCGPoint(pagePoint);

activeTouch.touch.timestamp = uiTouch.timestamp;
activeTouch.touch.timeStamp = RCTHighResTimeStampFromSeconds(uiTouch.timestamp);

if (RCTForceTouchAvailable()) {
activeTouch.touch.force = RCTZeroIfNaN(uiTouch.force / uiTouch.maximumPossibleForce);
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,7 @@ public class com/facebook/react/fabric/FabricUIManager : com/facebook/react/brid
public fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZ)V
public fun receiveEvent (IILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IZJ)V
public fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public fun removeUIManagerEventListener (Lcom/facebook/react/bridge/UIManagerListener;)V
public fun resolveCustomDirectEventName (Ljava/lang/String;)Ljava/lang/String;
Expand All @@ -2287,6 +2288,7 @@ public final class com/facebook/react/fabric/mounting/SurfaceMountingManager {
public final fun attachRootView (Landroid/view/View;Lcom/facebook/react/uimanager/ThemedReactContext;)V
public final fun deleteView (I)V
public final fun enqueuePendingEvent (ILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;I)V
public final fun enqueuePendingEvent (ILjava/lang/String;ZLcom/facebook/react/bridge/WritableMap;IJ)V
public final fun getContext ()Lcom/facebook/react/uimanager/ThemedReactContext;
public final fun getSurfaceId ()I
public final fun getView (I)Landroid/view/View;
Expand Down Expand Up @@ -4804,11 +4806,13 @@ public final class com/facebook/react/uimanager/events/RCTEventEmitter$DefaultIm
public abstract interface class com/facebook/react/uimanager/events/RCTModernEventEmitter : com/facebook/react/uimanager/events/RCTEventEmitter {
public abstract fun receiveEvent (IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public abstract fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;I)V
public abstract fun receiveEvent (IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;IJ)V
public abstract fun receiveEvent (ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
}

public final class com/facebook/react/uimanager/events/RCTModernEventEmitter$DefaultImpls {
public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;IILjava/lang/String;ZILcom/facebook/react/bridge/WritableMap;IJ)V
public static fun receiveEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;ILjava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V
public static fun receiveTouches (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;Ljava/lang/String;Lcom/facebook/react/bridge/WritableArray;Lcom/facebook/react/bridge/WritableArray;)V
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ internal class EventAnimationDriver(
private val eventPath: List<String>,
@JvmField internal var valueNode: ValueAnimatedNode,
) : RCTModernEventEmitter {
@Deprecated("Use the overload with eventTimestamp parameter instead.")
override fun receiveEvent(
surfaceId: Int,
targetTag: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1067,11 +1067,13 @@ public void updateRootLayoutSpecs(
return surfaceManager.getView(reactTag);
}

@Deprecated
@Override
public void receiveEvent(int reactTag, String eventName, @Nullable WritableMap params) {
receiveEvent(View.NO_ID, reactTag, eventName, false, params, EventCategoryDef.UNSPECIFIED);
}

@Deprecated
@Override
public void receiveEvent(
int surfaceId, int reactTag, String eventName, @Nullable WritableMap params) {
Expand All @@ -1091,17 +1093,44 @@ public void receiveEvent(
* @param canCoalesceEvent
* @param params
* @param eventCategory
* @deprecated Use the overload with eventTimestamp parameter instead.
*/
@Deprecated
public void receiveEvent(
int surfaceId,
int reactTag,
String eventName,
boolean canCoalesceEvent,
@Nullable WritableMap params,
@EventCategoryDef int eventCategory) {
receiveEvent(surfaceId, reactTag, eventName, canCoalesceEvent, params, eventCategory, false);
receiveEvent(
surfaceId,
reactTag,
eventName,
canCoalesceEvent,
params,
eventCategory,
false,
SystemClock.uptimeMillis());
}

/**
* receiveEvent API that emits an event to C++. If {@code canCoalesceEvent} is true, that signals
* that C++ may coalesce the event optionally. Otherwise, coalescing can happen in Java before
* emitting.
*
* <p>{@code customCoalesceKey} is currently unused.
*
* @param surfaceId
* @param reactTag
* @param eventName
* @param canCoalesceEvent
* @param params
* @param eventCategory
* @param experimentalIsSynchronous
* @deprecated Use the overload with eventTimestamp parameter instead.
*/
@Deprecated
@Override
public void receiveEvent(
int surfaceId,
Expand All @@ -1111,6 +1140,43 @@ public void receiveEvent(
@Nullable WritableMap params,
@EventCategoryDef int eventCategory,
boolean experimentalIsSynchronous) {
receiveEvent(
surfaceId,
reactTag,
eventName,
canCoalesceEvent,
params,
eventCategory,
experimentalIsSynchronous,
SystemClock.uptimeMillis());
}

/**
* receiveEvent API that emits an event to C++. If {@code canCoalesceEvent} is true, that signals
* that C++ may coalesce the event optionally. Otherwise, coalescing can happen in Java before
* emitting.
*
* <p>{@code customCoalesceKey} is currently unused.
*
* @param surfaceId
* @param reactTag
* @param eventName
* @param canCoalesceEvent
* @param params
* @param eventCategory
* @param experimentalIsSynchronous
* @param eventTimestamp
*/
@Override
public void receiveEvent(
int surfaceId,
int reactTag,
String eventName,
boolean canCoalesceEvent,
@Nullable WritableMap params,
@EventCategoryDef int eventCategory,
boolean experimentalIsSynchronous,
long eventTimestamp) {

if (ReactBuildConfig.DEBUG && surfaceId == View.NO_ID) {
FLog.d(TAG, "Emitted event without surfaceId: [%d] %s", reactTag, eventName);
Expand All @@ -1128,7 +1194,13 @@ public void receiveEvent(
// access to the event emitter later when the view is mounted. For now just save the event
// in the view state and trigger it later.
mMountingManager.enqueuePendingEvent(
surfaceId, reactTag, eventName, canCoalesceEvent, params, eventCategory);
surfaceId,
reactTag,
eventName,
canCoalesceEvent,
params,
eventCategory,
eventTimestamp);
} else {
// This can happen if the view has disappeared from the screen (because of async events)
FLog.i(TAG, "Unable to invoke event: " + eventName + " for reactTag: " + reactTag);
Expand All @@ -1142,13 +1214,13 @@ public void receiveEvent(
boolean firstEventForFrame =
mSynchronousEvents.add(new SynchronousEvent(surfaceId, reactTag, eventName));
if (firstEventForFrame) {
eventEmitter.dispatchEventSynchronously(eventName, params);
eventEmitter.dispatchEventSynchronously(eventName, params, eventTimestamp);
}
} else {
if (canCoalesceEvent) {
eventEmitter.dispatchUnique(eventName, params);
eventEmitter.dispatchUnique(eventName, params, eventTimestamp);
} else {
eventEmitter.dispatch(eventName, params, eventCategory);
eventEmitter.dispatch(eventName, params, eventCategory, eventTimestamp);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,49 @@ internal class EventEmitterWrapper private constructor() : HybridClassBase() {
eventName: String,
params: NativeMap?,
@EventCategoryDef category: Int,
eventTimestamp: Long,
)

private external fun dispatchEventSynchronously(eventName: String, params: NativeMap?)
private external fun dispatchEventSynchronously(
eventName: String,
params: NativeMap?,
eventTimestamp: Long,
)

private external fun dispatchUniqueEvent(eventName: String, params: NativeMap?)
private external fun dispatchUniqueEvent(
eventName: String,
params: NativeMap?,
eventTimestamp: Long,
)

/**
* Invokes the execution of the C++ EventEmitter.
*
* @param eventName [String] name of the event to execute.
* @param params [WritableMap] payload of the event
* @param eventCategory event category
* @param eventTimestamp timestamp when the event was triggered (in milliseconds since boot)
*/
@Synchronized
fun dispatch(eventName: String, params: WritableMap?, @EventCategoryDef eventCategory: Int) {
fun dispatch(
eventName: String,
params: WritableMap?,
@EventCategoryDef eventCategory: Int,
eventTimestamp: Long,
) {
if (!isValid) {
return
}
dispatchEvent(eventName, params as NativeMap?, eventCategory)
dispatchEvent(eventName, params as NativeMap?, eventCategory, eventTimestamp)
}

@Synchronized
fun dispatchEventSynchronously(eventName: String, params: WritableMap?) {
fun dispatchEventSynchronously(eventName: String, params: WritableMap?, eventTimestamp: Long) {
if (!isValid) {
return
}
UiThreadUtil.assertOnUiThread()
dispatchEventSynchronously(eventName, params as NativeMap?)
dispatchEventSynchronously(eventName, params as NativeMap?, eventTimestamp)
}

/**
Expand All @@ -62,13 +78,14 @@ internal class EventEmitterWrapper private constructor() : HybridClassBase() {
*
* @param eventName [String] name of the event to execute.
* @param params [WritableMap] payload of the event
* @param eventTimestamp timestamp when the event was triggered (in milliseconds since boot)
*/
@Synchronized
fun dispatchUnique(eventName: String, params: WritableMap?) {
fun dispatchUnique(eventName: String, params: WritableMap?, eventTimestamp: Long) {
if (!isValid) {
return
}
dispatchUniqueEvent(eventName, params as NativeMap?)
dispatchUniqueEvent(eventName, params as NativeMap?, eventTimestamp)
}

@Synchronized
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

package com.facebook.react.fabric.events

import android.os.SystemClock
import com.facebook.react.bridge.WritableMap
import com.facebook.react.fabric.FabricUIManager
import com.facebook.react.uimanager.events.EventCategoryDef
import com.facebook.react.uimanager.events.RCTModernEventEmitter
import com.facebook.systrace.Systrace

internal class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTModernEventEmitter {
@Deprecated("Use the overload with eventTimestamp parameter instead.")
override fun receiveEvent(
surfaceId: Int,
targetTag: Int,
Expand All @@ -22,10 +24,41 @@ internal class FabricEventEmitter(private val uiManager: FabricUIManager) : RCTM
customCoalesceKey: Int,
params: WritableMap?,
@EventCategoryDef category: Int,
) {
receiveEvent(
surfaceId,
targetTag,
eventName,
canCoalesceEvent,
customCoalesceKey,
params,
category,
SystemClock.uptimeMillis(),
)
}

override fun receiveEvent(
surfaceId: Int,
targetTag: Int,
eventName: String,
canCoalesceEvent: Boolean,
customCoalesceKey: Int,
params: WritableMap?,
@EventCategoryDef category: Int,
eventTimestamp: Long,
) {
Systrace.beginSection(Systrace.TRACE_TAG_REACT, "FabricEventEmitter.receiveEvent('$eventName')")
try {
uiManager.receiveEvent(surfaceId, targetTag, eventName, canCoalesceEvent, params, category)
uiManager.receiveEvent(
surfaceId,
targetTag,
eventName,
canCoalesceEvent,
params,
category,
false,
eventTimestamp,
)
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT)
}
Expand Down
Loading
Loading