Home | History | Annotate | Download | only in win
      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 
     45 static bool down;
     46 static bool dragMode = true;
     47 static bool replayingSavedEvents;
     48 static int timeOffset;
     49 static POINT lastMousePosition;
     50 
     51 struct DelayedMessage {
     52     MSG msg;
     53     unsigned delay;
     54 };
     55 
     56 static DelayedMessage msgQueue[1024];
     57 static unsigned endOfQueue;
     58 static unsigned startOfQueue;
     59 
     60 static bool didDragEnter;
     61 DraggingInfo* draggingInfo = 0;
     62 
     63 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
     64 {
     65     return JSValueMakeBoolean(context, dragMode);
     66 }
     67 
     68 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
     69 {
     70     dragMode = JSValueToBoolean(context, value);
     71     return true;
     72 }
     73 
     74 static 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 
     96 static 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 
    106 static DWORD currentEventTime()
    107 {
    108     return ::GetTickCount() + timeOffset;
    109 }
    110 
    111 static 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 
    124 static LRESULT dispatchMessage(const MSG* msg)
    125 {
    126     ASSERT(msg);
    127     ::TranslateMessage(msg);
    128     return ::DispatchMessage(msg);
    129 }
    130 
    131 static 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 
    147 static 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 
    171 static 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 
    217 static 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 
    225 static 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 
    262 static 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 
    302 static 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 
    329 static 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 
    351 void 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 
    433 static 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)
    541 static 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 
    577 static 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 
    591 static 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 
    605 static 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 
    619 static 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 
    633 static 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 
    648 static 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 
    661 static 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 
    676 JSObjectRef 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