15539cc7e2f5f755769323458ebea15b31c9f326
[WebKit-https.git] / WebKitTools / DumpRenderTree / win / EventSender.cpp
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 "DumpRenderTree.h"
30 #include "EventSender.h"
31
32 #include "DraggingInfo.h"
33
34 #include <WebCore/COMPtr.h>
35 #include <wtf/ASCIICType.h>
36 #include <wtf/Platform.h>
37 #include <JavaScriptCore/JavaScriptCore.h>
38 #include <JavaScriptCore/Assertions.h>
39 #include <WebKit/WebKit.h>
40 #include <windows.h>
41
42 #define WM_DRT_SEND_QUEUED_EVENT (WM_APP+1)
43
44 static bool down;
45 static bool dragMode = true;
46 static bool replayingSavedEvents;
47 static int timeOffset;
48 static POINT lastMousePosition;
49
50 struct DelayedMessage {
51     MSG msg;
52     unsigned delay;
53 };
54
55 static DelayedMessage msgQueue[1024];
56 static unsigned endOfQueue;
57 static unsigned startOfQueue;
58
59 static bool didDragEnter;
60 DraggingInfo* draggingInfo = 0;
61
62 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
63 {
64     return JSValueMakeBoolean(context, dragMode);
65 }
66
67 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
68 {
69     dragMode = JSValueToBoolean(context, value);
70     return true;
71 }
72
73 static JSValueRef getConstantCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
74 {
75     if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYDOWN"))
76         return JSValueMakeNumber(context, WM_KEYDOWN);
77     if (JSStringIsEqualToUTF8CString(propertyName, "WM_KEYUP"))
78         return JSValueMakeNumber(context, WM_KEYUP);
79     if (JSStringIsEqualToUTF8CString(propertyName, "WM_CHAR"))
80         return JSValueMakeNumber(context, WM_CHAR);
81     if (JSStringIsEqualToUTF8CString(propertyName, "WM_DEADCHAR"))
82         return JSValueMakeNumber(context, WM_DEADCHAR);
83     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYDOWN"))
84         return JSValueMakeNumber(context, WM_SYSKEYDOWN);
85     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSKEYUP"))
86         return JSValueMakeNumber(context, WM_SYSKEYUP);
87     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSCHAR"))
88         return JSValueMakeNumber(context, WM_SYSCHAR);
89     if (JSStringIsEqualToUTF8CString(propertyName, "WM_SYSDEADCHAR"))
90         return JSValueMakeNumber(context, WM_SYSDEADCHAR);
91     ASSERT_NOT_REACHED();
92     return JSValueMakeUndefined(context);
93 }
94
95 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
96 {
97     if (argumentCount > 0) {
98         msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
99         ASSERT(!exception || !*exception);
100     }
101
102     return JSValueMakeUndefined(context);
103 }
104
105 static DWORD currentEventTime()
106 {
107     return ::GetTickCount() + timeOffset;
108 }
109
110 static MSG makeMsg(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
111 {
112     MSG result = {0};
113     result.hwnd = hwnd;
114     result.message = message;
115     result.wParam = wParam;
116     result.lParam = lParam;
117     result.time = currentEventTime();
118     result.pt = lastMousePosition;
119
120     return result;
121 }
122
123 static LRESULT dispatchMessage(const MSG* msg)
124 {
125     ASSERT(msg);
126     ::TranslateMessage(msg);
127     return ::DispatchMessage(msg);
128 }
129
130 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
131 {
132     COMPtr<IWebFramePrivate> framePrivate;
133     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
134         framePrivate->layout();
135
136     down = true;
137     MSG msg = makeMsg(webViewWindow, WM_RBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
138     dispatchMessage(&msg);
139     down = false;
140     msg = makeMsg(webViewWindow, WM_RBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
141     dispatchMessage(&msg);
142     
143     return JSValueMakeUndefined(context);
144 }
145
146 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
147 {
148     COMPtr<IWebFramePrivate> framePrivate;
149     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
150         framePrivate->layout();
151
152     down = true;
153     MSG msg = makeMsg(webViewWindow, WM_LBUTTONDOWN, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
154     if (!msgQueue[endOfQueue].delay)
155         dispatchMessage(&msg);
156     else {
157         // replaySavedEvents has the required logic to make leapForward delays work
158         msgQueue[endOfQueue++].msg = msg;
159         replaySavedEvents();
160     }
161
162     return JSValueMakeUndefined(context);
163 }
164
165 static inline POINTL pointl(const POINT& point)
166 {
167     POINTL result;
168     result.x = point.x;
169     result.y = point.y;
170     return result;
171 }
172
173 static void doMouseUp(MSG msg)
174 {
175     COMPtr<IWebFramePrivate> framePrivate;
176     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
177         framePrivate->layout();
178
179     dispatchMessage(&msg);
180     down = false;
181
182     if (draggingInfo) {
183         COMPtr<IWebView> webView;
184         COMPtr<IDropTarget> webViewDropTarget;
185         if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
186             POINT screenPoint = msg.pt;
187             DWORD effect = 0;
188             ::ClientToScreen(webViewWindow, &screenPoint);
189             if (!didDragEnter) {
190                 webViewDropTarget->DragEnter(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
191                 didDragEnter = true;
192             }
193             HRESULT hr = draggingInfo->dropSource()->QueryContinueDrag(0, 0);
194             webViewDropTarget->DragOver(0, pointl(screenPoint), &effect);
195             if (hr == DRAGDROP_S_DROP && effect != DROPEFFECT_NONE) {
196                 DWORD effect = 0;
197                 webViewDropTarget->Drop(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
198             } else
199                 webViewDropTarget->DragLeave();
200
201             delete draggingInfo;
202             draggingInfo = 0;
203         }
204     }
205 }
206
207 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
208 {
209     MSG msg = makeMsg(webViewWindow, WM_LBUTTONUP, 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
210
211     if ((dragMode && !replayingSavedEvents) || msgQueue[endOfQueue].delay) {
212         msgQueue[endOfQueue++].msg = msg;
213         replaySavedEvents();
214     } else
215         doMouseUp(msg);
216
217     return JSValueMakeUndefined(context);
218 }
219
220 static void doMouseMove(MSG msg)
221 {
222     COMPtr<IWebFramePrivate> framePrivate;
223     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
224         framePrivate->layout();
225
226     dispatchMessage(&msg);
227
228     if (down && draggingInfo) {
229         POINT screenPoint = msg.pt;
230         ::ClientToScreen(webViewWindow, &screenPoint);
231
232         IWebView* webView;
233         COMPtr<IDropTarget> webViewDropTarget;
234         if (SUCCEEDED(frame->webView(&webView)) && SUCCEEDED(webView->QueryInterface(IID_IDropTarget, (void**)&webViewDropTarget))) {
235             DWORD effect = 0;
236             if (didDragEnter)
237                 webViewDropTarget->DragOver(MK_LBUTTON, pointl(screenPoint), &effect);
238             else {
239                 webViewDropTarget->DragEnter(draggingInfo->dataObject(), 0, pointl(screenPoint), &effect);
240                 didDragEnter = true;
241             }
242             draggingInfo->dropSource()->GiveFeedback(effect);
243         }
244     }
245 }
246
247 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
248 {
249     if (argumentCount < 2)
250         return JSValueMakeUndefined(context);
251
252     lastMousePosition.x = (int)JSValueToNumber(context, arguments[0], exception);
253     ASSERT(!exception || !*exception);
254     lastMousePosition.y = (int)JSValueToNumber(context, arguments[1], exception);
255     ASSERT(!exception || !*exception);
256
257     MSG msg = makeMsg(webViewWindow, WM_MOUSEMOVE, down ? MK_LBUTTON : 0, MAKELPARAM(lastMousePosition.x, lastMousePosition.y));
258
259     if (dragMode && down && !replayingSavedEvents) {
260         msgQueue[endOfQueue++].msg = msg;
261         return JSValueMakeUndefined(context);
262     }
263
264     doMouseMove(msg);
265
266     return JSValueMakeUndefined(context);
267 }
268
269 void replaySavedEvents()
270 {
271     replayingSavedEvents = true;
272   
273     MSG msg = { 0 };
274
275     while (startOfQueue < endOfQueue && !msgQueue[startOfQueue].delay) {
276         msg = msgQueue[startOfQueue++].msg;
277         switch (msg.message) {
278             case WM_LBUTTONUP:
279                 doMouseUp(msg);
280                 break;
281             case WM_MOUSEMOVE:
282                 doMouseMove(msg);
283                 break;
284             case WM_LBUTTONDOWN:
285                 dispatchMessage(&msg);
286                 break;
287             default:
288                 // Not reached
289                 break;
290         }
291     }
292
293     int numQueuedMessages = endOfQueue - startOfQueue;
294     if (!numQueuedMessages) {
295         startOfQueue = 0;
296         endOfQueue = 0;
297         replayingSavedEvents = false;
298         ASSERT(!down);
299         return;
300     }
301
302     if (msgQueue[startOfQueue].delay) {
303         ::Sleep(msgQueue[startOfQueue].delay);
304         msgQueue[startOfQueue].delay = 0;
305     }
306
307     ::PostMessage(webViewWindow, WM_DRT_SEND_QUEUED_EVENT, 0, 0);
308     while (::GetMessage(&msg, webViewWindow, 0, 0)) {
309         // FIXME: Why do we get a WM_MOUSELEAVE? it breaks tests
310         if (msg.message == WM_MOUSELEAVE)
311             continue;
312         if (msg.message != WM_DRT_SEND_QUEUED_EVENT) {
313             dispatchMessage(&msg);
314             continue;
315         }
316         msg = msgQueue[startOfQueue++].msg;
317         switch (msg.message) {
318             case WM_LBUTTONUP:
319                 doMouseUp(msg);
320                 break;
321             case WM_MOUSEMOVE:
322                 doMouseMove(msg);
323                 break;
324             case WM_LBUTTONDOWN:
325                 dispatchMessage(&msg);
326                 break;
327             default:
328                 // Not reached
329                 break;
330         }
331         if (startOfQueue >= endOfQueue)
332             break;
333         ::Sleep(msgQueue[startOfQueue].delay);
334         msgQueue[startOfQueue].delay = 0;
335         ::PostMessage(webViewWindow, WM_DRT_SEND_QUEUED_EVENT, 0, 0);
336     }
337     startOfQueue = 0;
338     endOfQueue = 0;
339
340     replayingSavedEvents = false;
341 }
342
343 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
344 {
345     if (argumentCount < 1)
346         return JSValueMakeUndefined(context);
347
348     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
349
350     COMPtr<IWebFramePrivate> framePrivate;
351     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
352         framePrivate->layout();
353     
354     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
355     ASSERT(!*exception);
356     int virtualKeyCode;
357     int charCode = 0;
358     int keyData = 1;
359     bool needsShiftKeyModifier = false;
360     if (JSStringIsEqualToUTF8CString(character, "leftArrow")) {
361         virtualKeyCode = VK_LEFT;
362         keyData += KF_EXTENDED << 16; // In this case, extended means "not keypad".
363     } else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) {
364         virtualKeyCode = VK_RIGHT;
365         keyData += KF_EXTENDED << 16;
366     } else if (JSStringIsEqualToUTF8CString(character, "upArrow")) {
367         virtualKeyCode = VK_UP;
368         keyData += KF_EXTENDED << 16;
369     } else if (JSStringIsEqualToUTF8CString(character, "downArrow")) {
370         virtualKeyCode = VK_DOWN;
371         keyData += KF_EXTENDED << 16;
372     } else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
373         virtualKeyCode = VK_PRIOR;
374     else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
375         virtualKeyCode = VK_NEXT;
376     else if (JSStringIsEqualToUTF8CString(character, "home"))
377         virtualKeyCode = VK_HOME;
378     else if (JSStringIsEqualToUTF8CString(character, "end"))
379         virtualKeyCode = VK_END;
380     else if (JSStringIsEqualToUTF8CString(character, "delete"))
381         virtualKeyCode = VK_BACK;
382     else {
383         charCode = JSStringGetCharactersPtr(character)[0];
384         virtualKeyCode = LOBYTE(VkKeyScan(charCode));
385         if (WTF::isASCIIUpper(charCode))
386             needsShiftKeyModifier = true;
387     }
388     JSStringRelease(character);
389
390     BYTE keyState[256];
391     if (argumentCount > 1 || needsShiftKeyModifier) {
392         ::GetKeyboardState(keyState);
393
394         BYTE newKeyState[256];
395         memcpy(newKeyState, keyState, sizeof(keyState));
396
397         if (needsShiftKeyModifier)
398             newKeyState[VK_SHIFT] = 0x80;
399
400         if (argumentCount > 1) {
401             JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
402             if (modifiersArray) {
403                 int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0);
404                 for (int i = 0; i < modifiersCount; ++i) {
405                     JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
406                     JSStringRef string = JSValueToStringCopy(context, value, 0);
407                     if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
408                         newKeyState[VK_CONTROL] = 0x80;
409                     else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
410                         newKeyState[VK_SHIFT] = 0x80;
411                     else if (JSStringIsEqualToUTF8CString(string, "altKey"))
412                         newKeyState[VK_MENU] = 0x80;
413
414                     JSStringRelease(string);
415                 }
416             }
417         }
418
419         ::SetKeyboardState(newKeyState);
420     }
421
422     MSG msg = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYDOWN : WM_KEYDOWN, virtualKeyCode, keyData);
423     if (virtualKeyCode != 255)
424         dispatchMessage(&msg);
425     else {
426         // For characters that do not exist in the active keyboard layout,
427         // ::Translate will not work, so we post an WM_CHAR event ourselves.
428         ::PostMessage(webViewWindow, WM_CHAR, charCode, 0);
429     }
430
431     // Tests expect that all messages are processed by the time keyDown() returns.
432     if (::PeekMessage(&msg, webViewWindow, WM_CHAR, WM_CHAR, PM_REMOVE) || ::PeekMessage(&msg, webViewWindow, WM_SYSCHAR, WM_SYSCHAR, PM_REMOVE))
433         ::DispatchMessage(&msg);
434
435     MSG msgUp = makeMsg(webViewWindow, (::GetKeyState(VK_MENU) & 0x8000) ? WM_SYSKEYUP : WM_KEYUP, virtualKeyCode, keyData);
436     ::DispatchMessage(&msgUp);
437
438     if (argumentCount > 1 || needsShiftKeyModifier)
439         ::SetKeyboardState(keyState);
440
441     return JSValueMakeUndefined(context);
442 }
443
444 // eventSender.dispatchMessage(message, wParam, lParam, time = currentEventTime(), x = lastMousePosition.x, y = lastMousePosition.y)
445 static JSValueRef dispatchMessageCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
446 {
447     if (argumentCount < 3)
448         return JSValueMakeUndefined(context);
449
450     COMPtr<IWebFramePrivate> framePrivate;
451     if (SUCCEEDED(frame->QueryInterface(&framePrivate)))
452         framePrivate->layout();
453     
454     MSG msg = {};
455     msg.hwnd = webViewWindow;
456     msg.message = JSValueToNumber(context, arguments[0], exception);
457     ASSERT(!*exception);
458     msg.wParam = JSValueToNumber(context, arguments[1], exception);
459     ASSERT(!*exception);
460     msg.lParam = static_cast<ULONG_PTR>(JSValueToNumber(context, arguments[2], exception));
461     ASSERT(!*exception);
462     if (argumentCount >= 4) {
463         msg.time = JSValueToNumber(context, arguments[3], exception);
464         ASSERT(!*exception);
465     }
466     if (!msg.time)
467         msg.time = currentEventTime();
468     if (argumentCount >= 6) {
469         msg.pt.x = JSValueToNumber(context, arguments[4], exception);
470         ASSERT(!*exception);
471         msg.pt.y = JSValueToNumber(context, arguments[5], exception);
472         ASSERT(!*exception);
473     } else
474         msg.pt = lastMousePosition;
475
476     ::DispatchMessage(&msg);
477
478     return JSValueMakeUndefined(context);
479 }
480
481 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
482 {
483     COMPtr<IWebView> webView;
484     if (FAILED(frame->webView(&webView)))
485         return JSValueMakeUndefined(context);
486
487     COMPtr<IWebIBActions> webIBActions(Query, webView);
488     if (!webIBActions)
489         return JSValueMakeUndefined(context);
490
491     webIBActions->makeTextLarger(0);
492     return JSValueMakeUndefined(context);
493 }
494
495 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
496 {
497     COMPtr<IWebView> webView;
498     if (FAILED(frame->webView(&webView)))
499         return JSValueMakeUndefined(context);
500
501     COMPtr<IWebIBActions> webIBActions(Query, webView);
502     if (!webIBActions)
503         return JSValueMakeUndefined(context);
504
505     webIBActions->makeTextSmaller(0);
506     return JSValueMakeUndefined(context);
507 }
508
509 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
510 {
511     COMPtr<IWebView> webView;
512     if (FAILED(frame->webView(&webView)))
513         return JSValueMakeUndefined(context);
514
515     COMPtr<IWebIBActions> webIBActions(Query, webView);
516     if (!webIBActions)
517         return JSValueMakeUndefined(context);
518
519     webIBActions->zoomPageIn(0);
520     return JSValueMakeUndefined(context);
521 }
522
523 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
524 {
525     COMPtr<IWebView> webView;
526     if (FAILED(frame->webView(&webView)))
527         return JSValueMakeUndefined(context);
528
529     COMPtr<IWebIBActions> webIBActions(Query, webView);
530     if (!webIBActions)
531         return JSValueMakeUndefined(context);
532
533     webIBActions->zoomPageOut(0);
534     return JSValueMakeUndefined(context);
535 }
536
537 static JSStaticFunction staticFunctions[] = {
538     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
539     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
540     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
541     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
542     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
543     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
544     { "dispatchMessage", dispatchMessageCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
545     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
546     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
547     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
548     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
549     { 0, 0, 0 }
550 };
551
552 static JSStaticValue staticValues[] = {
553     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
554     { "WM_KEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
555     { "WM_KEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
556     { "WM_CHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
557     { "WM_DEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
558     { "WM_SYSKEYDOWN", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
559     { "WM_SYSKEYUP", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
560     { "WM_SYSCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
561     { "WM_SYSDEADCHAR", getConstantCallback, 0, kJSPropertyAttributeReadOnly | kJSPropertyAttributeNone },
562     { 0, 0, 0, 0 }
563 };
564
565 static JSClassRef getClass(JSContextRef context)
566 {
567     static JSClassRef eventSenderClass = 0;
568
569     if (!eventSenderClass) {
570         JSClassDefinition classDefinition = {0};
571         classDefinition.staticFunctions = staticFunctions;
572         classDefinition.staticValues = staticValues;
573
574         eventSenderClass = JSClassCreate(&classDefinition);
575     }
576
577     return eventSenderClass;
578 }
579
580 JSObjectRef makeEventSender(JSContextRef context)
581 {
582     down = false;
583     dragMode = true;
584     replayingSavedEvents = false;
585     timeOffset = 0;
586     lastMousePosition.x = 0;
587     lastMousePosition.y = 0;
588
589     endOfQueue = 0;
590     startOfQueue = 0;
591
592     didDragEnter = false;
593     draggingInfo = 0;
594
595     return JSObjectMake(context, getClass(context), 0);
596 }