Bug 1249915 - Add ability to synthesize native touch events on Fennec for mochitests. r?snorp draft
authorKartikaya Gupta <kgupta@mozilla.com>
Sun, 21 Feb 2016 13:05:32 -0500
changeset 332793 92ad2d1107134622d2af44998b0751570895d24d
parent 332792 01db5539bd1d2a211c0975ae41ec36f262bfe6a2
child 332794 d49cf4c08ff1a984ffc686a6999eb722cb90c335
push id11237
push userkgupta@mozilla.com
push dateSun, 21 Feb 2016 18:06:13 +0000
reviewerssnorp
bugs1249915
milestone47.0a1
Bug 1249915 - Add ability to synthesize native touch events on Fennec for mochitests. r?snorp MozReview-Commit-ID: 6qMlAnQNBzo
mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
widget/android/GeneratedJNIWrappers.cpp
widget/android/GeneratedJNIWrappers.h
widget/android/nsWindow.cpp
widget/android/nsWindow.h
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/GeckoLayerClient.java
@@ -18,16 +18,18 @@ import org.mozilla.gecko.util.FloatUtils
 import org.mozilla.gecko.AppConstants;
 
 import android.content.Context;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
 import org.json.JSONObject;
 
 import java.util.ArrayList;
 import java.util.List;
 
 class GeckoLayerClient implements LayerView.Listener, PanZoomTarget
 {
     private static final String LOGTAG = "GeckoLayerClient";
@@ -101,16 +103,18 @@ class GeckoLayerClient implements LayerV
      * to the time that we receive the first-paint composite notification from the compositor.
      * Note that there is a small race condition with this; if there are two paints that both
      * have the first-paint flag set, and the second paint happens concurrently with the
      * composite for the first paint, then this flag may be set to true prematurely. Fixing this
      * is possible but risky; see https://bugzilla.mozilla.org/show_bug.cgi?id=797615#c751
      */
     private volatile boolean mContentDocumentIsDisplayed;
 
+    private SynthesizedEventState mPointerState;
+
     public GeckoLayerClient(Context context, LayerView view, EventDispatcher eventDispatcher) {
         // we can fill these in with dummy values because they are always written
         // to before being read
         mContext = context;
         mScreenSize = new IntSize(0, 0);
         mWindowSize = new IntSize(0, 0);
         mDisplayPort = new DisplayPortMetrics();
         mRecordDrawTimes = true;
@@ -704,16 +708,152 @@ class GeckoLayerClient implements LayerV
             mViewportMetrics = mViewportMetrics.setViewportOrigin(scrollX, scrollY)
                 .setZoomFactor(zoom)
                 .setPageRect(RectUtils.scale(cssPageRect, zoom), cssPageRect);
         }
         return syncViewportInfo(dpX, dpY, dpWidth, dpHeight, paintedResolution,
                     layersUpdated, paintSyncId);
     }
 
+    class PointerInfo {
+        public int pointerId;
+        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;
+            coords.pressure = (float)pressure;
+            coords.x = screenX;
+            coords.y = screenY;
+            return coords;
+        }
+    }
+
+    class SynthesizedEventState {
+        public final ArrayList<PointerInfo> pointers;
+        public long downTime;
+
+        SynthesizedEventState() {
+            pointers = new ArrayList<PointerInfo>();
+        }
+
+        int getPointerIndex(int pointerId) {
+            for (int i = 0; i < pointers.size(); i++) {
+                if (pointers.get(i).pointerId == pointerId) {
+                    return i;
+                }
+            }
+            return -1;
+        }
+
+        int addPointer(int pointerId) {
+            PointerInfo info = new PointerInfo();
+            info.pointerId = pointerId;
+            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;
+            }
+            return ids;
+        }
+
+        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();
+            }
+            return coords;
+        }
+    }
+
+    @WrapForJNI
+    public void synthesizeNativeTouchPoint(int pointerId, int eventType, int screenX,
+            int screenY, double pressure, int orientation)
+    {
+        if (mPointerState == null) {
+            mPointerState = new SynthesizedEventState();
+        }
+
+        // Find the pointer if it already exists
+        int pointerIndex = mPointerState.getPointerIndex(pointerId);
+
+        // Event-specific handling
+        switch (eventType) {
+            case MotionEvent.ACTION_POINTER_UP:
+                if (pointerIndex < 0) {
+                    Log.d(LOGTAG, "Requested synthesis of a pointer-up for a pointer that doesn't exist!");
+                    return;
+                }
+                if (mPointerState.pointers.size() == 1) {
+                    // Last pointer is going up
+                    eventType = MotionEvent.ACTION_UP;
+                }
+                break;
+            case MotionEvent.ACTION_CANCEL:
+                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);
+                    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;
+        }
+
+        // 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);
+Log.d("staktrace", "GLC: dispatching");
+        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)
+        {
+            mPointerState.pointers.remove(pointerIndex);
+        }
+    }
+
     @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
@@ -1432,16 +1432,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::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);
+}
+
 template<> const char mozilla::jni::Context<ImmutableViewportMetrics, jobject>::name[] =
         "org/mozilla/gecko/gfx/ImmutableViewportMetrics";
 
 constexpr char ImmutableViewportMetrics::New_t::name[];
 constexpr char ImmutableViewportMetrics::New_t::signature[];
 
 auto ImmutableViewportMetrics::New(float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, float a8, float a9, int32_t a10, int32_t a11, float a12) -> ImmutableViewportMetrics::LocalRef
 {
--- a/widget/android/GeneratedJNIWrappers.h
+++ b/widget/android/GeneratedJNIWrappers.h
@@ -3365,16 +3365,37 @@ 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 SynthesizeNativeTouchPoint_t {
+        typedef GeckoLayerClient Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                int32_t,
+                int32_t,
+                int32_t,
+                int32_t,
+                double,
+                int32_t> Args;
+        static constexpr char name[] = "synthesizeNativeTouchPoint";
+        static constexpr char signature[] =
+                "(IIIIDI)V";
+        static const bool isStatic = false;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+    };
+
+    auto SynthesizeNativeTouchPoint(int32_t, int32_t, int32_t, int32_t, double, int32_t) const -> void;
+
     static const bool isMultithreaded = true;
 
 };
 
 class ImmutableViewportMetrics : public mozilla::jni::ObjectBase<ImmutableViewportMetrics, jobject>
 {
 public:
     explicit ImmutableViewportMetrics(const Context& ctx) : ObjectBase<ImmutableViewportMetrics, jobject>(ctx) {}
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -3258,16 +3258,55 @@ nsWindow::GetIMEUpdatePreference()
     if (GetInputContext().mIMEState.mEnabled == IMEState::PLUGIN) {
       return nsIMEUpdatePreference();
     }
     return nsIMEUpdatePreference(
         nsIMEUpdatePreference::NOTIFY_SELECTION_CHANGE |
         nsIMEUpdatePreference::NOTIFY_TEXT_CHANGE);
 }
 
+nsresult
+nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                     TouchPointerState aPointerState,
+                                     ScreenIntPoint aPointerScreenPoint,
+                                     double aPointerPressure,
+                                     uint32_t aPointerOrientation,
+                                     nsIObserver* aObserver)
+{
+    mozilla::widget::AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+    int eventType;
+    switch (aPointerState) {
+    case TOUCH_CONTACT:
+        // This could be a ACTION_DOWN or ACTION_MOVE depending on the
+        // existing state; it is mapped to the right thing in Java.
+        eventType = sdk::MotionEvent::ACTION_POINTER_DOWN;
+        break;
+    case TOUCH_REMOVE:
+        // This could be turned into a ACTION_UP in Java
+        eventType = sdk::MotionEvent::ACTION_POINTER_UP;
+        break;
+    case TOUCH_CANCEL:
+        eventType = sdk::MotionEvent::ACTION_CANCEL;
+        break;
+    case TOUCH_HOVER:   // not supported for now
+    default:
+        return NS_ERROR_UNEXPECTED;
+    }
+
+    MOZ_ASSERT(mGLControllerSupport);
+    GeckoLayerClient::LocalRef client = mGLControllerSupport->GetLayerClient();
+    client->SynthesizeNativeTouchPoint(aPointerId, eventType,
+        aPointerScreenPoint.x, aPointerScreenPoint.y, aPointerPressure,
+        aPointerOrientation);
+
+    return NS_OK;
+}
+
+
 void
 nsWindow::DrawWindowUnderlay(LayerManagerComposite* aManager,
                              LayoutDeviceIntRect aRect)
 {
     if (Destroyed()) {
         return;
     }
     MOZ_ASSERT(mGLControllerSupport);
--- a/widget/android/nsWindow.h
+++ b/widget/android/nsWindow.h
@@ -181,16 +181,23 @@ public:
     virtual bool WidgetPaintsBackground() override;
 
     virtual uint32_t GetMaxTouchPoints() const override;
 
     void UpdateZoomConstraints(const uint32_t& aPresShellId,
                                const FrameMetrics::ViewID& aViewId,
                                const mozilla::Maybe<ZoomConstraints>& aConstraints) override;
 
+    nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                                        TouchPointerState aPointerState,
+                                        ScreenIntPoint aPointerScreenPoint,
+                                        double aPointerPressure,
+                                        uint32_t aPointerOrientation,
+                                        nsIObserver* aObserver) override;
+
 protected:
     void BringToFront();
     nsWindow *FindTopLevel();
     bool IsTopLevel();
 
     RefPtr<mozilla::TextComposition> GetIMEComposition();
     void RemoveIMEComposition();