f3799fb682e2e1a9ffeeee2f70ebb2a3b5f28542
[WebKit-https.git] / WebKitTools / DumpRenderTree / gtk / EventSender.cpp
1 /*
2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
4  * Copyright (C) 2009 Holger Hans Peter Freyther
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "EventSender.h"
33
34 #include "DumpRenderTree.h"
35
36 #include <JavaScriptCore/JSObjectRef.h>
37 #include <JavaScriptCore/JSRetainPtr.h>
38 #include <JavaScriptCore/JSStringRef.h>
39 #include <webkit/webkitwebframe.h>
40 #include <webkit/webkitwebview.h>
41 #include <wtf/ASCIICType.h>
42 #include <wtf/Platform.h>
43
44 #include <gdk/gdk.h>
45 #include <gdk/gdkkeysyms.h>
46 #include <string.h>
47
48 // FIXME: Implement support for synthesizing drop events.
49
50 extern "C" {
51     extern void webkit_web_frame_layout(WebKitWebFrame* frame);
52 }
53
54 static bool dragMode;
55 static int timeOffset = 0;
56
57 static int lastMousePositionX;
58 static int lastMousePositionY;
59 static int lastClickPositionX;
60 static int lastClickPositionY;
61 static int lastClickTimeOffset;
62 static int lastClickButton;
63 static int buttonCurrentlyDown;
64 static int clickCount;
65
66 struct DelayedMessage {
67     GdkEvent event;
68     gulong delay;
69 };
70
71 static DelayedMessage msgQueue[1024];
72
73 static unsigned endOfQueue;
74 static unsigned startOfQueue;
75
76 static const float zoomMultiplierRatio = 1.2f;
77
78 // Key event location code defined in DOM Level 3.
79 enum KeyLocationCode {
80     DOM_KEY_LOCATION_STANDARD      = 0x00,
81     DOM_KEY_LOCATION_LEFT          = 0x01,
82     DOM_KEY_LOCATION_RIGHT         = 0x02,
83     DOM_KEY_LOCATION_NUMPAD        = 0x03
84 };
85
86 static void sendOrQueueEvent(GdkEvent, bool = true);
87 static void dispatchEvent(GdkEvent event);
88 static guint getStateFlags();
89
90 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
91 {
92     return JSValueMakeBoolean(context, dragMode);
93 }
94
95 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
96 {
97     dragMode = JSValueToBoolean(context, value);
98     return true;
99 }
100
101 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
102 {
103     if (argumentCount > 0) {
104         msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
105         timeOffset += msgQueue[endOfQueue].delay;
106         ASSERT(!exception || !*exception);
107     }
108
109     return JSValueMakeUndefined(context);
110 }
111
112 #if !GTK_CHECK_VERSION(2,17,3)
113 static void getRootCoords(GtkWidget* view, int* rootX, int* rootY)
114 {
115     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
116     int tmpX, tmpY;
117
118     gtk_widget_translate_coordinates(view, window, lastMousePositionX, lastMousePositionY, &tmpX, &tmpY);
119
120     gdk_window_get_origin(window->window, rootX, rootY);
121
122     *rootX += tmpX;
123     *rootY += tmpY;
124 }
125 #endif
126
127 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber)
128 {
129     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
130     if (!view)
131         return false;
132
133     // The logic for mapping EventSender button numbers to GDK button
134     // numbers originates from the Windows EventSender.
135     int gdkButtonNumber = 3;
136     if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2)
137         gdkButtonNumber = eventSenderButtonNumber + 1;
138
139     // fast/events/mouse-click-events expects the 4th button
140     // to be event.button = 1, so send a middle-button event.
141     else if (eventSenderButtonNumber == 3)
142         gdkButtonNumber = 2;
143
144     memset(event, 0, sizeof(event));
145     event->button.button = gdkButtonNumber;
146     event->button.x = lastMousePositionX;
147     event->button.y = lastMousePositionY;
148     event->button.window = GTK_WIDGET(view)->window;
149     event->button.device = gdk_device_get_core_pointer();
150     event->button.state = getStateFlags();
151
152     // Mouse up & down events dispatched via g_signal_emit_by_name must offset
153     // their time value, so that WebKit can detect where sequences of mouse
154     // clicks begin and end. This should not interfere with GDK or GTK+ event
155     // processing, because the event is only seen by the widget.
156     event->button.time = GDK_CURRENT_TIME + timeOffset;
157
158     int xRoot, yRoot;
159 #if GTK_CHECK_VERSION(2, 17, 3)
160     gdk_window_get_root_coords(GTK_WIDGET(view)->window, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
161 #else
162     getRootCoords(GTK_WIDGET(view), &xRoot, &yRoot);
163 #endif
164     event->button.x_root = xRoot;
165     event->button.y_root = yRoot;
166
167     return true;
168 }
169
170 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
171 {
172     GdkEvent event;
173     if (!prepareMouseButtonEvent(&event, 2))
174         return JSValueMakeUndefined(context);
175
176     event.type = GDK_BUTTON_PRESS;
177     sendOrQueueEvent(event);
178     event.type = GDK_BUTTON_RELEASE;
179     sendOrQueueEvent(event);
180
181     return JSValueMakeUndefined(context);
182 }
183
184 static void updateClickCount(int button)
185 {
186     if (lastClickPositionX != lastMousePositionX
187         || lastClickPositionY != lastMousePositionY
188         || lastClickButton != button
189         || timeOffset - lastClickTimeOffset >= 1)
190         clickCount = 1;
191     else
192         clickCount++;
193 }
194
195 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
196 {
197     int button = 0;
198     if (argumentCount == 1) {
199         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
200         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
201     }
202
203     GdkEvent event;
204     if (!prepareMouseButtonEvent(&event, button))
205         return JSValueMakeUndefined(context);
206
207     buttonCurrentlyDown = event.button.button;
208
209     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
210     // the second button press during double-clicks. WebKit GTK+ selectively
211     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
212     // Since our events aren't ever going onto the GDK event queue, WebKit won't
213     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
214     // it here. Eventually this code should probably figure out a way to get all
215     // appropriate events onto the event queue and this work-around should be
216     // removed.
217     updateClickCount(event.button.button);
218     if (clickCount == 2)
219         event.type = GDK_2BUTTON_PRESS;
220     else if (clickCount == 3)
221         event.type = GDK_3BUTTON_PRESS;
222     else
223         event.type = GDK_BUTTON_PRESS;
224
225     sendOrQueueEvent(event);
226     return JSValueMakeUndefined(context);
227 }
228
229 static guint getStateFlags()
230 {
231     if (buttonCurrentlyDown == 1)
232         return GDK_BUTTON1_MASK;
233     if (buttonCurrentlyDown == 2)
234         return GDK_BUTTON2_MASK;
235     if (buttonCurrentlyDown == 3)
236         return GDK_BUTTON3_MASK;
237     return 0;
238 }
239
240 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
241 {
242     int button = 0;
243     if (argumentCount == 1) {
244         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
245         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
246     }
247
248     GdkEvent event;
249     if (!prepareMouseButtonEvent(&event, button))
250         return JSValueMakeUndefined(context);
251
252     lastClickPositionX = lastMousePositionX;
253     lastClickPositionY = lastMousePositionY;
254     lastClickButton = buttonCurrentlyDown;
255     lastClickTimeOffset = timeOffset;
256     buttonCurrentlyDown = 0;
257
258     event.type = GDK_BUTTON_RELEASE;
259     sendOrQueueEvent(event);
260     return JSValueMakeUndefined(context);
261 }
262
263 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
264 {
265     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
266     if (!view)
267         return JSValueMakeUndefined(context);
268
269     if (argumentCount < 2)
270         return JSValueMakeUndefined(context);
271
272     lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
273     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
274     lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
275     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
276
277     GdkEvent event;
278     memset(&event, 0, sizeof(event));
279     event.type = GDK_MOTION_NOTIFY;
280     event.motion.x = lastMousePositionX;
281     event.motion.y = lastMousePositionY;
282
283     event.motion.time = GDK_CURRENT_TIME;
284     event.motion.window = GTK_WIDGET(view)->window;
285     event.motion.device = gdk_device_get_core_pointer();
286     event.motion.state = getStateFlags();
287
288     int xRoot, yRoot;
289 #if GTK_CHECK_VERSION(2,17,3)
290     gdk_window_get_root_coords(GTK_WIDGET(view)->window, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
291 #else
292     getRootCoords(GTK_WIDGET(view), &xRoot, &yRoot);
293 #endif
294     event.motion.x_root = xRoot;
295     event.motion.y_root = yRoot;
296
297     sendOrQueueEvent(event, false);
298     return JSValueMakeUndefined(context);
299 }
300
301 static JSValueRef mouseWheelToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
302 {
303     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
304     if (!view)
305         return JSValueMakeUndefined(context);
306
307     if (argumentCount < 2)
308         return JSValueMakeUndefined(context);
309
310     int horizontal = (int)JSValueToNumber(context, arguments[0], exception);
311     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
312     int vertical = (int)JSValueToNumber(context, arguments[1], exception);
313     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
314
315     // GTK+ doesn't support multiple direction scrolls in the same event!
316     g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
317
318     GdkEvent event;
319     event.type = GDK_SCROLL;
320     event.scroll.x = lastMousePositionX;
321     event.scroll.y = lastMousePositionY;
322     event.scroll.time = GDK_CURRENT_TIME;
323     event.scroll.window = GTK_WIDGET(view)->window;
324
325     if (horizontal < 0)
326         event.scroll.direction = GDK_SCROLL_LEFT;
327     else if (horizontal > 0)
328         event.scroll.direction = GDK_SCROLL_RIGHT;
329     else if (vertical < 0)
330         event.scroll.direction = GDK_SCROLL_UP;
331     else if (vertical > 0)
332         event.scroll.direction = GDK_SCROLL_DOWN;
333     else
334         g_assert_not_reached();
335
336     sendOrQueueEvent(event);
337     return JSValueMakeUndefined(context);
338 }
339
340 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
341 {
342     if (argumentCount < 1)
343         return JSValueMakeUndefined(context);
344
345     // FIXME: Implement this completely once WebCore has complete drag and drop support
346     return JSValueMakeUndefined(context);
347 }
348
349 static void sendOrQueueEvent(GdkEvent event, bool shouldReplaySavedEvents)
350 {
351     // Mouse move events are queued if the previous event was queued or if a
352     // delay was set up by leapForward().
353     if (buttonCurrentlyDown || endOfQueue != startOfQueue || msgQueue[endOfQueue].delay) {
354         msgQueue[endOfQueue++].event = event;
355
356         if (shouldReplaySavedEvents)
357             replaySavedEvents();
358
359         return;
360     }
361
362     dispatchEvent(event);
363 }
364
365 static void dispatchEvent(GdkEvent event)
366 {
367     webkit_web_frame_layout(mainFrame);
368     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
369     if (!view)
370         return;
371
372     gtk_main_do_event(&event);
373 }
374
375 void replaySavedEvents()
376 {
377     // FIXME: Eventually we may need to have more sophisticated logic to
378     // track drag-and-drop operations.
379
380     // First send all the events that are ready to be sent
381     while (startOfQueue < endOfQueue) {
382         if (msgQueue[startOfQueue].delay) {
383             g_usleep(msgQueue[startOfQueue].delay * 1000);
384             msgQueue[startOfQueue].delay = 0;
385         }
386
387         dispatchEvent(msgQueue[startOfQueue++].event);
388     }
389
390     startOfQueue = 0;
391     endOfQueue = 0;
392 }
393
394 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
395 {
396     if (argumentCount < 1)
397         return JSValueMakeUndefined(context);
398
399     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
400
401     webkit_web_frame_layout(mainFrame);
402
403     // handle modifier keys.
404     int state = 0;
405     if (argumentCount > 1) {
406         JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
407         if (modifiersArray) {
408             for (int i = 0; i < JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); ++i) {
409                 JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
410                 JSStringRef string = JSValueToStringCopy(context, value, 0);
411                 if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
412                     state |= GDK_CONTROL_MASK;
413                 else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
414                     state |= GDK_SHIFT_MASK;
415                 else if (JSStringIsEqualToUTF8CString(string, "altKey"))
416                     state |= GDK_MOD1_MASK;
417
418                 JSStringRelease(string);
419             }
420         }
421     }
422
423     // handle location argument.
424     int location = DOM_KEY_LOCATION_STANDARD;
425     if (argumentCount > 2)
426         location = (int)JSValueToNumber(context, arguments[2], exception);
427
428     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
429     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
430     int gdkKeySym = GDK_VoidSymbol;
431     if (location == DOM_KEY_LOCATION_NUMPAD) {
432         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
433             gdkKeySym = GDK_KP_Left;
434         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
435             gdkKeySym = GDK_KP_Right;
436         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
437             gdkKeySym = GDK_KP_Up;
438         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
439             gdkKeySym = GDK_KP_Down;
440         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
441             gdkKeySym = GDK_KP_Page_Up;
442         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
443             gdkKeySym = GDK_KP_Page_Down;
444         else if (JSStringIsEqualToUTF8CString(character, "home"))
445             gdkKeySym = GDK_KP_Home;
446         else if (JSStringIsEqualToUTF8CString(character, "end"))
447             gdkKeySym = GDK_KP_End;
448         else
449             // Assume we only get arrow/pgUp/pgDn/home/end keys with
450             // location=NUMPAD for now.
451             g_assert_not_reached();
452     } else {
453         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
454             gdkKeySym = GDK_Left;
455         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
456             gdkKeySym = GDK_Right;
457         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
458             gdkKeySym = GDK_Up;
459         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
460             gdkKeySym = GDK_Down;
461         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
462             gdkKeySym = GDK_Page_Up;
463         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
464             gdkKeySym = GDK_Page_Down;
465         else if (JSStringIsEqualToUTF8CString(character, "home"))
466             gdkKeySym = GDK_Home;
467         else if (JSStringIsEqualToUTF8CString(character, "end"))
468             gdkKeySym = GDK_End;
469         else if (JSStringIsEqualToUTF8CString(character, "delete"))
470             gdkKeySym = GDK_Delete;
471         else if (JSStringIsEqualToUTF8CString(character, "F1"))
472             gdkKeySym = GDK_F1;
473         else if (JSStringIsEqualToUTF8CString(character, "F2"))
474             gdkKeySym = GDK_F2;
475         else if (JSStringIsEqualToUTF8CString(character, "F3"))
476             gdkKeySym = GDK_F3;
477         else if (JSStringIsEqualToUTF8CString(character, "F4"))
478             gdkKeySym = GDK_F4;
479         else if (JSStringIsEqualToUTF8CString(character, "F5"))
480             gdkKeySym = GDK_F5;
481         else if (JSStringIsEqualToUTF8CString(character, "F6"))
482             gdkKeySym = GDK_F6;
483         else if (JSStringIsEqualToUTF8CString(character, "F7"))
484             gdkKeySym = GDK_F7;
485         else if (JSStringIsEqualToUTF8CString(character, "F8"))
486             gdkKeySym = GDK_F8;
487         else if (JSStringIsEqualToUTF8CString(character, "F9"))
488             gdkKeySym = GDK_F9;
489         else if (JSStringIsEqualToUTF8CString(character, "F10"))
490             gdkKeySym = GDK_F10;
491         else if (JSStringIsEqualToUTF8CString(character, "F11"))
492             gdkKeySym = GDK_F11;
493         else if (JSStringIsEqualToUTF8CString(character, "F12"))
494             gdkKeySym = GDK_F12;
495         else {
496             int charCode = JSStringGetCharactersPtr(character)[0];
497             if (charCode == '\n' || charCode == '\r')
498                 gdkKeySym = GDK_Return;
499             else if (charCode == '\t')
500                 gdkKeySym = GDK_Tab;
501             else if (charCode == '\x8')
502                 gdkKeySym = GDK_BackSpace;
503             else {
504                 gdkKeySym = gdk_unicode_to_keyval(charCode);
505                 if (WTF::isASCIIUpper(charCode))
506                     state |= GDK_SHIFT_MASK;
507             }
508         }
509     }
510     JSStringRelease(character);
511
512     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
513     if (!view)
514         return JSValueMakeUndefined(context);
515
516     // create and send the event
517     GdkEvent event;
518     memset(&event, 0, sizeof(event));
519     event.key.keyval = gdkKeySym;
520     event.key.state = state;
521     event.key.window = GTK_WIDGET(view)->window;
522
523     // When synthesizing an event, an invalid hardware_keycode value
524     // can cause it to be badly processed by Gtk+.
525     GdkKeymapKey* keys;
526     gint n_keys;
527     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys, &n_keys)) {
528         event.key.hardware_keycode = keys[0].keycode;
529         g_free(keys);
530     }
531
532     event.key.type = GDK_KEY_PRESS;
533     dispatchEvent(event);
534
535     event.key.type = GDK_KEY_RELEASE;
536     dispatchEvent(event);
537
538     return JSValueMakeUndefined(context);
539 }
540
541 static void zoomIn(gboolean fullContentsZoom)
542 {
543     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
544     if (!view)
545         return;
546
547     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
548     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
549     webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
550 }
551
552 static void zoomOut(gboolean fullContentsZoom)
553 {
554     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
555     if (!view)
556         return;
557
558     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
559     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
560     webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
561 }
562
563 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
564 {
565     zoomIn(FALSE);
566     return JSValueMakeUndefined(context);
567 }
568
569 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
570 {
571     zoomOut(FALSE);
572     return JSValueMakeUndefined(context);
573 }
574
575 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
576 {
577     zoomIn(TRUE);
578     return JSValueMakeUndefined(context);
579 }
580
581 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
582 {
583     zoomOut(TRUE);
584     return JSValueMakeUndefined(context);
585 }
586
587 static JSStaticFunction staticFunctions[] = {
588     { "mouseWheelTo", mouseWheelToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
589     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
590     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
591     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
592     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
593     { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
594     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
595     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
596     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
597     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
598     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
599     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
600     { 0, 0, 0 }
601 };
602
603 static JSStaticValue staticValues[] = {
604     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
605     { 0, 0, 0, 0 }
606 };
607
608 static JSClassRef getClass(JSContextRef context)
609 {
610     static JSClassRef eventSenderClass = 0;
611
612     if (!eventSenderClass) {
613         JSClassDefinition classDefinition = {
614                 0, 0, 0, 0, 0, 0,
615                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
616         classDefinition.staticFunctions = staticFunctions;
617         classDefinition.staticValues = staticValues;
618
619         eventSenderClass = JSClassCreate(&classDefinition);
620     }
621
622     return eventSenderClass;
623 }
624
625 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
626 {
627     if (isTopFrame) {
628         dragMode = true;
629
630         // Fly forward in time one second when the main frame loads. This will
631         // ensure that when a test begins clicking in the same location as
632         // a previous test, those clicks won't be interpreted as continuations
633         // of the previous test's click sequences.
634         timeOffset += 1000;
635
636         lastMousePositionX = lastMousePositionY = 0;
637         lastClickPositionX = lastClickPositionY = 0;
638         lastClickTimeOffset = 0;
639         lastClickButton = 0;
640         buttonCurrentlyDown = 0;
641         clickCount = 0;
642
643         endOfQueue = 0;
644         startOfQueue = 0;
645     }
646
647     return JSObjectMake(context, getClass(context), 0);
648 }