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 "WebKitMutationObserver.h"
39 #include <GOwnPtrGtk.h>
40 #include <GRefPtrGtk.h>
41 #include <GtkVersioning.h>
42 #include <JavaScriptCore/JSObjectRef.h>
43 #include <JavaScriptCore/JSRetainPtr.h>
44 #include <JavaScriptCore/JSStringRef.h>
47 #include <gdk/gdkkeysyms.h>
48 #include <webkit/webkitwebframe.h>
49 #include <webkit/webkitwebview.h>
50 #include <wtf/ASCIICType.h>
51 #include <wtf/Platform.h>
52 #include <wtf/text/CString.h>
55 extern GtkMenu* webkit_web_view_get_context_menu(WebKitWebView*);
59 static int timeOffset = 0;
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;
71 struct DelayedMessage {
76 static DelayedMessage msgQueue[1024];
78 static unsigned endOfQueue;
79 static unsigned startOfQueue;
81 static const float zoomMultiplierRatio = 1.2f;
83 // Key event location code defined in DOM Level 3.
84 enum KeyLocationCode {
85 DOM_KEY_LOCATION_STANDARD = 0x00,
86 DOM_KEY_LOCATION_LEFT = 0x01,
87 DOM_KEY_LOCATION_RIGHT = 0x02,
88 DOM_KEY_LOCATION_NUMPAD = 0x03
91 static void sendOrQueueEvent(GdkEvent*, bool = true);
92 static void dispatchEvent(GdkEvent* event);
93 static guint getStateFlags();
95 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
97 return JSValueMakeBoolean(context, dragMode);
100 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
102 dragMode = JSValueToBoolean(context, value);
106 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
108 if (argumentCount > 0) {
109 msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
110 timeOffset += msgQueue[endOfQueue].delay;
111 ASSERT(!exception || !*exception);
114 return JSValueMakeUndefined(context);
117 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber, guint modifiers)
119 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
123 // The logic for mapping EventSender button numbers to GDK button
124 // numbers originates from the Windows EventSender.
125 int gdkButtonNumber = 3;
126 if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2)
127 gdkButtonNumber = eventSenderButtonNumber + 1;
129 // fast/events/mouse-click-events expects the 4th button
130 // to be event->button = 1, so send a middle-button event.
131 else if (eventSenderButtonNumber == 3)
134 event->button.button = gdkButtonNumber;
135 event->button.x = lastMousePositionX;
136 event->button.y = lastMousePositionY;
137 event->button.window = gtk_widget_get_window(GTK_WIDGET(view));
138 g_object_ref(event->button.window);
139 event->button.device = getDefaultGDKPointerDevice(event->button.window);
140 event->button.state = modifiers | getStateFlags();
141 event->button.time = GDK_CURRENT_TIME;
142 event->button.axes = 0;
145 gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
146 event->button.x_root = xRoot;
147 event->button.y_root = yRoot;
152 static JSValueRef getMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
154 GtkWidget* widget = GTK_WIDGET(JSObjectGetPrivate(object));
156 if (GTK_IS_SEPARATOR_MENU_ITEM(widget))
157 label = "<separator>";
159 label = gtk_menu_item_get_label(GTK_MENU_ITEM(widget));
161 return JSValueMakeString(context, JSStringCreateWithUTF8CString(label.data()));
164 static bool setMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
169 static JSValueRef menuItemClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
171 GtkMenuItem* item = GTK_MENU_ITEM(JSObjectGetPrivate(thisObject));
172 gtk_menu_item_activate(item);
173 return JSValueMakeUndefined(context);
176 static JSStaticFunction staticMenuItemFunctions[] = {
177 { "click", menuItemClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
181 static JSStaticValue staticMenuItemValues[] = {
182 { "title", getMenuItemTitleCallback, setMenuItemTitleCallback, kJSPropertyAttributeNone },
186 static JSClassRef getMenuItemClass()
188 static JSClassRef menuItemClass = 0;
190 if (!menuItemClass) {
191 JSClassDefinition classDefinition = {
193 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
194 classDefinition.staticFunctions = staticMenuItemFunctions;
195 classDefinition.staticValues = staticMenuItemValues;
197 menuItemClass = JSClassCreate(&classDefinition);
200 return menuItemClass;
204 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
206 GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
208 if (!prepareMouseButtonEvent(pressEvent, 2, 0))
209 return JSObjectMakeArray(context, 0, 0, 0);
211 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
212 sendOrQueueEvent(pressEvent);
214 JSValueRef valueRef = JSObjectMakeArray(context, 0, 0, 0);
215 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
216 GtkMenu* gtkMenu = webkit_web_view_get_context_menu(view);
218 GList* items = gtk_container_get_children(GTK_CONTAINER(gtkMenu));
219 JSValueRef arrayValues[g_list_length(items)];
221 for (GList* item = g_list_first(items); item; item = g_list_next(item)) {
222 arrayValues[index] = JSObjectMake(context, getMenuItemClass(), item->data);
226 valueRef = JSObjectMakeArray(context, index - 1, arrayValues, 0);
229 releaseEvent->type = GDK_BUTTON_RELEASE;
230 sendOrQueueEvent(releaseEvent);
234 static gboolean sendClick(gpointer)
236 GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS);
238 if (!prepareMouseButtonEvent(pressEvent, 1, 0)) {
239 gdk_event_free(pressEvent);
243 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
244 dispatchEvent(pressEvent);
245 releaseEvent->type = GDK_BUTTON_RELEASE;
246 dispatchEvent(releaseEvent);
251 static JSValueRef scheduleAsynchronousClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
253 g_timeout_add(0, sendClick, 0);
254 return JSValueMakeUndefined(context);
257 static void updateClickCount(int button)
259 if (lastClickPositionX != lastMousePositionX
260 || lastClickPositionY != lastMousePositionY
261 || lastClickButton != button
262 || timeOffset - lastClickTimeOffset >= 1)
268 static guint gdkModifierFromJSValue(JSContextRef context, const JSValueRef value)
270 JSStringRef string = JSValueToStringCopy(context, value, 0);
271 guint gdkModifier = 0;
272 if (JSStringIsEqualToUTF8CString(string, "ctrlKey")
273 || JSStringIsEqualToUTF8CString(string, "addSelectionKey"))
274 gdkModifier = GDK_CONTROL_MASK;
275 else if (JSStringIsEqualToUTF8CString(string, "shiftKey")
276 || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey"))
277 gdkModifier = GDK_SHIFT_MASK;
278 else if (JSStringIsEqualToUTF8CString(string, "altKey"))
279 gdkModifier = GDK_MOD1_MASK;
281 // Currently the metaKey as defined in WebCore/platform/gtk/PlatformMouseEventGtk.cpp
282 // is GDK_META_MASK. This code must be kept in sync with that file.
283 else if (JSStringIsEqualToUTF8CString(string, "metaKey"))
284 gdkModifier = GDK_META_MASK;
286 JSStringRelease(string);
290 static guint gdkModifersFromJSValue(JSContextRef context, const JSValueRef modifiers)
292 // The value may either be a string with a single modifier or an array of modifiers.
293 if (JSValueIsString(context, modifiers))
294 return gdkModifierFromJSValue(context, modifiers);
296 JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0);
300 guint gdkModifiers = 0;
301 JSRetainPtr<JSStringRef> lengthProperty(Adopt, JSStringCreateWithUTF8CString("length"));
302 int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty.get(), 0), 0);
303 for (int i = 0; i < modifiersCount; ++i)
304 gdkModifiers |= gdkModifierFromJSValue(context, JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0));
308 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
311 if (argumentCount == 1) {
312 button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
313 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
315 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
317 GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS);
318 if (!prepareMouseButtonEvent(event, button, modifiers))
319 return JSValueMakeUndefined(context);
321 // If the same mouse button is already in the down position don't send another event as it may confuse Xvfb.
322 if (buttonCurrentlyDown == event->button.button)
323 return JSValueMakeUndefined(context);
325 buttonCurrentlyDown = event->button.button;
327 // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
328 // the second button press during double-clicks. WebKit GTK+ selectively
329 // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
330 // Since our events aren't ever going onto the GDK event queue, WebKit won't
331 // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
332 // it here. Eventually this code should probably figure out a way to get all
333 // appropriate events onto the event queue and this work-around should be
335 updateClickCount(event->button.button);
337 event->type = GDK_2BUTTON_PRESS;
338 else if (clickCount == 3)
339 event->type = GDK_3BUTTON_PRESS;
341 sendOrQueueEvent(event);
342 return JSValueMakeUndefined(context);
345 static guint getStateFlags()
347 if (buttonCurrentlyDown == 1)
348 return GDK_BUTTON1_MASK;
349 if (buttonCurrentlyDown == 2)
350 return GDK_BUTTON2_MASK;
351 if (buttonCurrentlyDown == 3)
352 return GDK_BUTTON3_MASK;
356 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
359 if (argumentCount == 1) {
360 button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
361 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
363 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
365 GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE);
366 if (!prepareMouseButtonEvent(event, button, modifiers))
367 return JSValueMakeUndefined(context);
369 lastClickPositionX = lastMousePositionX;
370 lastClickPositionY = lastMousePositionY;
371 lastClickButton = buttonCurrentlyDown;
372 lastClickTimeOffset = timeOffset;
373 buttonCurrentlyDown = 0;
375 sendOrQueueEvent(event);
376 return JSValueMakeUndefined(context);
379 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
381 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
383 return JSValueMakeUndefined(context);
385 if (argumentCount < 2)
386 return JSValueMakeUndefined(context);
388 lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
389 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
390 lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
391 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
393 GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY);
394 event->motion.x = lastMousePositionX;
395 event->motion.y = lastMousePositionY;
397 event->motion.time = GDK_CURRENT_TIME;
398 event->motion.window = gtk_widget_get_window(GTK_WIDGET(view));
399 g_object_ref(event->motion.window);
400 event->button.device = getDefaultGDKPointerDevice(event->motion.window);
402 guint modifiers = argumentCount >= 3 ? gdkModifersFromJSValue(context, arguments[2]) : 0;
403 event->motion.state = modifiers | getStateFlags();
404 event->motion.axes = 0;
407 gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
408 event->motion.x_root = xRoot;
409 event->motion.y_root = yRoot;
411 sendOrQueueEvent(event, false);
412 return JSValueMakeUndefined(context);
415 static JSValueRef mouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
417 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
419 return JSValueMakeUndefined(context);
421 if (argumentCount < 2)
422 return JSValueMakeUndefined(context);
424 int horizontal = (int)JSValueToNumber(context, arguments[0], exception);
425 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
426 int vertical = (int)JSValueToNumber(context, arguments[1], exception);
427 g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
429 // GTK+ doesn't support multiple direction scrolls in the same event!
430 g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
432 GdkEvent* event = gdk_event_new(GDK_SCROLL);
433 event->scroll.x = lastMousePositionX;
434 event->scroll.y = lastMousePositionY;
435 event->scroll.time = GDK_CURRENT_TIME;
436 event->scroll.window = gtk_widget_get_window(GTK_WIDGET(view));
437 g_object_ref(event->scroll.window);
440 event->scroll.direction = GDK_SCROLL_RIGHT;
441 else if (horizontal > 0)
442 event->scroll.direction = GDK_SCROLL_LEFT;
443 else if (vertical < 0)
444 event->scroll.direction = GDK_SCROLL_DOWN;
445 else if (vertical > 0)
446 event->scroll.direction = GDK_SCROLL_UP;
448 g_assert_not_reached();
450 sendOrQueueEvent(event);
451 return JSValueMakeUndefined(context);
454 static JSValueRef continuousMouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
456 // GTK doesn't support continuous scroll events.
457 return JSValueMakeUndefined(context);
460 static void dragWithFilesDragDataGetCallback(GtkWidget*, GdkDragContext*, GtkSelectionData *data, guint, guint, gpointer userData)
462 gtk_selection_data_set_uris(data, static_cast<gchar**>(userData));
465 static void dragWithFilesDragEndCallback(GtkWidget* widget, GdkDragContext*, gpointer userData)
467 g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragEndCallback), userData);
468 g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragDataGetCallback), userData);
469 g_strfreev(static_cast<gchar**>(userData));
472 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
474 if (argumentCount < 1)
475 return JSValueMakeUndefined(context);
477 JSObjectRef filesArray = JSValueToObject(context, arguments[0], exception);
478 ASSERT(!exception || !*exception);
480 const gchar* mainFrameURI = webkit_web_frame_get_uri(mainFrame);
481 GRefPtr<GFile> testFile(adoptGRef(g_file_new_for_uri(mainFrameURI)));
482 GRefPtr<GFile> parentDirectory(g_file_get_parent(testFile.get()));
483 if (!parentDirectory)
484 return JSValueMakeUndefined(context);
486 // If this is an HTTP test, we still need to pass a local file path
487 // to WebCore. Even though the file doesn't exist, this should be fine
489 GOwnPtr<gchar> scheme(g_file_get_uri_scheme(parentDirectory.get()));
490 if (g_str_equal(scheme.get(), "http") || g_str_equal(scheme.get(), "https")) {
491 GOwnPtr<gchar> currentDirectory(g_get_current_dir());
492 parentDirectory = g_file_new_for_path(currentDirectory.get());
495 JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
496 int filesArrayLength = JSValueToNumber(context, JSObjectGetProperty(context, filesArray, lengthProperty, 0), 0);
497 JSStringRelease(lengthProperty);
499 gchar** draggedFilesURIList = g_new0(gchar*, filesArrayLength + 1);
500 for (int i = 0; i < filesArrayLength; ++i) {
501 JSStringRef filenameString = JSValueToStringCopy(context,
502 JSObjectGetPropertyAtIndex(context, filesArray, i, 0), 0);
503 size_t bufferSize = JSStringGetMaximumUTF8CStringSize(filenameString);
504 GOwnPtr<gchar> filenameBuffer(static_cast<gchar*>(g_malloc(bufferSize)));
505 JSStringGetUTF8CString(filenameString, filenameBuffer.get(), bufferSize);
506 JSStringRelease(filenameString);
508 GRefPtr<GFile> dragFile(g_file_get_child(parentDirectory.get(), filenameBuffer.get()));
509 draggedFilesURIList[i] = g_file_get_uri(dragFile.get());
512 GtkWidget* view = GTK_WIDGET(webkit_web_frame_get_web_view(mainFrame));
513 g_object_connect(G_OBJECT(view),
514 "signal::drag-end", dragWithFilesDragEndCallback, draggedFilesURIList,
515 "signal::drag-data-get", dragWithFilesDragDataGetCallback, draggedFilesURIList,
519 GdkWindow* viewGDKWindow = gtk_widget_get_window(view);
520 memset(&event, 0, sizeof(event));
521 event.type = GDK_MOTION_NOTIFY;
522 event.motion.x = lastMousePositionX;
523 event.motion.y = lastMousePositionY;
524 event.motion.time = GDK_CURRENT_TIME;
525 event.motion.window = viewGDKWindow;
526 event.motion.device = getDefaultGDKPointerDevice(viewGDKWindow);
527 event.motion.state = GDK_BUTTON1_MASK;
530 gdk_window_get_root_coords(viewGDKWindow, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
531 event.motion.x_root = xRoot;
532 event.motion.y_root = yRoot;
534 GtkTargetList* targetList = gtk_target_list_new(0, 0);
535 gtk_target_list_add_uri_targets(targetList, 0);
536 gtk_drag_begin(view, targetList, GDK_ACTION_COPY, 1, &event);
537 gtk_target_list_unref(targetList);
539 return JSValueMakeUndefined(context);
542 static void sendOrQueueEvent(GdkEvent* event, bool shouldReplaySavedEvents)
544 // Mouse move events are queued if the previous event was queued or if a
545 // delay was set up by leapForward().
546 if ((dragMode && buttonCurrentlyDown) || endOfQueue != startOfQueue || msgQueue[endOfQueue].delay) {
547 msgQueue[endOfQueue++].event = event;
549 if (shouldReplaySavedEvents)
555 dispatchEvent(event);
558 static void dispatchEvent(GdkEvent* event)
560 DumpRenderTreeSupportGtk::layoutFrame(mainFrame);
561 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
563 gdk_event_free(event);
567 // The widget focus may have been lost in the course of the test,
568 // so force another explicit focus grab here.
569 gtk_widget_grab_focus(GTK_WIDGET(view));
570 gtk_main_do_event(event);
572 if (!currentDragSourceContext) {
573 gdk_event_free(event);
577 if (event->type == GDK_MOTION_NOTIFY) {
578 // WebKit has called gtk_drag_start(), but because the main loop isn't
579 // running GDK internals don't know that the drag has started yet. Pump
580 // the main loop a little bit so that GDK is in the correct state.
581 while (gtk_events_pending())
582 gtk_main_iteration();
584 // Simulate a drag motion on the top-level GDK window.
585 GtkWidget* parentWidget = gtk_widget_get_parent(GTK_WIDGET(view));
586 GdkWindow* parentWidgetWindow = gtk_widget_get_window(parentWidget);
587 gdk_drag_motion(currentDragSourceContext, parentWidgetWindow, GDK_DRAG_PROTO_XDND,
588 event->motion.x_root, event->motion.y_root,
589 gdk_drag_context_get_selected_action(currentDragSourceContext),
590 gdk_drag_context_get_actions(currentDragSourceContext),
593 } else if (currentDragSourceContext && event->type == GDK_BUTTON_RELEASE) {
594 // We've released the mouse button, we should just be able to spin the
595 // event loop here and have GTK+ send the appropriate notifications for
596 // the end of the drag.
597 while (gtk_events_pending())
598 gtk_main_iteration();
601 gdk_event_free(event);
604 void replaySavedEvents()
606 // First send all the events that are ready to be sent
607 while (startOfQueue < endOfQueue) {
608 if (msgQueue[startOfQueue].delay) {
609 g_usleep(msgQueue[startOfQueue].delay * 1000);
610 msgQueue[startOfQueue].delay = 0;
613 dispatchEvent(msgQueue[startOfQueue++].event);
620 static GdkEvent* createKeyPressEvent(JSContextRef context, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
622 g_return_val_if_fail(argumentCount >= 1, 0);
623 guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0;
625 // handle location argument.
626 int location = DOM_KEY_LOCATION_STANDARD;
627 if (argumentCount > 2)
628 location = (int)JSValueToNumber(context, arguments[2], exception);
630 JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
631 g_return_val_if_fail((!exception || !*exception), 0);
633 int gdkKeySym = GDK_VoidSymbol;
634 if (location == DOM_KEY_LOCATION_NUMPAD) {
635 if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
636 gdkKeySym = GDK_KP_Left;
637 else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
638 gdkKeySym = GDK_KP_Right;
639 else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
640 gdkKeySym = GDK_KP_Up;
641 else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
642 gdkKeySym = GDK_KP_Down;
643 else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
644 gdkKeySym = GDK_KP_Page_Up;
645 else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
646 gdkKeySym = GDK_KP_Page_Down;
647 else if (JSStringIsEqualToUTF8CString(character, "home"))
648 gdkKeySym = GDK_KP_Home;
649 else if (JSStringIsEqualToUTF8CString(character, "end"))
650 gdkKeySym = GDK_KP_End;
651 else if (JSStringIsEqualToUTF8CString(character, "insert"))
652 gdkKeySym = GDK_KP_Insert;
653 else if (JSStringIsEqualToUTF8CString(character, "delete"))
654 gdkKeySym = GDK_KP_Delete;
656 // If we get some other key specified with the numpad location,
657 // crash here, so we add it sooner rather than later.
658 g_assert_not_reached();
660 if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
661 gdkKeySym = GDK_Left;
662 else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
663 gdkKeySym = GDK_Right;
664 else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
666 else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
667 gdkKeySym = GDK_Down;
668 else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
669 gdkKeySym = GDK_Page_Up;
670 else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
671 gdkKeySym = GDK_Page_Down;
672 else if (JSStringIsEqualToUTF8CString(character, "home"))
673 gdkKeySym = GDK_Home;
674 else if (JSStringIsEqualToUTF8CString(character, "end"))
676 else if (JSStringIsEqualToUTF8CString(character, "insert"))
677 gdkKeySym = GDK_Insert;
678 else if (JSStringIsEqualToUTF8CString(character, "delete"))
679 gdkKeySym = GDK_Delete;
680 else if (JSStringIsEqualToUTF8CString(character, "printScreen"))
681 gdkKeySym = GDK_Print;
682 else if (JSStringIsEqualToUTF8CString(character, "menu"))
683 gdkKeySym = GDK_Menu;
684 else if (JSStringIsEqualToUTF8CString(character, "F1"))
686 else if (JSStringIsEqualToUTF8CString(character, "F2"))
688 else if (JSStringIsEqualToUTF8CString(character, "F3"))
690 else if (JSStringIsEqualToUTF8CString(character, "F4"))
692 else if (JSStringIsEqualToUTF8CString(character, "F5"))
694 else if (JSStringIsEqualToUTF8CString(character, "F6"))
696 else if (JSStringIsEqualToUTF8CString(character, "F7"))
698 else if (JSStringIsEqualToUTF8CString(character, "F8"))
700 else if (JSStringIsEqualToUTF8CString(character, "F9"))
702 else if (JSStringIsEqualToUTF8CString(character, "F10"))
704 else if (JSStringIsEqualToUTF8CString(character, "F11"))
706 else if (JSStringIsEqualToUTF8CString(character, "F12"))
708 else if (JSStringIsEqualToUTF8CString(character, "leftAlt"))
709 gdkKeySym = GDK_Alt_L;
710 else if (JSStringIsEqualToUTF8CString(character, "leftControl"))
711 gdkKeySym = GDK_Control_L;
712 else if (JSStringIsEqualToUTF8CString(character, "leftShift"))
713 gdkKeySym = GDK_Shift_L;
714 else if (JSStringIsEqualToUTF8CString(character, "rightAlt"))
715 gdkKeySym = GDK_Alt_R;
716 else if (JSStringIsEqualToUTF8CString(character, "rightControl"))
717 gdkKeySym = GDK_Control_R;
718 else if (JSStringIsEqualToUTF8CString(character, "rightShift"))
719 gdkKeySym = GDK_Shift_R;
721 int charCode = JSStringGetCharactersPtr(character)[0];
722 if (charCode == '\n' || charCode == '\r')
723 gdkKeySym = GDK_Return;
724 else if (charCode == '\t')
726 else if (charCode == '\x8')
727 gdkKeySym = GDK_BackSpace;
729 gdkKeySym = gdk_unicode_to_keyval(charCode);
730 if (WTF::isASCIIUpper(charCode))
731 modifiers |= GDK_SHIFT_MASK;
735 JSStringRelease(character);
737 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
738 g_return_val_if_fail(view, 0);
740 GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS);
741 pressEvent->key.keyval = gdkKeySym;
742 pressEvent->key.state = modifiers;
743 pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(view));
744 g_object_ref(pressEvent->key.window);
745 #ifndef GTK_API_VERSION_2
746 gdk_event_set_device(pressEvent, getDefaultGDKPointerDevice(pressEvent->key.window));
749 // When synthesizing an event, an invalid hardware_keycode value
750 // can cause it to be badly processed by Gtk+.
751 GOwnPtr<GdkKeymapKey> keys;
753 if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys.outPtr(), &nKeys))
754 pressEvent->key.hardware_keycode = keys.get()[0].keycode;
759 static void sendKeyDown(GdkEvent* pressEvent)
761 g_return_if_fail(pressEvent);
762 GdkEvent* releaseEvent = gdk_event_copy(pressEvent);
763 releaseEvent->type = GDK_KEY_RELEASE;
765 dispatchEvent(pressEvent);
766 dispatchEvent(releaseEvent);
768 DumpRenderTreeSupportGtk::deliverAllMutationsIfNecessary();
771 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
773 GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
774 sendKeyDown(pressEvent);
776 return JSValueMakeUndefined(context);
779 static void zoomIn(gboolean fullContentsZoom)
781 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
785 webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
786 gfloat currentZoom = webkit_web_view_get_zoom_level(view);
787 webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
790 static void zoomOut(gboolean fullContentsZoom)
792 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
796 webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
797 gfloat currentZoom = webkit_web_view_get_zoom_level(view);
798 webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
801 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
804 return JSValueMakeUndefined(context);
807 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
810 return JSValueMakeUndefined(context);
813 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
816 return JSValueMakeUndefined(context);
819 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
822 return JSValueMakeUndefined(context);
825 static JSValueRef scalePageByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
827 if (argumentCount < 3)
828 return JSValueMakeUndefined(context);
830 float scaleFactor = JSValueToNumber(context, arguments[0], exception);
831 float x = JSValueToNumber(context, arguments[1], exception);
832 float y = JSValueToNumber(context, arguments[2], exception);
834 WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
836 return JSValueMakeUndefined(context);
838 DumpRenderTreeSupportGtk::scalePageBy(view, scaleFactor, x, y);
840 return JSValueMakeUndefined(context);
843 static gboolean sendAsynchronousKeyDown(gpointer userData)
845 sendKeyDown(static_cast<GdkEvent*>(userData));
849 static JSValueRef scheduleAsynchronousKeyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
851 GdkEvent* pressEvent = createKeyPressEvent(context, argumentCount, arguments, exception);
853 g_timeout_add(0, sendAsynchronousKeyDown, static_cast<gpointer>(pressEvent));
855 return JSValueMakeUndefined(context);
858 static JSStaticFunction staticFunctions[] = {
859 { "mouseScrollBy", mouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
860 { "continuousMouseScrollBy", continuousMouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
861 { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
862 { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
863 { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
864 { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
865 { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
866 { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
867 { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
868 { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
869 { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
870 { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
871 { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
872 { "scheduleAsynchronousClick", scheduleAsynchronousClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
873 { "scalePageBy", scalePageByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
874 { "scheduleAsynchronousKeyDown", scheduleAsynchronousKeyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
879 static JSStaticValue staticValues[] = {
880 { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
884 static JSClassRef getClass(JSContextRef context)
886 static JSClassRef eventSenderClass = 0;
888 if (!eventSenderClass) {
889 JSClassDefinition classDefinition = {
891 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
892 classDefinition.staticFunctions = staticFunctions;
893 classDefinition.staticValues = staticValues;
895 eventSenderClass = JSClassCreate(&classDefinition);
898 return eventSenderClass;
901 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
906 // Fly forward in time one second when the main frame loads. This will
907 // ensure that when a test begins clicking in the same location as
908 // a previous test, those clicks won't be interpreted as continuations
909 // of the previous test's click sequences.
912 lastMousePositionX = lastMousePositionY = 0;
913 lastClickPositionX = lastClickPositionY = 0;
914 lastClickTimeOffset = 0;
916 buttonCurrentlyDown = 0;
922 currentDragSourceContext = 0;
925 return JSObjectMake(context, getClass(context), 0);
928 void dragBeginCallback(GtkWidget*, GdkDragContext* context, gpointer)
930 currentDragSourceContext = context;
933 void dragEndCallback(GtkWidget*, GdkDragContext* context, gpointer)
935 currentDragSourceContext = 0;
938 gboolean dragFailedCallback(GtkWidget*, GdkDragContext* context, gpointer)
940 // Return TRUE here to disable the stupid GTK+ drag failed animation,
941 // which introduces asynchronous behavior into our drags.