/* * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com> * Copyright (C) 2009 Holger Hans Peter Freyther * Copyright (C) 2010 Igalia S.L. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "EventSender.h" #include "DumpRenderTree.h" #include "WebCoreSupport/DumpRenderTreeSupportGtk.h" #include <GOwnPtrGtk.h> #include <GRefPtrGtk.h> #include <GtkVersioning.h> #include <JavaScriptCore/JSObjectRef.h> #include <JavaScriptCore/JSRetainPtr.h> #include <JavaScriptCore/JSStringRef.h> #include <cstring> #include <gdk/gdk.h> #include <gdk/gdkkeysyms.h> #include <webkit/webkitwebframe.h> #include <webkit/webkitwebview.h> #include <wtf/ASCIICType.h> #include <wtf/Platform.h> #include <wtf/text/CString.h> extern "C" { extern GtkMenu* webkit_web_view_get_context_menu(WebKitWebView*); } static bool dragMode; static int timeOffset = 0; static int lastMousePositionX; static int lastMousePositionY; static int lastClickPositionX; static int lastClickPositionY; static int lastClickTimeOffset; static int lastClickButton; static int buttonCurrentlyDown; static int clickCount; GdkDragContext* currentDragSourceContext; struct DelayedMessage { GdkEvent* event; gulong delay; }; static DelayedMessage msgQueue[1024]; static unsigned endOfQueue; static unsigned startOfQueue; static const float zoomMultiplierRatio = 1.2f; // Key event location code defined in DOM Level 3. enum KeyLocationCode { DOM_KEY_LOCATION_STANDARD = 0x00, DOM_KEY_LOCATION_LEFT = 0x01, DOM_KEY_LOCATION_RIGHT = 0x02, DOM_KEY_LOCATION_NUMPAD = 0x03 }; static void sendOrQueueEvent(GdkEvent*, bool = true); static void dispatchEvent(GdkEvent* event); static guint getStateFlags(); static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { return JSValueMakeBoolean(context, dragMode); } static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) { dragMode = JSValueToBoolean(context, value); return true; } static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount > 0) { msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception); timeOffset += msgQueue[endOfQueue].delay; ASSERT(!exception || !*exception); } return JSValueMakeUndefined(context); } bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber, guint modifiers) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return false; // The logic for mapping EventSender button numbers to GDK button // numbers originates from the Windows EventSender. int gdkButtonNumber = 3; if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2) gdkButtonNumber = eventSenderButtonNumber + 1; // fast/events/mouse-click-events expects the 4th button // to be event->button = 1, so send a middle-button event. else if (eventSenderButtonNumber == 3) gdkButtonNumber = 2; event->button.button = gdkButtonNumber; event->button.x = lastMousePositionX; event->button.y = lastMousePositionY; event->button.window = gtk_widget_get_window(GTK_WIDGET(view)); g_object_ref(event->button.window); event->button.device = getDefaultGDKPointerDevice(event->button.window); event->button.state = modifiers | getStateFlags(); event->button.time = GDK_CURRENT_TIME; event->button.axes = 0; int xRoot, yRoot; gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot); event->button.x_root = xRoot; event->button.y_root = yRoot; return true; } static JSValueRef getMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception) { GtkWidget* widget = GTK_WIDGET(JSObjectGetPrivate(object)); CString label; if (GTK_IS_SEPARATOR_MENU_ITEM(widget)) label = "<separator>"; else label = gtk_menu_item_get_label(GTK_MENU_ITEM(widget)); return JSValueMakeString(context, JSStringCreateWithUTF8CString(label.data())); } static bool setMenuItemTitleCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception) { return true; } static JSValueRef menuItemClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { GtkMenuItem* item = GTK_MENU_ITEM(JSObjectGetPrivate(thisObject)); gtk_menu_item_activate(item); return JSValueMakeUndefined(context); } static JSStaticFunction staticMenuItemFunctions[] = { { "click", menuItemClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { 0, 0, 0 } }; static JSStaticValue staticMenuItemValues[] = { { "title", getMenuItemTitleCallback, setMenuItemTitleCallback, kJSPropertyAttributeNone }, { 0, 0, 0, 0 } }; static JSClassRef getMenuItemClass() { static JSClassRef menuItemClass = 0; if (!menuItemClass) { JSClassDefinition classDefinition = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; classDefinition.staticFunctions = staticMenuItemFunctions; classDefinition.staticValues = staticMenuItemValues; menuItemClass = JSClassCreate(&classDefinition); } return menuItemClass; } static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS); if (!prepareMouseButtonEvent(pressEvent, 2, 0)) return JSObjectMakeArray(context, 0, 0, 0); GdkEvent* releaseEvent = gdk_event_copy(pressEvent); sendOrQueueEvent(pressEvent); JSValueRef valueRef = JSObjectMakeArray(context, 0, 0, 0); WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); GtkMenu* gtkMenu = webkit_web_view_get_context_menu(view); if (gtkMenu) { GList* items = gtk_container_get_children(GTK_CONTAINER(gtkMenu)); JSValueRef arrayValues[g_list_length(items)]; int index = 0; for (GList* item = g_list_first(items); item; item = g_list_next(item)) { arrayValues[index] = JSObjectMake(context, getMenuItemClass(), item->data); index++; } if (index) valueRef = JSObjectMakeArray(context, index - 1, arrayValues, 0); } releaseEvent->type = GDK_BUTTON_RELEASE; sendOrQueueEvent(releaseEvent); return valueRef; } static gboolean sendClick(gpointer) { GdkEvent* pressEvent = gdk_event_new(GDK_BUTTON_PRESS); if (!prepareMouseButtonEvent(pressEvent, 1, 0)) { gdk_event_free(pressEvent); return FALSE; } GdkEvent* releaseEvent = gdk_event_copy(pressEvent); dispatchEvent(pressEvent); releaseEvent->type = GDK_BUTTON_RELEASE; dispatchEvent(releaseEvent); return FALSE; } static JSValueRef scheduleAsynchronousClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { g_idle_add(sendClick, 0); return JSValueMakeUndefined(context); } static void updateClickCount(int button) { if (lastClickPositionX != lastMousePositionX || lastClickPositionY != lastMousePositionY || lastClickButton != button || timeOffset - lastClickTimeOffset >= 1) clickCount = 1; else clickCount++; } static guint gdkModifierFromJSValue(JSContextRef context, const JSValueRef value) { JSStringRef string = JSValueToStringCopy(context, value, 0); guint gdkModifier = 0; if (JSStringIsEqualToUTF8CString(string, "ctrlKey") || JSStringIsEqualToUTF8CString(string, "addSelectionKey")) gdkModifier = GDK_CONTROL_MASK; else if (JSStringIsEqualToUTF8CString(string, "shiftKey") || JSStringIsEqualToUTF8CString(string, "rangeSelectionKey")) gdkModifier = GDK_SHIFT_MASK; else if (JSStringIsEqualToUTF8CString(string, "altKey")) gdkModifier = GDK_MOD1_MASK; // Currently the metaKey as defined in WebCore/platform/gtk/MouseEventGtk.cpp // is GDK_MOD2_MASK. This code must be kept in sync with that file. else if (JSStringIsEqualToUTF8CString(string, "metaKey")) gdkModifier = GDK_MOD2_MASK; JSStringRelease(string); return gdkModifier; } static guint gdkModifersFromJSValue(JSContextRef context, const JSValueRef modifiers) { // The value may either be a string with a single modifier or an array of modifiers. if (JSValueIsString(context, modifiers)) return gdkModifierFromJSValue(context, modifiers); JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0); if (!modifiersArray) return 0; guint gdkModifiers = 0; int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, JSStringCreateWithUTF8CString("length"), 0), 0); for (int i = 0; i < modifiersCount; ++i) gdkModifiers |= gdkModifierFromJSValue(context, JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0)); return gdkModifiers; } static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { int button = 0; if (argumentCount == 1) { button = static_cast<int>(JSValueToNumber(context, arguments[0], exception)); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); } guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0; GdkEvent* event = gdk_event_new(GDK_BUTTON_PRESS); if (!prepareMouseButtonEvent(event, button, modifiers)) return JSValueMakeUndefined(context); buttonCurrentlyDown = event->button.button; // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for // the second button press during double-clicks. WebKit GTK+ selectively // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek. // Since our events aren't ever going onto the GDK event queue, WebKit won't // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send // it here. Eventually this code should probably figure out a way to get all // appropriate events onto the event queue and this work-around should be // removed. updateClickCount(event->button.button); if (clickCount == 2) event->type = GDK_2BUTTON_PRESS; else if (clickCount == 3) event->type = GDK_3BUTTON_PRESS; sendOrQueueEvent(event); return JSValueMakeUndefined(context); } static guint getStateFlags() { if (buttonCurrentlyDown == 1) return GDK_BUTTON1_MASK; if (buttonCurrentlyDown == 2) return GDK_BUTTON2_MASK; if (buttonCurrentlyDown == 3) return GDK_BUTTON3_MASK; return 0; } static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { int button = 0; if (argumentCount == 1) { button = static_cast<int>(JSValueToNumber(context, arguments[0], exception)); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); } guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0; GdkEvent* event = gdk_event_new(GDK_BUTTON_RELEASE); if (!prepareMouseButtonEvent(event, button, modifiers)) return JSValueMakeUndefined(context); lastClickPositionX = lastMousePositionX; lastClickPositionY = lastMousePositionY; lastClickButton = buttonCurrentlyDown; lastClickTimeOffset = timeOffset; buttonCurrentlyDown = 0; sendOrQueueEvent(event); return JSValueMakeUndefined(context); } static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return JSValueMakeUndefined(context); if (argumentCount < 2) return JSValueMakeUndefined(context); lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); GdkEvent* event = gdk_event_new(GDK_MOTION_NOTIFY); event->motion.x = lastMousePositionX; event->motion.y = lastMousePositionY; event->motion.time = GDK_CURRENT_TIME; event->motion.window = gtk_widget_get_window(GTK_WIDGET(view)); g_object_ref(event->motion.window); event->button.device = getDefaultGDKPointerDevice(event->motion.window); event->motion.state = getStateFlags(); event->motion.axes = 0; int xRoot, yRoot; gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot); event->motion.x_root = xRoot; event->motion.y_root = yRoot; sendOrQueueEvent(event, false); return JSValueMakeUndefined(context); } static JSValueRef mouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return JSValueMakeUndefined(context); if (argumentCount < 2) return JSValueMakeUndefined(context); int horizontal = (int)JSValueToNumber(context, arguments[0], exception); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); int vertical = (int)JSValueToNumber(context, arguments[1], exception); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); // GTK+ doesn't support multiple direction scrolls in the same event! g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context)); GdkEvent* event = gdk_event_new(GDK_SCROLL); event->scroll.x = lastMousePositionX; event->scroll.y = lastMousePositionY; event->scroll.time = GDK_CURRENT_TIME; event->scroll.window = gtk_widget_get_window(GTK_WIDGET(view)); g_object_ref(event->scroll.window); if (horizontal < 0) event->scroll.direction = GDK_SCROLL_RIGHT; else if (horizontal > 0) event->scroll.direction = GDK_SCROLL_LEFT; else if (vertical < 0) event->scroll.direction = GDK_SCROLL_DOWN; else if (vertical > 0) event->scroll.direction = GDK_SCROLL_UP; else g_assert_not_reached(); sendOrQueueEvent(event); return JSValueMakeUndefined(context); } static JSValueRef continuousMouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { // GTK doesn't support continuous scroll events. return JSValueMakeUndefined(context); } static void dragWithFilesDragDataGetCallback(GtkWidget*, GdkDragContext*, GtkSelectionData *data, guint, guint, gpointer userData) { gtk_selection_data_set_uris(data, static_cast<gchar**>(userData)); } static void dragWithFilesDragEndCallback(GtkWidget* widget, GdkDragContext*, gpointer userData) { g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragEndCallback), userData); g_signal_handlers_disconnect_by_func(widget, reinterpret_cast<void*>(dragWithFilesDragDataGetCallback), userData); g_strfreev(static_cast<gchar**>(userData)); } static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) return JSValueMakeUndefined(context); JSObjectRef filesArray = JSValueToObject(context, arguments[0], exception); ASSERT(!exception || !*exception); const gchar* mainFrameURI = webkit_web_frame_get_uri(mainFrame); GRefPtr<GFile> testFile(adoptGRef(g_file_new_for_uri(mainFrameURI))); GRefPtr<GFile> parentDirectory(g_file_get_parent(testFile.get())); if (!parentDirectory) return JSValueMakeUndefined(context); // If this is an HTTP test, we still need to pass a local file path // to WebCore. Even though the file doesn't exist, this should be fine // for most tests. GOwnPtr<gchar> scheme(g_file_get_uri_scheme(parentDirectory.get())); if (g_str_equal(scheme.get(), "http") || g_str_equal(scheme.get(), "https")) { GOwnPtr<gchar> currentDirectory(g_get_current_dir()); parentDirectory = g_file_new_for_path(currentDirectory.get()); } JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length"); int filesArrayLength = JSValueToNumber(context, JSObjectGetProperty(context, filesArray, lengthProperty, 0), 0); JSStringRelease(lengthProperty); gchar** draggedFilesURIList = g_new0(gchar*, filesArrayLength + 1); for (int i = 0; i < filesArrayLength; ++i) { JSStringRef filenameString = JSValueToStringCopy(context, JSObjectGetPropertyAtIndex(context, filesArray, i, 0), 0); size_t bufferSize = JSStringGetMaximumUTF8CStringSize(filenameString); GOwnPtr<gchar> filenameBuffer(static_cast<gchar*>(g_malloc(bufferSize))); JSStringGetUTF8CString(filenameString, filenameBuffer.get(), bufferSize); JSStringRelease(filenameString); GRefPtr<GFile> dragFile(g_file_get_child(parentDirectory.get(), filenameBuffer.get())); draggedFilesURIList[i] = g_file_get_uri(dragFile.get()); } GtkWidget* view = GTK_WIDGET(webkit_web_frame_get_web_view(mainFrame)); g_object_connect(G_OBJECT(view), "signal::drag-end", dragWithFilesDragEndCallback, draggedFilesURIList, "signal::drag-data-get", dragWithFilesDragDataGetCallback, draggedFilesURIList, NULL); GdkEvent event; GdkWindow* viewGDKWindow = gtk_widget_get_window(view); memset(&event, 0, sizeof(event)); event.type = GDK_MOTION_NOTIFY; event.motion.x = lastMousePositionX; event.motion.y = lastMousePositionY; event.motion.time = GDK_CURRENT_TIME; event.motion.window = viewGDKWindow; event.motion.device = getDefaultGDKPointerDevice(viewGDKWindow); event.motion.state = GDK_BUTTON1_MASK; int xRoot, yRoot; gdk_window_get_root_coords(viewGDKWindow, lastMousePositionX, lastMousePositionY, &xRoot, &yRoot); event.motion.x_root = xRoot; event.motion.y_root = yRoot; GtkTargetList* targetList = gtk_target_list_new(0, 0); gtk_target_list_add_uri_targets(targetList, 0); gtk_drag_begin(view, targetList, GDK_ACTION_COPY, 1, &event); gtk_target_list_unref(targetList); return JSValueMakeUndefined(context); } static void sendOrQueueEvent(GdkEvent* event, bool shouldReplaySavedEvents) { // Mouse move events are queued if the previous event was queued or if a // delay was set up by leapForward(). if ((dragMode && buttonCurrentlyDown) || endOfQueue != startOfQueue || msgQueue[endOfQueue].delay) { msgQueue[endOfQueue++].event = event; if (shouldReplaySavedEvents) replaySavedEvents(); return; } dispatchEvent(event); } static void dispatchEvent(GdkEvent* event) { DumpRenderTreeSupportGtk::layoutFrame(mainFrame); WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) { gdk_event_free(event); return; } gtk_main_do_event(event); if (!currentDragSourceContext) { gdk_event_free(event); return; } if (event->type == GDK_MOTION_NOTIFY) { // WebKit has called gtk_drag_start(), but because the main loop isn't // running GDK internals don't know that the drag has started yet. Pump // the main loop a little bit so that GDK is in the correct state. while (gtk_events_pending()) gtk_main_iteration(); // Simulate a drag motion on the top-level GDK window. GtkWidget* parentWidget = gtk_widget_get_parent(GTK_WIDGET(view)); GdkWindow* parentWidgetWindow = gtk_widget_get_window(parentWidget); gdk_drag_motion(currentDragSourceContext, parentWidgetWindow, GDK_DRAG_PROTO_XDND, event->motion.x_root, event->motion.y_root, gdk_drag_context_get_selected_action(currentDragSourceContext), gdk_drag_context_get_actions(currentDragSourceContext), GDK_CURRENT_TIME); } else if (currentDragSourceContext && event->type == GDK_BUTTON_RELEASE) { // We've released the mouse button, we should just be able to spin the // event loop here and have GTK+ send the appropriate notifications for // the end of the drag. while (gtk_events_pending()) gtk_main_iteration(); } gdk_event_free(event); } void replaySavedEvents() { // First send all the events that are ready to be sent while (startOfQueue < endOfQueue) { if (msgQueue[startOfQueue].delay) { g_usleep(msgQueue[startOfQueue].delay * 1000); msgQueue[startOfQueue].delay = 0; } dispatchEvent(msgQueue[startOfQueue++].event); } startOfQueue = 0; endOfQueue = 0; } static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { if (argumentCount < 1) return JSValueMakeUndefined(context); guint modifiers = argumentCount >= 2 ? gdkModifersFromJSValue(context, arguments[1]) : 0; // handle location argument. int location = DOM_KEY_LOCATION_STANDARD; if (argumentCount > 2) location = (int)JSValueToNumber(context, arguments[2], exception); JSStringRef character = JSValueToStringCopy(context, arguments[0], exception); g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context)); int gdkKeySym = GDK_VoidSymbol; if (location == DOM_KEY_LOCATION_NUMPAD) { if (JSStringIsEqualToUTF8CString(character, "leftArrow")) gdkKeySym = GDK_KP_Left; else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) gdkKeySym = GDK_KP_Right; else if (JSStringIsEqualToUTF8CString(character, "upArrow")) gdkKeySym = GDK_KP_Up; else if (JSStringIsEqualToUTF8CString(character, "downArrow")) gdkKeySym = GDK_KP_Down; else if (JSStringIsEqualToUTF8CString(character, "pageUp")) gdkKeySym = GDK_KP_Page_Up; else if (JSStringIsEqualToUTF8CString(character, "pageDown")) gdkKeySym = GDK_KP_Page_Down; else if (JSStringIsEqualToUTF8CString(character, "home")) gdkKeySym = GDK_KP_Home; else if (JSStringIsEqualToUTF8CString(character, "end")) gdkKeySym = GDK_KP_End; else if (JSStringIsEqualToUTF8CString(character, "insert")) gdkKeySym = GDK_KP_Insert; else if (JSStringIsEqualToUTF8CString(character, "delete")) gdkKeySym = GDK_KP_Delete; else // If we get some other key specified with the numpad location, // crash here, so we add it sooner rather than later. g_assert_not_reached(); } else { if (JSStringIsEqualToUTF8CString(character, "leftArrow")) gdkKeySym = GDK_Left; else if (JSStringIsEqualToUTF8CString(character, "rightArrow")) gdkKeySym = GDK_Right; else if (JSStringIsEqualToUTF8CString(character, "upArrow")) gdkKeySym = GDK_Up; else if (JSStringIsEqualToUTF8CString(character, "downArrow")) gdkKeySym = GDK_Down; else if (JSStringIsEqualToUTF8CString(character, "pageUp")) gdkKeySym = GDK_Page_Up; else if (JSStringIsEqualToUTF8CString(character, "pageDown")) gdkKeySym = GDK_Page_Down; else if (JSStringIsEqualToUTF8CString(character, "home")) gdkKeySym = GDK_Home; else if (JSStringIsEqualToUTF8CString(character, "end")) gdkKeySym = GDK_End; else if (JSStringIsEqualToUTF8CString(character, "insert")) gdkKeySym = GDK_Insert; else if (JSStringIsEqualToUTF8CString(character, "delete")) gdkKeySym = GDK_Delete; else if (JSStringIsEqualToUTF8CString(character, "printScreen")) gdkKeySym = GDK_Print; else if (JSStringIsEqualToUTF8CString(character, "menu")) gdkKeySym = GDK_Menu; else if (JSStringIsEqualToUTF8CString(character, "F1")) gdkKeySym = GDK_F1; else if (JSStringIsEqualToUTF8CString(character, "F2")) gdkKeySym = GDK_F2; else if (JSStringIsEqualToUTF8CString(character, "F3")) gdkKeySym = GDK_F3; else if (JSStringIsEqualToUTF8CString(character, "F4")) gdkKeySym = GDK_F4; else if (JSStringIsEqualToUTF8CString(character, "F5")) gdkKeySym = GDK_F5; else if (JSStringIsEqualToUTF8CString(character, "F6")) gdkKeySym = GDK_F6; else if (JSStringIsEqualToUTF8CString(character, "F7")) gdkKeySym = GDK_F7; else if (JSStringIsEqualToUTF8CString(character, "F8")) gdkKeySym = GDK_F8; else if (JSStringIsEqualToUTF8CString(character, "F9")) gdkKeySym = GDK_F9; else if (JSStringIsEqualToUTF8CString(character, "F10")) gdkKeySym = GDK_F10; else if (JSStringIsEqualToUTF8CString(character, "F11")) gdkKeySym = GDK_F11; else if (JSStringIsEqualToUTF8CString(character, "F12")) gdkKeySym = GDK_F12; else { int charCode = JSStringGetCharactersPtr(character)[0]; if (charCode == '\n' || charCode == '\r') gdkKeySym = GDK_Return; else if (charCode == '\t') gdkKeySym = GDK_Tab; else if (charCode == '\x8') gdkKeySym = GDK_BackSpace; else { gdkKeySym = gdk_unicode_to_keyval(charCode); if (WTF::isASCIIUpper(charCode)) modifiers |= GDK_SHIFT_MASK; } } } JSStringRelease(character); WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return JSValueMakeUndefined(context); // create and send the event GdkEvent* pressEvent = gdk_event_new(GDK_KEY_PRESS); pressEvent->key.keyval = gdkKeySym; pressEvent->key.state = modifiers; pressEvent->key.window = gtk_widget_get_window(GTK_WIDGET(view)); g_object_ref(pressEvent->key.window); #ifndef GTK_API_VERSION_2 gdk_event_set_device(pressEvent, getDefaultGDKPointerDevice(pressEvent->key.window)); #endif // When synthesizing an event, an invalid hardware_keycode value // can cause it to be badly processed by Gtk+. GdkKeymapKey* keys; gint n_keys; if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys, &n_keys)) { pressEvent->key.hardware_keycode = keys[0].keycode; g_free(keys); } GdkEvent* releaseEvent = gdk_event_copy(pressEvent); dispatchEvent(pressEvent); releaseEvent->key.type = GDK_KEY_RELEASE; dispatchEvent(releaseEvent); return JSValueMakeUndefined(context); } static void zoomIn(gboolean fullContentsZoom) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return; webkit_web_view_set_full_content_zoom(view, fullContentsZoom); gfloat currentZoom = webkit_web_view_get_zoom_level(view); webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio); } static void zoomOut(gboolean fullContentsZoom) { WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame); if (!view) return; webkit_web_view_set_full_content_zoom(view, fullContentsZoom); gfloat currentZoom = webkit_web_view_get_zoom_level(view); webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio); } static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { zoomIn(FALSE); return JSValueMakeUndefined(context); } static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { zoomOut(FALSE); return JSValueMakeUndefined(context); } static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { zoomIn(TRUE); return JSValueMakeUndefined(context); } static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception) { zoomOut(TRUE); return JSValueMakeUndefined(context); } static JSStaticFunction staticFunctions[] = { { "mouseScrollBy", mouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "continuousMouseScrollBy", continuousMouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { "scheduleAsynchronousClick", scheduleAsynchronousClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete }, { 0, 0, 0 } }; static JSStaticValue staticValues[] = { { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone }, { 0, 0, 0, 0 } }; static JSClassRef getClass(JSContextRef context) { static JSClassRef eventSenderClass = 0; if (!eventSenderClass) { JSClassDefinition classDefinition = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; classDefinition.staticFunctions = staticFunctions; classDefinition.staticValues = staticValues; eventSenderClass = JSClassCreate(&classDefinition); } return eventSenderClass; } JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame) { if (isTopFrame) { dragMode = true; // Fly forward in time one second when the main frame loads. This will // ensure that when a test begins clicking in the same location as // a previous test, those clicks won't be interpreted as continuations // of the previous test's click sequences. timeOffset += 1000; lastMousePositionX = lastMousePositionY = 0; lastClickPositionX = lastClickPositionY = 0; lastClickTimeOffset = 0; lastClickButton = 0; buttonCurrentlyDown = 0; clickCount = 0; endOfQueue = 0; startOfQueue = 0; currentDragSourceContext = 0; } return JSObjectMake(context, getClass(context), 0); } void dragBeginCallback(GtkWidget*, GdkDragContext* context, gpointer) { currentDragSourceContext = context; } void dragEndCallback(GtkWidget*, GdkDragContext* context, gpointer) { currentDragSourceContext = 0; } gboolean dragFailedCallback(GtkWidget*, GdkDragContext* context, gpointer) { // Return TRUE here to disable the stupid GTK+ drag failed animation, // which introduces asynchronous behavior into our drags. return TRUE; }