mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Remove old touch processing for Fabric
Summary: With the updates to touch processing rolled out, we can remove the feature flag and clean up the old code. The old path is now used exclusively by legacy renderer and Fabric uses new `EventEmitter#receiveTouches` method to process touches. Changelog: [Changed][Android] - Update touch processing internals Reviewed By: mdvacca Differential Revision: D32953664 fbshipit-source-id: 517a4ce6ce9bc15528c2db94d7d11bdff8b78743
This commit is contained in:
committed by
Facebook GitHub Bot
parent
0088c22b3d
commit
3b6d8af290
@@ -105,10 +105,6 @@ public class ReactFeatureFlags {
|
||||
|
||||
public static boolean enableScrollViewSnapToAlignmentProp = true;
|
||||
|
||||
public static boolean useDispatchUniqueForCoalescableEvents = false;
|
||||
|
||||
public static boolean useUpdatedTouchPreprocessing = false;
|
||||
|
||||
/** TODO: T103427072 Delete ReactFeatureFlags.enableNestedTextOnPressEventFix */
|
||||
public static boolean enableNestedTextOnPressEventFix = true;
|
||||
|
||||
|
||||
+7
-146
@@ -7,27 +7,16 @@
|
||||
|
||||
package com.facebook.react.fabric.events;
|
||||
|
||||
import static com.facebook.react.uimanager.events.TouchesHelper.CHANGED_TOUCHES_KEY;
|
||||
import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY;
|
||||
import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_SURFACE_KEY;
|
||||
import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY;
|
||||
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.facebook.common.logging.FLog;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.bridge.WritableNativeArray;
|
||||
import com.facebook.react.bridge.WritableNativeMap;
|
||||
import com.facebook.react.fabric.FabricUIManager;
|
||||
import com.facebook.react.uimanager.events.EventCategoryDef;
|
||||
import com.facebook.react.uimanager.events.RCTModernEventEmitter;
|
||||
import com.facebook.react.uimanager.events.TouchEventType;
|
||||
import com.facebook.react.uimanager.events.TouchEvent;
|
||||
import com.facebook.react.uimanager.events.TouchesHelper;
|
||||
import com.facebook.systrace.Systrace;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class FabricEventEmitter implements RCTModernEventEmitter {
|
||||
|
||||
@@ -67,145 +56,17 @@ public class FabricEventEmitter implements RCTModernEventEmitter {
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes touches in a JS compatible way and send it to Fabric core
|
||||
*
|
||||
* @param eventName the event name (see {@link TouchEventType})
|
||||
* @param touches all the touch data extracted from MotionEvent
|
||||
* @param changedIndices the indices of the pointers that changed (MOVE/CANCEL includes all
|
||||
* touches, START/END only the one that was added/removed)
|
||||
*/
|
||||
/** Touches are dispatched by {@link #receiveTouches(TouchEvent)} */
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
@NonNull String eventName,
|
||||
@NonNull WritableArray touches,
|
||||
@NonNull WritableArray changedIndices) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"FabricEventEmitter.receiveTouches('" + eventName + "')");
|
||||
|
||||
boolean isFinalEvent =
|
||||
TouchEventType.END.getJsName().equalsIgnoreCase(eventName)
|
||||
|| TouchEventType.CANCEL.getJsName().equalsIgnoreCase(eventName);
|
||||
|
||||
Pair<WritableArray, WritableArray> result =
|
||||
isFinalEvent
|
||||
? removeTouchesAtIndices(touches, changedIndices)
|
||||
: touchSubsequence(touches, changedIndices);
|
||||
|
||||
WritableArray changedTouches = result.first;
|
||||
touches = result.second;
|
||||
|
||||
int eventCategory = getTouchCategory(eventName);
|
||||
for (int jj = 0; jj < changedTouches.size(); jj++) {
|
||||
WritableMap touch = getWritableMap(changedTouches.getMap(jj));
|
||||
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
|
||||
// the `changedTouches`/`touches`. This saves allocations.
|
||||
|
||||
touch.putArray(CHANGED_TOUCHES_KEY, copyWritableArray(changedTouches));
|
||||
touch.putArray(TOUCHES_KEY, copyWritableArray(touches));
|
||||
WritableMap nativeEvent = touch;
|
||||
int rootNodeID = 0;
|
||||
int targetSurfaceId = nativeEvent.getInt(TARGET_SURFACE_KEY);
|
||||
int targetReactTag = nativeEvent.getInt(TARGET_KEY);
|
||||
if (targetReactTag < 1) {
|
||||
FLog.e(TAG, "A view is reporting that a touch occurred on tag zero.");
|
||||
} else {
|
||||
rootNodeID = targetReactTag;
|
||||
}
|
||||
|
||||
receiveEvent(targetSurfaceId, rootNodeID, eventName, false, 0, touch, eventCategory);
|
||||
}
|
||||
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
throw new IllegalStateException("EventEmitter#receiveTouches is not supported by Fabric");
|
||||
}
|
||||
|
||||
/** TODO T31905686 optimize this to avoid copying arrays */
|
||||
private WritableArray copyWritableArray(@NonNull WritableArray array) {
|
||||
WritableNativeArray ret = new WritableNativeArray();
|
||||
for (int i = 0; i < array.size(); i++) {
|
||||
ret.pushMap(getWritableMap(array.getMap(i)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys `touches` by removing touch objects at indices `indices`. This is to maintain
|
||||
* compatibility with W3C touch "end" events, where the active touches don't include the set that
|
||||
* has just been "ended".
|
||||
*
|
||||
* <p>This method was originally in ReactNativeRenderer.js
|
||||
*
|
||||
* <p>TODO: this method is a copy from ReactNativeRenderer.removeTouchesAtIndices and it needs to
|
||||
* be rewritten in a more efficient way,
|
||||
*
|
||||
* @param touches {@link WritableArray} Deserialized touch objects.
|
||||
* @param indices {WritableArray} Indices to remove from `touches`.
|
||||
* @return {Array<Touch>} Subsequence of removed touch objects.
|
||||
*/
|
||||
private @NonNull Pair<WritableArray, WritableArray> removeTouchesAtIndices(
|
||||
@NonNull WritableArray touches, @NonNull WritableArray indices) {
|
||||
WritableArray rippedOut = new WritableNativeArray();
|
||||
// use an unsafe downcast to alias to nullable elements,
|
||||
// so we can delete and then compact.
|
||||
WritableArray tempTouches = new WritableNativeArray();
|
||||
Set<Integer> rippedOutIndices = new HashSet<>();
|
||||
for (int i = 0; i < indices.size(); i++) {
|
||||
int index = indices.getInt(i);
|
||||
rippedOut.pushMap(getWritableMap(touches.getMap(index)));
|
||||
rippedOutIndices.add(index);
|
||||
}
|
||||
for (int j = 0; j < touches.size(); j++) {
|
||||
if (!rippedOutIndices.contains(j)) {
|
||||
tempTouches.pushMap(getWritableMap(touches.getMap(j)));
|
||||
}
|
||||
}
|
||||
|
||||
return new Pair<>(rippedOut, tempTouches);
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects a subsequence of `Touch`es, without destroying `touches`.
|
||||
*
|
||||
* <p>This method was originally in ReactNativeRenderer.js
|
||||
*
|
||||
* @param touches {@link WritableArray} Deserialized touch objects.
|
||||
* @param changedIndices {@link WritableArray} Indices by which to pull subsequence.
|
||||
* @return {Array<Touch>} Subsequence of touch objects.
|
||||
*/
|
||||
private @NonNull Pair<WritableArray, WritableArray> touchSubsequence(
|
||||
@NonNull WritableArray touches, @NonNull WritableArray changedIndices) {
|
||||
WritableArray result = new WritableNativeArray();
|
||||
for (int i = 0; i < changedIndices.size(); i++) {
|
||||
result.pushMap(getWritableMap(touches.getMap(changedIndices.getInt(i))));
|
||||
}
|
||||
return new Pair<>(result, touches);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: this is required because the WritableNativeArray.getMap() returns a ReadableMap instead
|
||||
* of the original writableMap. this will change in the near future.
|
||||
*
|
||||
* @param readableMap {@link ReadableMap} source map
|
||||
*/
|
||||
private @NonNull WritableMap getWritableMap(@NonNull ReadableMap readableMap) {
|
||||
WritableNativeMap map = new WritableNativeMap();
|
||||
map.merge(readableMap);
|
||||
return map;
|
||||
}
|
||||
|
||||
@EventCategoryDef
|
||||
private static int getTouchCategory(String touchEventType) {
|
||||
int category = EventCategoryDef.UNSPECIFIED;
|
||||
if (TouchEventType.MOVE.getJsName().equals(touchEventType)) {
|
||||
category = EventCategoryDef.CONTINUOUS;
|
||||
} else if (TouchEventType.START.getJsName().equals(touchEventType)) {
|
||||
category = EventCategoryDef.CONTINUOUS_START;
|
||||
} else if (TouchEventType.END.getJsName().equals(touchEventType)
|
||||
|| TouchEventType.CANCEL.getJsName().equals(touchEventType)) {
|
||||
category = EventCategoryDef.CONTINUOUS_END;
|
||||
}
|
||||
|
||||
return category;
|
||||
@Override
|
||||
public void receiveTouches(TouchEvent event) {
|
||||
TouchesHelper.sendTouchEvent(this, event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +159,7 @@ public abstract class Event<T extends Event> {
|
||||
/**
|
||||
* Dispatch this event to JS using the given event emitter. Compatible with old and new renderer.
|
||||
* Instead of using this or dispatchModern, it is recommended that you simply override
|
||||
* `getEventData`. In the future
|
||||
* `getEventData`.
|
||||
*/
|
||||
@Deprecated
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
@@ -192,25 +192,14 @@ public abstract class Event<T extends Event> {
|
||||
* non-null, this will use the RCTModernEventEmitter API. Otherwise, it falls back to the
|
||||
* old-style dispatch function. For Event classes that need to do something different, this method
|
||||
* can always be overridden entirely, but it is not recommended.
|
||||
*/
|
||||
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
|
||||
if (getSurfaceId() != -1) {
|
||||
WritableMap eventData = getEventData();
|
||||
if (eventData != null) {
|
||||
rctEventEmitter.receiveEvent(getSurfaceId(), getViewTag(), getEventName(), eventData);
|
||||
return;
|
||||
}
|
||||
}
|
||||
dispatch(rctEventEmitter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch this event to JS using a V2 version of dispatchModern. See all comments from
|
||||
* `dispatchModern` - all still apply. This method additionally allows C++ to coalesce events
|
||||
* (Fabric only). This will ONLY be called in an experimental path, and in Fabric only.
|
||||
*
|
||||
* <p>This method additionally allows C++ to coalesce events and detect continuous ones for
|
||||
* concurrent mode (Fabric only).
|
||||
*
|
||||
* @see #dispatch
|
||||
*/
|
||||
@Deprecated
|
||||
public void dispatchModernV2(RCTModernEventEmitter rctEventEmitter) {
|
||||
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
|
||||
if (getSurfaceId() != -1) {
|
||||
WritableMap eventData = getEventData();
|
||||
if (eventData != null) {
|
||||
|
||||
+1
-6
@@ -13,7 +13,6 @@ import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
import com.facebook.react.modules.core.ChoreographerCompat;
|
||||
import com.facebook.react.modules.core.ReactChoreographer;
|
||||
import com.facebook.react.uimanager.common.UIManagerType;
|
||||
@@ -368,11 +367,7 @@ public class EventDispatcherImpl implements EventDispatcher, LifecycleEventListe
|
||||
Systrace.endAsyncFlow(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, event.getEventName(), event.getUniqueID());
|
||||
|
||||
if (ReactFeatureFlags.useDispatchUniqueForCoalescableEvents) {
|
||||
event.dispatchModernV2(mReactEventEmitter);
|
||||
} else {
|
||||
event.dispatchModern(mReactEventEmitter);
|
||||
}
|
||||
event.dispatchModern(mReactEventEmitter);
|
||||
event.dispose();
|
||||
}
|
||||
clearEventsToDispatch();
|
||||
|
||||
+1
-1
@@ -84,7 +84,7 @@ public class LockFreeEventDispatcherImpl implements EventDispatcher, LifecycleEv
|
||||
listener.onEventDispatch(event);
|
||||
}
|
||||
|
||||
event.dispatchModernV2(mReactEventEmitter);
|
||||
event.dispatchModern(mReactEventEmitter);
|
||||
event.dispose();
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -30,4 +30,6 @@ public interface RCTModernEventEmitter extends RCTEventEmitter {
|
||||
int customCoalesceKey,
|
||||
@Nullable WritableMap event,
|
||||
@EventCategoryDef int category);
|
||||
|
||||
void receiveTouches(TouchEvent event);
|
||||
}
|
||||
|
||||
+17
-4
@@ -70,14 +70,27 @@ public class ReactEventEmitter implements RCTModernEventEmitter {
|
||||
@Override
|
||||
public void receiveTouches(
|
||||
String eventName, WritableArray touches, WritableArray changedIndices) {
|
||||
/*
|
||||
* This method should be unused by default processing pipeline, but leaving it here to make sure
|
||||
* that any custom code using it in legacy renderer is compatible
|
||||
*/
|
||||
Assertions.assertCondition(touches.size() > 0);
|
||||
|
||||
int reactTag = touches.getMap(0).getInt(TARGET_KEY);
|
||||
@UIManagerType int uiManagerType = ViewUtil.getUIManagerType(reactTag);
|
||||
if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) {
|
||||
mFabricEventEmitter.receiveTouches(eventName, touches, changedIndices);
|
||||
} else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) {
|
||||
if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) {
|
||||
mRCTEventEmitter.receiveTouches(eventName, touches, changedIndices);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void receiveTouches(TouchEvent event) {
|
||||
int reactTag = event.getViewTag();
|
||||
@UIManagerType int uiManagerType = ViewUtil.getUIManagerType(reactTag);
|
||||
if (uiManagerType == UIManagerType.FABRIC && mFabricEventEmitter != null) {
|
||||
mFabricEventEmitter.receiveTouches(event);
|
||||
} else if (uiManagerType == UIManagerType.DEFAULT && getEventEmitter(reactTag) != null) {
|
||||
TouchesHelper.sendTouchesLegacy(mRCTEventEmitter, event);
|
||||
} else {
|
||||
ReactSoftExceptionLogger.logSoftException(
|
||||
TAG,
|
||||
@@ -87,7 +100,7 @@ public class ReactEventEmitter implements RCTModernEventEmitter {
|
||||
+ "] UIManagerType["
|
||||
+ uiManagerType
|
||||
+ "] EventName["
|
||||
+ eventName
|
||||
+ event.getEventName()
|
||||
+ "]"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import androidx.core.util.Pools;
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.config.ReactFeatureFlags;
|
||||
|
||||
/**
|
||||
* An event representing the start, end or movement of a touch. Corresponds to a single {@link
|
||||
@@ -183,29 +182,14 @@ public class TouchEvent extends Event<TouchEvent> {
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
if (verifyMotionEvent()) {
|
||||
TouchesHelper.sendTouchEvent(rctEventEmitter, this);
|
||||
TouchesHelper.sendTouchesLegacy(rctEventEmitter, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
|
||||
if (ReactFeatureFlags.useUpdatedTouchPreprocessing) {
|
||||
if (verifyMotionEvent()) {
|
||||
TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ false);
|
||||
}
|
||||
} else {
|
||||
dispatch(rctEventEmitter);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchModernV2(RCTModernEventEmitter rctEventEmitter) {
|
||||
if (ReactFeatureFlags.useUpdatedTouchPreprocessing) {
|
||||
if (verifyMotionEvent()) {
|
||||
TouchesHelper.sendTouchEventModern(rctEventEmitter, this, /* useDispatchV2 */ true);
|
||||
}
|
||||
} else {
|
||||
dispatch(rctEventEmitter);
|
||||
if (verifyMotionEvent()) {
|
||||
rctEventEmitter.receiveTouches(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.PixelUtil;
|
||||
import com.facebook.systrace.Systrace;
|
||||
|
||||
/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
|
||||
public class TouchesHelper {
|
||||
@@ -74,15 +75,16 @@ public class TouchesHelper {
|
||||
|
||||
/**
|
||||
* Generate and send touch event to RCTEventEmitter JS module associated with the given {@param
|
||||
* context}. Touch event can encode multiple concurrent touches (pointers).
|
||||
* context} for legacy renderer. Touch event can encode multiple concurrent touches (pointers).
|
||||
*
|
||||
* @param rctEventEmitter Event emitter used to execute JS module call
|
||||
* @param touchEvent native touch event to read pointers count and coordinates from
|
||||
*/
|
||||
public static void sendTouchEvent(RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) {
|
||||
public static void sendTouchesLegacy(RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) {
|
||||
TouchEventType type = touchEvent.getTouchEventType();
|
||||
|
||||
WritableArray pointers = getWritableArray(createPointersArray(touchEvent));
|
||||
WritableArray pointers =
|
||||
getWritableArray(/* copyObjects */ false, createPointersArray(touchEvent));
|
||||
MotionEvent motionEvent = touchEvent.getMotionEvent();
|
||||
|
||||
// For START and END events send only index of the pointer that is associated with that event
|
||||
@@ -111,10 +113,11 @@ public class TouchesHelper {
|
||||
*
|
||||
* @param eventEmitter emitter to dispatch event to
|
||||
* @param event the touch event to extract data from
|
||||
* @param useDispatchV2 whether to dispatch additional data used by {@link Event#dispatchModernV2}
|
||||
*/
|
||||
public static void sendTouchEventModern(
|
||||
RCTModernEventEmitter eventEmitter, TouchEvent event, boolean useDispatchV2) {
|
||||
public static void sendTouchEvent(RCTModernEventEmitter eventEmitter, TouchEvent event) {
|
||||
Systrace.beginSection(
|
||||
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
|
||||
"TouchesHelper.sentTouchEventModern(" + event.getEventName() + ")");
|
||||
TouchEventType type = event.getTouchEventType();
|
||||
MotionEvent motionEvent = event.getMotionEvent();
|
||||
|
||||
@@ -166,24 +169,17 @@ public class TouchesHelper {
|
||||
eventData.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray);
|
||||
eventData.putArray(TOUCHES_KEY, touchesArray);
|
||||
|
||||
if (useDispatchV2) {
|
||||
eventEmitter.receiveEvent(
|
||||
event.getSurfaceId(),
|
||||
event.getViewTag(),
|
||||
event.getEventName(),
|
||||
event.canCoalesce(),
|
||||
0,
|
||||
eventData,
|
||||
event.getEventCategory());
|
||||
} else {
|
||||
eventEmitter.receiveEvent(
|
||||
event.getSurfaceId(), event.getViewTag(), event.getEventName(), eventData);
|
||||
}
|
||||
eventEmitter.receiveEvent(
|
||||
event.getSurfaceId(),
|
||||
event.getViewTag(),
|
||||
event.getEventName(),
|
||||
event.canCoalesce(),
|
||||
0,
|
||||
eventData,
|
||||
event.getEventCategory());
|
||||
}
|
||||
}
|
||||
|
||||
private static WritableArray getWritableArray(WritableMap... objects) {
|
||||
return getWritableArray(false, objects);
|
||||
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
|
||||
}
|
||||
|
||||
private static WritableArray getWritableArray(boolean copyObjects, WritableMap... objects) {
|
||||
|
||||
Reference in New Issue
Block a user