Bug 1264017 - Add synthesized mouse support to Android. r=rbarker

MozReview-Commit-ID: HT4U7WKTd5Z
This commit is contained in:
Kartikaya Gupta
2016-05-16 12:17:17 -04:00
parent 94e6a966bd
commit 3ef033f656
7 changed files with 167 additions and 22 deletions

View File

@@ -7,6 +7,10 @@ function getPlatform() {
if (navigator.platform.indexOf("Mac") == 0) { if (navigator.platform.indexOf("Mac") == 0) {
return "mac"; return "mac";
} }
// Check for Android before Linux
if (navigator.appVersion.indexOf("Android") >= 0) {
return "android"
}
if (navigator.platform.indexOf("Linux") == 0) { if (navigator.platform.indexOf("Linux") == 0) {
return "linux"; return "linux";
} }
@@ -49,7 +53,9 @@ function nativeMouseDownEventMsg() {
case "windows": return 2; // MOUSEEVENTF_LEFTDOWN case "windows": return 2; // MOUSEEVENTF_LEFTDOWN
case "mac": return 1; // NSLeftMouseDown case "mac": return 1; // NSLeftMouseDown
case "linux": return 4; // GDK_BUTTON_PRESS case "linux": return 4; // GDK_BUTTON_PRESS
case "android": return 5; // ACTION_POINTER_DOWN
} }
throw "Native mouse-down events not supported on platform " + getPlatform();
} }
function nativeMouseMoveEventMsg() { function nativeMouseMoveEventMsg() {
@@ -57,8 +63,9 @@ function nativeMouseMoveEventMsg() {
case "windows": return 1; // MOUSEEVENTF_MOVE case "windows": return 1; // MOUSEEVENTF_MOVE
case "mac": return 5; // NSMouseMoved case "mac": return 5; // NSMouseMoved
case "linux": return 3; // GDK_MOTION_NOTIFY case "linux": return 3; // GDK_MOTION_NOTIFY
case "android": return 7; // ACTION_HOVER_MOVE
} }
throw "Native wheel events not supported on platform " + getPlatform(); throw "Native mouse-move events not supported on platform " + getPlatform();
} }
function nativeMouseUpEventMsg() { function nativeMouseUpEventMsg() {
@@ -66,7 +73,9 @@ function nativeMouseUpEventMsg() {
case "windows": return 4; // MOUSEEVENTF_LEFTUP case "windows": return 4; // MOUSEEVENTF_LEFTUP
case "mac": return 2; // NSLeftMouseUp case "mac": return 2; // NSLeftMouseUp
case "linux": return 7; // GDK_BUTTON_RELEASE case "linux": return 7; // GDK_BUTTON_RELEASE
case "android": return 6; // ACTION_POINTER_UP
} }
throw "Native mouse-up events not supported on platform " + getPlatform();
} }
// Convert (aX, aY), in CSS pixels relative to aElement's bounding rect, // Convert (aX, aY), in CSS pixels relative to aElement's bounding rect,

View File

@@ -45,5 +45,3 @@ skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
[test_scroll_window.html] [test_scroll_window.html]
skip-if = (toolkit == 'android') # wheel events not supported on mobile skip-if = (toolkit == 'android') # wheel events not supported on mobile
[test_click.html] [test_click.html]
# Very similar to test_tap, but we don't yet have mouse event synthesization on android
skip-if = (os == 'android')

View File

@@ -713,7 +713,14 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
} }
class PointerInfo { class PointerInfo {
// We reserve one pointer ID for the mouse, so that tests don't have
// to worry about tracking pointer IDs if they just want to test mouse
// event synthesization. If somebody tries to use this ID for a
// synthesized touch event we'll throw an exception.
public static final int RESERVED_MOUSE_POINTER_ID = 100000;
public int pointerId; public int pointerId;
public int source;
public int screenX; public int screenX;
public int screenY; public int screenY;
public double pressure; public double pressure;
@@ -746,34 +753,63 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
return -1; return -1;
} }
int addPointer(int pointerId) { int addPointer(int pointerId, int source) {
PointerInfo info = new PointerInfo(); PointerInfo info = new PointerInfo();
info.pointerId = pointerId; info.pointerId = pointerId;
info.source = source;
pointers.add(info); pointers.add(info);
return pointers.size() - 1; return pointers.size() - 1;
} }
int[] getPointerIds() { int getPointerCount(int source) {
int[] ids = new int[pointers.size()]; int count = 0;
for (int i = 0; i < ids.length; i++) { for (int i = 0; i < pointers.size(); i++) {
ids[i] = pointers.get(i).pointerId; if (pointers.get(i).source == source) {
count++;
} }
return ids; }
return count;
} }
MotionEvent.PointerCoords[] getPointerCoords() { MotionEvent.PointerProperties[] getPointerProperties(int source) {
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointers.size()]; MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[getPointerCount(source)];
for (int i = 0; i < coords.length; i++) { int index = 0;
coords[i] = pointers.get(i).getCoords(); for (int i = 0; i < pointers.size(); i++) {
if (pointers.get(i).source == source) {
MotionEvent.PointerProperties p = new MotionEvent.PointerProperties();
p.id = pointers.get(i).pointerId;
switch (source) {
case InputDevice.SOURCE_TOUCHSCREEN:
p.toolType = MotionEvent.TOOL_TYPE_FINGER;
break;
case InputDevice.SOURCE_MOUSE:
p.toolType = MotionEvent.TOOL_TYPE_MOUSE;
break;
}
props[index++] = p;
}
}
return props;
}
MotionEvent.PointerCoords[] getPointerCoords(int source) {
MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[getPointerCount(source)];
int index = 0;
for (int i = 0; i < pointers.size(); i++) {
if (pointers.get(i).source == source) {
coords[index++] = pointers.get(i).getCoords();
}
} }
return coords; return coords;
} }
} }
@WrapForJNI private void synthesizeNativePointer(int source, int pointerId,
public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX, int eventType, int screenX, int screenY, double pressure,
int screenY, double pressure, int orientation) int orientation)
{ {
Log.d(LOGTAG, "Synthesizing pointer from " + source + " id " + pointerId + " at " + screenX + ", " + screenY);
if (mPointerState == null) { if (mPointerState == null) {
mPointerState = new SynthesizedEventState(); mPointerState = new SynthesizedEventState();
} }
@@ -802,7 +838,7 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
case MotionEvent.ACTION_POINTER_DOWN: case MotionEvent.ACTION_POINTER_DOWN:
if (pointerIndex < 0) { if (pointerIndex < 0) {
// Adding a new pointer // Adding a new pointer
pointerIndex = mPointerState.addPointer(pointerId); pointerIndex = mPointerState.addPointer(pointerId, source);
if (pointerIndex == 0) { if (pointerIndex == 0) {
// first pointer // first pointer
eventType = MotionEvent.ACTION_DOWN; eventType = MotionEvent.ACTION_DOWN;
@@ -813,6 +849,18 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
eventType = MotionEvent.ACTION_MOVE; eventType = MotionEvent.ACTION_MOVE;
} }
break; break;
case MotionEvent.ACTION_HOVER_MOVE:
if (pointerIndex < 0) {
// Mouse-move a pointer without it going "down". However
// in order to send the right MotionEvent without a lot of
// duplicated code, we add the pointer to mPointerState,
// and then remove it at the bottom of this function.
pointerIndex = mPointerState.addPointer(pointerId, source);
} else {
// We're moving an existing mouse pointer that went down.
eventType = MotionEvent.ACTION_MOVE;
}
break;
} }
// Update the pointer with the new info // Update the pointer with the new info
@@ -826,10 +874,23 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT); int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
action &= MotionEvent.ACTION_POINTER_INDEX_MASK; action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
action |= (eventType & MotionEvent.ACTION_MASK); action |= (eventType & MotionEvent.ACTION_MASK);
final MotionEvent event = MotionEvent.obtain(mPointerState.downTime, boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
SystemClock.uptimeMillis(), action, mPointerState.pointers.size(), (eventType == MotionEvent.ACTION_DOWN || eventType == MotionEvent.ACTION_MOVE);
mPointerState.getPointerIds(), mPointerState.getPointerCoords(), final MotionEvent event = MotionEvent.obtain(
0, 0, 0, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); /*downTime*/ mPointerState.downTime,
/*eventTime*/ SystemClock.uptimeMillis(),
/*action*/ action,
/*pointerCount*/ mPointerState.getPointerCount(source),
/*pointerProperties*/ mPointerState.getPointerProperties(source),
/*pointerCoords*/ mPointerState.getPointerCoords(source),
/*metaState*/ 0,
/*buttonState*/ (isButtonDown ? MotionEvent.BUTTON_PRIMARY : 0),
/*xPrecision*/ 0,
/*yPrecision*/ 0,
/*deviceId*/ 0,
/*edgeFlags*/ 0,
/*source*/ source,
/*flags*/ 0);
mView.post(new Runnable() { mView.post(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -841,12 +902,30 @@ class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
// Forget about removed pointers // Forget about removed pointers
if (eventType == MotionEvent.ACTION_POINTER_UP || if (eventType == MotionEvent.ACTION_POINTER_UP ||
eventType == MotionEvent.ACTION_UP || eventType == MotionEvent.ACTION_UP ||
eventType == MotionEvent.ACTION_CANCEL) eventType == MotionEvent.ACTION_CANCEL ||
eventType == MotionEvent.ACTION_HOVER_MOVE)
{ {
mPointerState.pointers.remove(pointerIndex); mPointerState.pointers.remove(pointerIndex);
} }
} }
@WrapForJNI
public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
int screenY, double pressure, int orientation)
{
if (pointerId == PointerInfo.RESERVED_MOUSE_POINTER_ID) {
throw new IllegalArgumentException("Use a different pointer ID in your test, this one is reserved for mouse");
}
synthesizeNativePointer(InputDevice.SOURCE_TOUCHSCREEN, pointerId,
eventType, screenX, screenY, pressure, orientation);
}
@WrapForJNI
public void synthesizeNativeMouseEvent(int eventType, int screenX, int screenY) {
synthesizeNativePointer(InputDevice.SOURCE_MOUSE, PointerInfo.RESERVED_MOUSE_POINTER_ID,
eventType, screenX, screenY, 0, 0);
}
@WrapForJNI(allowMultithread = true) @WrapForJNI(allowMultithread = true)
public LayerRenderer.Frame createFrame() { public LayerRenderer.Frame createFrame() {
// Create the shaders and textures if necessary. // Create the shaders and textures if necessary.

View File

@@ -1444,6 +1444,14 @@ auto GeckoLayerClient::SyncViewportInfo(int32_t a0, int32_t a1, int32_t a2, int3
return mozilla::jni::Method<SyncViewportInfo_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6); return mozilla::jni::Method<SyncViewportInfo_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5, a6);
} }
constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::name[];
constexpr char GeckoLayerClient::SynthesizeNativeMouseEvent_t::signature[];
auto GeckoLayerClient::SynthesizeNativeMouseEvent(int32_t a0, int32_t a1, int32_t a2) const -> void
{
return mozilla::jni::Method<SynthesizeNativeMouseEvent_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2);
}
constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::name[]; constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::name[];
constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::signature[]; constexpr char GeckoLayerClient::SynthesizeNativeTouchPoint_t::signature[];

View File

@@ -3438,6 +3438,24 @@ public:
auto SyncViewportInfo(int32_t, int32_t, int32_t, int32_t, float, bool, int32_t) const -> mozilla::jni::Object::LocalRef; auto SyncViewportInfo(int32_t, int32_t, int32_t, int32_t, float, bool, int32_t) const -> mozilla::jni::Object::LocalRef;
struct SynthesizeNativeMouseEvent_t {
typedef GeckoLayerClient Owner;
typedef void ReturnType;
typedef void SetterType;
typedef mozilla::jni::Args<
int32_t,
int32_t,
int32_t> Args;
static constexpr char name[] = "synthesizeNativeMouseEvent";
static constexpr char signature[] =
"(III)V";
static const bool isStatic = false;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
};
auto SynthesizeNativeMouseEvent(int32_t, int32_t, int32_t) const -> void;
struct SynthesizeNativeTouchPoint_t { struct SynthesizeNativeTouchPoint_t {
typedef GeckoLayerClient Owner; typedef GeckoLayerClient Owner;
typedef void ReturnType; typedef void ReturnType;

View File

@@ -3405,6 +3405,33 @@ nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
return NS_OK; return NS_OK;
} }
nsresult
nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
uint32_t aNativeMessage,
uint32_t aModifierFlags,
nsIObserver* aObserver)
{
mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
MOZ_ASSERT(mGLControllerSupport);
GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
client->SynthesizeNativeMouseEvent(aNativeMessage, aPoint.x, aPoint.y);
return NS_OK;
}
nsresult
nsWindow::SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
nsIObserver* aObserver)
{
mozilla::widget::AutoObserverNotifier notifier(aObserver, "mouseevent");
MOZ_ASSERT(mGLControllerSupport);
GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
client->SynthesizeNativeMouseEvent(sdk::MotionEvent::ACTION_HOVER_MOVE, aPoint.x, aPoint.y);
return NS_OK;
}
void void
nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager, nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager,

View File

@@ -200,6 +200,12 @@ public:
double aPointerPressure, double aPointerPressure,
uint32_t aPointerOrientation, uint32_t aPointerOrientation,
nsIObserver* aObserver) override; nsIObserver* aObserver) override;
nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
uint32_t aNativeMessage,
uint32_t aModifierFlags,
nsIObserver* aObserver) override;
nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
nsIObserver* aObserver) override;
protected: protected:
void BringToFront(); void BringToFront();