Implement touch event emulation in the WebCore layer
authorapavlov@chromium.org <apavlov@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Feb 2012 10:10:54 +0000 (10:10 +0000)
committerapavlov@chromium.org <apavlov@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Feb 2012 10:10:54 +0000 (10:10 +0000)
https://bugs.webkit.org/show_bug.cgi?id=77105

Reviewed by Ryosuke Niwa.

Source/WebCore:

This change essentially maps mouse events into single-touch events in the following way:
- mousedown -> touchstart
- mouseup -> touchend
- mousemove -> touchmove (between mousedown and mouseup).

Test: fast/events/touch/emulate-touch-events.html

* page/EventHandler.cpp:
(SyntheticTouchPoint): A synthetic touch point built from PlatformMouseEvent.
(WebCore::SyntheticTouchPoint::SyntheticTouchPoint):
(SyntheticSingleTouchEvent): A synthetic touch point event built from PlatformMouseEvent.
(WebCore::SyntheticSingleTouchEvent::SyntheticSingleTouchEvent):
(WebCore::EventHandler::handleMouseReleaseEvent): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
(WebCore::EventHandler::handleMousePressEvent): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
(WebCore::EventHandler::mouseMoved): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
(WebCore::EventHandler::dispatchSyntheticTouchEventIfEnabled): Dispatch a synthetic touch event if necessary.
* page/EventHandler.h: Added new method.
* page/Settings.cpp:
(WebCore::Settings::Settings): Added m_touchEventEmulationEnabled initializer.
* page/Settings.h: Added m_touchEventEmulationEnabled, getter, and setter.
(WebCore::Settings::setTouchEventEmulationEnabled): Added.
(WebCore::Settings::isTouchEventEmulationEnabled): Added.
* platform/PlatformTouchPoint.h:
(WebCore::PlatformTouchPoint::PlatformTouchPoint): Unconditionally compile the parameterless ctor.
* testing/InternalSettings.cpp:
(WebCore::InternalSettings::setTouchEventEmulationEnabled): Added for testing.
* testing/InternalSettings.h: Added setTouchEventEmulationEnabled() for testing.
* testing/InternalSettings.idl: Added setTouchEventEmulationEnabled() for testing.

LayoutTests:

* fast/events/touch/emulate-touch-events-expected.txt: Added.
* fast/events/touch/emulate-touch-events.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@106642 268f45cc-cd09-0410-ab3c-d52691b4dbfc

12 files changed:
LayoutTests/ChangeLog
LayoutTests/fast/events/touch/emulate-touch-events-expected.txt [new file with mode: 0644]
LayoutTests/fast/events/touch/emulate-touch-events.html [new file with mode: 0644]
Source/WebCore/ChangeLog
Source/WebCore/page/EventHandler.cpp
Source/WebCore/page/EventHandler.h
Source/WebCore/page/Settings.cpp
Source/WebCore/page/Settings.h
Source/WebCore/platform/PlatformTouchPoint.h
Source/WebCore/testing/InternalSettings.cpp
Source/WebCore/testing/InternalSettings.h
Source/WebCore/testing/InternalSettings.idl

index 0d3e1ea..a14fca8 100644 (file)
@@ -1,3 +1,13 @@
+2012-01-27  Alexander Pavlov  <apavlov@chromium.org>
+
+        Implement touch event emulation in the WebCore layer
+        https://bugs.webkit.org/show_bug.cgi?id=77105
+
+        Reviewed by Ryosuke Niwa.
+
+        * fast/events/touch/emulate-touch-events-expected.txt: Added.
+        * fast/events/touch/emulate-touch-events.html: Added.
+
 2012-02-03  Adam Barth  <abarth@webkit.org>
 
         Group all the security failures together.
diff --git a/LayoutTests/fast/events/touch/emulate-touch-events-expected.txt b/LayoutTests/fast/events/touch/emulate-touch-events-expected.txt
new file mode 100644 (file)
index 0000000..8cf826e
--- /dev/null
@@ -0,0 +1,61 @@
+This tests single touch event emulation using mouse events.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS lastEvent.type is "touchstart"
+PASS lastEvent.touches.length is 1
+PASS lastEvent.changedTouches.length is 1
+PASS lastEvent.targetTouches.length is 1
+PASS lastEvent.pageX is 0
+PASS lastEvent.pageY is 0
+PASS lastEvent.shiftKey is true
+PASS lastEvent.altKey is true
+PASS lastEvent.ctrlKey is false
+PASS lastEvent.metaKey is false
+PASS lastEvent.touches[0].target.id is "touchtarget"
+PASS lastEvent.touches[0].pageX is 10
+PASS lastEvent.touches[0].pageY is 10
+PASS lastEvent.touches[0].clientX is 10
+PASS lastEvent.touches[0].clientY is 10
+PASS lastEvent.touches[0].identifier is 0
+PASS lastEvent.changedTouches[0].pageX is 10
+PASS lastEvent.changedTouches[0].pageY is 10
+PASS lastEvent.changedTouches[0].clientX is 10
+PASS lastEvent.changedTouches[0].clientY is 10
+PASS lastEvent.changedTouches[0].identifier is 0
+PASS lastEvent.targetTouches[0].pageX is 10
+PASS lastEvent.targetTouches[0].pageY is 10
+PASS lastEvent.targetTouches[0].clientX is 10
+PASS lastEvent.targetTouches[0].clientY is 10
+PASS lastEvent.targetTouches[0].identifier is 0
+PASS lastEvent.type is "touchmove"
+PASS lastEvent.touches.length is 1
+PASS lastEvent.changedTouches.length is 1
+PASS lastEvent.targetTouches.length is 1
+PASS lastEvent.pageX is 0
+PASS lastEvent.pageY is 0
+PASS lastEvent.touches[0].pageX is 20
+PASS lastEvent.touches[0].pageY is 30
+PASS lastEvent.touches[0].clientX is 20
+PASS lastEvent.touches[0].clientY is 30
+PASS lastEvent.touches[0].identifier is 0
+PASS lastEvent.type is "touchend"
+PASS lastEvent.touches.length is 0
+PASS lastEvent.changedTouches.length is 1
+PASS lastEvent.targetTouches.length is 0
+PASS lastEvent.pageX is 0
+PASS lastEvent.pageY is 0
+PASS lastEvent.changedTouches[0].pageX is 20
+PASS lastEvent.changedTouches[0].pageY is 30
+PASS lastEvent.changedTouches[0].clientX is 20
+PASS lastEvent.changedTouches[0].clientY is 30
+PASS lastEvent.changedTouches[0].identifier is 0
+PASS lastEvent.shiftKey is false
+PASS lastEvent.altKey is true
+PASS lastEvent.ctrlKey is true
+PASS lastEvent.metaKey is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/events/touch/emulate-touch-events.html b/LayoutTests/fast/events/touch/emulate-touch-events.html
new file mode 100644 (file)
index 0000000..4651f6d
--- /dev/null
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src="../../js/resources/js-test-pre.js"></script>
+<div id="touchtarget" style="width: 100px; height: 100px; background-color: blue"></div>
+<p id="description"></p>
+<div id="console"></div>
+<script>
+var div = document.getElementById("touchtarget");
+var lastEvent = null;
+var touchEventsReceived = 0;
+var EXPECTED_TOUCH_EVENTS_TOTAL = 3;
+
+function touchEventCallback() {
+    if (window.eventSender) {
+        lastEvent = event;
+        verifyTouch(touchEventsReceived++);
+    } else
+        debug(event.type);
+
+    if (window.layoutTestController && touchEventsReceived == EXPECTED_TOUCH_EVENTS_TOTAL)
+        finishJSTest();
+}
+
+div.addEventListener("touchstart", touchEventCallback, false);
+div.addEventListener("touchmove", touchEventCallback, false);
+div.addEventListener("touchend", touchEventCallback, false);
+
+function verifyTouchEvent(type, totalTouchCount, changedTouchCount, targetTouchCount)
+{
+    shouldBeEqualToString("lastEvent.type", type);
+    shouldBe("lastEvent.touches.length", totalTouchCount.toString());
+    shouldBe("lastEvent.changedTouches.length", changedTouchCount.toString());
+    shouldBe("lastEvent.targetTouches.length", targetTouchCount.toString());
+    shouldBe("lastEvent.pageX", "0");
+    shouldBe("lastEvent.pageY", "0");
+}
+
+function verifyTouchPoint(list, point, x, y, id)
+{
+    shouldBe("lastEvent." + list + "[" + point + "].pageX", x.toString());
+    shouldBe("lastEvent." + list + "[" + point + "].pageY", y.toString());
+    shouldBe("lastEvent." + list + "[" + point + "].clientX", x.toString());
+    shouldBe("lastEvent." + list + "[" + point + "].clientY", y.toString());
+    shouldBe("lastEvent." + list + "[" + point + "].identifier", id.toString());
+}
+
+function verifyTouch(which) {
+    switch (which) {
+    case 0:
+        verifyTouchEvent("touchstart", 1, 1, 1);
+        shouldBe("lastEvent.shiftKey", "true");
+        shouldBe("lastEvent.altKey", "true");
+        shouldBe("lastEvent.ctrlKey", "false");
+        shouldBe("lastEvent.metaKey", "false");
+        shouldBeEqualToString("lastEvent.touches[0].target.id", "touchtarget");
+        verifyTouchPoint("touches", 0, 10, 10, 0);
+        verifyTouchPoint("changedTouches", 0, 10, 10, 0);
+        verifyTouchPoint("targetTouches", 0, 10, 10, 0);
+        break;
+    case 1:
+        verifyTouchEvent("touchmove", 1, 1, 1);
+        verifyTouchPoint("touches", 0, 20, 30, 0);
+        break;
+    case 2:
+        verifyTouchEvent("touchend", 0, 1, 0);
+        verifyTouchPoint("changedTouches", 0, 20, 30, 0);
+        shouldBe("lastEvent.shiftKey", "false");
+        shouldBe("lastEvent.altKey", "true");
+        shouldBe("lastEvent.ctrlKey", "true");
+        shouldBe("lastEvent.metaKey", "false");
+        break;
+    default:
+        testFailed("Wrong number of touch events! (" + which + ")");
+    }
+}
+
+function mouseEventSequence()
+{
+    eventSender.mouseMoveTo(10, 10);
+    eventSender.mouseDown(0, ["shiftKey", "altKey"]);
+    eventSender.mouseMoveTo(20, 30);
+    eventSender.mouseUp(0, ["altKey", "ctrlKey"]);
+}
+
+if (window.eventSender && window.internals && window.internals.settings) {
+    description("This tests single touch event emulation using mouse events.");
+
+    window.eventSender.dragMode = false;
+    window.jsTestIsAsync = true;
+    window.internals.settings.setTouchEventEmulationEnabled(true);
+    mouseEventSequence();
+} else
+    debug("This test requires DumpRenderTree. Tap on the blue rect to log.");
+</script>
+<script src="../../js/resources/js-test-post.js"></script>
+</body>
+</html>
index 5bf1547..bb85c15 100644 (file)
@@ -1,3 +1,39 @@
+2012-01-27  Alexander Pavlov  <apavlov@chromium.org>
+
+        Implement touch event emulation in the WebCore layer
+        https://bugs.webkit.org/show_bug.cgi?id=77105
+
+        Reviewed by Ryosuke Niwa.
+
+        This change essentially maps mouse events into single-touch events in the following way:
+        - mousedown -> touchstart
+        - mouseup -> touchend
+        - mousemove -> touchmove (between mousedown and mouseup).
+
+        Test: fast/events/touch/emulate-touch-events.html
+
+        * page/EventHandler.cpp:
+        (SyntheticTouchPoint): A synthetic touch point built from PlatformMouseEvent.
+        (WebCore::SyntheticTouchPoint::SyntheticTouchPoint):
+        (SyntheticSingleTouchEvent): A synthetic touch point event built from PlatformMouseEvent.
+        (WebCore::SyntheticSingleTouchEvent::SyntheticSingleTouchEvent):
+        (WebCore::EventHandler::handleMouseReleaseEvent): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
+        (WebCore::EventHandler::handleMousePressEvent): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
+        (WebCore::EventHandler::mouseMoved): Invoke maybeDispatchSyntheticTouchEvent() and bail out if necessary.
+        (WebCore::EventHandler::dispatchSyntheticTouchEventIfEnabled): Dispatch a synthetic touch event if necessary.
+        * page/EventHandler.h: Added new method.
+        * page/Settings.cpp:
+        (WebCore::Settings::Settings): Added m_touchEventEmulationEnabled initializer.
+        * page/Settings.h: Added m_touchEventEmulationEnabled, getter, and setter.
+        (WebCore::Settings::setTouchEventEmulationEnabled): Added.
+        (WebCore::Settings::isTouchEventEmulationEnabled): Added.
+        * platform/PlatformTouchPoint.h:
+        (WebCore::PlatformTouchPoint::PlatformTouchPoint): Unconditionally compile the parameterless ctor.
+        * testing/InternalSettings.cpp:
+        (WebCore::InternalSettings::setTouchEventEmulationEnabled): Added for testing.
+        * testing/InternalSettings.h: Added setTouchEventEmulationEnabled() for testing.
+        * testing/InternalSettings.idl: Added setTouchEventEmulationEnabled() for testing.
+
 2012-02-03  Kentaro Hara  <haraken@chromium.org>
 
         Remove [NoCPPCustom] IDL
index a2f322e..f3baea2 100644 (file)
@@ -60,6 +60,7 @@
 #include "MouseEvent.h"
 #include "MouseEventWithHitTestResults.h"
 #include "Page.h"
+#include "PlatformEvent.h"
 #include "PlatformKeyboardEvent.h"
 #include "PlatformWheelEvent.h"
 #include "PluginDocument.h"
@@ -142,6 +143,73 @@ private:
     Cursor m_cursor;
 };
 
+#if ENABLE(TOUCH_EVENTS)
+class SyntheticTouchPoint : public PlatformTouchPoint {
+public:
+
+    // The default values are based on http://dvcs.w3.org/hg/webevents/raw-file/tip/touchevents.html
+    explicit SyntheticTouchPoint(const PlatformMouseEvent& event)
+    {
+        const static int idDefaultValue = 0;
+        const static int radiusYDefaultValue = 1;
+        const static int radiusXDefaultValue = 1;
+        const static float rotationAngleDefaultValue = 0.0f;
+        const static float forceDefaultValue = 1.0f;
+
+        m_id = idDefaultValue; // There is only one active TouchPoint.
+        m_screenPos = event.globalPosition();
+        m_pos = event.position();
+        m_radiusY = radiusYDefaultValue;
+        m_radiusX = radiusXDefaultValue;
+        m_rotationAngle = rotationAngleDefaultValue;
+        m_force = forceDefaultValue;
+
+        PlatformEvent::Type type = event.type();
+        ASSERT(type == PlatformEvent::MouseMoved || type == PlatformEvent::MousePressed || type == PlatformEvent::MouseReleased);
+
+        switch (type) {
+        case PlatformEvent::MouseMoved:
+            m_state = TouchMoved;
+            break;
+        case PlatformEvent::MousePressed:
+            m_state = TouchPressed;
+            break;
+        case PlatformEvent::MouseReleased:
+            m_state = TouchReleased;
+            break;
+        default:
+            ASSERT_NOT_REACHED();
+            break;
+        }
+    }
+};
+
+class SyntheticSingleTouchEvent : public PlatformTouchEvent {
+public:
+    explicit SyntheticSingleTouchEvent(const PlatformMouseEvent& event)
+    {
+        switch (event.type()) {
+        case PlatformEvent::MouseMoved:
+            m_type = TouchMove;
+            break;
+        case PlatformEvent::MousePressed:
+            m_type = TouchStart;
+            break;
+        case PlatformEvent::MouseReleased:
+            m_type = TouchEnd;
+            break;
+        default:
+            ASSERT_NOT_REACHED();
+            m_type = NoType;
+            break;
+        }
+        m_timestamp = event.timestamp();
+        m_modifiers = event.modifiers();
+        m_touchPoints.append(SyntheticTouchPoint(event));
+    }
+};
+#endif
+
 static inline bool scrollNode(float delta, WheelEvent::Granularity granularity, ScrollDirection positiveDirection, ScrollDirection negativeDirection, Node* node, Node** stopNode)
 {
     if (!delta)
@@ -739,6 +807,12 @@ bool EventHandler::handleMouseUp(const MouseEventWithHitTestResults& event)
 
 bool EventHandler::handleMouseReleaseEvent(const MouseEventWithHitTestResults& event)
 {
+#if ENABLE(TOUCH_EVENTS)
+    bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(event.event());
+    if (defaultPrevented)
+        return true;
+#endif
+
     if (m_autoscrollInProgress)
         stopAutoscrollTimer();
 
@@ -1375,6 +1449,12 @@ bool EventHandler::handleMousePressEvent(const PlatformMouseEvent& mouseEvent)
 {
     RefPtr<FrameView> protector(m_frame->view());
 
+#if ENABLE(TOUCH_EVENTS)
+    bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(mouseEvent);
+    if (defaultPrevented)
+        return true;
+#endif
+
     UserGestureIndicator gestureIndicator(DefinitelyProcessingUserGesture);
 
     // FIXME (bug 68185): this call should be made at another abstraction layer
@@ -1561,6 +1641,13 @@ bool EventHandler::mouseMoved(const PlatformMouseEvent& event)
 {
     RefPtr<FrameView> protector(m_frame->view());
 
+#if ENABLE(TOUCH_EVENTS)
+    // FIXME: this should be moved elsewhere to also be able to dispatch touchcancel events.
+    bool defaultPrevented = dispatchSyntheticTouchEventIfEnabled(event);
+    if (defaultPrevented)
+        return true;
+#endif
+
     HitTestResult hoveredNode = HitTestResult(LayoutPoint());
     bool result = handleMouseMoveEvent(event, &hoveredNode);
 
@@ -3362,7 +3449,21 @@ bool EventHandler::handleTouchEvent(const PlatformTouchEvent& event)
     return defaultPrevented;
 }
 
+bool EventHandler::dispatchSyntheticTouchEventIfEnabled(const PlatformMouseEvent& event)
+{
+    if (!m_frame->document()->settings()->isTouchEventEmulationEnabled())
+        return false;
 
+    PlatformEvent::Type eventType = event.type();
+    if (eventType != PlatformEvent::MouseMoved && eventType != PlatformEvent::MousePressed && eventType != PlatformEvent::MouseReleased)
+        return false;
+
+    if (eventType == PlatformEvent::MouseMoved && !m_touchPressed)
+        return false;
+
+    SyntheticSingleTouchEvent touchEvent(event);
+    return handleTouchEvent(touchEvent);
+}
 
 #endif
 
index 547bfe5..7733a0f 100644 (file)
@@ -264,6 +264,10 @@ private:
     void fakeMouseMoveEventTimerFired(Timer<EventHandler>*);
     void cancelFakeMouseMoveEvent();
 
+#if ENABLE(TOUCH_EVENTS)
+    bool dispatchSyntheticTouchEventIfEnabled(const PlatformMouseEvent&);
+#endif
+
     void invalidateClick();
 
     Node* nodeUnderMouse() const;
index 599cb31..9233497 100644 (file)
@@ -240,6 +240,9 @@ Settings::Settings(Page* page)
     , m_scrollingCoordinatorEnabled(false)
 #endif
     , m_notificationsEnabled(true)
+#if ENABLE(TOUCH_EVENTS)
+    , m_touchEventEmulationEnabled(false)
+#endif
     , m_loadsImagesAutomaticallyTimer(this, &Settings::loadsImagesAutomaticallyTimerFired)
 {
     // A Frame may not have been created yet, so we initialize the AtomicString 
index 5ef8eea..d5325a7 100644 (file)
@@ -523,6 +523,11 @@ namespace WebCore {
         void setNotificationsEnabled(bool enabled) { m_notificationsEnabled = enabled; }
         bool notificationsEnabled() const { return m_notificationsEnabled; }
 
+#if ENABLE(TOUCH_EVENTS)
+        void setTouchEventEmulationEnabled(bool enabled) { m_touchEventEmulationEnabled = enabled; }
+        bool isTouchEventEmulationEnabled() const { return m_touchEventEmulationEnabled; }
+#endif
+
     private:
         Settings(Page*);
 
@@ -669,6 +674,10 @@ namespace WebCore {
 
         bool m_notificationsEnabled : 1;
 
+#if ENABLE(TOUCH_EVENTS)
+        bool m_touchEventEmulationEnabled : 1;
+#endif
+
         Timer<Settings> m_loadsImagesAutomaticallyTimer;
         void loadsImagesAutomaticallyTimerFired(Timer<Settings>*);
 
index d66eb23..b18192d 100644 (file)
@@ -44,9 +44,11 @@ public:
         TouchStateEnd // Placeholder: must remain the last item.
     };
 
+    // This is necessary for us to be able to build synthetic events.
+    PlatformTouchPoint() { };
+
 #if PLATFORM(QT)
     PlatformTouchPoint(const QTouchEvent::TouchPoint&);
-    PlatformTouchPoint() {};
 #elif PLATFORM(EFL)
     PlatformTouchPoint(unsigned id, const IntPoint& windowPos, State);
 #endif
index a4dbb44..c2b045d 100644 (file)
@@ -271,4 +271,15 @@ void InternalSettings::setPerTileDrawingEnabled(bool enabled, ExceptionCode& ec)
     settings()->setPerTileDrawingEnabled(enabled);
 }
 
+void InternalSettings::setTouchEventEmulationEnabled(bool enabled, ExceptionCode& ec)
+{
+#if ENABLE(TOUCH_EVENTS)
+    InternalSettingsGuardForSettings();
+    settings()->setTouchEventEmulationEnabled(enabled);
+#else
+    UNUSED_PARAM(enabled);
+    UNUSED_PARAM(ec);
+#endif
+}
+
 }
index be9328e..6480092 100644 (file)
@@ -64,6 +64,7 @@ public:
     float pageScaleFactor(ExceptionCode&);
     void setPageScaleFactor(float scaleFactor, int x, int y, ExceptionCode&);
     void setPerTileDrawingEnabled(bool enabled, ExceptionCode&);
+    void setTouchEventEmulationEnabled(bool enabled, ExceptionCode&);
 
 private:
     InternalSettings(Frame*, InternalSettings* old);
index c2f5b2a..0360083 100644 (file)
@@ -44,6 +44,7 @@ module window {
         boolean unifiedTextCheckingEnabled() raises (DOMException);
         float pageScaleFactor() raises(DOMException);
         void setPageScaleFactor(in float scaleFactor, in long x, in long y) raises(DOMException);
+        void setTouchEventEmulationEnabled(in boolean enabled) raises(DOMException);
     };
 }