Skip to content

Commit 8823928

Browse files
Luna Weipull[bot]
authored andcommitted
PointerEvents: Mark when listened to for touch interactions
Summary: Changelog: [Internal] - Bypass dispatching an event if no view along the hierarchy is listening to it. Only applied for touch-based interactions. Next change will add optimization for mouse interactions Reviewed By: vincentriemer Differential Revision: D35739417 fbshipit-source-id: 134ffefef3bb4f97bf3e63b6bccc0caca464dfbd
1 parent eb0542b commit 8823928

File tree

8 files changed

+311
-45
lines changed

8 files changed

+311
-45
lines changed

ReactAndroid/src/main/java/com/facebook/react/fabric/jni/viewPropConversions.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ constexpr MapBuffer::Key VP_SHADOW_COLOR = 31;
5555
constexpr MapBuffer::Key VP_TEST_ID = 32;
5656
constexpr MapBuffer::Key VP_TRANSFORM = 33;
5757
constexpr MapBuffer::Key VP_ZINDEX = 34;
58+
constexpr MapBuffer::Key VP_POINTER_ENTER2 = 35;
59+
constexpr MapBuffer::Key VP_POINTER_LEAVE2 = 36;
60+
constexpr MapBuffer::Key VP_POINTER_MOVE2 = 37;
61+
constexpr MapBuffer::Key VP_POINTER_ENTER2_CAPTURE = 38;
62+
constexpr MapBuffer::Key VP_POINTER_LEAVE2_CAPTURE = 39;
63+
constexpr MapBuffer::Key VP_POINTER_MOVE2_CAPTURE = 40;
5864

5965
// Yoga values
6066
constexpr MapBuffer::Key YG_BORDER_WIDTH = 100;
@@ -462,6 +468,22 @@ static inline MapBuffer viewPropsDiff(
462468
VP_POINTER_LEAVE, newProps.events[ViewEvents::Offset::PointerLeave]);
463469
builder.putBool(
464470
VP_POINTER_MOVE, newProps.events[ViewEvents::Offset::PointerMove]);
471+
472+
builder.putBool(
473+
VP_POINTER_ENTER2, newProps.events[ViewEvents::Offset::PointerEnter2]);
474+
builder.putBool(
475+
VP_POINTER_ENTER2_CAPTURE,
476+
newProps.events[ViewEvents::Offset::PointerEnter2Capture]);
477+
builder.putBool(
478+
VP_POINTER_LEAVE2, newProps.events[ViewEvents::Offset::PointerLeave2]);
479+
builder.putBool(
480+
VP_POINTER_LEAVE2_CAPTURE,
481+
newProps.events[ViewEvents::Offset::PointerLeave2Capture]);
482+
builder.putBool(
483+
VP_POINTER_MOVE2, newProps.events[ViewEvents::Offset::PointerMove2]);
484+
builder.putBool(
485+
VP_POINTER_MOVE2_CAPTURE,
486+
newProps.events[ViewEvents::Offset::PointerMove2Capture]);
465487
}
466488

467489
if (oldProps.removeClippedSubviews != newProps.removeClippedSubviews) {

ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,39 @@ public void setPointerMove(@NonNull T view, boolean value) {
557557
view.setTag(R.id.pointer_move, value);
558558
}
559559

560+
/* Experimental W3C Pointer events start */
561+
@ReactProp(name = "onPointerEnter2")
562+
public void setPointerEnter2(@NonNull T view, boolean value) {
563+
view.setTag(R.id.pointer_enter2, value);
564+
}
565+
566+
@ReactProp(name = "onPointerEnter2Capture")
567+
public void setPointerEnter2Capture(@NonNull T view, boolean value) {
568+
view.setTag(R.id.pointer_enter2_capture, value);
569+
}
570+
571+
@ReactProp(name = "onPointerLeave2")
572+
public void setPointerLeave2(@NonNull T view, boolean value) {
573+
view.setTag(R.id.pointer_leave2, value);
574+
}
575+
576+
@ReactProp(name = "onPointerLeave2Capture")
577+
public void setPointerLeave2Capture(@NonNull T view, boolean value) {
578+
view.setTag(R.id.pointer_leave2_capture, value);
579+
}
580+
581+
@ReactProp(name = "onPointerMove2")
582+
public void setPointerMove2(@NonNull T view, boolean value) {
583+
view.setTag(R.id.pointer_move2, value);
584+
}
585+
586+
@ReactProp(name = "onPointerMove2Capture")
587+
public void setPointerMove2Capture(@NonNull T view, boolean value) {
588+
view.setTag(R.id.pointer_move2_capture, value);
589+
}
590+
591+
/* Experimental W3C Pointer events end */
592+
560593
@ReactProp(name = "onMoveShouldSetResponder")
561594
public void setMoveShouldSetResponder(@NonNull T view, boolean value) {
562595
// no-op, handled by JSResponder

ReactAndroid/src/main/java/com/facebook/react/uimanager/JSPointerDispatcher.java

Lines changed: 111 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import com.facebook.react.uimanager.events.EventDispatcher;
1818
import com.facebook.react.uimanager.events.PointerEvent;
1919
import com.facebook.react.uimanager.events.PointerEventHelper;
20+
import com.facebook.react.uimanager.events.PointerEventHelper.EVENT;
2021
import com.facebook.react.uimanager.events.TouchEvent;
2122
import com.facebook.react.uimanager.events.TouchEventCoalescingKeyHelper;
2223
import java.util.Collections;
@@ -37,7 +38,7 @@ public class JSPointerDispatcher {
3738
private final TouchEventCoalescingKeyHelper mTouchEventCoalescingKeyHelper =
3839
new TouchEventCoalescingKeyHelper();
3940

40-
private static final float ONMOVE_EPSILON = 1f;
41+
private static final float ONMOVE_EPSILON = 0.1f;
4142

4243
// Set globally for hover interactions, referenced for coalescing hover events
4344
private long mHoverInteractionKey = TouchEvent.UNSET;
@@ -82,7 +83,9 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
8283
if (hitPath.isEmpty()) {
8384
return;
8485
}
85-
int targetTag = hitPath.get(0).getViewId();
86+
87+
TouchTargetHelper.ViewTarget activeViewTarget = hitPath.get(0);
88+
int activeTargetTag = activeViewTarget.getViewId();
8689

8790
if (supportsHover) {
8891
if (action == MotionEvent.ACTION_HOVER_MOVE) {
@@ -107,15 +110,17 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
107110
mTouchEventCoalescingKeyHelper.addCoalescingKey(mDownStartTime);
108111

109112
if (!supportsHover) {
110-
// Enter root -> child
111-
for (int i = hitPath.size(); i-- > 0; ) {
112-
int tag = hitPath.get(i).getViewId();
113-
eventDispatcher.dispatchEvent(
114-
PointerEvent.obtain(PointerEventHelper.POINTER_ENTER, surfaceId, tag, motionEvent));
115-
}
113+
dispatchNonBubblingEventForPathWhenListened(
114+
EVENT.ENTER, EVENT.ENTER_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
115+
}
116+
117+
boolean listeningForDown =
118+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
119+
if (listeningForDown) {
120+
eventDispatcher.dispatchEvent(
121+
PointerEvent.obtain(
122+
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
116123
}
117-
eventDispatcher.dispatchEvent(
118-
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
119124

120125
return;
121126
}
@@ -129,25 +134,47 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
129134
// New pointer goes down, this can only happen after ACTION_DOWN is sent for the first pointer
130135
if (action == MotionEvent.ACTION_POINTER_DOWN) {
131136
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
132-
eventDispatcher.dispatchEvent(
133-
PointerEvent.obtain(PointerEventHelper.POINTER_DOWN, surfaceId, targetTag, motionEvent));
137+
138+
boolean listeningForDown =
139+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.DOWN, EVENT.DOWN_CAPTURE);
140+
if (listeningForDown) {
141+
eventDispatcher.dispatchEvent(
142+
PointerEvent.obtain(
143+
PointerEventHelper.POINTER_DOWN, surfaceId, activeTargetTag, motionEvent));
144+
}
134145

135146
return;
136147
}
137148

138149
if (action == MotionEvent.ACTION_MOVE) {
139150
int coalescingKey = mTouchEventCoalescingKeyHelper.getCoalescingKey(mDownStartTime);
140-
eventDispatcher.dispatchEvent(
141-
PointerEvent.obtain(
142-
PointerEventHelper.POINTER_MOVE, surfaceId, targetTag, motionEvent, coalescingKey));
151+
152+
boolean listeningForMove =
153+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.MOVE, EVENT.MOVE_CAPTURE);
154+
if (listeningForMove) {
155+
eventDispatcher.dispatchEvent(
156+
PointerEvent.obtain(
157+
PointerEventHelper.POINTER_MOVE,
158+
surfaceId,
159+
activeTargetTag,
160+
motionEvent,
161+
coalescingKey));
162+
}
163+
143164
return;
144165
}
145166

146167
// Exactly one of the pointers goes up, not the last one
147168
if (action == MotionEvent.ACTION_POINTER_UP) {
148169
mTouchEventCoalescingKeyHelper.incrementCoalescingKey(mDownStartTime);
149-
eventDispatcher.dispatchEvent(
150-
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
170+
171+
boolean listeningForUp =
172+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
173+
if (listeningForUp) {
174+
eventDispatcher.dispatchEvent(
175+
PointerEvent.obtain(
176+
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
177+
}
151178

152179
return;
153180
}
@@ -159,16 +186,17 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
159186
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
160187
mDownStartTime = TouchEvent.UNSET;
161188

162-
eventDispatcher.dispatchEvent(
163-
PointerEvent.obtain(PointerEventHelper.POINTER_UP, surfaceId, targetTag, motionEvent));
189+
boolean listeningForUp =
190+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.UP, EVENT.UP_CAPTURE);
191+
if (listeningForUp) {
192+
eventDispatcher.dispatchEvent(
193+
PointerEvent.obtain(
194+
PointerEventHelper.POINTER_UP, surfaceId, activeTargetTag, motionEvent));
195+
}
164196

165197
if (!supportsHover) {
166-
// Leave child -> root
167-
for (int i = 0; i < hitPath.size(); i++) {
168-
int tag = hitPath.get(i).getViewId();
169-
eventDispatcher.dispatchEvent(
170-
PointerEvent.obtain(PointerEventHelper.POINTER_LEAVE, surfaceId, tag, motionEvent));
171-
}
198+
dispatchNonBubblingEventForPathWhenListened(
199+
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
172200
}
173201
return;
174202
}
@@ -183,15 +211,54 @@ public void handleMotionEvent(MotionEvent motionEvent, EventDispatcher eventDisp
183211
"Warning : Motion Event was ignored. Action="
184212
+ action
185213
+ " Target="
186-
+ targetTag
214+
+ activeTargetTag
187215
+ " Supports Hover="
188216
+ supportsHover);
189217
}
190218

191-
private int findTargetTagAndSetCoordinates(MotionEvent ev) {
192-
// This method updates `mTargetCoordinates` with coordinates for the motion event.
193-
return TouchTargetHelper.findTargetTagAndCoordinatesForTouch(
194-
ev.getX(), ev.getY(), mRootViewGroup, mTargetCoordinates, null);
219+
private static boolean isAnyoneListeningForBubblingEvent(
220+
List<ViewTarget> hitPath, EVENT event, EVENT captureEvent) {
221+
for (ViewTarget viewTarget : hitPath) {
222+
if (PointerEventHelper.isListening(viewTarget.getView(), event)
223+
|| PointerEventHelper.isListening(viewTarget.getView(), captureEvent)) {
224+
return true;
225+
}
226+
}
227+
return false;
228+
}
229+
230+
/*
231+
Dispatch event only if ancestor is listening to relevant event.
232+
This should only be relevant for ENTER/LEAVE events.
233+
@param hitPath - ordered from inner target to root
234+
*/
235+
236+
/** Dispatch non-bubbling event along the hit path only when relevant listeners */
237+
private static void dispatchNonBubblingEventForPathWhenListened(
238+
EVENT event,
239+
EVENT captureEvent,
240+
List<ViewTarget> hitPath,
241+
EventDispatcher dispatcher,
242+
int surfaceId,
243+
MotionEvent motionEvent) {
244+
245+
boolean ancestorListening = false;
246+
String eventName = PointerEventHelper.getDispatchableEventName(event);
247+
if (eventName == null) {
248+
return;
249+
}
250+
251+
// iterate through hitPath from ancestor -> target
252+
for (int i = hitPath.size() - 1; i >= 0; i--) {
253+
View view = hitPath.get(i).getView();
254+
int viewId = hitPath.get(i).getViewId();
255+
if (ancestorListening
256+
|| (i == 0 && PointerEventHelper.isListening(view, event))
257+
|| PointerEventHelper.isListening(view, captureEvent)) {
258+
dispatcher.dispatchEvent(PointerEvent.obtain(eventName, surfaceId, viewId, motionEvent));
259+
ancestorListening = true;
260+
}
261+
}
195262
}
196263

197264
// called on hover_move motion events only
@@ -311,21 +378,21 @@ private void dispatchCancelEvent(
311378
int surfaceId = UIManagerHelper.getSurfaceId(mRootViewGroup);
312379

313380
if (!hitPath.isEmpty()) {
314-
int targetTag = hitPath.get(0).getViewId();
315-
// Question: Does cancel fire on all in hit path?
316-
Assertions.assertNotNull(eventDispatcher)
317-
.dispatchEvent(
318-
PointerEvent.obtain(
319-
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
320-
321-
for (ViewTarget viewTarget : hitPath) {
322-
eventDispatcher.dispatchEvent(
323-
PointerEvent.obtain(
324-
PointerEventHelper.POINTER_LEAVE, surfaceId, viewTarget.getViewId(), motionEvent));
381+
boolean listeningForCancel =
382+
isAnyoneListeningForBubblingEvent(hitPath, EVENT.CANCEL, EVENT.CANCEL_CAPTURE);
383+
if (listeningForCancel) {
384+
int targetTag = hitPath.get(0).getViewId();
385+
Assertions.assertNotNull(eventDispatcher)
386+
.dispatchEvent(
387+
PointerEvent.obtain(
388+
PointerEventHelper.POINTER_CANCEL, surfaceId, targetTag, motionEvent));
325389
}
326-
}
327390

328-
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
329-
mDownStartTime = TouchEvent.UNSET;
391+
dispatchNonBubblingEventForPathWhenListened(
392+
EVENT.LEAVE, EVENT.LEAVE_CAPTURE, hitPath, eventDispatcher, surfaceId, motionEvent);
393+
394+
mTouchEventCoalescingKeyHelper.removeCoalescingKey(mDownStartTime);
395+
mDownStartTime = TouchEvent.UNSET;
396+
}
330397
}
331398
}

0 commit comments

Comments
 (0)