blob: 8481f1d7a5e2f16e6ede52cfc806f1fe26d9b430 [file] [log] [blame]
/*
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
* Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
* Copyright (C) 2009 Holger Hans Peter Freyther
* Copyright (C) 2010 Igalia S.L.
* Copyright (c) 2010 Motorola Mobility, Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "EventSenderProxy.h"
#include "PlatformWebView.h"
#include "TestController.h"
#include <WebCore/GtkUtilities.h>
#include <WebCore/GtkVersioning.h>
#include <WebCore/NotImplemented.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <wtf/StdLibExtras.h>
#include <wtf/UniqueArray.h>
#include <wtf/glib/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,
DOMKeyLocationLeft = 0x01,
DOMKeyLocationRight = 0x02,
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)
{
}
EventSenderProxy::~EventSenderProxy()
{
}
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 (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;
if (wkModifiers & kWKEventModifiersCapsLockKey)
modifiers |= GDK_LOCK_MASK;
return modifiers;
}
GdkEvent* EventSenderProxy::createMouseButtonEvent(GdkEventType eventType, unsigned button, WKEventModifiers modifiers)
{
GdkEvent* mouseEvent = gdk_event_new(eventType);
mouseEvent->button.button = 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_seat_get_pointer(gdk_display_get_default_seat(gdk_window_get_display(mouseEvent->button.window))));
mouseEvent->button.state = webkitModifiersToGDKModifiers(modifiers) | m_mouseButtonsCurrentlyDown;
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;
}
void EventSenderProxy::dispatchEvent(GdkEvent* event)
{
ASSERT(m_testController->mainWebView());
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) {
if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
return GDK_KEY_KP_Left;
if (WKStringIsEqualToUTF8CString(keyRef, "rightArror"))
return GDK_KEY_KP_Right;
if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
return GDK_KEY_KP_Up;
if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
return GDK_KEY_KP_Down;
if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
return GDK_KEY_KP_Page_Up;
if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
return GDK_KEY_KP_Page_Down;
if (WKStringIsEqualToUTF8CString(keyRef, "home"))
return GDK_KEY_KP_Home;
if (WKStringIsEqualToUTF8CString(keyRef, "end"))
return GDK_KEY_KP_End;
if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
return GDK_KEY_KP_Insert;
if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
return GDK_KEY_KP_Delete;
return GDK_KEY_VoidSymbol;
}
if (WKStringIsEqualToUTF8CString(keyRef, "leftControl"))
return GDK_KEY_Control_L;
if (WKStringIsEqualToUTF8CString(keyRef, "rightControl"))
return GDK_KEY_Control_R;
if (WKStringIsEqualToUTF8CString(keyRef, "leftShift"))
return GDK_KEY_Shift_L;
if (WKStringIsEqualToUTF8CString(keyRef, "rightShift"))
return GDK_KEY_Shift_R;
if (WKStringIsEqualToUTF8CString(keyRef, "leftAlt"))
return GDK_KEY_Alt_L;
if (WKStringIsEqualToUTF8CString(keyRef, "rightAlt"))
return GDK_KEY_Alt_R;
if (WKStringIsEqualToUTF8CString(keyRef, "leftArrow"))
return GDK_KEY_Left;
if (WKStringIsEqualToUTF8CString(keyRef, "rightArrow"))
return GDK_KEY_Right;
if (WKStringIsEqualToUTF8CString(keyRef, "upArrow"))
return GDK_KEY_Up;
if (WKStringIsEqualToUTF8CString(keyRef, "downArrow"))
return GDK_KEY_Down;
if (WKStringIsEqualToUTF8CString(keyRef, "pageUp"))
return GDK_KEY_Page_Up;
if (WKStringIsEqualToUTF8CString(keyRef, "pageDown"))
return GDK_KEY_Page_Down;
if (WKStringIsEqualToUTF8CString(keyRef, "home"))
return GDK_KEY_Home;
if (WKStringIsEqualToUTF8CString(keyRef, "end"))
return GDK_KEY_End;
if (WKStringIsEqualToUTF8CString(keyRef, "insert"))
return GDK_KEY_Insert;
if (WKStringIsEqualToUTF8CString(keyRef, "delete"))
return GDK_KEY_Delete;
if (WKStringIsEqualToUTF8CString(keyRef, "printScreen"))
return GDK_KEY_Print;
if (WKStringIsEqualToUTF8CString(keyRef, "menu"))
return GDK_KEY_Menu;
if (WKStringIsEqualToUTF8CString(keyRef, "F1"))
return GDK_KEY_F1;
if (WKStringIsEqualToUTF8CString(keyRef, "F2"))
return GDK_KEY_F2;
if (WKStringIsEqualToUTF8CString(keyRef, "F3"))
return GDK_KEY_F3;
if (WKStringIsEqualToUTF8CString(keyRef, "F4"))
return GDK_KEY_F4;
if (WKStringIsEqualToUTF8CString(keyRef, "F5"))
return GDK_KEY_F5;
if (WKStringIsEqualToUTF8CString(keyRef, "F6"))
return GDK_KEY_F6;
if (WKStringIsEqualToUTF8CString(keyRef, "F7"))
return GDK_KEY_F7;
if (WKStringIsEqualToUTF8CString(keyRef, "F8"))
return GDK_KEY_F8;
if (WKStringIsEqualToUTF8CString(keyRef, "F9"))
return GDK_KEY_F9;
if (WKStringIsEqualToUTF8CString(keyRef, "F10"))
return GDK_KEY_F10;
if (WKStringIsEqualToUTF8CString(keyRef, "F11"))
return GDK_KEY_F11;
if (WKStringIsEqualToUTF8CString(keyRef, "F12"))
return GDK_KEY_F12;
if (WKStringIsEqualToUTF8CString(keyRef, "escape"))
return GDK_KEY_Escape;
size_t bufferSize = WKStringGetMaximumUTF8CStringSize(keyRef);
auto buffer = makeUniqueArray<char>(bufferSize);
WKStringGetUTF8CString(keyRef, buffer.get(), bufferSize);
char charCode = buffer.get()[0];
if (charCode == '\n' || charCode == '\r')
return GDK_KEY_Return;
if (charCode == '\t')
return GDK_KEY_Tab;
if (charCode == '\x8')
return GDK_KEY_BackSpace;
if (charCode == 0x001B)
return GDK_KEY_Escape;
if (WTF::isASCIIUpper(charCode))
*modifiers |= GDK_SHIFT_MASK;
return gdk_unicode_to_keyval(static_cast<guint32>(buffer.get()[0]));
}
void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers wkModifiers, unsigned location)
{
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()->platformWindow()));
g_object_ref(pressEvent->key.window);
GdkDisplay* display = gdk_window_get_display(pressEvent->key.window);
gdk_event_set_device(pressEvent, gdk_seat_get_pointer(gdk_display_get_default_seat(display)));
GUniqueOutPtr<GdkKeymapKey> keys;
gint nKeys;
if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_for_display(display), gdkKeySym, &keys.outPtr(), &nKeys) && nKeys)
pressEvent->key.hardware_keycode = keys.get()[0].keycode;
GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
dispatchEvent(pressEvent);
releaseEvent->key.type = GDK_KEY_RELEASE;
dispatchEvent(releaseEvent);
}
void EventSenderProxy::mouseDown(unsigned button, WKEventModifiers wkModifiers)
{
unsigned gdkButton = eventSenderButtonToGDKButton(button);
auto modifier = WebCore::stateModifierForGdkButton(gdkButton);
// If the same mouse button is already in the down position don't
// send another event as it may confuse Xvfb.
if (m_mouseButtonsCurrentlyDown & modifier)
return;
// 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, gdkButton, wkModifiers);
m_mouseButtonsCurrentlyDown |= modifier;
sendOrQueueEvent(event);
}
void EventSenderProxy::mouseUp(unsigned button, WKEventModifiers wkModifiers)
{
m_clickButton = kWKEventMouseButtonNoButton;
unsigned gdkButton = eventSenderButtonToGDKButton(button);
GdkEvent* event = createMouseButtonEvent(GDK_BUTTON_RELEASE, gdkButton, wkModifiers);
auto modifier = WebCore::stateModifierForGdkButton(gdkButton);
m_mouseButtonsCurrentlyDown &= ~modifier;
sendOrQueueEvent(event);
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_seat_get_pointer(gdk_display_get_default_seat(gdk_window_get_display(event->motion.window))));
event->motion.state = m_mouseButtonsCurrentlyDown;
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_seat_get_pointer(gdk_display_get_default_seat(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.
if (paged) {
WTFLogAlways("EventSenderProxy::continuousMouseScrollBy not implemented for paged scroll events");
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_seat_get_pointer(gdk_display_get_default_seat(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_seat_get_pointer(gdk_display_get_default_seat(gdk_window_get_display(touchEvent->button.window))));
touchEvent->touch.time = GDK_CURRENT_TIME;
return touchEvent;
}
#if ENABLE(TOUCH_EVENTS)
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 && static_cast<size_t>(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 && static_cast<size_t>(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));
}
}
#endif // ENABLE(TOUCH_EVENTS)
} // namespace WTR