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