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