fa3af649bfcd03a20293ff58023a763c5781cd90
[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 extern "C" {
49     extern void webkit_web_frame_layout(WebKitWebFrame* frame);
50 }
51
52 static bool dragMode;
53 static int timeOffset = 0;
54
55 static int lastMousePositionX;
56 static int lastMousePositionY;
57 static int lastClickPositionX;
58 static int lastClickPositionY;
59 static int lastClickTimeOffset;
60 static int lastClickButton;
61 static int buttonCurrentlyDown;
62 static int clickCount;
63 GdkDragContext* currentDragSourceContext;
64
65 struct DelayedMessage {
66     GdkEvent* event;
67     gulong delay;
68 };
69
70 static DelayedMessage msgQueue[1024];
71
72 static unsigned endOfQueue;
73 static unsigned startOfQueue;
74
75 static const float zoomMultiplierRatio = 1.2f;
76
77 // Key event location code defined in DOM Level 3.
78 enum KeyLocationCode {
79     DOM_KEY_LOCATION_STANDARD      = 0x00,
80     DOM_KEY_LOCATION_LEFT          = 0x01,
81     DOM_KEY_LOCATION_RIGHT         = 0x02,
82     DOM_KEY_LOCATION_NUMPAD        = 0x03
83 };
84
85 static void sendOrQueueEvent(GdkEvent*, bool = true);
86 static void dispatchEvent(GdkEvent* event);
87 static guint getStateFlags();
88
89 #if !GTK_CHECK_VERSION(2, 17, 3)
90 static void gdk_window_get_root_coords(GdkWindow* window, gint x, gint y, gint* rootX, gint* rootY)
91 {
92     gdk_window_get_root_origin(window, rootX, rootY);
93     *rootX = *rootX + x;
94     *rootY = *rootY + y;
95 }
96 #endif
97
98 #if !GTK_CHECK_VERSION(2, 14, 0)
99 static GdkWindow* gtk_widget_get_window(GtkWidget* widget)
100 {
101     g_return_val_if_fail(GTK_IS_WIDGET(widget), 0);
102     return widget->window;
103 }
104 #endif
105
106 #if !GTK_CHECK_VERSION(2, 21, 2)
107 static GdkDragAction gdk_drag_context_get_selected_action(GdkDragContext* context)
108 {
109     g_return_val_if_fail(GDK_IS_DRAG_CONTEXT(context), static_cast<GdkDragAction>(0));
110     return context->action;
111 }
112
113 static GdkDragAction gdk_drag_context_get_actions(GdkDragContext* context)
114 {
115     g_return_val_if_fail(GDK_IS_DRAG_CONTEXT(context), GDK_ACTION_DEFAULT);
116     return context->actions;
117 }
118 #endif
119
120 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
121 {
122     return JSValueMakeBoolean(context, dragMode);
123 }
124
125 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
126 {
127     dragMode = JSValueToBoolean(context, value);
128     return true;
129 }
130
131 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
132 {
133     if (argumentCount > 0) {
134         msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
135         timeOffset += msgQueue[endOfQueue].delay;
136         ASSERT(!exception || !*exception);
137     }
138
139     return JSValueMakeUndefined(context);
140 }
141
142 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber)
143 {
144     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
145     if (!view)
146         return false;
147
148     // The logic for mapping EventSender button numbers to GDK button
149     // numbers originates from the Windows EventSender.
150     int gdkButtonNumber = 3;
151     if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2)
152         gdkButtonNumber = eventSenderButtonNumber + 1;
153
154     // fast/events/mouse-click-events expects the 4th button
155     // to be event->button = 1, so send a middle-button event.
156     else if (eventSenderButtonNumber == 3)
157         gdkButtonNumber = 2;
158
159     event->button.button = gdkButtonNumber;
160     event->button.x = lastMousePositionX;
161     event->button.y = lastMousePositionY;
162     event->button.window = gtk_widget_get_window(GTK_WIDGET(view));
163     g_object_ref(event->button.window);
164     event->button.device = gdk_device_get_core_pointer();
165     event->button.state = getStateFlags();
166     event->button.time = GDK_CURRENT_TIME;
167     event->button.axes = 0;
168
169     int xRoot, yRoot;
170     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
171     event->button.x_root = xRoot;
172     event->button.y_root = yRoot;
173
174     return true;
175 }
176
177 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
178 {
179     GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
180     if (!prepareMouseButtonEvent(pressEvent, 2))
181         return JSValueMakeUndefined(context);
182
183     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
184     sendOrQueueEvent(pressEvent);
185     releaseEvent->type = GDK_BUTTON_RELEASE;
186     sendOrQueueEvent(releaseEvent);
187
188     return JSValueMakeUndefined(context);
189 }
190
191 static void updateClickCount(int button)
192 {
193     if (lastClickPositionX != lastMousePositionX
194         || lastClickPositionY != lastMousePositionY
195         || lastClickButton != button
196         || timeOffset - lastClickTimeOffset >= 1)
197         clickCount = 1;
198     else
199         clickCount++;
200 }
201
202 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
203 {
204     int button = 0;
205     if (argumentCount == 1) {
206         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
207         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
208     }
209
210     GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
211     if (!prepareMouseButtonEvent(event, button))
212         return JSValueMakeUndefined(context);
213
214     buttonCurrentlyDown = event->button.button;
215
216     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
217     // the second button press during double-clicks. WebKit GTK+ selectively
218     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
219     // Since our events aren't ever going onto the GDK event queue, WebKit won't
220     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
221     // it here. Eventually this code should probably figure out a way to get all
222     // appropriate events onto the event queue and this work-around should be
223     // removed.
224     updateClickCount(event->button.button);
225     if (clickCount == 2)
226         event->type = GDK_2BUTTON_PRESS;
227     else if (clickCount == 3)
228         event->type = GDK_3BUTTON_PRESS;
229
230     sendOrQueueEvent(event);
231     return JSValueMakeUndefined(context);
232 }
233
234 static guint getStateFlags()
235 {
236     if (buttonCurrentlyDown == 1)
237         return GDK_BUTTON1_MASK;
238     if (buttonCurrentlyDown == 2)
239         return GDK_BUTTON2_MASK;
240     if (buttonCurrentlyDown == 3)
241         return GDK_BUTTON3_MASK;
242     return 0;
243 }
244
245 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
246 {
247     int button = 0;
248     if (argumentCount == 1) {
249         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
250         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
251     }
252
253     GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
254     if (!prepareMouseButtonEvent(event, button))
255         return JSValueMakeUndefined(context);
256
257     lastClickPositionX = lastMousePositionX;
258     lastClickPositionY = lastMousePositionY;
259     lastClickButton = buttonCurrentlyDown;
260     lastClickTimeOffset = timeOffset;
261     buttonCurrentlyDown = 0;
262
263     sendOrQueueEvent(event);
264     return JSValueMakeUndefined(context);
265 }
266
267 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
268 {
269     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
270     if (!view)
271         return JSValueMakeUndefined(context);
272
273     if (argumentCount < 2)
274         return JSValueMakeUndefined(context);
275
276     lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
277     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
278     lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
279     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
280
281     GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
282     event->motion.x = lastMousePositionX;
283     event->motion.y = lastMousePositionY;
284
285     event->motion.time = GDK_CURRENT_TIME;
286     event->motion.window = gtk_widget_get_window(GTK_WIDGET(view));
287     g_object_ref(event->motion.window);
288     event->motion.device = gdk_device_get_core_pointer();
289     event->motion.state = getStateFlags();
290     event->motion.axes = 0;
291
292     int xRoot, yRoot;
293     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
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 = gdk_event_new(GDK_SCROLL);
319     event->scroll.x = lastMousePositionX;
320     event->scroll.y = lastMousePositionY;
321     event->scroll.time = GDK_CURRENT_TIME;
322     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(view));
323     g_object_ref(event->scroll.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 ((dragMode && 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         gdk_event_free(event);
371         return;
372     }
373
374     gtk_main_do_event(event);
375
376     if (!currentDragSourceContext) {
377         gdk_event_free(event);
378         return;
379     }
380
381     if (event->type == GDK_MOTION_NOTIFY) {
382         // WebKit has called gtk_drag_start(), but because the main loop isn't
383         // running GDK internals don't know that the drag has started yet. Pump
384         // the main loop a little bit so that GDK is in the correct state.
385         while (gtk_events_pending())
386             gtk_main_iteration();
387
388         // Simulate a drag motion on the top-level GDK window.
389         GtkWidget* parentWidget = gtk_widget_get_parent(GTK_WIDGET(view));
390         GdkWindow* parentWidgetWindow = gtk_widget_get_window(parentWidget);
391         gdk_drag_motion(currentDragSourceContext, parentWidgetWindow, GDK_DRAG_PROTO_XDND,
392             event->motion.x_root, event->motion.y_root,
393             gdk_drag_context_get_selected_action(currentDragSourceContext),
394             gdk_drag_context_get_actions(currentDragSourceContext),
395             GDK_CURRENT_TIME);
396
397     } else if (currentDragSourceContext && event->type == GDK_BUTTON_RELEASE) {
398         // We've released the mouse button, we should just be able to spin the
399         // event loop here and have GTK+ send the appropriate notifications for
400         // the end of the drag.
401         while (gtk_events_pending())
402             gtk_main_iteration();
403     }
404
405     gdk_event_free(event);
406 }
407
408 void replaySavedEvents()
409 {
410     // First send all the events that are ready to be sent
411     while (startOfQueue < endOfQueue) {
412         if (msgQueue[startOfQueue].delay) {
413             g_usleep(msgQueue[startOfQueue].delay * 1000);
414             msgQueue[startOfQueue].delay = 0;
415         }
416
417         dispatchEvent(msgQueue[startOfQueue++].event);
418     }
419
420     startOfQueue = 0;
421     endOfQueue = 0;
422 }
423
424 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
425 {
426     if (argumentCount < 1)
427         return JSValueMakeUndefined(context);
428
429     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
430
431     webkit_web_frame_layout(mainFrame);
432
433     // handle modifier keys.
434     int state = 0;
435     if (argumentCount > 1) {
436         JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
437         if (modifiersArray) {
438             for (int i = 0; i < JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); ++i) {
439                 JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
440                 JSStringRef string = JSValueToStringCopy(context, value, 0);
441                 if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
442                     state |= GDK_CONTROL_MASK;
443                 else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
444                     state |= GDK_SHIFT_MASK;
445                 else if (JSStringIsEqualToUTF8CString(string, "altKey"))
446                     state |= GDK_MOD1_MASK;
447
448                 JSStringRelease(string);
449             }
450         }
451     }
452
453     // handle location argument.
454     int location = DOM_KEY_LOCATION_STANDARD;
455     if (argumentCount > 2)
456         location = (int)JSValueToNumber(context, arguments[2], exception);
457
458     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
459     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
460     int gdkKeySym = GDK_VoidSymbol;
461     if (location == DOM_KEY_LOCATION_NUMPAD) {
462         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
463             gdkKeySym = GDK_KP_Left;
464         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
465             gdkKeySym = GDK_KP_Right;
466         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
467             gdkKeySym = GDK_KP_Up;
468         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
469             gdkKeySym = GDK_KP_Down;
470         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
471             gdkKeySym = GDK_KP_Page_Up;
472         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
473             gdkKeySym = GDK_KP_Page_Down;
474         else if (JSStringIsEqualToUTF8CString(character, "home"))
475             gdkKeySym = GDK_KP_Home;
476         else if (JSStringIsEqualToUTF8CString(character, "end"))
477             gdkKeySym = GDK_KP_End;
478         else if (JSStringIsEqualToUTF8CString(character, "insert"))
479             gdkKeySym = GDK_KP_Insert;
480         else if (JSStringIsEqualToUTF8CString(character, "delete"))
481             gdkKeySym = GDK_KP_Delete;
482         else
483             // If we get some other key specified with the numpad location,
484             // crash here, so we add it sooner rather than later.
485             g_assert_not_reached();
486     } else {
487         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
488             gdkKeySym = GDK_Left;
489         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
490             gdkKeySym = GDK_Right;
491         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
492             gdkKeySym = GDK_Up;
493         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
494             gdkKeySym = GDK_Down;
495         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
496             gdkKeySym = GDK_Page_Up;
497         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
498             gdkKeySym = GDK_Page_Down;
499         else if (JSStringIsEqualToUTF8CString(character, "home"))
500             gdkKeySym = GDK_Home;
501         else if (JSStringIsEqualToUTF8CString(character, "end"))
502             gdkKeySym = GDK_End;
503         else if (JSStringIsEqualToUTF8CString(character, "insert"))
504             gdkKeySym = GDK_Insert;
505         else if (JSStringIsEqualToUTF8CString(character, "delete"))
506             gdkKeySym = GDK_Delete;
507         else if (JSStringIsEqualToUTF8CString(character, "printScreen"))
508             gdkKeySym = GDK_Print;
509         else if (JSStringIsEqualToUTF8CString(character, "F1"))
510             gdkKeySym = GDK_F1;
511         else if (JSStringIsEqualToUTF8CString(character, "F2"))
512             gdkKeySym = GDK_F2;
513         else if (JSStringIsEqualToUTF8CString(character, "F3"))
514             gdkKeySym = GDK_F3;
515         else if (JSStringIsEqualToUTF8CString(character, "F4"))
516             gdkKeySym = GDK_F4;
517         else if (JSStringIsEqualToUTF8CString(character, "F5"))
518             gdkKeySym = GDK_F5;
519         else if (JSStringIsEqualToUTF8CString(character, "F6"))
520             gdkKeySym = GDK_F6;
521         else if (JSStringIsEqualToUTF8CString(character, "F7"))
522             gdkKeySym = GDK_F7;
523         else if (JSStringIsEqualToUTF8CString(character, "F8"))
524             gdkKeySym = GDK_F8;
525         else if (JSStringIsEqualToUTF8CString(character, "F9"))
526             gdkKeySym = GDK_F9;
527         else if (JSStringIsEqualToUTF8CString(character, "F10"))
528             gdkKeySym = GDK_F10;
529         else if (JSStringIsEqualToUTF8CString(character, "F11"))
530             gdkKeySym = GDK_F11;
531         else if (JSStringIsEqualToUTF8CString(character, "F12"))
532             gdkKeySym = GDK_F12;
533         else {
534             int charCode = JSStringGetCharactersPtr(character)[0];
535             if (charCode == '\n' || charCode == '\r')
536                 gdkKeySym = GDK_Return;
537             else if (charCode == '\t')
538                 gdkKeySym = GDK_Tab;
539             else if (charCode == '\x8')
540                 gdkKeySym = GDK_BackSpace;
541             else {
542                 gdkKeySym = gdk_unicode_to_keyval(charCode);
543                 if (WTF::isASCIIUpper(charCode))
544                     state |= GDK_SHIFT_MASK;
545             }
546         }
547     }
548     JSStringRelease(character);
549
550     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
551     if (!view)
552         return JSValueMakeUndefined(context);
553
554     // create and send the event
555     GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
556     pressEvent->key.keyval = gdkKeySym;
557     pressEvent->key.state = state;
558     pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(view));
559     g_object_ref(pressEvent->key.window);
560 #ifndef GTK_API_VERSION_2
561     gdk_event_set_device(pressEvent, gdk_device_get_associated_device(gdk_display_get_core_pointer(gdk_drawable_get_display(pressEvent->key.window))));
562 #endif
563     // When synthesizing an event, an invalid hardware_keycode value
564     // can cause it to be badly processed by Gtk+.
565     GdkKeymapKey* keys;
566     gint n_keys;
567     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys, &n_keys)) {
568         pressEvent->key.hardware_keycode = keys[0].keycode;
569         g_free(keys);
570     }
571
572     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
573     dispatchEvent(pressEvent);
574     releaseEvent->key.type = GDK_KEY_RELEASE;
575     dispatchEvent(releaseEvent);
576
577     return JSValueMakeUndefined(context);
578 }
579
580 static void zoomIn(gboolean fullContentsZoom)
581 {
582     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
583     if (!view)
584         return;
585
586     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
587     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
588     webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
589 }
590
591 static void zoomOut(gboolean fullContentsZoom)
592 {
593     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
594     if (!view)
595         return;
596
597     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
598     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
599     webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
600 }
601
602 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
603 {
604     zoomIn(FALSE);
605     return JSValueMakeUndefined(context);
606 }
607
608 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
609 {
610     zoomOut(FALSE);
611     return JSValueMakeUndefined(context);
612 }
613
614 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
615 {
616     zoomIn(TRUE);
617     return JSValueMakeUndefined(context);
618 }
619
620 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
621 {
622     zoomOut(TRUE);
623     return JSValueMakeUndefined(context);
624 }
625
626 static JSStaticFunction staticFunctions[] = {
627     { "mouseWheelTo", mouseWheelToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
628     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
629     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
630     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
631     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
632     { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
633     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
634     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
635     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
636     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
637     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
638     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
639     { 0, 0, 0 }
640 };
641
642 static JSStaticValue staticValues[] = {
643     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
644     { 0, 0, 0, 0 }
645 };
646
647 static JSClassRef getClass(JSContextRef context)
648 {
649     static JSClassRef eventSenderClass = 0;
650
651     if (!eventSenderClass) {
652         JSClassDefinition classDefinition = {
653                 0, 0, 0, 0, 0, 0,
654                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
655         classDefinition.staticFunctions = staticFunctions;
656         classDefinition.staticValues = staticValues;
657
658         eventSenderClass = JSClassCreate(&classDefinition);
659     }
660
661     return eventSenderClass;
662 }
663
664 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
665 {
666     if (isTopFrame) {
667         dragMode = true;
668
669         // Fly forward in time one second when the main frame loads. This will
670         // ensure that when a test begins clicking in the same location as
671         // a previous test, those clicks won't be interpreted as continuations
672         // of the previous test's click sequences.
673         timeOffset += 1000;
674
675         lastMousePositionX = lastMousePositionY = 0;
676         lastClickPositionX = lastClickPositionY = 0;
677         lastClickTimeOffset = 0;
678         lastClickButton = 0;
679         buttonCurrentlyDown = 0;
680         clickCount = 0;
681
682         endOfQueue = 0;
683         startOfQueue = 0;
684
685         currentDragSourceContext = 0;
686     }
687
688     return JSObjectMake(context, getClass(context), 0);
689 }
690
691 void dragBeginCallback(GtkWidget*, GdkDragContext* context, gpointer)
692 {
693     currentDragSourceContext = context;
694 }
695
696 void dragEndCallback(GtkWidget*, GdkDragContext* context, gpointer)
697 {
698     currentDragSourceContext = 0;
699 }
700
701 gboolean dragFailedCallback(GtkWidget*, GdkDragContext* context, gpointer)
702 {
703     // Return TRUE here to disable the stupid GTK+ drag failed animation,
704     // which introduces asynchronous behavior into our drags.
705     return TRUE;
706 }