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