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