[Release] Webkit-EFL Ver. 2.0_beta_118996_0.6.24
[framework/web/webkit-efl.git] / Tools / 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  * Copyright (C) 2012 ChangSeok Oh <shivamidow@gmail.com>
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1.  Redistributions of source code must retain the above copyright
13  *     notice, this list of conditions and the following disclaimer.
14  * 2.  Redistributions in binary form must reproduce the above copyright
15  *     notice, this list of conditions and the following disclaimer in the
16  *     documentation and/or other materials provided with the distribution.
17  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
18  *     its contributors may be used to endorse or promote products derived
19  *     from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
22  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "config.h"
34 #include "EventSender.h"
35
36 #include "DumpRenderTree.h"
37 #include "WebCoreSupport/DumpRenderTreeSupportGtk.h"
38 #include "WebKitMutationObserver.h"
39 #include <GOwnPtrGtk.h>
40 #include <GRefPtrGtk.h>
41 #include <GtkVersioning.h>
42 #include <JavaScriptCore/JSObjectRef.h>
43 #include <JavaScriptCore/JSRetainPtr.h>
44 #include <JavaScriptCore/JSStringRef.h>
45 #include <cstring>
46 #include <gdk/gdk.h>
47 #include <gdk/gdkkeysyms.h>
48 #include <webkit/webkitwebframe.h>
49 #include <webkit/webkitwebview.h>
50 #include <wtf/ASCIICType.h>
51 #include <wtf/Platform.h>
52 #include <wtf/text/CString.h>
53
54 extern "C" {
55     extern GtkMenu* webkit_web_view_get_context_menu(WebKitWebView*);
56 }
57
58 static bool dragMode;
59 static int timeOffset = 0;
60
61 static int lastMousePositionX;
62 static int lastMousePositionY;
63 static int lastClickPositionX;
64 static int lastClickPositionY;
65 static int lastClickTimeOffset;
66 static int lastClickButton;
67 static unsigned buttonCurrentlyDown;
68 static int clickCount;
69 GdkDragContext* currentDragSourceContext;
70
71 struct DelayedMessage {
72     GdkEvent* event;
73     gulong delay;
74 };
75
76 static DelayedMessage msgQueue[1024];
77
78 static unsigned endOfQueue;
79 static unsigned startOfQueue;
80
81 static const float zoomMultiplierRatio = 1.2f;
82
83 // Key event location code defined in DOM Level 3.
84 enum KeyLocationCode {
85     DOM_KEY_LOCATION_STANDARD      = 0x00,
86     DOM_KEY_LOCATION_LEFT          = 0x01,
87     DOM_KEY_LOCATION_RIGHT         = 0x02,
88     DOM_KEY_LOCATION_NUMPAD        = 0x03
89 };
90
91 static void sendOrQueueEvent(GdkEvent*, bool = true);
92 static void dispatchEvent(GdkEvent* event);
93 static guint getStateFlags();
94
95 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
96 {
97     return JSValueMakeBoolean(context, dragMode);
98 }
99
100 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
101 {
102     dragMode = JSValueToBoolean(context, value);
103     return true;
104 }
105
106 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
107 {
108     if (argumentCount > 0) {
109         msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
110         timeOffset += msgQueue[endOfQueue].delay;
111         ASSERT(!exception || !*exception);
112     }
113
114     return JSValueMakeUndefined(context);
115 }
116
117 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber, guint modifiers)
118 {
119     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
120     if (!view)
121         return false;
122
123     // The logic for mapping EventSender button numbers to GDK button
124     // numbers originates from the Windows EventSender.
125     int gdkButtonNumber = 3;
126     if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2)
127         gdkButtonNumber = eventSenderButtonNumber + 1;
128
129     // fast/events/mouse-click-events expects the 4th button
130     // to be event->button = 1, so send a middle-button event.
131     else if (eventSenderButtonNumber == 3)
132         gdkButtonNumber = 2;
133
134     event->button.button = gdkButtonNumber;
135     event->button.x = lastMousePositionX;
136     event->button.y = lastMousePositionY;
137     event->button.window = gtk_widget_get_window(GTK_WIDGET(view));
138     g_object_ref(event->button.window);
139     event->button.device = getDefaultGDKPointerDevice(event->button.window);
140     event->button.state = modifiers | getStateFlags();
141     event->button.time = GDK_CURRENT_TIME;
142     event->button.axes = 0;
143
144     int xRoot, yRoot;
145     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
146     event->button.x_root = xRoot;
147     event->button.y_root = yRoot;
148
149     return true;
150 }
151
152 static JSValueRef getMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
153 {
154     GtkWidget* widget = GTK_WIDGET(JSObjectGetPrivate(object));
155     CString label;
156     if (GTK_IS_SEPARATOR_MENU_ITEM(widget))
157         label = "<separator>";
158     else
159         label = gtk_menu_item_get_label(GTK_MENU_ITEM(widget));
160
161     return JSValueMakeString(context, JSStringCreateWithUTF8CString(label.data()));
162 }
163
164 static bool setMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
165 {
166     return true;
167 }
168
169 static JSValueRef menuItemClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
170 {
171     GtkMenuItem* item = GTK_MENU_ITEM(JSObjectGetPrivate(thisObject));
172     gtk_menu_item_activate(item);
173     return JSValueMakeUndefined(context);
174 }
175
176 static JSStaticFunction staticMenuItemFunctions[] = {
177     { "click", menuItemClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
178     { 0, 0, 0 }
179 };
180
181 static JSStaticValue staticMenuItemValues[] = {
182     { "title", getMenuItemTitleCallback, setMenuItemTitleCallback, kJSPropertyAttributeNone },
183     { 0, 0, 0, 0 }
184 };
185
186 static JSClassRef getMenuItemClass()
187 {
188     static JSClassRef menuItemClass = 0;
189
190     if (!menuItemClass) {
191         JSClassDefinition classDefinition = {
192                 0, 0, 0, 0, 0, 0,
193                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
194         classDefinition.staticFunctions = staticMenuItemFunctions;
195         classDefinition.staticValues = staticMenuItemValues;
196
197         menuItemClass = JSClassCreate(&classDefinition);
198     }
199
200     return menuItemClass;
201 }
202
203
204 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
205 {
206     GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
207
208     if (!prepareMouseButtonEvent(pressEvent, 2, 0))
209         return JSObjectMakeArray(context, 0, 0, 0);
210
211     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
212     sendOrQueueEvent(pressEvent);
213
214     JSValueRef valueRef = JSObjectMakeArray(context, 0, 0, 0);
215     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
216     GtkMenu* gtkMenu = webkit_web_view_get_context_menu(view);
217     if (gtkMenu) {
218         GList* items = gtk_container_get_children(GTK_CONTAINER(gtkMenu));
219         JSValueRef arrayValues[g_list_length(items)];
220         int index = 0;
221         for (GList* item = g_list_first(items); item; item = g_list_next(item)) {
222             arrayValues[index] = JSObjectMake(context, getMenuItemClass(), item->data);
223             index++;
224         }
225         if (index)
226             valueRef = JSObjectMakeArray(context, index - 1, arrayValues, 0);
227     }
228
229     releaseEvent->type = GDK_BUTTON_RELEASE;
230     sendOrQueueEvent(releaseEvent);
231     return valueRef;
232 }
233
234 static gboolean sendClick(gpointer)
235 {
236     GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
237
238     if (!prepareMouseButtonEvent(pressEvent, 1, 0)) {
239         gdk_event_free(pressEvent);
240         return FALSE;
241     }
242
243     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
244     dispatchEvent(pressEvent);
245     releaseEvent->type = GDK_BUTTON_RELEASE;
246     dispatchEvent(releaseEvent);
247
248     return FALSE;
249 }
250
251 static JSValueRef scheduleAsynchronousClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
252 {
253     g_timeout_add(0, sendClick, 0);
254     return JSValueMakeUndefined(context);
255 }
256
257 static void updateClickCount(int button)
258 {
259     if (lastClickPositionX != lastMousePositionX
260         || lastClickPositionY != lastMousePositionY
261         || lastClickButton != button
262         || timeOffset - lastClickTimeOffset >= 1)
263         clickCount = 1;
264     else
265         clickCount++;
266 }
267
268 static guint gdkModifierFromJSValue(JSContextRef context, const JSValueRef value)
269 {
270     JSStringRef string = JSValueToStringCopy(context, value, 0);
271     guint gdkModifier = 0;
272     if (JSStringIsEqualToUTF8CString(string, "ctrlKey")
273         || JSStringIsEqualToUTF8CString(string, "addSelectionKey"))
274         gdkModifier = GDK_CONTROL_MASK;
275     else if (JSStringIsEqualToUTF8CString(string, "shiftKey")
276              || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey"))
277         gdkModifier = GDK_SHIFT_MASK;
278     else if (JSStringIsEqualToUTF8CString(string, "altKey"))
279         gdkModifier = GDK_MOD1_MASK;
280     
281     // Currently the metaKey as defined in WebCore/platform/gtk/PlatformMouseEventGtk.cpp
282     // is GDK_META_MASK. This code must be kept in sync with that file.
283     else if (JSStringIsEqualToUTF8CString(string, "metaKey"))
284         gdkModifier = GDK_META_MASK;
285     
286     JSStringRelease(string);
287     return gdkModifier;
288 }
289
290 static guint gdkModifersFromJSValue(JSContextRef context, const JSValueRef modifiers)
291 {
292     // The value may either be a string with a single modifier or an array of modifiers.
293     if (JSValueIsString(context, modifiers))
294         return gdkModifierFromJSValue(context, modifiers);
295
296     JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0);
297     if (!modifiersArray)
298         return 0;
299
300     guint gdkModifiers = 0;
301     JSRetainPtr<JSStringRef> lengthProperty(Adopt, JSStringCreateWithUTF8CString("length"));
302     int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty.get(), 0), 0);
303     for (int i = 0; i < modifiersCount; ++i)
304         gdkModifiers |= gdkModifierFromJSValue(context, JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0));
305     return gdkModifiers;
306 }
307
308 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
309 {
310     int button = 0;
311     if (argumentCount == 1) {
312         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
313         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
314     }
315     guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
316
317     GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
318     if (!prepareMouseButtonEvent(event, button, modifiers))
319         return JSValueMakeUndefined(context);
320
321     // If the same mouse button is already in the down position don't send another event as it may confuse Xvfb.
322     if (buttonCurrentlyDown == event->button.button)
323         return JSValueMakeUndefined(context);
324
325     buttonCurrentlyDown = event->button.button;
326
327     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
328     // the second button press during double-clicks. WebKit GTK+ selectively
329     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
330     // Since our events aren't ever going onto the GDK event queue, WebKit won't
331     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
332     // it here. Eventually this code should probably figure out a way to get all
333     // appropriate events onto the event queue and this work-around should be
334     // removed.
335     updateClickCount(event->button.button);
336     if (clickCount == 2)
337         event->type = GDK_2BUTTON_PRESS;
338     else if (clickCount == 3)
339         event->type = GDK_3BUTTON_PRESS;
340
341     sendOrQueueEvent(event);
342     return JSValueMakeUndefined(context);
343 }
344
345 static guint getStateFlags()
346 {
347     if (buttonCurrentlyDown == 1)
348         return GDK_BUTTON1_MASK;
349     if (buttonCurrentlyDown == 2)
350         return GDK_BUTTON2_MASK;
351     if (buttonCurrentlyDown == 3)
352         return GDK_BUTTON3_MASK;
353     return 0;
354 }
355
356 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
357 {
358     int button = 0;
359     if (argumentCount == 1) {
360         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
361         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
362     }
363     guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
364
365     GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
366     if (!prepareMouseButtonEvent(event, button, modifiers))
367         return JSValueMakeUndefined(context);
368
369     lastClickPositionX = lastMousePositionX;
370     lastClickPositionY = lastMousePositionY;
371     lastClickButton = buttonCurrentlyDown;
372     lastClickTimeOffset = timeOffset;
373     buttonCurrentlyDown = 0;
374
375     sendOrQueueEvent(event);
376     return JSValueMakeUndefined(context);
377 }
378
379 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
380 {
381     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
382     if (!view)
383         return JSValueMakeUndefined(context);
384
385     if (argumentCount < 2)
386         return JSValueMakeUndefined(context);
387
388     lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
389     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
390     lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
391     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
392
393     GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
394     event->motion.x = lastMousePositionX;
395     event->motion.y = lastMousePositionY;
396
397     event->motion.time = GDK_CURRENT_TIME;
398     event->motion.window = gtk_widget_get_window(GTK_WIDGET(view));
399     g_object_ref(event->motion.window);
400     event->button.device = getDefaultGDKPointerDevice(event->motion.window);
401
402     guint modifiers = argumentCount >= 3 ? gdkModifersFromJSValue(context, arguments[2]) : 0;
403     event->motion.state = modifiers | getStateFlags();
404     event->motion.axes = 0;
405
406     int xRoot, yRoot;
407     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
408     event->motion.x_root = xRoot;
409     event->motion.y_root = yRoot;
410
411     sendOrQueueEvent(event, false);
412     return JSValueMakeUndefined(context);
413 }
414
415 static JSValueRef mouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
416 {
417     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
418     if (!view)
419         return JSValueMakeUndefined(context);
420
421     if (argumentCount < 2)
422         return JSValueMakeUndefined(context);
423
424     int horizontal = (int)JSValueToNumber(context, arguments[0], exception);
425     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
426     int vertical = (int)JSValueToNumber(context, arguments[1], exception);
427     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
428
429     // GTK+ doesn't support multiple direction scrolls in the same event!
430     g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
431
432     GdkEvent* event = gdk_event_new(GDK_SCROLL);
433     event->scroll.x = lastMousePositionX;
434     event->scroll.y = lastMousePositionY;
435     event->scroll.time = GDK_CURRENT_TIME;
436     event->scroll.window = gtk_widget_get_window(GTK_WIDGET(view));
437     g_object_ref(event->scroll.window);
438
439     if (horizontal < 0)
440         event->scroll.direction = GDK_SCROLL_RIGHT;
441     else if (horizontal > 0)
442         event->scroll.direction = GDK_SCROLL_LEFT;
443     else if (vertical < 0)
444         event->scroll.direction = GDK_SCROLL_DOWN;
445     else if (vertical > 0)
446         event->scroll.direction = GDK_SCROLL_UP;
447     else
448         g_assert_not_reached();
449
450     sendOrQueueEvent(event);
451     return JSValueMakeUndefined(context);
452 }
453
454 static JSValueRef continuousMouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
455 {
456     // GTK doesn't support continuous scroll events.
457     return JSValueMakeUndefined(context);
458 }
459
460 static void dragWithFilesDragDataGetCallback(GtkWidget*, GdkDragContext*, GtkSelectionData *data, guint, guint, gpointer userData)
461 {
462     gtk_selection_data_set_uris(data, static_cast<gchar**>(userData));
463 }
464
465 static void dragWithFilesDragEndCallback(GtkWidget* widget, GdkDragContext*, gpointer userData)
466 {
467     g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragEndCallback), userData);
468     g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragDataGetCallback), userData);
469     g_strfreev(static_cast<gchar**>(userData));
470 }
471
472 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
473 {
474     if (argumentCount < 1)
475         return JSValueMakeUndefined(context);
476
477     JSObjectRef filesArray = JSValueToObject(context, arguments[0], exception);
478     ASSERT(!exception || !*exception);
479
480     const gchar* mainFrameURI = webkit_web_frame_get_uri(mainFrame);
481     GRefPtr<GFile> testFile(adoptGRef(g_file_new_for_uri(mainFrameURI)));
482     GRefPtr<GFile> parentDirectory(g_file_get_parent(testFile.get()));
483     if (!parentDirectory)
484         return JSValueMakeUndefined(context);
485
486     // If this is an HTTP test, we still need to pass a local file path
487     // to WebCore. Even though the file doesn't exist, this should be fine
488     // for most tests.
489     GOwnPtr<gchar> scheme(g_file_get_uri_scheme(parentDirectory.get()));
490     if (g_str_equal(scheme.get(), "http") || g_str_equal(scheme.get(), "https")) {
491         GOwnPtr<gchar> currentDirectory(g_get_current_dir());
492         parentDirectory = g_file_new_for_path(currentDirectory.get());
493     }
494
495     JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
496     int filesArrayLength = JSValueToNumber(context, JSObjectGetProperty(context, filesArray, lengthProperty, 0), 0);
497     JSStringRelease(lengthProperty);
498
499     gchar** draggedFilesURIList = g_new0(gchar*, filesArrayLength + 1);
500     for (int i = 0; i < filesArrayLength; ++i) {
501         JSStringRef filenameString = JSValueToStringCopy(context,
502                                                          JSObjectGetPropertyAtIndex(context, filesArray, i, 0), 0);
503         size_t bufferSize = JSStringGetMaximumUTF8CStringSize(filenameString);
504         GOwnPtr<gchar> filenameBuffer(static_cast<gchar*>(g_malloc(bufferSize)));
505         JSStringGetUTF8CString(filenameString, filenameBuffer.get(), bufferSize);
506         JSStringRelease(filenameString);
507
508         GRefPtr<GFile> dragFile(g_file_get_child(parentDirectory.get(), filenameBuffer.get()));
509         draggedFilesURIList[i] = g_file_get_uri(dragFile.get());
510     }
511
512     GtkWidget* view = GTK_WIDGET(webkit_web_frame_get_web_view(mainFrame));
513     g_object_connect(G_OBJECT(view),
514         "signal::drag-end", dragWithFilesDragEndCallback, draggedFilesURIList,
515         "signal::drag-data-get", dragWithFilesDragDataGetCallback, draggedFilesURIList,
516         NULL);
517
518     GdkEvent event;
519     GdkWindow* viewGDKWindow = gtk_widget_get_window(view);
520     memset(&event, 0, sizeof(event));
521     event.type = GDK_MOTION_NOTIFY;
522     event.motion.x = lastMousePositionX;
523     event.motion.y = lastMousePositionY;
524     event.motion.time = GDK_CURRENT_TIME;
525     event.motion.window = viewGDKWindow;
526     event.motion.device = getDefaultGDKPointerDevice(viewGDKWindow);
527     event.motion.state = GDK_BUTTON1_MASK;
528
529     int xRoot, yRoot;
530     gdk_window_get_root_coords(viewGDKWindow, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
531     event.motion.x_root = xRoot;
532     event.motion.y_root = yRoot;
533
534     GtkTargetList* targetList = gtk_target_list_new(0, 0);
535     gtk_target_list_add_uri_targets(targetList, 0);
536     gtk_drag_begin(view, targetList, GDK_ACTION_COPY, 1, &event);
537     gtk_target_list_unref(targetList);
538
539     return JSValueMakeUndefined(context);
540 }
541
542 static void sendOrQueueEvent(GdkEvent* event, bool shouldReplaySavedEvents)
543 {
544     // Mouse move events are queued if the previous event was queued or if a
545     // delay was set up by leapForward().
546     if ((dragMode && buttonCurrentlyDown) || endOfQueue != startOfQueue || msgQueue[endOfQueue].delay) {
547         msgQueue[endOfQueue++].event = event;
548
549         if (shouldReplaySavedEvents)
550             replaySavedEvents();
551
552         return;
553     }
554
555     dispatchEvent(event);
556 }
557
558 static void dispatchEvent(GdkEvent* event)
559 {
560     DumpRenderTreeSupportGtk::layoutFrame(mainFrame);
561     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
562     if (!view) {
563         gdk_event_free(event);
564         return;
565     }
566
567     // The widget focus may have been lost in the course of the test,
568     // so force another explicit focus grab here.
569     gtk_widget_grab_focus(GTK_WIDGET(view));
570     gtk_main_do_event(event);
571
572     if (!currentDragSourceContext) {
573         gdk_event_free(event);
574         return;
575     }
576
577     if (event->type == GDK_MOTION_NOTIFY) {
578         // WebKit has called gtk_drag_start(), but because the main loop isn't
579         // running GDK internals don't know that the drag has started yet. Pump
580         // the main loop a little bit so that GDK is in the correct state.
581         while (gtk_events_pending())
582             gtk_main_iteration();
583
584         // Simulate a drag motion on the top-level GDK window.
585         GtkWidget* parentWidget = gtk_widget_get_parent(GTK_WIDGET(view));
586         GdkWindow* parentWidgetWindow = gtk_widget_get_window(parentWidget);
587         gdk_drag_motion(currentDragSourceContext, parentWidgetWindow, GDK_DRAG_PROTO_XDND,
588             event->motion.x_root, event->motion.y_root,
589             gdk_drag_context_get_selected_action(currentDragSourceContext),
590             gdk_drag_context_get_actions(currentDragSourceContext),
591             GDK_CURRENT_TIME);
592
593     } else if (currentDragSourceContext && event->type == GDK_BUTTON_RELEASE) {
594         // We've released the mouse button, we should just be able to spin the
595         // event loop here and have GTK+ send the appropriate notifications for
596         // the end of the drag.
597         while (gtk_events_pending())
598             gtk_main_iteration();
599     }
600
601     gdk_event_free(event);
602 }
603
604 void replaySavedEvents()
605 {
606     // First send all the events that are ready to be sent
607     while (startOfQueue < endOfQueue) {
608         if (msgQueue[startOfQueue].delay) {
609             g_usleep(msgQueue[startOfQueue].delay * 1000);
610             msgQueue[startOfQueue].delay = 0;
611         }
612
613         dispatchEvent(msgQueue[startOfQueue++].event);
614     }
615
616     startOfQueue = 0;
617     endOfQueue = 0;
618 }
619
620 static GdkEvent* createKeyPressEvent(JSContextRef context, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
621 {
622     g_return_val_if_fail(argumentCount >= 1, 0);
623     guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
624
625     // handle location argument.
626     int location = DOM_KEY_LOCATION_STANDARD;
627     if (argumentCount > 2)
628         location = (int)JSValueToNumber(context, arguments[2], exception);
629
630     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
631     g_return_val_if_fail((!exception || !*exception), 0);
632
633     int gdkKeySym = GDK_VoidSymbol;
634     if (location == DOM_KEY_LOCATION_NUMPAD) {
635         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
636             gdkKeySym = GDK_KP_Left;
637         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
638             gdkKeySym = GDK_KP_Right;
639         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
640             gdkKeySym = GDK_KP_Up;
641         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
642             gdkKeySym = GDK_KP_Down;
643         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
644             gdkKeySym = GDK_KP_Page_Up;
645         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
646             gdkKeySym = GDK_KP_Page_Down;
647         else if (JSStringIsEqualToUTF8CString(character, "home"))
648             gdkKeySym = GDK_KP_Home;
649         else if (JSStringIsEqualToUTF8CString(character, "end"))
650             gdkKeySym = GDK_KP_End;
651         else if (JSStringIsEqualToUTF8CString(character, "insert"))
652             gdkKeySym = GDK_KP_Insert;
653         else if (JSStringIsEqualToUTF8CString(character, "delete"))
654             gdkKeySym = GDK_KP_Delete;
655         else
656             // If we get some other key specified with the numpad location,
657             // crash here, so we add it sooner rather than later.
658             g_assert_not_reached();
659     } else {
660         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
661             gdkKeySym = GDK_Left;
662         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
663             gdkKeySym = GDK_Right;
664         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
665             gdkKeySym = GDK_Up;
666         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
667             gdkKeySym = GDK_Down;
668         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
669             gdkKeySym = GDK_Page_Up;
670         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
671             gdkKeySym = GDK_Page_Down;
672         else if (JSStringIsEqualToUTF8CString(character, "home"))
673             gdkKeySym = GDK_Home;
674         else if (JSStringIsEqualToUTF8CString(character, "end"))
675             gdkKeySym = GDK_End;
676         else if (JSStringIsEqualToUTF8CString(character, "insert"))
677             gdkKeySym = GDK_Insert;
678         else if (JSStringIsEqualToUTF8CString(character, "delete"))
679             gdkKeySym = GDK_Delete;
680         else if (JSStringIsEqualToUTF8CString(character, "printScreen"))
681             gdkKeySym = GDK_Print;
682         else if (JSStringIsEqualToUTF8CString(character, "menu"))
683             gdkKeySym = GDK_Menu;
684         else if (JSStringIsEqualToUTF8CString(character, "F1"))
685             gdkKeySym = GDK_F1;
686         else if (JSStringIsEqualToUTF8CString(character, "F2"))
687             gdkKeySym = GDK_F2;
688         else if (JSStringIsEqualToUTF8CString(character, "F3"))
689             gdkKeySym = GDK_F3;
690         else if (JSStringIsEqualToUTF8CString(character, "F4"))
691             gdkKeySym = GDK_F4;
692         else if (JSStringIsEqualToUTF8CString(character, "F5"))
693             gdkKeySym = GDK_F5;
694         else if (JSStringIsEqualToUTF8CString(character, "F6"))
695             gdkKeySym = GDK_F6;
696         else if (JSStringIsEqualToUTF8CString(character, "F7"))
697             gdkKeySym = GDK_F7;
698         else if (JSStringIsEqualToUTF8CString(character, "F8"))
699             gdkKeySym = GDK_F8;
700         else if (JSStringIsEqualToUTF8CString(character, "F9"))
701             gdkKeySym = GDK_F9;
702         else if (JSStringIsEqualToUTF8CString(character, "F10"))
703             gdkKeySym = GDK_F10;
704         else if (JSStringIsEqualToUTF8CString(character, "F11"))
705             gdkKeySym = GDK_F11;
706         else if (JSStringIsEqualToUTF8CString(character, "F12"))
707             gdkKeySym = GDK_F12;
708         else if (JSStringIsEqualToUTF8CString(character, "leftAlt"))
709             gdkKeySym = GDK_Alt_L;
710         else if (JSStringIsEqualToUTF8CString(character, "leftControl"))
711             gdkKeySym = GDK_Control_L;
712         else if (JSStringIsEqualToUTF8CString(character, "leftShift"))
713             gdkKeySym = GDK_Shift_L;
714         else if (JSStringIsEqualToUTF8CString(character, "rightAlt"))
715             gdkKeySym = GDK_Alt_R;
716         else if (JSStringIsEqualToUTF8CString(character, "rightControl"))
717             gdkKeySym = GDK_Control_R;
718         else if (JSStringIsEqualToUTF8CString(character, "rightShift"))
719             gdkKeySym = GDK_Shift_R;
720         else {
721             int charCode = JSStringGetCharactersPtr(character)[0];
722             if (charCode == '\n' || charCode == '\r')
723                 gdkKeySym = GDK_Return;
724             else if (charCode == '\t')
725                 gdkKeySym = GDK_Tab;
726             else if (charCode == '\x8')
727                 gdkKeySym = GDK_BackSpace;
728             else {
729                 gdkKeySym = gdk_unicode_to_keyval(charCode);
730                 if (WTF::isASCIIUpper(charCode))
731                     modifiers |= GDK_SHIFT_MASK;
732             }
733         }
734     }
735     JSStringRelease(character);
736
737     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
738     g_return_val_if_fail(view, 0);
739
740     GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
741     pressEvent->key.keyval = gdkKeySym;
742     pressEvent->key.state = modifiers;
743     pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(view));
744     g_object_ref(pressEvent->key.window);
745 #ifndef GTK_API_VERSION_2
746     gdk_event_set_device(pressEvent, getDefaultGDKPointerDevice(pressEvent->key.window));
747 #endif
748
749     // When synthesizing an event, an invalid hardware_keycode value
750     // can cause it to be badly processed by Gtk+.
751     GOwnPtr<GdkKeymapKey> keys;
752     gint nKeys;
753     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys.outPtr(), &nKeys))
754         pressEvent->key.hardware_keycode = keys.get()[0].keycode;
755
756     return pressEvent;
757 }
758
759 static void sendKeyDown(GdkEvent* pressEvent)
760 {
761     g_return_if_fail(pressEvent);
762     GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
763     releaseEvent->type = GDK_KEY_RELEASE;
764
765     dispatchEvent(pressEvent);
766     dispatchEvent(releaseEvent);
767
768     DumpRenderTreeSupportGtk::deliverAllMutationsIfNecessary();
769 }
770
771 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
772 {
773     GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
774     sendKeyDown(pressEvent);
775
776     return JSValueMakeUndefined(context);
777 }
778
779 static void zoomIn(gboolean fullContentsZoom)
780 {
781     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
782     if (!view)
783         return;
784
785     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
786     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
787     webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
788 }
789
790 static void zoomOut(gboolean fullContentsZoom)
791 {
792     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
793     if (!view)
794         return;
795
796     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
797     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
798     webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
799 }
800
801 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
802 {
803     zoomIn(FALSE);
804     return JSValueMakeUndefined(context);
805 }
806
807 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
808 {
809     zoomOut(FALSE);
810     return JSValueMakeUndefined(context);
811 }
812
813 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
814 {
815     zoomIn(TRUE);
816     return JSValueMakeUndefined(context);
817 }
818
819 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
820 {
821     zoomOut(TRUE);
822     return JSValueMakeUndefined(context);
823 }
824
825 static JSValueRef scalePageByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
826 {
827     if (argumentCount < 3)
828         return JSValueMakeUndefined(context);
829
830     float scaleFactor = JSValueToNumber(context, arguments[0], exception);
831     float x = JSValueToNumber(context, arguments[1], exception);
832     float y = JSValueToNumber(context, arguments[2], exception);
833
834     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
835     if (!view)
836         return JSValueMakeUndefined(context);
837
838     DumpRenderTreeSupportGtk::scalePageBy(view, scaleFactor, x, y);
839
840     return JSValueMakeUndefined(context);
841 }
842
843 static gboolean sendAsynchronousKeyDown(gpointer userData)
844 {
845     sendKeyDown(static_cast<GdkEvent*>(userData));
846     return FALSE;
847 }
848
849 static JSValueRef scheduleAsynchronousKeyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
850 {
851     GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
852     if (pressEvent)
853         g_timeout_add(0, sendAsynchronousKeyDown, static_cast<gpointer>(pressEvent));
854
855     return JSValueMakeUndefined(context);
856 }
857
858 static JSStaticFunction staticFunctions[] = {
859     { "mouseScrollBy", mouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
860     { "continuousMouseScrollBy", continuousMouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
861     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
862     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
863     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
864     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
865     { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
866     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
867     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
868     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
869     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
870     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
871     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
872     { "scheduleAsynchronousClick", scheduleAsynchronousClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
873     { "scalePageBy", scalePageByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
874     { "scheduleAsynchronousKeyDown", scheduleAsynchronousKeyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
875
876     { 0, 0, 0 }
877 };
878
879 static JSStaticValue staticValues[] = {
880     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
881     { 0, 0, 0, 0 }
882 };
883
884 static JSClassRef getClass(JSContextRef context)
885 {
886     static JSClassRef eventSenderClass = 0;
887
888     if (!eventSenderClass) {
889         JSClassDefinition classDefinition = {
890                 0, 0, 0, 0, 0, 0,
891                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
892         classDefinition.staticFunctions = staticFunctions;
893         classDefinition.staticValues = staticValues;
894
895         eventSenderClass = JSClassCreate(&classDefinition);
896     }
897
898     return eventSenderClass;
899 }
900
901 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
902 {
903     if (isTopFrame) {
904         dragMode = true;
905
906         // Fly forward in time one second when the main frame loads. This will
907         // ensure that when a test begins clicking in the same location as
908         // a previous test, those clicks won't be interpreted as continuations
909         // of the previous test's click sequences.
910         timeOffset += 1000;
911
912         lastMousePositionX = lastMousePositionY = 0;
913         lastClickPositionX = lastClickPositionY = 0;
914         lastClickTimeOffset = 0;
915         lastClickButton = 0;
916         buttonCurrentlyDown = 0;
917         clickCount = 0;
918
919         endOfQueue = 0;
920         startOfQueue = 0;
921
922         currentDragSourceContext = 0;
923     }
924
925     return JSObjectMake(context, getClass(context), 0);
926 }
927
928 void dragBeginCallback(GtkWidget*, GdkDragContext* context, gpointer)
929 {
930     currentDragSourceContext = context;
931 }
932
933 void dragEndCallback(GtkWidget*, GdkDragContext* context, gpointer)
934 {
935     currentDragSourceContext = 0;
936 }
937
938 gboolean dragFailedCallback(GtkWidget*, GdkDragContext* context, gpointer)
939 {
940     // Return TRUE here to disable the stupid GTK+ drag failed animation,
941     // which introduces asynchronous behavior into our drags.
942     return TRUE;
943 }