| /* |
| * 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 |