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