Unreviewed, rolling out r164859.
[WebKit-https.git] / Tools / WebKitTestRunner / gtk / EventSenderProxyGtk.cpp
index d2b94e3..847980e 100644 (file)
 #include "config.h"
 #include "EventSenderProxy.h"
 
+#include "NotImplemented.h"
 #include "PlatformWebView.h"
 #include "TestController.h"
-#include <GOwnPtrGtk.h>
-#include <GtkVersioning.h>
-#include <gdk/gdk.h>
 #include <gdk/gdkkeysyms.h>
 #include <gtk/gtk.h>
+#include <wtf/StdLibExtras.h>
+#include <wtf/gobject/GUniquePtr.h>
 #include <wtf/text/WTFString.h>
 
 namespace WTR {
+
+// WebCore and layout tests assume this value
+static const float pixelsPerScrollTick = 40;
+
 // Key event location code defined in DOM Level 3.
 enum KeyLocationCode {
     DOMKeyLocationStandard      = 0x00,
@@ -51,34 +55,141 @@ enum KeyLocationCode {
     DOMKeyLocationNumpad        = 0x03
 };
 
+
+struct WTREventQueueItem {
+    GdkEvent* event;
+    gulong delay;
+
+    WTREventQueueItem()
+        : event(0)
+        , delay(0)
+    {
+    }
+    WTREventQueueItem(GdkEvent* event, gulong delay)
+        : event(event)
+        , delay(delay)
+    {
+    }
+};
+
 EventSenderProxy::EventSenderProxy(TestController* testController)
     : m_testController(testController)
+    , m_time(0)
+    , m_leftMouseButtonDown(false)
+    , m_clickCount(0)
+    , m_clickTime(0)
+    , m_clickButton(kWKEventMouseButtonNoButton)
+    , m_mouseButtonCurrentlyDown(0)
 {
 }
 
-static void dispatchEvent(GdkEvent* event)
+EventSenderProxy::~EventSenderProxy()
 {
-    gtk_main_do_event(event);
-    gdk_event_free(event);
 }
 
+static guint getMouseButtonModifiers(int gdkButton)
+{
+    if (gdkButton == 1)
+        return GDK_BUTTON1_MASK;
+    if (gdkButton == 2)
+        return GDK_BUTTON2_MASK;
+    if (gdkButton == 3)
+        return GDK_BUTTON3_MASK;
+    return 0;
+}
 
-static guint getModifiers(WKEventModifiers modifiersRef)
+static unsigned eventSenderButtonToGDKButton(unsigned button)
+{
+    int mouseButton = 3;
+    if (button <= 2)
+        mouseButton = button + 1;
+    // fast/events/mouse-click-events expects the 4th button to be treated as the middle button.
+    else if (button == 3)
+        mouseButton = 2;
+
+    return mouseButton;
+}
+
+static guint webkitModifiersToGDKModifiers(WKEventModifiers wkModifiers)
 {
     guint modifiers = 0;
 
-    if (modifiersRef & kWKEventModifiersControlKey)
-        modifiers |= kWKEventModifiersControlKey;
-    if (modifiersRef & kWKEventModifiersShiftKey)
-        modifiers |= kWKEventModifiersShiftKey;
-    if (modifiersRef & kWKEventModifiersAltKey)
-        modifiers |= kWKEventModifiersAltKey;
-    if (modifiersRef & kWKEventModifiersMetaKey)
-        modifiers |= kWKEventModifiersMetaKey;
+    if (wkModifiers & kWKEventModifiersControlKey)
+        modifiers |= GDK_CONTROL_MASK;
+    if (wkModifiers & kWKEventModifiersShiftKey)
+        modifiers |= GDK_SHIFT_MASK;
+    if (wkModifiers & kWKEventModifiersAltKey)
+        modifiers |= GDK_MOD1_MASK;
+    if (wkModifiers & kWKEventModifiersMetaKey)
+        modifiers |= GDK_META_MASK;
 
     return modifiers;
 }
 
+GdkEvent* EventSenderProxy::createMouseButtonEvent(GdkEventType eventType, unsigned button, WKEventModifiers modifiers)
+{
+    GdkEvent* mouseEvent = gdk_event_new(eventType);
+
+    mouseEvent->button.button = eventSenderButtonToGDKButton(button);
+    mouseEvent->button.x = m_position.x;
+    mouseEvent->button.y = m_position.y;
+    mouseEvent->button.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    g_object_ref(mouseEvent->button.window);
+    gdk_event_set_device(mouseEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(mouseEvent->button.window))));
+    mouseEvent->button.state = webkitModifiersToGDKModifiers(modifiers) | getMouseButtonModifiers(mouseEvent->button.button);
+    mouseEvent->button.time = GDK_CURRENT_TIME;
+    mouseEvent->button.axes = 0;
+
+    int xRoot, yRoot;
+    gdk_window_get_root_coords(mouseEvent->button.window, m_position.x, m_position.y, &xRoot, &yRoot);
+    mouseEvent->button.x_root = xRoot;
+    mouseEvent->button.y_root = yRoot;
+
+    return mouseEvent;
+}
+
+void EventSenderProxy::updateClickCountForButton(int button)
+{
+    if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
+        ++m_clickCount;
+        m_clickTime = m_time;
+        return;
+    }
+
+    m_clickCount = 1;
+    m_clickTime = m_time;
+    m_clickPosition = m_position;
+    m_clickButton = button;
+}
+
+static void dispatchEvent(GdkEvent* event)
+{
+    gtk_main_do_event(event);
+    gdk_event_free(event);
+}
+
+void EventSenderProxy::replaySavedEvents()
+{
+    while (!m_eventQueue.isEmpty()) {
+        WTREventQueueItem item = m_eventQueue.takeFirst();
+        if (item.delay)
+            g_usleep(item.delay * 1000);
+
+        dispatchEvent(item.event);
+    }
+}
+
+void EventSenderProxy::sendOrQueueEvent(GdkEvent* event)
+{
+    if (m_eventQueue.isEmpty() || !m_eventQueue.last().delay) {
+        dispatchEvent(event);
+        return;
+    }
+
+    m_eventQueue.last().event = event;
+    replaySavedEvents();
+}
+
 int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifiers)
 {
     if (location == DOMKeyLocationNumpad) {
@@ -108,7 +219,7 @@ int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifier
 
     if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
         return GDK_KEY_Left;
-    if (WKStringIsEqualToUTF8CString(keyRef, "rightArror"))
+    if (WKStringIsEqualToUTF8CString(keyRef, "rightArrow"))
         return GDK_KEY_Right;
     if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
         return GDK_KEY_Up;
@@ -155,10 +266,10 @@ int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifier
     if (WKStringIsEqualToUTF8CString(keyRef, "F12"))
         return GDK_KEY_F12;
 
-    size_t stringSize = WKStringGetLength(keyRef);
-    char* buffer = new char[stringSize];
-    WKStringGetUTF8CString(keyRef, buffer, stringSize);
-    int charCode = buffer[0];
+    size_t bufferSize = WKStringGetMaximumUTF8CStringSize(keyRef);
+    auto buffer = std::make_unique<char[]>(bufferSize);
+    WKStringGetUTF8CString(keyRef, buffer.get(), bufferSize);
+    char charCode = buffer.get()[0];
 
     if (charCode == '\n' || charCode == '\r')
         return GDK_KEY_Return;
@@ -170,25 +281,25 @@ int getGDKKeySymForKeyRef(WKStringRef keyRef, unsigned location, guint* modifier
     if (WTF::isASCIIUpper(charCode))
         *modifiers |= GDK_SHIFT_MASK;
 
-    return gdk_unicode_to_keyval(charCode);
+    return gdk_unicode_to_keyval(static_cast<guint32>(buffer.get()[0]));
 }
 
-void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers modifiersRef, unsigned location)
+void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers wkModifiers, unsigned location)
 {
-    guint modifiers = getModifiers(modifiersRef);
+    guint modifiers = webkitModifiersToGDKModifiers(wkModifiers);
     int gdkKeySym = getGDKKeySymForKeyRef(keyRef, location, &modifiers);
 
     GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
     pressEvent->key.keyval = gdkKeySym;
     pressEvent->key.state = modifiers;
-    pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformWindow()));
     g_object_ref(pressEvent->key.window);
-    gdk_event_set_device(pressEvent, getDefaultGDKPointerDevice(pressEvent->key.window));
+    gdk_event_set_device(pressEvent, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(pressEvent->key.window))));
 
-    GOwnPtr<GdkKeymapKey> keys;
+    GUniqueOutPtr<GdkKeymapKey> keys;
     gint nKeys;
     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys.outPtr(), &nKeys))
-        pressEvent->key.hardware_keycode = keys.outPtr()[0].keycode;
+        pressEvent->key.hardware_keycode = keys.get()[0].keycode;
 
     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
     dispatchEvent(pressEvent);
@@ -196,4 +307,262 @@ void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers modifiersRef
     dispatchEvent(releaseEvent);
 }
 
+void EventSenderProxy::mouseDown(unsigned button, WKEventModifiers wkModifiers)
+{
+    // If the same mouse button is already in the down position don't
+    // send another event as it may confuse Xvfb.
+    unsigned gdkButton = eventSenderButtonToGDKButton(button);
+    if (m_mouseButtonCurrentlyDown == gdkButton)
+        return;
+
+    m_mouseButtonCurrentlyDown = gdkButton;
+
+    // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
+    // the second button press during double-clicks. WebKit GTK+ selectively
+    // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
+    // Since our events aren't ever going onto the GDK event queue, WebKit won't
+    // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
+    // it here. Eventually this code should probably figure out a way to get all
+    // appropriate events onto the event queue and this work-around should be
+    // removed.
+    updateClickCountForButton(button);
+
+    GdkEventType eventType;
+    if (m_clickCount == 2)
+        eventType = GDK_2BUTTON_PRESS;
+    else if (m_clickCount == 3)
+        eventType = GDK_3BUTTON_PRESS;
+    else
+        eventType = GDK_BUTTON_PRESS;
+
+    GdkEvent* event = createMouseButtonEvent(eventType, button, wkModifiers);
+    sendOrQueueEvent(event);
+}
+
+void EventSenderProxy::mouseUp(unsigned button, WKEventModifiers wkModifiers)
+{
+    m_clickButton = kWKEventMouseButtonNoButton;
+    GdkEvent* event = createMouseButtonEvent(GDK_BUTTON_RELEASE, button, wkModifiers);
+    sendOrQueueEvent(event);
+
+    if (m_mouseButtonCurrentlyDown == event->button.button)
+        m_mouseButtonCurrentlyDown = 0;
+    m_clickPosition = m_position;
+    m_clickTime = GDK_CURRENT_TIME;
+}
+
+void EventSenderProxy::mouseMoveTo(double x, double y)
+{
+    m_position.x = x;
+    m_position.y = y;
+
+    GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
+    event->motion.x = m_position.x;
+    event->motion.y = m_position.y;
+
+    event->motion.time = GDK_CURRENT_TIME;
+    event->motion.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    g_object_ref(event->motion.window);
+    gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->motion.window))));
+    event->motion.state = 0 | getMouseButtonModifiers(m_mouseButtonCurrentlyDown);
+    event->motion.axes = 0;
+
+    int xRoot, yRoot;
+    gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView())), m_position.x, m_position.y , &xRoot, &yRoot);
+    event->motion.x_root = xRoot;
+    event->motion.y_root = yRoot;
+
+    sendOrQueueEvent(event);
+}
+
+void EventSenderProxy::mouseScrollBy(int horizontal, int vertical)
+{
+    // Copy behaviour of Qt and EFL - just return in case of (0,0) mouse scroll
+    if (!horizontal && !vertical)
+        return;
+
+    GdkEvent* event = gdk_event_new(GDK_SCROLL);
+    event->scroll.x = m_position.x;
+    event->scroll.y = m_position.y;
+    event->scroll.time = GDK_CURRENT_TIME;
+    event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    g_object_ref(event->scroll.window);
+    gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
+
+    // For more than one tick in a scroll, we need smooth scroll event
+    if ((horizontal && vertical) || horizontal > 1 || horizontal < -1 || vertical > 1 || vertical < -1) {
+        event->scroll.direction = GDK_SCROLL_SMOOTH;
+        event->scroll.delta_x = -horizontal;
+        event->scroll.delta_y = -vertical;
+
+        sendOrQueueEvent(event);
+        return;
+    }
+
+    if (horizontal < 0)
+        event->scroll.direction = GDK_SCROLL_RIGHT;
+    else if (horizontal > 0)
+        event->scroll.direction = GDK_SCROLL_LEFT;
+    else if (vertical < 0)
+        event->scroll.direction = GDK_SCROLL_DOWN;
+    else if (vertical > 0)
+        event->scroll.direction = GDK_SCROLL_UP;
+    else
+        g_assert_not_reached();
+
+    sendOrQueueEvent(event);
+}
+
+void EventSenderProxy::continuousMouseScrollBy(int horizontal, int vertical, bool paged)
+{
+    // Gtk+ does not support paged scroll events.
+    g_return_if_fail(!paged);
+
+    GdkEvent* event = gdk_event_new(GDK_SCROLL);
+    event->scroll.x = m_position.x;
+    event->scroll.y = m_position.y;
+    event->scroll.time = GDK_CURRENT_TIME;
+    event->scroll.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    g_object_ref(event->scroll.window);
+    gdk_event_set_device(event, gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(event->scroll.window))));
+
+    event->scroll.direction = GDK_SCROLL_SMOOTH;
+    event->scroll.delta_x = -horizontal / pixelsPerScrollTick;
+    event->scroll.delta_y = -vertical / pixelsPerScrollTick;
+
+    sendOrQueueEvent(event);
+}
+
+void EventSenderProxy::mouseScrollByWithWheelAndMomentumPhases(int x, int y, int /*phase*/, int /*momentum*/)
+{
+    // Gtk+ does not have the concept of wheel gesture phases or momentum. Just relay to
+    // the mouse wheel handler.
+    mouseScrollBy(x, y);
+}
+
+void EventSenderProxy::leapForward(int milliseconds)
+{
+    if (m_eventQueue.isEmpty())
+        m_eventQueue.append(WTREventQueueItem());
+
+    m_eventQueue.last().delay = milliseconds;
+    m_time += milliseconds / 1000.0;
+}
+
+void updateEventCoordinates(GdkEvent* touchEvent, int x, int y)
+{
+    touchEvent->touch.x = x;
+    touchEvent->touch.y = y;
+
+    int xRoot, yRoot;
+    gdk_window_get_root_coords(touchEvent->touch.window, x, y, &xRoot, &yRoot);
+    touchEvent->touch.x_root = xRoot;
+    touchEvent->touch.y_root = yRoot;
+}
+
+GUniquePtr<GdkEvent> EventSenderProxy::createTouchEvent(GdkEventType eventType, int id)
+{
+    GUniquePtr<GdkEvent> touchEvent(gdk_event_new(eventType));
+
+    touchEvent->touch.sequence = static_cast<GdkEventSequence*>(GINT_TO_POINTER(id));
+    touchEvent->touch.window = gtk_widget_get_window(GTK_WIDGET(m_testController->mainWebView()->platformView()));
+    g_object_ref(touchEvent->touch.window);
+    gdk_event_set_device(touchEvent.get(), gdk_device_manager_get_client_pointer(gdk_display_get_device_manager(gdk_window_get_display(touchEvent->button.window))));
+    touchEvent->touch.time = GDK_CURRENT_TIME;
+
+    return touchEvent;
+}
+
+void EventSenderProxy::addTouchPoint(int x, int y)
+{
+    // Touch ID is array index plus one, so 0 is skipped.
+    GUniquePtr<GdkEvent> event = createTouchEvent(static_cast<GdkEventType>(GDK_TOUCH_BEGIN), m_touchEvents.size() + 1);
+    updateEventCoordinates(event.get(), x, y);
+    m_updatedTouchEvents.add(GPOINTER_TO_INT(event->touch.sequence));
+    m_touchEvents.append(std::move(event));
+}
+
+void EventSenderProxy::updateTouchPoint(int index, int x, int y)
+{
+    ASSERT(index >= 0 && index < m_touchEvents.size());
+
+    const auto& event = m_touchEvents[index];
+    ASSERT(event);
+
+    event->type = GDK_TOUCH_UPDATE;
+    updateEventCoordinates(event.get(), x, y);
+    m_updatedTouchEvents.add(GPOINTER_TO_INT(event->touch.sequence));
+}
+
+void EventSenderProxy::sendUpdatedTouchEvents()
+{
+    for (auto id : m_updatedTouchEvents)
+        sendOrQueueEvent(gdk_event_copy(m_touchEvents[id - 1].get()));
+
+    m_updatedTouchEvents.clear();
+}
+
+void EventSenderProxy::touchStart()
+{
+    sendUpdatedTouchEvents();
+}
+
+void EventSenderProxy::touchMove()
+{
+    sendUpdatedTouchEvents();
+}
+
+void EventSenderProxy::touchEnd()
+{
+    sendUpdatedTouchEvents();
+}
+
+void EventSenderProxy::touchCancel()
+{
+    notImplemented();
+}
+
+void EventSenderProxy::clearTouchPoints()
+{
+    m_updatedTouchEvents.clear();
+    m_touchEvents.clear();
+}
+
+void EventSenderProxy::releaseTouchPoint(int index)
+{
+    ASSERT(index >= 0 && index < m_touchEvents.size());
+
+    const auto& event = m_touchEvents[index];
+    event->type = GDK_TOUCH_END;
+    m_updatedTouchEvents.add(GPOINTER_TO_INT(event->touch.sequence));
+}
+
+void EventSenderProxy::cancelTouchPoint(int index)
+{
+    notImplemented();
+}
+
+void EventSenderProxy::setTouchPointRadius(int radiusX, int radiusY)
+{
+    notImplemented();
+}
+
+void EventSenderProxy::setTouchModifier(WKEventModifiers modifier, bool enable)
+{
+    guint state = webkitModifiersToGDKModifiers(modifier);
+
+    for (const auto& event : m_touchEvents) {
+        if (event->type == GDK_TOUCH_END)
+            continue;
+
+        if (enable)
+            event->touch.state |= state;
+        else
+            event->touch.state &= ~(state);
+
+        m_updatedTouchEvents.add(GPOINTER_TO_INT(event->touch.sequence));
+    }
+}
+
+
 } // namespace WTR