Bug 1264017 - Add synthesized mouse support to Android. r=rbarker
MozReview-Commit-ID: 708j300kN3M
--- a/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js
@@ -2,16 +2,20 @@
function getPlatform() {
if (navigator.platform.indexOf("Win") == 0) {
return "windows";
}
if (navigator.platform.indexOf("Mac") == 0) {
return "mac";
}
+ // Check for Android before Linux
+ if (navigator.appVersion.indexOf("Android") >= 0) {
+ return "android"
+ }
if (navigator.platform.indexOf("Linux") == 0) {
return "linux";
}
return "unknown";
}
function nativeVerticalWheelEventMsg() {
switch (getPlatform()) {
@@ -44,34 +48,39 @@ function nativeScrollUnits(aElement, aDi
return aDimen;
}
function nativeMouseDownEventMsg() {
switch (getPlatform()) {
case "windows": return 2; // MOUSEEVENTF_LEFTDOWN
case "mac": return 1; // NSLeftMouseDown
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() {
switch (getPlatform()) {
case "windows": return 1; // MOUSEEVENTF_MOVE
case "mac": return 5; // NSMouseMoved
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() {
switch (getPlatform()) {
case "windows": return 4; // MOUSEEVENTF_LEFTUP
case "mac": return 2; // NSLeftMouseUp
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,
// to device pixels relative to aElement's containing window.
function coordinatesRelativeToWindow(aX, aY, aElement) {
var targetWindow = aElement.ownerDocument.defaultView;
var scale = targetWindow.devicePixelRatio;
var rect = aElement.getBoundingClientRect();
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -40,10 +40,8 @@ skip-if = (os == 'android') || (os == 'b
[test_frame_reconstruction.html]
[test_tap.html]
# Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
# On OS X we don't support touch events at all.
skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
[test_scroll_window.html]
skip-if = (toolkit == 'android') # wheel events not supported on mobile
[test_click.html]
-# Very similar to test_tap, but we don't yet have mouse event synthesization on android
-skip-if = (os == 'android')
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -708,17 +708,24 @@ class GeckoLayerClient implements LayerV
.setZoomFactor(zoom)
.setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
}
return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution,
layersUpdated, paintSyncId);
}
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 source;
public int screenX;
public int screenY;
public double pressure;
public int orientation;
public MotionEvent.PointerCoords getCoords() {
MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
coords.orientation = orientation;
@@ -741,44 +748,73 @@ class GeckoLayerClient implements LayerV
for (int i = 0; i < pointers.size(); i++) {
if (pointers.get(i).pointerId == pointerId) {
return i;
}
}
return -1;
}
- int addPointer(int pointerId) {
+ int addPointer(int pointerId, int source) {
PointerInfo info = new PointerInfo();
info.pointerId = pointerId;
+ info.source = source;
pointers.add(info);
return pointers.size() - 1;
}
- int[] getPointerIds() {
- int[] ids = new int[pointers.size()];
- for (int i = 0; i < ids.length; i++) {
- ids[i] = pointers.get(i).pointerId;
+ int getPointerCount(int source) {
+ int count = 0;
+ for (int i = 0; i < pointers.size(); i++) {
+ if (pointers.get(i).source == source) {
+ count++;
+ }
}
- return ids;
+ return count;
}
- MotionEvent.PointerCoords[] getPointerCoords() {
- MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointers.size()];
- for (int i = 0; i < coords.length; i++) {
- coords[i] = pointers.get(i).getCoords();
+ MotionEvent.PointerProperties[] getPointerProperties(int source) {
+ MotionEvent.PointerProperties[] props = new MotionEvent.PointerProperties[getPointerCount(source)];
+ int index = 0;
+ 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;
}
}
- @WrapForJNI
- public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
- int screenY, double pressure, int orientation)
+ private void synthesizeNativePointer(int source, int pointerId,
+ int eventType, int screenX, int screenY, double pressure,
+ int orientation)
{
+ Log.d(LOGTAG, "Synthesizing pointer from " + source + " id " + pointerId + " at " + screenX + ", " + screenY);
+
if (mPointerState == null) {
mPointerState = new SynthesizedEventState();
}
// Find the pointer if it already exists
int pointerIndex = mPointerState.getPointerIndex(pointerId);
// Event-specific handling
@@ -797,61 +833,104 @@ class GeckoLayerClient implements LayerV
if (pointerIndex < 0) {
Log.d(LOGTAG, "Requested synthesis of a pointer-cancel for a pointer that doesn't exist!");
return;
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
if (pointerIndex < 0) {
// Adding a new pointer
- pointerIndex = mPointerState.addPointer(pointerId);
+ pointerIndex = mPointerState.addPointer(pointerId, source);
if (pointerIndex == 0) {
// first pointer
eventType = MotionEvent.ACTION_DOWN;
mPointerState.downTime = SystemClock.uptimeMillis();
}
} else {
// We're moving an existing pointer
eventType = MotionEvent.ACTION_MOVE;
}
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
PointerInfo info = mPointerState.pointers.get(pointerIndex);
info.screenX = screenX;
info.screenY = screenY;
info.pressure = pressure;
info.orientation = orientation;
// Dispatch the event
int action = (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT);
action &= MotionEvent.ACTION_POINTER_INDEX_MASK;
action |= (eventType & MotionEvent.ACTION_MASK);
- final MotionEvent event = MotionEvent.obtain(mPointerState.downTime,
- SystemClock.uptimeMillis(), action, mPointerState.pointers.size(),
- mPointerState.getPointerIds(), mPointerState.getPointerCoords(),
- 0, 0, 0, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0);
+ boolean isButtonDown = (source == InputDevice.SOURCE_MOUSE) &&
+ (eventType == MotionEvent.ACTION_DOWN || eventType == MotionEvent.ACTION_MOVE);
+ final MotionEvent event = MotionEvent.obtain(
+ /*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() {
@Override
public void run() {
event.offsetLocation(0, mView.getSurfaceTranslation());
mView.dispatchTouchEvent(event);
}
});
// Forget about removed pointers
if (eventType == MotionEvent.ACTION_POINTER_UP ||
eventType == MotionEvent.ACTION_UP ||
- eventType == MotionEvent.ACTION_CANCEL)
+ eventType == MotionEvent.ACTION_CANCEL ||
+ eventType == MotionEvent.ACTION_HOVER_MOVE)
{
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)
public LayerRenderer.Frame createFrame() {
// Create the shaders and textures if necessary.
if (!mLayerRendererInitialized) {
if (mLayerRenderer == null) {
return null;
}
mLayerRenderer.checkMonitoringEnabled();
--- a/widget/android/GeneratedJNIWrappers.cpp
+++ b/widget/android/GeneratedJNIWrappers.cpp
@@ -1439,16 +1439,24 @@ auto GeckoLayerClient::SyncFrameMetrics(
constexpr char GeckoLayerClient::SyncViewportInfo_t::name[];
constexpr char GeckoLayerClient::SyncViewportInfo_t::signature[];
auto GeckoLayerClient::SyncViewportInfo(int32_t a0, int32_t a1, int32_t a2, int32_t a3, float a4, bool a5, int32_t a6) const -> mozilla::jni::Object::LocalRef
{
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::signature[];
auto GeckoLayerClient::SynthesizeNativeTouchPoint(int32_t a0, int32_t a1, int32_t a2, int32_t a3, double a4, int32_t a5) const -> void
{
return mozilla::jni::Method<SynthesizeNativeTouchPoint_t>::Call(GeckoLayerClient::mCtx, nullptr, a0, a1, a2, a3, a4, a5);
}
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -3433,16 +3433,34 @@ public:
"(IIIIFZI)Lorg/mozilla/gecko/gfx/ViewTransform;";
static const bool isStatic = false;
static const mozilla::jni::ExceptionMode exceptionMode =
mozilla::jni::ExceptionMode::ABORT;
};
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 {
typedef GeckoLayerClient Owner;
typedef void ReturnType;
typedef void SetterType;
typedef mozilla::jni::Args<
int32_t,
int32_t,
int32_t,
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -3400,16 +3400,43 @@ nsWindow::SynthesizeNativeTouchPoint(uin
MOZ_ASSERT(mGLControllerSupport);
GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
client->SynthesizeNativeTouchPoint(aPointerId, eventType,
aPoint.x, aPoint.y, aPointerPressure, aPointerOrientation);
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
nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager,
LayoutDeviceIntRect aRect)
{
if (Destroyed()) {
return;
}
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -195,16 +195,22 @@ public:
const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
TouchPointerState aPointerState,
LayoutDeviceIntPoint aPoint,
double aPointerPressure,
uint32_t aPointerOrientation,
nsIObserver* aObserver) override;
+ nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+ nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override;
protected:
void BringToFront();
nsWindow *FindTopLevel();
bool IsTopLevel();
RefPtr<mozilla::TextComposition> GetIMEComposition();
void RemoveIMEComposition();