1/*
2 * Copyright (C) 2010 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 *     * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *     * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 *     * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31// This file contains the definition for EventSender.
32//
33// Some notes about drag and drop handling:
34// Windows drag and drop goes through a system call to doDragDrop.  At that
35// point, program control is given to Windows which then periodically makes
36// callbacks into the webview.  This won't work for layout tests, so instead,
37// we queue up all the mouse move and mouse up events.  When the test tries to
38// start a drag (by calling EvenSendingController::doDragDrop), we take the
39// events in the queue and replay them.
40// The behavior of queuing events and replaying them can be disabled by a
41// layout test by setting eventSender.dragMode to false.
42
43#include "config.h"
44#include "EventSender.h"
45
46#include "TestShell.h"
47#include "WebContextMenuData.h"
48#include "WebDragData.h"
49#include "WebDragOperation.h"
50#include "WebPoint.h"
51#include "WebString.h"
52#include "WebTouchPoint.h"
53#include "WebView.h"
54#include "webkit/support/webkit_support.h"
55#include <wtf/Deque.h>
56#include <wtf/StringExtras.h>
57
58#if OS(WINDOWS)
59#include "win/WebInputEventFactory.h"
60#endif
61
62// FIXME: layout before each event?
63
64using namespace std;
65using namespace WebKit;
66
67WebPoint EventSender::lastMousePos;
68WebMouseEvent::Button EventSender::pressedButton = WebMouseEvent::ButtonNone;
69WebMouseEvent::Button EventSender::lastButtonType = WebMouseEvent::ButtonNone;
70
71struct SavedEvent {
72    enum SavedEventType {
73        Unspecified,
74        MouseUp,
75        MouseMove,
76        LeapForward
77    };
78
79    SavedEventType type;
80    WebMouseEvent::Button buttonType; // For MouseUp.
81    WebPoint pos; // For MouseMove.
82    int milliseconds; // For LeapForward.
83
84    SavedEvent()
85        : type(Unspecified)
86        , buttonType(WebMouseEvent::ButtonNone)
87        , milliseconds(0) {}
88};
89
90static WebDragData currentDragData;
91static WebDragOperation currentDragEffect;
92static WebDragOperationsMask currentDragEffectsAllowed;
93static bool replayingSavedEvents = false;
94static Deque<SavedEvent> mouseEventQueue;
95static int touchModifiers;
96static Vector<WebTouchPoint> touchPoints;
97
98// Time and place of the last mouse up event.
99static double lastClickTimeSec = 0;
100static WebPoint lastClickPos;
101static int clickCount = 0;
102
103// maximum distance (in space and time) for a mouse click
104// to register as a double or triple click
105static const double multipleClickTimeSec = 1;
106static const int multipleClickRadiusPixels = 5;
107
108// How much we should scroll per event - the value here is chosen to
109// match the WebKit impl and layout test results.
110static const float scrollbarPixelsPerTick = 40.0f;
111
112inline bool outsideMultiClickRadius(const WebPoint& a, const WebPoint& b)
113{
114    return ((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y)) >
115        multipleClickRadiusPixels * multipleClickRadiusPixels;
116}
117
118// Used to offset the time the event hander things an event happened.  This is
119// done so tests can run without a delay, but bypass checks that are time
120// dependent (e.g., dragging has a timeout vs selection).
121static uint32 timeOffsetMs = 0;
122
123static double getCurrentEventTimeSec()
124{
125    return (webkit_support::GetCurrentTimeInMillisecond() + timeOffsetMs) / 1000.0;
126}
127
128static void advanceEventTime(int32_t deltaMs)
129{
130    timeOffsetMs += deltaMs;
131}
132
133static void initMouseEvent(WebInputEvent::Type t, WebMouseEvent::Button b,
134                           const gfx::Point& pos, WebMouseEvent* e)
135{
136    e->type = t;
137    e->button = b;
138    e->modifiers = 0;
139    e->x = pos.x();
140    e->y = pos.y();
141    e->globalX = pos.x();
142    e->globalY = pos.y();
143    e->timeStampSeconds = getCurrentEventTimeSec();
144    e->clickCount = clickCount;
145}
146
147// Returns true if the specified key is the system key.
148static bool applyKeyModifier(const string& modifierName, WebInputEvent* event)
149{
150    bool isSystemKey = false;
151    const char* characters = modifierName.c_str();
152    if (!strcmp(characters, "ctrlKey")
153#if !OS(MAC_OS_X)
154        || !strcmp(characters, "addSelectionKey")
155#endif
156        ) {
157        event->modifiers |= WebInputEvent::ControlKey;
158    } else if (!strcmp(characters, "shiftKey") || !strcmp(characters, "rangeSelectionKey"))
159        event->modifiers |= WebInputEvent::ShiftKey;
160    else if (!strcmp(characters, "altKey")) {
161        event->modifiers |= WebInputEvent::AltKey;
162#if !OS(MAC_OS_X)
163        // On Windows all keys with Alt modifier will be marked as system key.
164        // We keep the same behavior on Linux and everywhere non-Mac, see:
165        // WebKit/chromium/src/gtk/WebInputEventFactory.cpp
166        // If we want to change this behavior on Linux, this piece of code must be
167        // kept in sync with the related code in above file.
168        isSystemKey = true;
169#endif
170#if OS(MAC_OS_X)
171    } else if (!strcmp(characters, "metaKey") || !strcmp(characters, "addSelectionKey")) {
172        event->modifiers |= WebInputEvent::MetaKey;
173        // On Mac only command key presses are marked as system key.
174        // See the related code in: WebKit/chromium/src/mac/WebInputEventFactory.cpp
175        // It must be kept in sync with the related code in above file.
176        isSystemKey = true;
177#else
178    } else if (!strcmp(characters, "metaKey")) {
179        event->modifiers |= WebInputEvent::MetaKey;
180#endif
181    }
182    return isSystemKey;
183}
184
185static bool applyKeyModifiers(const CppVariant* argument, WebInputEvent* event)
186{
187    bool isSystemKey = false;
188    if (argument->isObject()) {
189        Vector<string> modifiers = argument->toStringVector();
190        for (Vector<string>::const_iterator i = modifiers.begin(); i != modifiers.end(); ++i)
191            isSystemKey |= applyKeyModifier(*i, event);
192    } else if (argument->isString())
193        isSystemKey = applyKeyModifier(argument->toString(), event);
194    return isSystemKey;
195}
196
197// Get the edit command corresponding to a keyboard event.
198// Returns true if the specified event corresponds to an edit command, the name
199// of the edit command will be stored in |*name|.
200bool getEditCommand(const WebKeyboardEvent& event, string* name)
201{
202#if OS(MAC_OS_X)
203    // We only cares about Left,Right,Up,Down keys with Command or Command+Shift
204    // modifiers. These key events correspond to some special movement and
205    // selection editor commands, and was supposed to be handled in
206    // WebKit/chromium/src/EditorClientImpl.cpp. But these keys will be marked
207    // as system key, which prevents them from being handled. Thus they must be
208    // handled specially.
209    if ((event.modifiers & ~WebKeyboardEvent::ShiftKey) != WebKeyboardEvent::MetaKey)
210        return false;
211
212    switch (event.windowsKeyCode) {
213    case webkit_support::VKEY_LEFT:
214        *name = "MoveToBeginningOfLine";
215        break;
216    case webkit_support::VKEY_RIGHT:
217        *name = "MoveToEndOfLine";
218        break;
219    case webkit_support::VKEY_UP:
220        *name = "MoveToBeginningOfDocument";
221        break;
222    case webkit_support::VKEY_DOWN:
223        *name = "MoveToEndOfDocument";
224        break;
225    default:
226        return false;
227    }
228
229    if (event.modifiers & WebKeyboardEvent::ShiftKey)
230        name->append("AndModifySelection");
231
232    return true;
233#else
234    return false;
235#endif
236}
237
238// Key event location code introduced in DOM Level 3.
239// See also: http://www.w3.org/TR/DOM-Level-3-Events/#events-keyboardevents
240enum KeyLocationCode {
241    DOMKeyLocationStandard      = 0x00,
242    DOMKeyLocationLeft          = 0x01,
243    DOMKeyLocationRight         = 0x02,
244    DOMKeyLocationNumpad        = 0x03
245};
246
247EventSender::EventSender(TestShell* shell)
248    : m_shell(shell)
249{
250    // Initialize the map that associates methods of this class with the names
251    // they will use when called by JavaScript.  The actual binding of those
252    // names to their methods will be done by calling bindToJavaScript() (defined
253    // by CppBoundClass, the parent to EventSender).
254    bindMethod("addTouchPoint", &EventSender::addTouchPoint);
255    bindMethod("beginDragWithFiles", &EventSender::beginDragWithFiles);
256    bindMethod("cancelTouchPoint", &EventSender::cancelTouchPoint);
257    bindMethod("clearKillRing", &EventSender::clearKillRing);
258    bindMethod("clearTouchPoints", &EventSender::clearTouchPoints);
259    bindMethod("contextClick", &EventSender::contextClick);
260    bindMethod("continuousMouseScrollBy", &EventSender::continuousMouseScrollBy);
261    bindMethod("dispatchMessage", &EventSender::dispatchMessage);
262    bindMethod("enableDOMUIEventLogging", &EventSender::enableDOMUIEventLogging);
263    bindMethod("fireKeyboardEventsToElement", &EventSender::fireKeyboardEventsToElement);
264    bindMethod("keyDown", &EventSender::keyDown);
265    bindMethod("leapForward", &EventSender::leapForward);
266    bindMethod("mouseDown", &EventSender::mouseDown);
267    bindMethod("mouseMoveTo", &EventSender::mouseMoveTo);
268    bindMethod("mouseScrollBy", &EventSender::mouseScrollBy);
269    bindMethod("mouseUp", &EventSender::mouseUp);
270    bindMethod("releaseTouchPoint", &EventSender::releaseTouchPoint);
271    bindMethod("scheduleAsynchronousClick", &EventSender::scheduleAsynchronousClick);
272    bindMethod("setTouchModifier", &EventSender::setTouchModifier);
273    bindMethod("textZoomIn", &EventSender::textZoomIn);
274    bindMethod("textZoomOut", &EventSender::textZoomOut);
275    bindMethod("touchCancel", &EventSender::touchCancel);
276    bindMethod("touchEnd", &EventSender::touchEnd);
277    bindMethod("touchMove", &EventSender::touchMove);
278    bindMethod("touchStart", &EventSender::touchStart);
279    bindMethod("updateTouchPoint", &EventSender::updateTouchPoint);
280    bindMethod("zoomPageIn", &EventSender::zoomPageIn);
281    bindMethod("zoomPageOut", &EventSender::zoomPageOut);
282
283    // When set to true (the default value), we batch mouse move and mouse up
284    // events so we can simulate drag & drop.
285    bindProperty("dragMode", &dragMode);
286#if OS(WINDOWS)
287    bindProperty("WM_KEYDOWN", &wmKeyDown);
288    bindProperty("WM_KEYUP", &wmKeyUp);
289    bindProperty("WM_CHAR", &wmChar);
290    bindProperty("WM_DEADCHAR", &wmDeadChar);
291    bindProperty("WM_SYSKEYDOWN", &wmSysKeyDown);
292    bindProperty("WM_SYSKEYUP", &wmSysKeyUp);
293    bindProperty("WM_SYSCHAR", &wmSysChar);
294    bindProperty("WM_SYSDEADCHAR", &wmSysDeadChar);
295#endif
296}
297
298void EventSender::reset()
299{
300    // The test should have finished a drag and the mouse button state.
301    ASSERT(currentDragData.isNull());
302    currentDragData.reset();
303    currentDragEffect = WebKit::WebDragOperationNone;
304    currentDragEffectsAllowed = WebKit::WebDragOperationNone;
305    pressedButton = WebMouseEvent::ButtonNone;
306    dragMode.set(true);
307#if OS(WINDOWS)
308    wmKeyDown.set(WM_KEYDOWN);
309    wmKeyUp.set(WM_KEYUP);
310    wmChar.set(WM_CHAR);
311    wmDeadChar.set(WM_DEADCHAR);
312    wmSysKeyDown.set(WM_SYSKEYDOWN);
313    wmSysKeyUp.set(WM_SYSKEYUP);
314    wmSysChar.set(WM_SYSCHAR);
315    wmSysDeadChar.set(WM_SYSDEADCHAR);
316#endif
317    lastMousePos = WebPoint(0, 0);
318    lastClickTimeSec = 0;
319    lastClickPos = WebPoint(0, 0);
320    clickCount = 0;
321    lastButtonType = WebMouseEvent::ButtonNone;
322    timeOffsetMs = 0;
323    touchModifiers = 0;
324    touchPoints.clear();
325    m_taskList.revokeAll();
326}
327
328WebView* EventSender::webview()
329{
330    return m_shell->webView();
331}
332
333void EventSender::doDragDrop(const WebDragData& dragData, WebDragOperationsMask mask)
334{
335    WebMouseEvent event;
336    initMouseEvent(WebInputEvent::MouseDown, pressedButton, lastMousePos, &event);
337    WebPoint clientPoint(event.x, event.y);
338    WebPoint screenPoint(event.globalX, event.globalY);
339    currentDragData = dragData;
340    currentDragEffectsAllowed = mask;
341    currentDragEffect = webview()->dragTargetDragEnter(dragData, clientPoint, screenPoint, currentDragEffectsAllowed);
342
343    // Finish processing events.
344    replaySavedEvents();
345}
346
347WebMouseEvent::Button EventSender::getButtonTypeFromButtonNumber(int buttonCode)
348{
349    if (!buttonCode)
350        return WebMouseEvent::ButtonLeft;
351    if (buttonCode == 2)
352        return WebMouseEvent::ButtonRight;
353    return WebMouseEvent::ButtonMiddle;
354}
355
356int EventSender::getButtonNumberFromSingleArg(const CppArgumentList& arguments)
357{
358    int buttonCode = 0;
359    if (arguments.size() > 0 && arguments[0].isNumber())
360        buttonCode = arguments[0].toInt32();
361    return buttonCode;
362}
363
364void EventSender::updateClickCountForButton(WebMouseEvent::Button buttonType)
365{
366    if ((getCurrentEventTimeSec() - lastClickTimeSec < multipleClickTimeSec)
367        && (!outsideMultiClickRadius(lastMousePos, lastClickPos))
368        && (buttonType == lastButtonType))
369        ++clickCount;
370    else {
371        clickCount = 1;
372        lastButtonType = buttonType;
373    }
374}
375
376//
377// Implemented javascript methods.
378//
379
380void EventSender::mouseDown(const CppArgumentList& arguments, CppVariant* result)
381{
382    if (result) // Could be 0 if invoked asynchronously.
383        result->setNull();
384
385    webview()->layout();
386
387    int buttonNumber = getButtonNumberFromSingleArg(arguments);
388    ASSERT(buttonNumber != -1);
389
390    WebMouseEvent::Button buttonType = getButtonTypeFromButtonNumber(buttonNumber);
391
392    updateClickCountForButton(buttonType);
393
394    WebMouseEvent event;
395    pressedButton = buttonType;
396    initMouseEvent(WebInputEvent::MouseDown, buttonType, lastMousePos, &event);
397    if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString()))
398        applyKeyModifiers(&(arguments[1]), &event);
399    webview()->handleInputEvent(event);
400}
401
402void EventSender::mouseUp(const CppArgumentList& arguments, CppVariant* result)
403{
404    if (result) // Could be 0 if invoked asynchronously.
405        result->setNull();
406
407    webview()->layout();
408
409    int buttonNumber = getButtonNumberFromSingleArg(arguments);
410    ASSERT(buttonNumber != -1);
411
412    WebMouseEvent::Button buttonType = getButtonTypeFromButtonNumber(buttonNumber);
413
414    if (isDragMode() && !replayingSavedEvents) {
415        SavedEvent savedEvent;
416        savedEvent.type = SavedEvent::MouseUp;
417        savedEvent.buttonType = buttonType;
418        mouseEventQueue.append(savedEvent);
419        replaySavedEvents();
420    } else {
421        WebMouseEvent event;
422        initMouseEvent(WebInputEvent::MouseUp, buttonType, lastMousePos, &event);
423        if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString()))
424            applyKeyModifiers(&(arguments[1]), &event);
425        doMouseUp(event);
426    }
427}
428
429void EventSender::doMouseUp(const WebMouseEvent& e)
430{
431    webview()->handleInputEvent(e);
432
433    pressedButton = WebMouseEvent::ButtonNone;
434    lastClickTimeSec = e.timeStampSeconds;
435    lastClickPos = lastMousePos;
436
437    // If we're in a drag operation, complete it.
438    if (currentDragData.isNull())
439        return;
440    WebPoint clientPoint(e.x, e.y);
441    WebPoint screenPoint(e.globalX, e.globalY);
442
443    currentDragEffect = webview()->dragTargetDragOver(clientPoint, screenPoint, currentDragEffectsAllowed);
444    if (currentDragEffect)
445        webview()->dragTargetDrop(clientPoint, screenPoint);
446    else
447        webview()->dragTargetDragLeave();
448    webview()->dragSourceEndedAt(clientPoint, screenPoint, currentDragEffect);
449    webview()->dragSourceSystemDragEnded();
450
451    currentDragData.reset();
452}
453
454void EventSender::mouseMoveTo(const CppArgumentList& arguments, CppVariant* result)
455{
456    result->setNull();
457
458    if (arguments.size() < 2 || !arguments[0].isNumber() || !arguments[1].isNumber())
459        return;
460    webview()->layout();
461
462    WebPoint mousePos(arguments[0].toInt32(), arguments[1].toInt32());
463
464    if (isDragMode() && pressedButton == WebMouseEvent::ButtonLeft && !replayingSavedEvents) {
465        SavedEvent savedEvent;
466        savedEvent.type = SavedEvent::MouseMove;
467        savedEvent.pos = mousePos;
468        mouseEventQueue.append(savedEvent);
469    } else {
470        WebMouseEvent event;
471        initMouseEvent(WebInputEvent::MouseMove, pressedButton, mousePos, &event);
472        doMouseMove(event);
473    }
474}
475
476void EventSender::doMouseMove(const WebMouseEvent& e)
477{
478    lastMousePos = WebPoint(e.x, e.y);
479
480    webview()->handleInputEvent(e);
481
482    if (pressedButton == WebMouseEvent::ButtonNone || currentDragData.isNull())
483        return;
484    WebPoint clientPoint(e.x, e.y);
485    WebPoint screenPoint(e.globalX, e.globalY);
486    currentDragEffect = webview()->dragTargetDragOver(clientPoint, screenPoint, currentDragEffectsAllowed);
487}
488
489void EventSender::keyDown(const CppArgumentList& arguments, CppVariant* result)
490{
491    result->setNull();
492    if (arguments.size() < 1 || !arguments[0].isString())
493        return;
494    bool generateChar = false;
495
496    // FIXME: I'm not exactly sure how we should convert the string to a key
497    // event. This seems to work in the cases I tested.
498    // FIXME: Should we also generate a KEY_UP?
499    string codeStr = arguments[0].toString();
500
501    // Convert \n -> VK_RETURN.  Some layout tests use \n to mean "Enter", when
502    // Windows uses \r for "Enter".
503    int code = 0;
504    int text = 0;
505    bool needsShiftKeyModifier = false;
506    if ("\n" == codeStr) {
507        generateChar = true;
508        text = code = webkit_support::VKEY_RETURN;
509    } else if ("rightArrow" == codeStr)
510        code = webkit_support::VKEY_RIGHT;
511    else if ("downArrow" == codeStr)
512        code = webkit_support::VKEY_DOWN;
513    else if ("leftArrow" == codeStr)
514        code = webkit_support::VKEY_LEFT;
515    else if ("upArrow" == codeStr)
516        code = webkit_support::VKEY_UP;
517    else if ("insert" == codeStr)
518        code = webkit_support::VKEY_INSERT;
519    else if ("delete" == codeStr)
520        code = webkit_support::VKEY_DELETE;
521    else if ("pageUp" == codeStr)
522        code = webkit_support::VKEY_PRIOR;
523    else if ("pageDown" == codeStr)
524        code = webkit_support::VKEY_NEXT;
525    else if ("home" == codeStr)
526        code = webkit_support::VKEY_HOME;
527    else if ("end" == codeStr)
528        code = webkit_support::VKEY_END;
529    else if ("printScreen" == codeStr)
530        code = webkit_support::VKEY_SNAPSHOT;
531    else if ("menu" == codeStr)
532        // FIXME: Change this to webkit_support::VKEY_APPS.
533        code = 0x5D;
534    else {
535        // Compare the input string with the function-key names defined by the
536        // DOM spec (i.e. "F1",...,"F24"). If the input string is a function-key
537        // name, set its key code.
538        for (int i = 1; i <= 24; ++i) {
539            char functionChars[10];
540            snprintf(functionChars, 10, "F%d", i);
541            string functionKeyName(functionChars);
542            if (functionKeyName == codeStr) {
543                code = webkit_support::VKEY_F1 + (i - 1);
544                break;
545            }
546        }
547        if (!code) {
548            WebString webCodeStr = WebString::fromUTF8(codeStr.data(), codeStr.size());
549            ASSERT(webCodeStr.length() == 1);
550            text = code = webCodeStr.data()[0];
551            needsShiftKeyModifier = needsShiftModifier(code);
552            if ((code & 0xFF) >= 'a' && (code & 0xFF) <= 'z')
553                code -= 'a' - 'A';
554            generateChar = true;
555        }
556    }
557
558    // For one generated keyboard event, we need to generate a keyDown/keyUp
559    // pair; refer to EventSender.cpp in Tools/DumpRenderTree/win.
560    // On Windows, we might also need to generate a char event to mimic the
561    // Windows event flow; on other platforms we create a merged event and test
562    // the event flow that that platform provides.
563    WebKeyboardEvent eventDown, eventChar, eventUp;
564    eventDown.type = WebInputEvent::RawKeyDown;
565    eventDown.modifiers = 0;
566    eventDown.windowsKeyCode = code;
567    if (generateChar) {
568        eventDown.text[0] = text;
569        eventDown.unmodifiedText[0] = text;
570    }
571    eventDown.setKeyIdentifierFromWindowsKeyCode();
572
573    if (arguments.size() >= 2 && (arguments[1].isObject() || arguments[1].isString()))
574        eventDown.isSystemKey = applyKeyModifiers(&(arguments[1]), &eventDown);
575
576    if (needsShiftKeyModifier)
577        eventDown.modifiers |= WebInputEvent::ShiftKey;
578
579    // See if KeyLocation argument is given.
580    if (arguments.size() >= 3 && arguments[2].isNumber()) {
581        int location = arguments[2].toInt32();
582        if (location == DOMKeyLocationNumpad)
583            eventDown.modifiers |= WebInputEvent::IsKeyPad;
584    }
585
586    eventChar = eventUp = eventDown;
587    eventUp.type = WebInputEvent::KeyUp;
588    // EventSender.m forces a layout here, with at least one
589    // test (fast/forms/focus-control-to-page.html) relying on this.
590    webview()->layout();
591
592    // In the browser, if a keyboard event corresponds to an editor command,
593    // the command will be dispatched to the renderer just before dispatching
594    // the keyboard event, and then it will be executed in the
595    // RenderView::handleCurrentKeyboardEvent() method, which is called from
596    // third_party/WebKit/Source/WebKit/chromium/src/EditorClientImpl.cpp.
597    // We just simulate the same behavior here.
598    string editCommand;
599    if (getEditCommand(eventDown, &editCommand))
600        m_shell->webViewHost()->setEditCommand(editCommand, "");
601
602    webview()->handleInputEvent(eventDown);
603
604    m_shell->webViewHost()->clearEditCommand();
605
606    if (generateChar) {
607        eventChar.type = WebInputEvent::Char;
608        eventChar.keyIdentifier[0] = '\0';
609        webview()->handleInputEvent(eventChar);
610    }
611
612    webview()->handleInputEvent(eventUp);
613}
614
615void EventSender::dispatchMessage(const CppArgumentList& arguments, CppVariant* result)
616{
617    result->setNull();
618
619#if OS(WINDOWS)
620    if (arguments.size() == 3) {
621        // Grab the message id to see if we need to dispatch it.
622        int msg = arguments[0].toInt32();
623
624        // WebKit's version of this function stuffs a MSG struct and uses
625        // TranslateMessage and DispatchMessage. We use a WebKeyboardEvent, which
626        // doesn't need to receive the DeadChar and SysDeadChar messages.
627        if (msg == WM_DEADCHAR || msg == WM_SYSDEADCHAR)
628            return;
629
630        webview()->layout();
631
632        unsigned long lparam = static_cast<unsigned long>(arguments[2].toDouble());
633        webview()->handleInputEvent(WebInputEventFactory::keyboardEvent(0, msg, arguments[1].toInt32(), lparam));
634    } else
635        ASSERT_NOT_REACHED();
636#endif
637}
638
639bool EventSender::needsShiftModifier(int keyCode)
640{
641    // If code is an uppercase letter, assign a SHIFT key to
642    // eventDown.modifier, this logic comes from
643    // Tools/DumpRenderTree/win/EventSender.cpp
644    return (keyCode & 0xFF) >= 'A' && (keyCode & 0xFF) <= 'Z';
645}
646
647void EventSender::leapForward(const CppArgumentList& arguments, CppVariant* result)
648{
649    result->setNull();
650
651    if (arguments.size() < 1 || !arguments[0].isNumber())
652        return;
653
654    int milliseconds = arguments[0].toInt32();
655    if (isDragMode() && pressedButton == WebMouseEvent::ButtonLeft && !replayingSavedEvents) {
656        SavedEvent savedEvent;
657        savedEvent.type = SavedEvent::LeapForward;
658        savedEvent.milliseconds = milliseconds;
659        mouseEventQueue.append(savedEvent);
660    } else
661        doLeapForward(milliseconds);
662}
663
664void EventSender::doLeapForward(int milliseconds)
665{
666    advanceEventTime(milliseconds);
667}
668
669// Apple's port of WebKit zooms by a factor of 1.2 (see
670// WebKit/WebView/WebView.mm)
671void EventSender::textZoomIn(const CppArgumentList&, CppVariant* result)
672{
673    webview()->setZoomLevel(true, webview()->zoomLevel() + 1);
674    result->setNull();
675}
676
677void EventSender::textZoomOut(const CppArgumentList&, CppVariant* result)
678{
679    webview()->setZoomLevel(true, webview()->zoomLevel() - 1);
680    result->setNull();
681}
682
683void EventSender::zoomPageIn(const CppArgumentList&, CppVariant* result)
684{
685    webview()->setZoomLevel(false, webview()->zoomLevel() + 1);
686    result->setNull();
687}
688
689void EventSender::zoomPageOut(const CppArgumentList&, CppVariant* result)
690{
691    webview()->setZoomLevel(false, webview()->zoomLevel() - 1);
692    result->setNull();
693}
694
695void EventSender::mouseScrollBy(const CppArgumentList& arguments, CppVariant* result)
696{
697    handleMouseWheel(arguments, result, false);
698}
699
700void EventSender::continuousMouseScrollBy(const CppArgumentList& arguments, CppVariant* result)
701{
702    handleMouseWheel(arguments, result, true);
703}
704
705void EventSender::replaySavedEvents()
706{
707    replayingSavedEvents = true;
708    while (!mouseEventQueue.isEmpty()) {
709        SavedEvent e = mouseEventQueue.takeFirst();
710
711        switch (e.type) {
712        case SavedEvent::MouseMove: {
713            WebMouseEvent event;
714            initMouseEvent(WebInputEvent::MouseMove, pressedButton, e.pos, &event);
715            doMouseMove(event);
716            break;
717        }
718        case SavedEvent::LeapForward:
719            doLeapForward(e.milliseconds);
720            break;
721        case SavedEvent::MouseUp: {
722            WebMouseEvent event;
723            initMouseEvent(WebInputEvent::MouseUp, e.buttonType, lastMousePos, &event);
724            doMouseUp(event);
725            break;
726        }
727        default:
728            ASSERT_NOT_REACHED();
729        }
730    }
731
732    replayingSavedEvents = false;
733}
734
735// Because actual context menu is implemented by the browser side,
736// this function does only what LayoutTests are expecting:
737// - Many test checks the count of items. So returning non-zero value makes sense.
738// - Some test compares the count before and after some action. So changing the count based on flags
739//   also makes sense. This function is doing such for some flags.
740// - Some test even checks actual string content. So providing it would be also helpful.
741//
742static Vector<WebString> makeMenuItemStringsFor(WebContextMenuData* contextMenu, MockSpellCheck* spellcheck)
743{
744    // These constants are based on Safari's context menu because tests are made for it.
745    static const char* nonEditableMenuStrings[] = { "Back", "Reload Page", "Open in Dashbaord", "<separator>", "View Source", "Save Page As", "Print Page", "Inspect Element", 0 };
746    static const char* editableMenuStrings[] = { "Cut", "Copy", "<separator>", "Paste", "Spelling and Grammar", "Substitutions, Transformations", "Font", "Speech", "Paragraph Direction", "<separator>", 0 };
747
748    // This is possible because mouse events are cancelleable.
749    if (!contextMenu)
750        return Vector<WebString>();
751
752    Vector<WebString> strings;
753
754    if (contextMenu->isEditable) {
755        for (const char** item = editableMenuStrings; *item; ++item)
756            strings.append(WebString::fromUTF8(*item));
757        Vector<WebString> suggestions;
758        spellcheck->fillSuggestionList(contextMenu->misspelledWord, &suggestions);
759        for (size_t i = 0; i < suggestions.size(); ++i)
760            strings.append(suggestions[i]);
761    } else {
762        for (const char** item = nonEditableMenuStrings; *item; ++item)
763            strings.append(WebString::fromUTF8(*item));
764    }
765
766    return strings;
767}
768
769
770void EventSender::contextClick(const CppArgumentList& arguments, CppVariant* result)
771{
772    webview()->layout();
773
774    updateClickCountForButton(WebMouseEvent::ButtonRight);
775
776    // Clears last context menu data because we need to know if the context menu be requested
777    // after following mouse events.
778    m_shell->webViewHost()->clearContextMenuData();
779
780    // Generate right mouse down and up.
781    WebMouseEvent event;
782    pressedButton = WebMouseEvent::ButtonRight;
783    initMouseEvent(WebInputEvent::MouseDown, WebMouseEvent::ButtonRight, lastMousePos, &event);
784    webview()->handleInputEvent(event);
785
786    initMouseEvent(WebInputEvent::MouseUp, WebMouseEvent::ButtonRight, lastMousePos, &event);
787    webview()->handleInputEvent(event);
788
789    pressedButton = WebMouseEvent::ButtonNone;
790
791    WebContextMenuData* lastContextMenu = m_shell->webViewHost()->lastContextMenuData();
792    result->set(WebBindings::makeStringArray(makeMenuItemStringsFor(lastContextMenu, m_shell->webViewHost()->mockSpellCheck())));
793}
794
795class MouseDownTask: public MethodTask<EventSender> {
796public:
797    MouseDownTask(EventSender* obj, const CppArgumentList& arg)
798        : MethodTask<EventSender>(obj), m_arguments(arg) {}
799    virtual void runIfValid() { m_object->mouseDown(m_arguments, 0); }
800private:
801    CppArgumentList m_arguments;
802};
803
804class MouseUpTask: public MethodTask<EventSender> {
805public:
806    MouseUpTask(EventSender* obj, const CppArgumentList& arg)
807        : MethodTask<EventSender>(obj), m_arguments(arg) {}
808    virtual void runIfValid() { m_object->mouseUp(m_arguments, 0); }
809private:
810    CppArgumentList m_arguments;
811};
812
813void EventSender::scheduleAsynchronousClick(const CppArgumentList& arguments, CppVariant* result)
814{
815    result->setNull();
816    postTask(new MouseDownTask(this, arguments));
817    postTask(new MouseUpTask(this, arguments));
818}
819
820void EventSender::beginDragWithFiles(const CppArgumentList& arguments, CppVariant* result)
821{
822    currentDragData.initialize();
823    Vector<string> files = arguments[0].toStringVector();
824    for (size_t i = 0; i < files.size(); ++i)
825        currentDragData.appendToFilenames(webkit_support::GetAbsoluteWebStringFromUTF8Path(files[i]));
826    currentDragEffectsAllowed = WebKit::WebDragOperationCopy;
827
828    // Provide a drag source.
829    webview()->dragTargetDragEnter(currentDragData, lastMousePos, lastMousePos, currentDragEffectsAllowed);
830
831    // dragMode saves events and then replays them later. We don't need/want that.
832    dragMode.set(false);
833
834    // Make the rest of eventSender think a drag is in progress.
835    pressedButton = WebMouseEvent::ButtonLeft;
836
837    result->setNull();
838}
839
840void EventSender::addTouchPoint(const CppArgumentList& arguments, CppVariant* result)
841{
842    result->setNull();
843
844    WebTouchPoint touchPoint;
845    touchPoint.state = WebTouchPoint::StatePressed;
846    touchPoint.position = WebPoint(arguments[0].toInt32(), arguments[1].toInt32());
847    touchPoint.screenPosition = touchPoint.position;
848    touchPoint.id = touchPoints.size();
849    touchPoints.append(touchPoint);
850}
851
852void EventSender::clearTouchPoints(const CppArgumentList&, CppVariant* result)
853{
854    result->setNull();
855    touchPoints.clear();
856}
857
858void EventSender::releaseTouchPoint(const CppArgumentList& arguments, CppVariant* result)
859{
860    result->setNull();
861
862    const unsigned index = arguments[0].toInt32();
863    ASSERT(index < touchPoints.size());
864
865    WebTouchPoint* touchPoint = &touchPoints[index];
866    touchPoint->state = WebTouchPoint::StateReleased;
867}
868
869void EventSender::setTouchModifier(const CppArgumentList& arguments, CppVariant* result)
870{
871    result->setNull();
872
873    int mask = 0;
874    const string keyName = arguments[0].toString();
875    if (keyName == "shift")
876        mask = WebInputEvent::ShiftKey;
877    else if (keyName == "alt")
878        mask = WebInputEvent::AltKey;
879    else if (keyName == "ctrl")
880        mask = WebInputEvent::ControlKey;
881    else if (keyName == "meta")
882        mask = WebInputEvent::MetaKey;
883
884    if (arguments[1].toBoolean())
885        touchModifiers |= mask;
886    else
887        touchModifiers &= ~mask;
888}
889
890void EventSender::updateTouchPoint(const CppArgumentList& arguments, CppVariant* result)
891{
892    result->setNull();
893
894    const unsigned index = arguments[0].toInt32();
895    ASSERT(index < touchPoints.size());
896
897    WebPoint position(arguments[1].toInt32(), arguments[2].toInt32());
898    WebTouchPoint* touchPoint = &touchPoints[index];
899    touchPoint->state = WebTouchPoint::StateMoved;
900    touchPoint->position = position;
901    touchPoint->screenPosition = position;
902}
903
904void EventSender::cancelTouchPoint(const CppArgumentList& arguments, CppVariant* result)
905{
906    result->setNull();
907
908    const unsigned index = arguments[0].toInt32();
909    ASSERT(index < touchPoints.size());
910
911    WebTouchPoint* touchPoint = &touchPoints[index];
912    touchPoint->state = WebTouchPoint::StateCancelled;
913}
914
915void EventSender::sendCurrentTouchEvent(const WebInputEvent::Type type)
916{
917    ASSERT(static_cast<unsigned>(WebTouchEvent::touchPointsLengthCap) > touchPoints.size());
918    webview()->layout();
919
920    WebTouchEvent touchEvent;
921    touchEvent.type = type;
922    touchEvent.modifiers = touchModifiers;
923    touchEvent.timeStampSeconds = getCurrentEventTimeSec();
924    touchEvent.touchPointsLength = touchPoints.size();
925    for (unsigned i = 0; i < touchPoints.size(); ++i)
926        touchEvent.touchPoints[i] = touchPoints[i];
927    webview()->handleInputEvent(touchEvent);
928
929    for (unsigned i = 0; i < touchPoints.size(); ++i) {
930        WebTouchPoint* touchPoint = &touchPoints[i];
931        if (touchPoint->state == WebTouchPoint::StateReleased) {
932            touchPoints.remove(i);
933            --i;
934        } else
935            touchPoint->state = WebTouchPoint::StateStationary;
936    }
937}
938
939void EventSender::handleMouseWheel(const CppArgumentList& arguments, CppVariant* result, bool continuous)
940{
941    result->setNull();
942
943    if (arguments.size() < 2 || !arguments[0].isNumber() || !arguments[1].isNumber())
944        return;
945
946    // Force a layout here just to make sure every position has been
947    // determined before we send events (as well as all the other methods
948    // that send an event do).
949    webview()->layout();
950
951    int horizontal = arguments[0].toInt32();
952    int vertical = arguments[1].toInt32();
953
954    WebMouseWheelEvent event;
955    initMouseEvent(WebInputEvent::MouseWheel, pressedButton, lastMousePos, &event);
956    event.wheelTicksX = static_cast<float>(horizontal);
957    event.wheelTicksY = static_cast<float>(vertical);
958    event.deltaX = event.wheelTicksX;
959    event.deltaY = event.wheelTicksY;
960    if (continuous) {
961        event.wheelTicksX /= scrollbarPixelsPerTick;
962        event.wheelTicksY /= scrollbarPixelsPerTick;
963    } else {
964        event.deltaX *= scrollbarPixelsPerTick;
965        event.deltaY *= scrollbarPixelsPerTick;
966    }
967    webview()->handleInputEvent(event);
968}
969
970void EventSender::touchEnd(const CppArgumentList&, CppVariant* result)
971{
972    result->setNull();
973    sendCurrentTouchEvent(WebInputEvent::TouchEnd);
974}
975
976void EventSender::touchMove(const CppArgumentList&, CppVariant* result)
977{
978    result->setNull();
979    sendCurrentTouchEvent(WebInputEvent::TouchMove);
980}
981
982void EventSender::touchStart(const CppArgumentList&, CppVariant* result)
983{
984    result->setNull();
985    sendCurrentTouchEvent(WebInputEvent::TouchStart);
986}
987
988void EventSender::touchCancel(const CppArgumentList&, CppVariant* result)
989{
990    result->setNull();
991    sendCurrentTouchEvent(WebInputEvent::TouchCancel);
992}
993
994//
995// Unimplemented stubs
996//
997
998void EventSender::enableDOMUIEventLogging(const CppArgumentList&, CppVariant* result)
999{
1000    result->setNull();
1001}
1002
1003void EventSender::fireKeyboardEventsToElement(const CppArgumentList&, CppVariant* result)
1004{
1005    result->setNull();
1006}
1007
1008void EventSender::clearKillRing(const CppArgumentList&, CppVariant* result)
1009{
1010    result->setNull();
1011}
1012