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>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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.
34 #include "EventSender.h"
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>
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>
54 extern GtkMenu* webkit_web_view_get_context_menu(WebKitWebView*);
58 static int timeOffset = 0;
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;
70 struct DelayedMessage {
75 static DelayedMessage msgQueue[1024];
77 static unsigned endOfQueue;
78 static unsigned startOfQueue;
80 static const float zoomMultiplierRatio = 1.2f;
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
90 static void sendOrQueueEvent(GdkEvent*, bool = true);
91 static void dispatchEvent(GdkEvent* event);
92 static guint getStateFlags();
94 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
96 return JSValueMakeBoolean(context, dragMode);
99 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
101 dragMode = JSValueToBoolean(context, value);
105 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
107 if (argumentCount > 0) {
108 msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
109 timeOffset += msgQueue[endOfQueue].delay;
110 ASSERT(!exception || !*exception);
113 return JSValueMakeUndefined(context);
116 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber, guint modifiers)
118 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
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;
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)
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;
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;
151 static JSValueRef getMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
153 GtkWidget* widget = GTK_WIDGET(JSObjectGetPrivate(object));
155 if (GTK_IS_SEPARATOR_MENU_ITEM(widget))
156 label = "<separator>";
158 label = gtk_menu_item_get_label(GTK_MENU_ITEM(widget));
160 return JSValueMakeString(context, JSStringCreateWithUTF8CString(label.data()));
163 static bool setMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
168 static JSValueRef menuItemClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
170 GtkMenuItem* item = GTK_MENU_ITEM(JSObjectGetPrivate(thisObject));
171 gtk_menu_item_activate(item);
172 return JSValueMakeUndefined(context);
175 static JSStaticFunction staticMenuItemFunctions[] = {
176 { "click", menuItemClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
180 static JSStaticValue staticMenuItemValues[] = {
181 { "title", getMenuItemTitleCallback, setMenuItemTitleCallback, kJSPropertyAttributeNone },
185 static JSClassRef getMenuItemClass()
187 static JSClassRef menuItemClass = 0;
189 if (!menuItemClass) {
190 JSClassDefinition classDefinition = {
192 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
193 classDefinition.staticFunctions = staticMenuItemFunctions;
194 classDefinition.staticValues = staticMenuItemValues;
196 menuItemClass = JSClassCreate(&classDefinition);
199 return menuItemClass;
203 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
205 GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
207 if (!prepareMouseButtonEvent(pressEvent, 2, 0)) {
208 gdk_event_free(pressEvent);
209 return JSObjectMakeArray(context, 0, 0, 0);
212 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
213 sendOrQueueEvent(pressEvent);
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);
219 GList* items = gtk_container_get_children(GTK_CONTAINER(gtkMenu));
220 JSValueRef arrayValues[g_list_length(items)];
222 for (GList* item = g_list_first(items); item; item = g_list_next(item)) {
223 arrayValues[index] = JSObjectMake(context, getMenuItemClass(), item->data);
227 valueRef = JSObjectMakeArray(context, index - 1, arrayValues, 0);
230 releaseEvent->type = GDK_BUTTON_RELEASE;
231 sendOrQueueEvent(releaseEvent);
235 static gboolean sendClick(gpointer)
237 GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
239 if (!prepareMouseButtonEvent(pressEvent, 1, 0)) {
240 gdk_event_free(pressEvent);
244 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
245 dispatchEvent(pressEvent);
246 releaseEvent->type = GDK_BUTTON_RELEASE;
247 dispatchEvent(releaseEvent);
252 static JSValueRef scheduleAsynchronousClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
254 g_timeout_add(0, sendClick, 0);
255 return JSValueMakeUndefined(context);
258 static void updateClickCount(int button)
260 if (lastClickPositionX != lastMousePositionX
261 || lastClickPositionY != lastMousePositionY
262 || lastClickButton != button
263 || timeOffset - lastClickTimeOffset >= 1)
269 static guint gdkModifierFromJSValue(JSContextRef context, const JSValueRef value)
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;
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;
287 JSStringRelease(string);
291 static guint gdkModifersFromJSValue(JSContextRef context, const JSValueRef modifiers)
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);
297 JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0);
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));
309 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
312 if (argumentCount == 1) {
313 button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
314 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
316 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
318 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
319 if (!prepareMouseButtonEvent(event, button, modifiers)) {
320 gdk_event_free(event);
321 return JSValueMakeUndefined(context);
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);
330 buttonCurrentlyDown = event->button.button;
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
340 updateClickCount(event->button.button);
342 event->type = GDK_2BUTTON_PRESS;
343 else if (clickCount == 3)
344 event->type = GDK_3BUTTON_PRESS;
346 sendOrQueueEvent(event);
347 return JSValueMakeUndefined(context);
350 static guint getStateFlags()
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;
361 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
364 if (argumentCount == 1) {
365 button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
366 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
368 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
370 GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
371 if (!prepareMouseButtonEvent(event, button, modifiers)) {
372 gdk_event_free(event);
373 return JSValueMakeUndefined(context);
376 lastClickPositionX = lastMousePositionX;
377 lastClickPositionY = lastMousePositionY;
378 lastClickButton = buttonCurrentlyDown;
379 lastClickTimeOffset = timeOffset;
380 buttonCurrentlyDown = 0;
382 sendOrQueueEvent(event);
383 return JSValueMakeUndefined(context);
386 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
388 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
390 return JSValueMakeUndefined(context);
392 if (argumentCount < 2)
393 return JSValueMakeUndefined(context);
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));
400 GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
401 event->motion.x = lastMousePositionX;
402 event->motion.y = lastMousePositionY;
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);
409 guint modifiers = argumentCount >= 3 ? gdkModifersFromJSValue(context, arguments[2]) : 0;
410 event->motion.state = modifiers | getStateFlags();
411 event->motion.axes = 0;
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;
418 sendOrQueueEvent(event, false);
419 return JSValueMakeUndefined(context);
422 static JSValueRef mouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
424 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
426 return JSValueMakeUndefined(context);
428 if (argumentCount < 2)
429 return JSValueMakeUndefined(context);
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));
436 // GTK+ doesn't support multiple direction scrolls in the same event!
437 g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
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);
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;
455 g_assert_not_reached();
457 sendOrQueueEvent(event);
458 return JSValueMakeUndefined(context);
461 static JSValueRef continuousMouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
463 // GTK doesn't support continuous scroll events.
464 return JSValueMakeUndefined(context);
467 static void dragWithFilesDragDataGetCallback(GtkWidget*, GdkDragContext*, GtkSelectionData *data, guint, guint, gpointer userData)
469 gtk_selection_data_set_uris(data, static_cast<gchar**>(userData));
472 static void dragWithFilesDragEndCallback(GtkWidget* widget, GdkDragContext*, gpointer userData)
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));
479 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
481 if (argumentCount < 1)
482 return JSValueMakeUndefined(context);
484 JSObjectRef filesArray = JSValueToObject(context, arguments[0], exception);
485 ASSERT(!exception || !*exception);
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);
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
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());
502 JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
503 int filesArrayLength = JSValueToNumber(context, JSObjectGetProperty(context, filesArray, lengthProperty, 0), 0);
504 JSStringRelease(lengthProperty);
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);
515 GRefPtr<GFile> dragFile(g_file_get_child(parentDirectory.get(), filenameBuffer.get()));
516 draggedFilesURIList[i] = g_file_get_uri(dragFile.get());
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,
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;
537 gdk_window_get_root_coords(viewGDKWindow, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
538 event.motion.x_root = xRoot;
539 event.motion.y_root = yRoot;
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);
546 return JSValueMakeUndefined(context);
549 static void sendOrQueueEvent(GdkEvent* event, bool shouldReplaySavedEvents)
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;
556 if (shouldReplaySavedEvents)
562 dispatchEvent(event);
565 static void dispatchEvent(GdkEvent* event)
567 DumpRenderTreeSupportGtk::layoutFrame(mainFrame);
568 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
570 gdk_event_free(event);
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);
579 if (!currentDragSourceContext) {
580 gdk_event_free(event);
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();
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),
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();
608 gdk_event_free(event);
611 void replaySavedEvents()
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;
620 dispatchEvent(msgQueue[startOfQueue++].event);
627 static GdkEvent* createKeyPressEvent(JSContextRef context, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
629 g_return_val_if_fail(argumentCount >= 1, 0);
630 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
632 // handle location argument.
633 int location = DOM_KEY_LOCATION_STANDARD;
634 if (argumentCount > 2)
635 location = (int)JSValueToNumber(context, arguments[2], exception);
637 JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
638 g_return_val_if_fail((!exception || !*exception), 0);
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;
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();
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"))
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"))
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"))
693 else if (JSStringIsEqualToUTF8CString(character, "F2"))
695 else if (JSStringIsEqualToUTF8CString(character, "F3"))
697 else if (JSStringIsEqualToUTF8CString(character, "F4"))
699 else if (JSStringIsEqualToUTF8CString(character, "F5"))
701 else if (JSStringIsEqualToUTF8CString(character, "F6"))
703 else if (JSStringIsEqualToUTF8CString(character, "F7"))
705 else if (JSStringIsEqualToUTF8CString(character, "F8"))
707 else if (JSStringIsEqualToUTF8CString(character, "F9"))
709 else if (JSStringIsEqualToUTF8CString(character, "F10"))
711 else if (JSStringIsEqualToUTF8CString(character, "F11"))
713 else if (JSStringIsEqualToUTF8CString(character, "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;
728 int charCode = JSStringGetCharactersPtr(character)[0];
729 if (charCode == '\n' || charCode == '\r')
730 gdkKeySym = GDK_Return;
731 else if (charCode == '\t')
733 else if (charCode == '\x8')
734 gdkKeySym = GDK_BackSpace;
736 gdkKeySym = gdk_unicode_to_keyval(charCode);
737 if (WTF::isASCIIUpper(charCode))
738 modifiers |= GDK_SHIFT_MASK;
742 JSStringRelease(character);
744 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
745 g_return_val_if_fail(view, 0);
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));
756 // When synthesizing an event, an invalid hardware_keycode value
757 // can cause it to be badly processed by Gtk+.
758 GOwnPtr<GdkKeymapKey> keys;
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;
766 static void sendKeyDown(GdkEvent* pressEvent)
768 g_return_if_fail(pressEvent);
769 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
770 releaseEvent->type = GDK_KEY_RELEASE;
772 dispatchEvent(pressEvent);
773 dispatchEvent(releaseEvent);
775 DumpRenderTreeSupportGtk::deliverAllMutationsIfNecessary();
778 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
780 GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
781 sendKeyDown(pressEvent);
783 return JSValueMakeUndefined(context);
786 static void zoomIn(gboolean fullContentsZoom)
788 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
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);
797 static void zoomOut(gboolean fullContentsZoom)
799 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
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);
808 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
811 return JSValueMakeUndefined(context);
814 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
817 return JSValueMakeUndefined(context);
820 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
823 return JSValueMakeUndefined(context);
826 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
829 return JSValueMakeUndefined(context);
832 static JSValueRef scalePageByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
834 if (argumentCount < 3)
835 return JSValueMakeUndefined(context);
837 float scaleFactor = JSValueToNumber(context, arguments[0], exception);
838 float x = JSValueToNumber(context, arguments[1], exception);
839 float y = JSValueToNumber(context, arguments[2], exception);
841 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
843 return JSValueMakeUndefined(context);
845 DumpRenderTreeSupportGtk::scalePageBy(view, scaleFactor, x, y);
847 return JSValueMakeUndefined(context);
850 static gboolean sendAsynchronousKeyDown(gpointer userData)
852 sendKeyDown(static_cast<GdkEvent*>(userData));
856 static JSValueRef scheduleAsynchronousKeyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
858 GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
860 g_timeout_add(0, sendAsynchronousKeyDown, static_cast<gpointer>(pressEvent));
862 return JSValueMakeUndefined(context);
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 },
886 static JSStaticValue staticValues[] = {
887 { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
891 static JSClassRef getClass(JSContextRef context)
893 static JSClassRef eventSenderClass = 0;
895 if (!eventSenderClass) {
896 JSClassDefinition classDefinition = {
898 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
899 classDefinition.staticFunctions = staticFunctions;
900 classDefinition.staticValues = staticValues;
902 eventSenderClass = JSClassCreate(&classDefinition);
905 return eventSenderClass;
908 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
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.
919 lastMousePositionX = lastMousePositionY = 0;
920 lastClickPositionX = lastClickPositionY = 0;
921 lastClickTimeOffset = 0;
923 buttonCurrentlyDown = 0;
929 currentDragSourceContext = 0;
932 return JSObjectMake(context, getClass(context), 0);
935 void dragBeginCallback(GtkWidget*, GdkDragContext* context, gpointer)
937 currentDragSourceContext = context;
940 void dragEndCallback(GtkWidget*, GdkDragContext* context, gpointer)
942 currentDragSourceContext = 0;
945 gboolean dragFailedCallback(GtkWidget*, GdkDragContext* context, gpointer)
947 // Return TRUE here to disable the stupid GTK+ drag failed animation,
948 // which introduces asynchronous behavior into our drags.