1/*
2 * Copyright (C) 2007, 2008 Apple 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
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "EventSender.h"
31
32#include "DraggingInfo.h"
33#include "DumpRenderTree.h"
34
35#include <WebCore/COMPtr.h>
36#include <wtf/ASCIICType.h>
37#include <wtf/Platform.h>
38#include <JavaScriptCore/JavaScriptCore.h>
39#include <JavaScriptCore/Assertions.h>
40#include <WebKit/WebKit.h>
41#include <windows.h>
42
43#define WM_DRT_SEND_QUEUED_EVENT (WM_APP+1)
44
45static bool down;
46static bool dragMode = true;
47static bool replayingSavedEvents;
48static int timeOffset;
49static POINT lastMousePosition;
50
51struct DelayedMessage {
52    MSG msg;
53    unsigned delay;
54};
55
56static DelayedMessage msgQueue[1024];
57static unsigned endOfQueue;
58static unsigned startOfQueue;
59
60static bool didDragEnter;
61DraggingInfo* draggingInfo = 0;
62
63static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
64{
65    return JSValueMakeBoolean(context, dragMode);
66}
67
68static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
69{
70    dragMode = JSValueToBoolean(context, value);
71    return true;
72}
73
74static JSValueRef getConstantCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
75{
76    if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYDOWN"))
77        return JSValueMakeNumber(context, WM_KEYDOWN);
78    if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYUP"))
79        return JSValueMakeNumber(context, WM_KEYUP);
80    if (JSStringIsEqualToUTF8CString(propertyName, "WM_CHAR"))
81        return JSValueMakeNumber(context, WM_CHAR);
82    if (JSStringIsEqualToUTF8CString(propertyName, "WM_DEADCHAR"))
83        return JSValueMakeNumber(context, WM_DEADCHAR);
84    if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYDOWN"))
85        return JSValueMakeNumber(context, WM_SYSKEYDOWN);
86    if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYUP"))
87        return JSValueMakeNumber(context, WM_SYSKEYUP);
88    if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSCHAR"))
89        return JSValueMakeNumber(context, WM_SYSCHAR);
90    if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSDEADCHAR"))
91        return JSValueMakeNumber(context, WM_SYSDEADCHAR);
92    ASSERT_NOT_REACHED();
93    return JSValueMakeUndefined(context);
94}
95
96static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
97{
98    if (argumentCount > 0) {
99        msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
100        ASSERT(!exception || !*exception);
101    }
102
103    return JSValueMakeUndefined(context);
104}
105
106static DWORD currentEventTime()
107{
108    return ::GetTickCount() + timeOffset;
109}
110
111static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
112{
113    MSG result = {0};
114    result.hwnd = hwnd;
115    result.message = message;
116    result.wParam = wParam;
117    result.lParam = lParam;
118    result.time = currentEventTime();
119    result.pt = lastMousePosition;
120
121    return result;
122}
123
124static LRESULT dispatchMessage(const MSG* msg)
125{
126    ASSERT(msg);
127    ::TranslateMessage(msg);
128    return ::DispatchMessage(msg);
129}
130
131static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
132{
133    COMPtr<IWebFramePrivate> framePrivate;
134    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
135        framePrivate->layout();
136
137    down = true;
138    MSG msg = makeMsg(webViewWindow, WM_RBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
139    dispatchMessage(&msg);
140    down = false;
141    msg = makeMsg(webViewWindow, WM_RBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
142    dispatchMessage(&msg);
143
144    return JSValueMakeUndefined(context);
145}
146
147static WPARAM buildModifierFlags(JSContextRef context, const JSValueRef modifiers)
148{
149    JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0);
150    if (!modifiersArray)
151        return 0;
152
153    WPARAM flags = 0;
154    int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, JSStringCreateWithUTF8CString("length"), 0), 0);
155    for (int i = 0; i < modifiersCount; ++i) {
156        JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
157        JSStringRef string = JSValueToStringCopy(context, value, 0);
158        if (JSStringIsEqualToUTF8CString(string, "ctrlKey")
159            || JSStringIsEqualToUTF8CString(string, "addSelectionKey"))
160            flags |= MK_CONTROL;
161        else if (JSStringIsEqualToUTF8CString(string, "shiftKey")
162                 || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey"))
163            flags |= MK_SHIFT;
164        // No way to specifiy altKey in a MSG.
165
166        JSStringRelease(string);
167    }
168    return flags;
169}
170
171static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
172{
173    COMPtr<IWebFramePrivate> framePrivate;
174    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
175        framePrivate->layout();
176
177    down = true;
178    int mouseType = WM_LBUTTONDOWN;
179    if (argumentCount >= 1) {
180        int mouseNumber = JSValueToNumber(context, arguments[0], exception);
181        switch (mouseNumber) {
182        case 0:
183            mouseType = WM_LBUTTONDOWN;
184            break;
185        case 1:
186            mouseType = WM_MBUTTONDOWN;
187            break;
188        case 2:
189            mouseType = WM_RBUTTONDOWN;
190            break;
191        case 3:
192            // fast/events/mouse-click-events expects the 4th button has event.button = 1, so send an WM_BUTTONDOWN
193            mouseType = WM_MBUTTONDOWN;
194            break;
195        default:
196            mouseType = WM_LBUTTONDOWN;
197            break;
198        }
199    }
200
201    WPARAM wparam = 0;
202    if (argumentCount >= 2)
203        wparam |= buildModifierFlags(context, arguments[1]);
204
205    MSG msg = makeMsg(webViewWindow, mouseType, wparam, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
206    if (!msgQueue[endOfQueue].delay)
207        dispatchMessage(&msg);
208    else {
209        // replaySavedEvents has the required logic to make leapForward delays work
210        msgQueue[endOfQueue++].msg = msg;
211        replaySavedEvents();
212    }
213
214    return JSValueMakeUndefined(context);
215}
216
217static inline POINTL pointl(const POINT& point)
218{
219    POINTL result;
220    result.x = point.x;
221    result.y = point.y;
222    return result;
223}
224
225static void doMouseUp(MSG msg, HRESULT* oleDragAndDropReturnValue = 0)
226{
227    COMPtr<IWebFramePrivate> framePrivate;
228    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
229        framePrivate->layout();
230
231    dispatchMessage(&msg);
232    down = false;
233
234    if (draggingInfo) {
235        COMPtr<IWebView> webView;
236        COMPtr<IDropTarget> webViewDropTarget;
237        if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
238            POINT screenPoint = msg.pt;
239            DWORD effect = 0;
240            ::ClientToScreen(webViewWindow, &screenPoint);
241            if (!didDragEnter) {
242                webViewDropTarget->DragEnter(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
243                didDragEnter = true;
244            }
245            HRESULT hr = draggingInfo->dropSource()->QueryContinueDrag(0, 0);
246            if (oleDragAndDropReturnValue)
247                *oleDragAndDropReturnValue = hr;
248            webViewDropTarget->DragOver(0, pointl(screenPoint), &effect);
249            if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) {
250                DWORD effect = 0;
251                webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
252                draggingInfo->setPerformedDropEffect(effect);
253            } else
254                webViewDropTarget->DragLeave();
255
256            // Reset didDragEnter so that another drag started within the same frame works properly.
257            didDragEnter = false;
258        }
259    }
260}
261
262static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
263{
264    int mouseType = WM_LBUTTONUP;
265    if (argumentCount >= 1) {
266        int mouseNumber = JSValueToNumber(context, arguments[0], exception);
267        switch (mouseNumber) {
268        case 0:
269            mouseType = WM_LBUTTONUP;
270            break;
271        case 1:
272            mouseType = WM_MBUTTONUP;
273            break;
274        case 2:
275            mouseType = WM_RBUTTONUP;
276            break;
277        case 3:
278            // fast/events/mouse-click-events expects the 4th button has event.button = 1, so send an WM_MBUTTONUP
279            mouseType = WM_MBUTTONUP;
280            break;
281        default:
282            mouseType = WM_LBUTTONUP;
283            break;
284        }
285    }
286
287    WPARAM wparam = 0;
288    if (argumentCount >= 2)
289        wparam |= buildModifierFlags(context, arguments[1]);
290
291    MSG msg = makeMsg(webViewWindow, mouseType, wparam, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
292
293    if ((dragMode && !replayingSavedEvents) || msgQueue[endOfQueue].delay) {
294        msgQueue[endOfQueue++].msg = msg;
295        replaySavedEvents();
296    } else
297        doMouseUp(msg);
298
299    return JSValueMakeUndefined(context);
300}
301
302static void doMouseMove(MSG msg)
303{
304    COMPtr<IWebFramePrivate> framePrivate;
305    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
306        framePrivate->layout();
307
308    dispatchMessage(&msg);
309
310    if (down && draggingInfo) {
311        POINT screenPoint = msg.pt;
312        ::ClientToScreen(webViewWindow, &screenPoint);
313
314        IWebView* webView;
315        COMPtr<IDropTarget> webViewDropTarget;
316        if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
317            DWORD effect = 0;
318            if (didDragEnter)
319                webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect);
320            else {
321                webViewDropTarget->DragEnter(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
322                didDragEnter = true;
323            }
324            draggingInfo->dropSource()->GiveFeedback(effect);
325        }
326    }
327}
328
329static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
330{
331    if (argumentCount < 2)
332        return JSValueMakeUndefined(context);
333
334    lastMousePosition.x = (int)JSValueToNumber(context, arguments[0], exception);
335    ASSERT(!exception || !*exception);
336    lastMousePosition.y = (int)JSValueToNumber(context, arguments[1], exception);
337    ASSERT(!exception || !*exception);
338
339    MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
340
341    if (dragMode && down && !replayingSavedEvents) {
342        msgQueue[endOfQueue++].msg = msg;
343        return JSValueMakeUndefined(context);
344    }
345
346    doMouseMove(msg);
347
348    return JSValueMakeUndefined(context);
349}
350
351void replaySavedEvents(HRESULT* oleDragAndDropReturnValue)
352{
353    replayingSavedEvents = true;
354
355    MSG msg = { 0 };
356
357    while (startOfQueue < endOfQueue && !msgQueue[startOfQueue].delay) {
358        msg = msgQueue[startOfQueue++].msg;
359        switch (msg.message) {
360            case WM_LBUTTONUP:
361            case WM_RBUTTONUP:
362            case WM_MBUTTONUP:
363                doMouseUp(msg, oleDragAndDropReturnValue);
364                break;
365            case WM_MOUSEMOVE:
366                doMouseMove(msg);
367                break;
368            case WM_LBUTTONDOWN:
369            case WM_RBUTTONDOWN:
370            case WM_MBUTTONDOWN:
371                dispatchMessage(&msg);
372                break;
373            default:
374                // Not reached
375                break;
376        }
377    }
378
379    int numQueuedMessages = endOfQueue - startOfQueue;
380    if (!numQueuedMessages) {
381        startOfQueue = 0;
382        endOfQueue = 0;
383        replayingSavedEvents = false;
384        ASSERT(!down);
385        return;
386    }
387
388    if (msgQueue[startOfQueue].delay) {
389        ::Sleep(msgQueue[startOfQueue].delay);
390        msgQueue[startOfQueue].delay = 0;
391    }
392
393    ::PostMessage(webViewWindow, WM_DRT_SEND_QUEUED_EVENT, 0, 0);
394    while (::GetMessage(&msg, webViewWindow, 0, 0)) {
395        // FIXME: Why do we get a WM_MOUSELEAVE? it breaks tests
396        if (msg.message == WM_MOUSELEAVE)
397            continue;
398        if (msg.message != WM_DRT_SEND_QUEUED_EVENT) {
399            dispatchMessage(&msg);
400            continue;
401        }
402        msg = msgQueue[startOfQueue++].msg;
403        switch (msg.message) {
404            case WM_LBUTTONUP:
405            case WM_RBUTTONUP:
406            case WM_MBUTTONUP:
407                doMouseUp(msg, oleDragAndDropReturnValue);
408                break;
409            case WM_MOUSEMOVE:
410                doMouseMove(msg);
411                break;
412            case WM_LBUTTONDOWN:
413            case WM_RBUTTONDOWN:
414            case WM_MBUTTONDOWN:
415                dispatchMessage(&msg);
416                break;
417            default:
418                // Not reached
419                break;
420        }
421        if (startOfQueue >= endOfQueue)
422            break;
423        ::Sleep(msgQueue[startOfQueue].delay);
424        msgQueue[startOfQueue].delay = 0;
425        ::PostMessage(webViewWindow, WM_DRT_SEND_QUEUED_EVENT, 0, 0);
426    }
427    startOfQueue = 0;
428    endOfQueue = 0;
429
430    replayingSavedEvents = false;
431}
432
433static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
434{
435    if (argumentCount < 1)
436        return JSValueMakeUndefined(context);
437
438    static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
439
440    COMPtr<IWebFramePrivate> framePrivate;
441    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
442        framePrivate->layout();
443
444    JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
445    ASSERT(!*exception);
446    int virtualKeyCode;
447    int charCode = 0;
448    int keyData = 1;
449    bool needsShiftKeyModifier = false;
450    if (JSStringIsEqualToUTF8CString(character, "leftArrow")) {
451        virtualKeyCode = VK_LEFT;
452        keyData += KF_EXTENDED << 16; // In this case, extended means "not keypad".
453    } else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) {
454        virtualKeyCode = VK_RIGHT;
455        keyData += KF_EXTENDED << 16;
456    } else if (JSStringIsEqualToUTF8CString(character, "upArrow")) {
457        virtualKeyCode = VK_UP;
458        keyData += KF_EXTENDED << 16;
459    } else if (JSStringIsEqualToUTF8CString(character, "downArrow")) {
460        virtualKeyCode = VK_DOWN;
461        keyData += KF_EXTENDED << 16;
462    } else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
463        virtualKeyCode = VK_PRIOR;
464    else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
465        virtualKeyCode = VK_NEXT;
466    else if (JSStringIsEqualToUTF8CString(character, "home"))
467        virtualKeyCode = VK_HOME;
468    else if (JSStringIsEqualToUTF8CString(character, "end"))
469        virtualKeyCode = VK_END;
470    else if (JSStringIsEqualToUTF8CString(character, "insert"))
471        virtualKeyCode = VK_INSERT;
472    else if (JSStringIsEqualToUTF8CString(character, "delete"))
473        virtualKeyCode = VK_DELETE;
474    else if (JSStringIsEqualToUTF8CString(character, "printScreen"))
475        virtualKeyCode = VK_SNAPSHOT;
476    else if (JSStringIsEqualToUTF8CString(character, "menu"))
477        virtualKeyCode = VK_APPS;
478    else {
479        charCode = JSStringGetCharactersPtr(character)[0];
480        virtualKeyCode = LOBYTE(VkKeyScan(charCode));
481        if (WTF::isASCIIUpper(charCode))
482            needsShiftKeyModifier = true;
483    }
484    JSStringRelease(character);
485
486    BYTE keyState[256];
487    if (argumentCount > 1 || needsShiftKeyModifier) {
488        ::GetKeyboardState(keyState);
489
490        BYTE newKeyState[256];
491        memcpy(newKeyState, keyState, sizeof(keyState));
492
493        if (needsShiftKeyModifier)
494            newKeyState[VK_SHIFT] = 0x80;
495
496        if (argumentCount > 1) {
497            JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], 0);
498            if (modifiersArray) {
499                int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0);
500                for (int i = 0; i < modifiersCount; ++i) {
501                    JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
502                    JSStringRef string = JSValueToStringCopy(context, value, 0);
503                    if (JSStringIsEqualToUTF8CString(string, "ctrlKey") || JSStringIsEqualToUTF8CString(string, "addSelectionKey"))
504                        newKeyState[VK_CONTROL] = 0x80;
505                    else if (JSStringIsEqualToUTF8CString(string, "shiftKey") || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey"))
506                        newKeyState[VK_SHIFT] = 0x80;
507                    else if (JSStringIsEqualToUTF8CString(string, "altKey"))
508                        newKeyState[VK_MENU] = 0x80;
509
510                    JSStringRelease(string);
511                }
512            }
513        }
514
515        ::SetKeyboardState(newKeyState);
516    }
517
518    MSG msg = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYDOWN : WM_KEYDOWN, virtualKeyCode, keyData);
519    if (virtualKeyCode != 255)
520        dispatchMessage(&msg);
521    else {
522        // For characters that do not exist in the active keyboard layout,
523        // ::Translate will not work, so we post an WM_CHAR event ourselves.
524        ::PostMessage(webViewWindow, WM_CHAR, charCode, 0);
525    }
526
527    // Tests expect that all messages are processed by the time keyDown() returns.
528    if (::PeekMessage(&msg, webViewWindow, WM_CHAR, WM_CHAR, PM_REMOVE) || ::PeekMessage(&msg, webViewWindow, WM_SYSCHAR, WM_SYSCHAR, PM_REMOVE))
529        ::DispatchMessage(&msg);
530
531    MSG msgUp = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYUP : WM_KEYUP, virtualKeyCode, keyData);
532    ::DispatchMessage(&msgUp);
533
534    if (argumentCount > 1 || needsShiftKeyModifier)
535        ::SetKeyboardState(keyState);
536
537    return JSValueMakeUndefined(context);
538}
539
540// eventSender.dispatchMessage(message, wParam, lParam, time = currentEventTime(), x = lastMousePosition.x, y = lastMousePosition.y)
541static JSValueRef dispatchMessageCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
542{
543    if (argumentCount < 3)
544        return JSValueMakeUndefined(context);
545
546    COMPtr<IWebFramePrivate> framePrivate;
547    if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
548        framePrivate->layout();
549
550    MSG msg = {};
551    msg.hwnd = webViewWindow;
552    msg.message = JSValueToNumber(context, arguments[0], exception);
553    ASSERT(!*exception);
554    msg.wParam = JSValueToNumber(context, arguments[1], exception);
555    ASSERT(!*exception);
556    msg.lParam = static_cast<ULONG_PTR>(JSValueToNumber(context, arguments[2], exception));
557    ASSERT(!*exception);
558    if (argumentCount >= 4) {
559        msg.time = JSValueToNumber(context, arguments[3], exception);
560        ASSERT(!*exception);
561    }
562    if (!msg.time)
563        msg.time = currentEventTime();
564    if (argumentCount >= 6) {
565        msg.pt.x = JSValueToNumber(context, arguments[4], exception);
566        ASSERT(!*exception);
567        msg.pt.y = JSValueToNumber(context, arguments[5], exception);
568        ASSERT(!*exception);
569    } else
570        msg.pt = lastMousePosition;
571
572    ::DispatchMessage(&msg);
573
574    return JSValueMakeUndefined(context);
575}
576
577static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
578{
579    COMPtr<IWebView> webView;
580    if (FAILED(frame->webView(&webView)))
581        return JSValueMakeUndefined(context);
582
583    COMPtr<IWebIBActions> webIBActions(Query, webView);
584    if (!webIBActions)
585        return JSValueMakeUndefined(context);
586
587    webIBActions->makeTextLarger(0);
588    return JSValueMakeUndefined(context);
589}
590
591static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
592{
593    COMPtr<IWebView> webView;
594    if (FAILED(frame->webView(&webView)))
595        return JSValueMakeUndefined(context);
596
597    COMPtr<IWebIBActions> webIBActions(Query, webView);
598    if (!webIBActions)
599        return JSValueMakeUndefined(context);
600
601    webIBActions->makeTextSmaller(0);
602    return JSValueMakeUndefined(context);
603}
604
605static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
606{
607    COMPtr<IWebView> webView;
608    if (FAILED(frame->webView(&webView)))
609        return JSValueMakeUndefined(context);
610
611    COMPtr<IWebIBActions> webIBActions(Query, webView);
612    if (!webIBActions)
613        return JSValueMakeUndefined(context);
614
615    webIBActions->zoomPageIn(0);
616    return JSValueMakeUndefined(context);
617}
618
619static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
620{
621    COMPtr<IWebView> webView;
622    if (FAILED(frame->webView(&webView)))
623        return JSValueMakeUndefined(context);
624
625    COMPtr<IWebIBActions> webIBActions(Query, webView);
626    if (!webIBActions)
627        return JSValueMakeUndefined(context);
628
629    webIBActions->zoomPageOut(0);
630    return JSValueMakeUndefined(context);
631}
632
633static JSStaticFunction staticFunctions[] = {
634    { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
635    { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
636    { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
637    { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
638    { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
639    { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
640    { "dispatchMessage", dispatchMessageCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
641    { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
642    { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
643    { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
644    { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
645    { 0, 0, 0 }
646};
647
648static JSStaticValue staticValues[] = {
649    { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
650    { "WM_KEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
651    { "WM_KEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
652    { "WM_CHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
653    { "WM_DEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
654    { "WM_SYSKEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
655    { "WM_SYSKEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
656    { "WM_SYSCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
657    { "WM_SYSDEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
658    { 0, 0, 0, 0 }
659};
660
661static JSClassRef getClass(JSContextRef context)
662{
663    static JSClassRef eventSenderClass = 0;
664
665    if (!eventSenderClass) {
666        JSClassDefinition classDefinition = {0};
667        classDefinition.staticFunctions = staticFunctions;
668        classDefinition.staticValues = staticValues;
669
670        eventSenderClass = JSClassCreate(&classDefinition);
671    }
672
673    return eventSenderClass;
674}
675
676JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
677{
678    if (isTopFrame) {
679        down = false;
680        dragMode = true;
681        replayingSavedEvents = false;
682        timeOffset = 0;
683        lastMousePosition.x = 0;
684        lastMousePosition.y = 0;
685
686        endOfQueue = 0;
687        startOfQueue = 0;
688
689        didDragEnter = false;
690        draggingInfo = 0;
691    }
692    return JSObjectMake(context, getClass(context), 0);
693}
694