blob: 5d4de9d2020d3baa282bba59d81c8ed7864f32cd [file] [log] [blame]
/*
* Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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 <QGraphicsSceneMouseEvent>
#include <QKeyEvent>
#include <QtTest/QtTest>
#include <WebKit2/WKPagePrivate.h>
#include <WebKit2/WKStringQt.h>
#include <qpa/qwindowsysteminterface.h>
namespace WTR {
#define KEYCODE_DEL 127
#define KEYCODE_BACKSPACE 8
#define KEYCODE_LEFTARROW 0xf702
#define KEYCODE_RIGHTARROW 0xf703
#define KEYCODE_UPARROW 0xf700
#define KEYCODE_DOWNARROW 0xf701
struct WTREventQueue {
QEvent* m_event;
int m_delay;
};
static WTREventQueue eventQueue[1024];
static unsigned endOfQueue;
static bool isReplayingEvents;
EventSenderProxy::EventSenderProxy(TestController* testController)
: m_testController(testController)
, m_time(0)
, m_position()
, m_leftMouseButtonDown(false)
, m_clickCount(0)
, m_clickTime(0)
, m_clickPosition()
, m_clickButton(kWKEventMouseButtonNoButton)
, m_mouseButtons(0)
#if ENABLE(TOUCH_EVENTS)
, m_touchActive(false)
#endif
{
memset(eventQueue, 0, sizeof(eventQueue));
endOfQueue = 0;
isReplayingEvents = false;
}
static Qt::MouseButton getMouseButton(unsigned button)
{
Qt::MouseButton mouseButton;
switch (button) {
case 0:
mouseButton = Qt::LeftButton;
break;
case 1:
mouseButton = Qt::MidButton;
break;
case 2:
mouseButton = Qt::RightButton;
break;
case 3:
// fast/events/mouse-click-events expects the 4th button to be treated as the middle button
mouseButton = Qt::MidButton;
break;
default:
mouseButton = Qt::LeftButton;
break;
}
return mouseButton;
}
static Qt::KeyboardModifiers getModifiers(WKEventModifiers modifiersRef)
{
Qt::KeyboardModifiers modifiers = 0;
if (modifiersRef & kWKEventModifiersControlKey)
modifiers |= Qt::ControlModifier;
if (modifiersRef & kWKEventModifiersShiftKey)
modifiers |= Qt::ShiftModifier;
if (modifiersRef & kWKEventModifiersAltKey)
modifiers |= Qt::AltModifier;
if (modifiersRef & kWKEventModifiersMetaKey)
modifiers |= Qt::MetaModifier;
return modifiers;
}
void EventSenderProxy::keyDown(WKStringRef keyRef, WKEventModifiers modifiersRef, unsigned location)
{
const QString key = WKStringCopyQString(keyRef);
QString keyText = key;
Qt::KeyboardModifiers modifiers = getModifiers(modifiersRef);
if (location == 3)
modifiers |= Qt::KeypadModifier;
int code = 0;
if (key.length() == 1) {
code = key.unicode()->unicode();
// map special keycodes used by the tests to something that works for Qt/X11
if (code == '\r') {
code = Qt::Key_Return;
} else if (code == '\t') {
code = Qt::Key_Tab;
if (modifiers == Qt::ShiftModifier)
code = Qt::Key_Backtab;
keyText = QStringLiteral("\t");
} else if (code == KEYCODE_DEL || code == KEYCODE_BACKSPACE) {
code = Qt::Key_Backspace;
if (modifiers == Qt::AltModifier)
modifiers = Qt::ControlModifier;
keyText = QString();
} else if (code == 'o' && modifiers == Qt::ControlModifier) {
// Mimic the emacs ctrl-o binding on Mac by inserting a paragraph
// separator and then putting the cursor back to its original
// position. Allows us to pass emacs-ctrl-o.html
keyText = QLatin1String("\n");
code = '\n';
modifiers = 0;
QKeyEvent event(QEvent::KeyPress, code, modifiers, keyText);
m_testController->mainWebView()->sendEvent(&event);
QKeyEvent event2(QEvent::KeyRelease, code, modifiers, keyText);
m_testController->mainWebView()->sendEvent(&event2);
keyText = QString();
code = Qt::Key_Left;
} else if (code == 'y' && modifiers == Qt::ControlModifier) {
keyText = QLatin1String("c");
code = 'c';
} else if (code == 'k' && modifiers == Qt::ControlModifier) {
keyText = QLatin1String("x");
code = 'x';
} else if (code == 'a' && modifiers == Qt::ControlModifier) {
keyText = QString();
code = Qt::Key_Home;
modifiers = 0;
} else if (code == KEYCODE_LEFTARROW) {
keyText = QString();
code = Qt::Key_Left;
if (modifiers & Qt::MetaModifier) {
code = Qt::Key_Home;
modifiers &= ~Qt::MetaModifier;
}
} else if (code == KEYCODE_RIGHTARROW) {
keyText = QString();
code = Qt::Key_Right;
if (modifiers & Qt::MetaModifier) {
code = Qt::Key_End;
modifiers &= ~Qt::MetaModifier;
}
} else if (code == KEYCODE_UPARROW) {
keyText = QString();
code = Qt::Key_Up;
if (modifiers & Qt::MetaModifier) {
code = Qt::Key_PageUp;
modifiers &= ~Qt::MetaModifier;
}
} else if (code == KEYCODE_DOWNARROW) {
keyText = QString();
code = Qt::Key_Down;
if (modifiers & Qt::MetaModifier) {
code = Qt::Key_PageDown;
modifiers &= ~Qt::MetaModifier;
}
} else
code = key.unicode()->toUpper().unicode();
} else {
if (key.startsWith(QLatin1Char('F')) && key.count() <= 3) {
keyText = keyText.mid(1);
int functionKey = keyText.toInt();
Q_ASSERT(functionKey >= 1 && functionKey <= 35);
code = Qt::Key_F1 + (functionKey - 1);
// map special keycode strings used by the tests to something that works for Qt/X11
} else if (key == QLatin1String("leftArrow")) {
keyText = QString();
code = Qt::Key_Left;
} else if (key == QLatin1String("rightArrow")) {
keyText = QString();
code = Qt::Key_Right;
} else if (key == QLatin1String("upArrow")) {
keyText = QString();
code = Qt::Key_Up;
} else if (key == QLatin1String("downArrow")) {
keyText = QString();
code = Qt::Key_Down;
} else if (key == QLatin1String("pageUp")) {
keyText = QString();
code = Qt::Key_PageUp;
} else if (key == QLatin1String("pageDown")) {
keyText = QString();
code = Qt::Key_PageDown;
} else if (key == QLatin1String("home")) {
keyText = QString();
code = Qt::Key_Home;
} else if (key == QLatin1String("end")) {
keyText = QString();
code = Qt::Key_End;
} else if (key == QLatin1String("insert")) {
keyText = QString();
code = Qt::Key_Insert;
} else if (key == QLatin1String("delete")) {
keyText = QString();
code = Qt::Key_Delete;
} else if (key == QLatin1String("printScreen")) {
keyText = QString();
code = Qt::Key_Print;
} else if (key == QLatin1String("menu")) {
keyText = QString();
code = Qt::Key_Menu;
}
}
QKeyEvent* pressEvent = new QKeyEvent(QEvent::KeyPress, code, modifiers, keyText);
sendOrQueueEvent(pressEvent);
QKeyEvent* releaseEvent = new QKeyEvent(QEvent::KeyRelease, code, modifiers, keyText);
sendOrQueueEvent(releaseEvent);
}
void EventSenderProxy::updateClickCountForButton(int button)
{
if (m_time - m_clickTime < QApplication::doubleClickInterval() && 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::mouseDown(unsigned button, WKEventModifiers wkModifiers)
{
Qt::KeyboardModifiers modifiers = getModifiers(wkModifiers);
Qt::MouseButton mouseButton = getMouseButton(button);
updateClickCountForButton(button);
m_mouseButtons |= mouseButton;
QPoint mousePos(m_position.x, m_position.y);
QMouseEvent* event = new QMouseEvent((m_clickCount == 2) ? QEvent::MouseButtonDblClick : QEvent::MouseButtonPress,
mousePos, mousePos, mouseButton, m_mouseButtons, modifiers);
sendOrQueueEvent(event);
}
void EventSenderProxy::mouseUp(unsigned button, WKEventModifiers)
{
Qt::MouseButton mouseButton = getMouseButton(button);
m_mouseButtons &= ~mouseButton;
QPoint mousePos(m_position.x, m_position.y);
QMouseEvent* event = new QMouseEvent(QEvent::MouseButtonRelease,
mousePos, mousePos, mouseButton, m_mouseButtons, Qt::NoModifier);
sendOrQueueEvent(event);
}
void EventSenderProxy::mouseMoveTo(double x, double y)
{
m_position.x = x;
m_position.y = y;
QPoint mousePos(m_position.x, m_position.y);
QMouseEvent* event = new QMouseEvent(QEvent::MouseMove,
mousePos, mousePos, Qt::NoButton, m_mouseButtons, Qt::NoModifier);
sendOrQueueEvent(event);
}
void EventSenderProxy::mouseScrollBy(int, int)
{
// FIXME: Implement this.
}
void EventSenderProxy::continuousMouseScrollBy(int, int, bool)
{
// FIXME: Implement this.
}
void EventSenderProxy::leapForward(int ms)
{
eventQueue[endOfQueue].m_delay = ms;
m_time += ms;
}
#if ENABLE(TOUCH_EVENTS)
void EventSenderProxy::addTouchPoint(int x, int y)
{
const int id = m_touchPoints.isEmpty() ? 0 : m_touchPoints.last().id() + 1;
const QPointF pos(x, y);
QTouchEvent::TouchPoint point(id);
point.setPos(pos);
point.setStartPos(pos);
point.setState(Qt::TouchPointPressed);
if (!m_touchPointRadius.isNull())
point.setRect(QRectF(pos - m_touchPointRadius, pos + m_touchPointRadius));
m_touchPoints.append(point);
}
void EventSenderProxy::updateTouchPoint(int index, int x, int y)
{
ASSERT(index >= 0 && index < m_touchPoints.count());
QPointF pos(x, y);
QTouchEvent::TouchPoint &p = m_touchPoints[index];
p.setPos(pos);
p.setState(Qt::TouchPointMoved);
if (!m_touchPointRadius.isNull())
p.setRect(QRectF(pos - m_touchPointRadius, pos + m_touchPointRadius));
}
void EventSenderProxy::setTouchModifier(WKEventModifiers modifier, bool enable)
{
Qt::KeyboardModifiers mod = getModifiers(modifier);
if (enable)
m_touchModifiers |= mod;
else
m_touchModifiers &= ~mod;
}
void EventSenderProxy::setTouchPointRadius(int radiusX, int radiusY)
{
m_touchPointRadius = QPoint(radiusX, radiusY);
}
void EventSenderProxy::touchStart()
{
if (!m_touchActive) {
sendTouchEvent(QEvent::TouchBegin);
m_touchActive = true;
} else
sendTouchEvent(QEvent::TouchUpdate);
}
void EventSenderProxy::touchMove()
{
sendTouchEvent(QEvent::TouchUpdate);
}
void EventSenderProxy::touchEnd()
{
for (int i = 0; i < m_touchPoints.count(); ++i) {
if (m_touchPoints[i].state() != Qt::TouchPointReleased) {
sendTouchEvent(QEvent::TouchUpdate);
return;
}
}
sendTouchEvent(QEvent::TouchEnd);
m_touchActive = false;
}
void EventSenderProxy::touchCancel()
{
sendTouchEvent(QEvent::TouchCancel);
m_touchActive = false;
}
void EventSenderProxy::clearTouchPoints()
{
m_touchPoints.clear();
m_touchModifiers = Qt::KeyboardModifiers();
m_touchActive = false;
m_touchPointRadius = QPoint();
}
void EventSenderProxy::releaseTouchPoint(int index)
{
if (index < 0 || index >= m_touchPoints.count())
return;
m_touchPoints[index].setState(Qt::TouchPointReleased);
}
void EventSenderProxy::cancelTouchPoint(int index)
{
// FIXME: No cancellation state in Qt 5, mapped to release instead.
// PlatformTouchEvent conversion later will map all touch points to
// cancelled.
releaseTouchPoint(index);
}
void EventSenderProxy::sendTouchEvent(QEvent::Type type)
{
static QTouchDevice* device = 0;
if (!device) {
device = new QTouchDevice;
device->setType(QTouchDevice::TouchScreen);
QWindowSystemInterface::registerTouchDevice(device);
}
QTouchEvent event(type, device, m_touchModifiers);
event.setTouchPoints(m_touchPoints);
m_testController->mainWebView()->sendEvent(&event);
QList<QTouchEvent::TouchPoint>::Iterator it = m_touchPoints.begin();
while (it != m_touchPoints.end()) {
if (it->state() == Qt::TouchPointReleased)
it = m_touchPoints.erase(it);
else {
it->setState(Qt::TouchPointStationary);
++it;
}
}
}
#endif
void EventSenderProxy::sendOrQueueEvent(QEvent* event)
{
if (!endOfQueue && !eventQueue[endOfQueue].m_delay) {
m_testController->mainWebView()->sendEvent(event);
delete event;
return;
}
eventQueue[endOfQueue++].m_event = event;
replaySavedEvents();
}
void EventSenderProxy::replaySavedEvents()
{
if (isReplayingEvents)
return;
isReplayingEvents = true;
int i = 0;
while (i < endOfQueue) {
WTREventQueue& ev = eventQueue[i];
if (ev.m_delay)
QTest::qWait(ev.m_delay);
i++;
m_testController->mainWebView()->sendEvent(ev.m_event);
delete ev.m_event;
ev.m_delay = 0;
}
endOfQueue = 0;
isReplayingEvents = false;
}
} // namespace WTR