Bug 1249915 - Add ability to synthesize native touch events on Fennec for mochitests. r?snorp
MozReview-Commit-ID: 6qMlAnQNBzo
--- 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();