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  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  *
     10  * 1.  Redistributions of source code must retain the above copyright
     11  *     notice, this list of conditions and the following disclaimer.
     12  * 2.  Redistributions in binary form must reproduce the above copyright
     13  *     notice, this list of conditions and the following disclaimer in the
     14  *     documentation and/or other materials provided with the distribution.
     15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     16  *     its contributors may be used to endorse or promote products derived
     17  *     from this software without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "EventSender.h"
     33 
     34 #include "DumpRenderTree.h"
     35 
     36 #include <JavaScriptCore/JSObjectRef.h>
     37 #include <JavaScriptCore/JSRetainPtr.h>
     38 #include <JavaScriptCore/JSStringRef.h>
     39 #include <webkit/webkitwebframe.h>
     40 #include <webkit/webkitwebview.h>
     41 #include <wtf/ASCIICType.h>
     42 #include <wtf/Platform.h>
     43 
     44 #include <gdk/gdk.h>
     45 #include <gdk/gdkkeysyms.h>
     46 #include <string.h>
     47 
     48 // TODO: Currently drag and drop related code is left out and
     49 // should be merged once we have drag and drop support in WebCore.
     50 
     51 extern "C" {
     52     extern void webkit_web_frame_layout(WebKitWebFrame* frame);
     53 }
     54 
     55 static bool down = false;
     56 static bool currentEventButton = 1;
     57 static bool dragMode = true;
     58 static bool replayingSavedEvents = false;
     59 static int lastMousePositionX;
     60 static int lastMousePositionY;
     61 
     62 static int lastClickPositionX;
     63 static int lastClickPositionY;
     64 static int clickCount = 0;
     65 
     66 struct DelayedMessage {
     67     GdkEvent event;
     68     gulong delay;
     69     gboolean isDragEvent;
     70 };
     71 
     72 static DelayedMessage msgQueue[1024];
     73 
     74 static unsigned endOfQueue;
     75 static unsigned startOfQueue;
     76 
     77 static const float zoomMultiplierRatio = 1.2f;
     78 
     79 // Key event location code defined in DOM Level 3.
     80 enum KeyLocationCode {
     81     DOM_KEY_LOCATION_STANDARD      = 0x00,
     82     DOM_KEY_LOCATION_LEFT          = 0x01,
     83     DOM_KEY_LOCATION_RIGHT         = 0x02,
     84     DOM_KEY_LOCATION_NUMPAD        = 0x03
     85 };
     86 
     87 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
     88 {
     89     return JSValueMakeBoolean(context, dragMode);
     90 }
     91 
     92 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
     93 {
     94     dragMode = JSValueToBoolean(context, value);
     95     return true;
     96 }
     97 
     98 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
     99 {
    100     // FIXME: Add proper support for forward leaps
    101     return JSValueMakeUndefined(context);
    102 }
    103 
    104 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    105 {
    106     webkit_web_frame_layout(mainFrame);
    107 
    108     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    109     if (!view)
    110         return JSValueMakeUndefined(context);
    111 
    112     GdkEvent event;
    113     memset(&event, 0, sizeof(event));
    114     event.button.button = 3;
    115     event.button.x = lastMousePositionX;
    116     event.button.y = lastMousePositionY;
    117     event.button.window = GTK_WIDGET(view)->window;
    118 
    119     gboolean return_val;
    120     down = true;
    121     event.type = GDK_BUTTON_PRESS;
    122     g_signal_emit_by_name(view, "button_press_event", &event, &return_val);
    123 
    124     down = false;
    125     event.type = GDK_BUTTON_RELEASE;
    126     g_signal_emit_by_name(view, "button_release_event", &event, &return_val);
    127 
    128     return JSValueMakeUndefined(context);
    129 }
    130 
    131 static void updateClickCount(int button)
    132 {
    133     // FIXME: take the last clicked button number and the time of last click into account.
    134     if (lastClickPositionX != lastMousePositionX || lastClickPositionY != lastMousePositionY || currentEventButton != button)
    135         clickCount = 1;
    136     else
    137         clickCount++;
    138 }
    139 
    140 #if !GTK_CHECK_VERSION(2,17,3)
    141 static void getRootCoords(GtkWidget* view, int* rootX, int* rootY)
    142 {
    143     GtkWidget* window = gtk_widget_get_toplevel(GTK_WIDGET(view));
    144     int tmpX, tmpY;
    145 
    146     gtk_widget_translate_coordinates(view, window, lastMousePositionX, lastMousePositionY, &tmpX, &tmpY);
    147 
    148     gdk_window_get_origin(window->window, rootX, rootY);
    149 
    150     *rootX += tmpX;
    151     *rootY += tmpY;
    152 }
    153 #endif
    154 
    155 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    156 {
    157     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    158     if (!view)
    159         return JSValueMakeUndefined(context);
    160 
    161     down = true;
    162 
    163     GdkEvent event;
    164     memset(&event, 0, sizeof(event));
    165     event.type = GDK_BUTTON_PRESS;
    166     event.button.button = 1;
    167 
    168     if (argumentCount == 1) {
    169         event.button.button = (int)JSValueToNumber(context, arguments[0], exception) + 1;
    170         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    171     }
    172 
    173     currentEventButton = event.button.button;
    174 
    175     event.button.x = lastMousePositionX;
    176     event.button.y = lastMousePositionY;
    177     event.button.window = GTK_WIDGET(view)->window;
    178     event.button.time = GDK_CURRENT_TIME;
    179     event.button.device = gdk_device_get_core_pointer();
    180 
    181     int x_root, y_root;
    182 #if GTK_CHECK_VERSION(2,17,3)
    183     gdk_window_get_root_coords(GTK_WIDGET(view)->window, lastMousePositionX, lastMousePositionY, &x_root, &y_root);
    184 #else
    185     getRootCoords(GTK_WIDGET(view), &x_root, &y_root);
    186 #endif
    187 
    188     event.button.x_root = x_root;
    189     event.button.y_root = y_root;
    190 
    191     updateClickCount(event.button.button);
    192 
    193     if (!msgQueue[endOfQueue].delay) {
    194         webkit_web_frame_layout(mainFrame);
    195 
    196         gboolean return_val;
    197         g_signal_emit_by_name(view, "button_press_event", &event, &return_val);
    198         if (clickCount == 2) {
    199             event.type = GDK_2BUTTON_PRESS;
    200             g_signal_emit_by_name(view, "button_press_event", &event, &return_val);
    201         }
    202     } else {
    203         // replaySavedEvents should have the required logic to make leapForward delays work
    204         msgQueue[endOfQueue++].event = event;
    205         replaySavedEvents();
    206     }
    207 
    208     return JSValueMakeUndefined(context);
    209 }
    210 
    211 static guint getStateFlags()
    212 {
    213     guint state = 0;
    214 
    215     if (down) {
    216         if (currentEventButton == 1)
    217             state = GDK_BUTTON1_MASK;
    218         else if (currentEventButton == 2)
    219             state = GDK_BUTTON2_MASK;
    220         else if (currentEventButton == 3)
    221             state = GDK_BUTTON3_MASK;
    222     } else
    223         state = 0;
    224 
    225     return state;
    226 }
    227 
    228 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    229 {
    230 
    231     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    232     if (!view)
    233         return JSValueMakeUndefined(context);
    234 
    235     GdkEvent event;
    236     memset(&event, 0, sizeof(event));
    237     event.type = GDK_BUTTON_RELEASE;
    238     event.button.button = 1;
    239 
    240     if (argumentCount == 1) {
    241         event.button.button = (int)JSValueToNumber(context, arguments[0], exception) + 1;
    242         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    243     }
    244 
    245     currentEventButton = event.button.button;
    246 
    247     event.button.x = lastMousePositionX;
    248     event.button.y = lastMousePositionY;
    249     event.button.window = GTK_WIDGET(view)->window;
    250     event.button.time = GDK_CURRENT_TIME;
    251     event.button.device = gdk_device_get_core_pointer();
    252     event.button.state = getStateFlags();
    253 
    254     down = false;
    255 
    256     int x_root, y_root;
    257 #if GTK_CHECK_VERSION(2,17,3)
    258     gdk_window_get_root_coords(GTK_WIDGET(view)->window, lastMousePositionX, lastMousePositionY, &x_root, &y_root);
    259 #else
    260     getRootCoords(GTK_WIDGET(view), &x_root, &y_root);
    261 #endif
    262 
    263     event.button.x_root = x_root;
    264     event.button.y_root = y_root;
    265 
    266     if ((dragMode && !replayingSavedEvents) || msgQueue[endOfQueue].delay) {
    267         msgQueue[endOfQueue].event = event;
    268         msgQueue[endOfQueue++].isDragEvent = true;
    269         replaySavedEvents();
    270     } else {
    271         webkit_web_frame_layout(mainFrame);
    272 
    273         gboolean return_val;
    274         g_signal_emit_by_name(view, "button_release_event", &event, &return_val);
    275     }
    276 
    277     lastClickPositionX = lastMousePositionX;
    278     lastClickPositionY = lastMousePositionY;
    279 
    280     return JSValueMakeUndefined(context);
    281 }
    282 
    283 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    284 {
    285     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    286     if (!view)
    287         return JSValueMakeUndefined(context);
    288 
    289     if (argumentCount < 2)
    290         return JSValueMakeUndefined(context);
    291 
    292     lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
    293     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    294     lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
    295     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    296 
    297     GdkEvent event;
    298     memset(&event, 0, sizeof(event));
    299     event.type = GDK_MOTION_NOTIFY;
    300     event.motion.x = lastMousePositionX;
    301     event.motion.y = lastMousePositionY;
    302     event.motion.time = GDK_CURRENT_TIME;
    303     event.motion.window = GTK_WIDGET(view)->window;
    304     event.motion.device = gdk_device_get_core_pointer();
    305 
    306     int x_root, y_root;
    307 #if GTK_CHECK_VERSION(2,17,3)
    308     gdk_window_get_root_coords(GTK_WIDGET(view)->window, lastMousePositionX, lastMousePositionY, &x_root, &y_root);
    309 #else
    310     getRootCoords(GTK_WIDGET(view), &x_root, &y_root);
    311 #endif
    312 
    313     event.motion.x_root = x_root;
    314     event.motion.y_root = y_root;
    315 
    316     event.motion.state = getStateFlags();
    317 
    318     if (dragMode && down && !replayingSavedEvents) {
    319         msgQueue[endOfQueue].event = event;
    320         msgQueue[endOfQueue++].isDragEvent = true;
    321     } else {
    322         webkit_web_frame_layout(mainFrame);
    323 
    324         gboolean return_val;
    325         g_signal_emit_by_name(view, "motion_notify_event", &event, &return_val);
    326     }
    327 
    328     return JSValueMakeUndefined(context);
    329 }
    330 
    331 static JSValueRef mouseWheelToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    332 {
    333     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    334     if (!view)
    335         return JSValueMakeUndefined(context);
    336 
    337     if (argumentCount < 2)
    338         return JSValueMakeUndefined(context);
    339 
    340     int horizontal = (int)JSValueToNumber(context, arguments[0], exception);
    341     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    342     int vertical = (int)JSValueToNumber(context, arguments[1], exception);
    343     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    344 
    345     // GTK+ doesn't support multiple direction scrolls in the same event!
    346     g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
    347 
    348     GdkEvent event;
    349     event.type = GDK_SCROLL;
    350     event.scroll.x = lastMousePositionX;
    351     event.scroll.y = lastMousePositionY;
    352     event.scroll.time = GDK_CURRENT_TIME;
    353     event.scroll.window = GTK_WIDGET(view)->window;
    354 
    355     if (horizontal < 0)
    356         event.scroll.direction = GDK_SCROLL_LEFT;
    357     else if (horizontal > 0)
    358         event.scroll.direction = GDK_SCROLL_RIGHT;
    359     else if (vertical < 0)
    360         event.scroll.direction = GDK_SCROLL_UP;
    361     else if (vertical > 0)
    362         event.scroll.direction = GDK_SCROLL_DOWN;
    363     else
    364         g_assert_not_reached();
    365 
    366     if (dragMode && down && !replayingSavedEvents) {
    367         msgQueue[endOfQueue].event = event;
    368         msgQueue[endOfQueue++].isDragEvent = true;
    369     } else {
    370         webkit_web_frame_layout(mainFrame);
    371         gtk_main_do_event(&event);
    372     }
    373 
    374     return JSValueMakeUndefined(context);
    375 }
    376 
    377 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    378 {
    379     if (argumentCount < 1)
    380         return JSValueMakeUndefined(context);
    381 
    382     // FIXME: Implement this completely once WebCore has complete drag and drop support
    383     return JSValueMakeUndefined(context);
    384 }
    385 
    386 void replaySavedEvents()
    387 {
    388     // FIXME: This doesn't deal with forward leaps, but it should.
    389 
    390     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    391     if (!view)
    392         return;
    393 
    394     replayingSavedEvents = true;
    395 
    396     for (unsigned queuePos = 0; queuePos < endOfQueue; queuePos++) {
    397         GdkEvent event = msgQueue[queuePos].event;
    398         gboolean return_val;
    399 
    400         switch (event.type) {
    401         case GDK_BUTTON_RELEASE:
    402             g_signal_emit_by_name(view, "button_release_event", &event, &return_val);
    403             break;
    404         case GDK_BUTTON_PRESS:
    405             g_signal_emit_by_name(view, "button_press_event", &event, &return_val);
    406             break;
    407         case GDK_MOTION_NOTIFY:
    408             g_signal_emit_by_name(view, "motion_notify_event", &event, &return_val);
    409             break;
    410         default:
    411             continue;
    412         }
    413 
    414         startOfQueue++;
    415     }
    416 
    417     int numQueuedMessages = endOfQueue - startOfQueue;
    418     if (!numQueuedMessages) {
    419         startOfQueue = 0;
    420         endOfQueue = 0;
    421         replayingSavedEvents = false;
    422         return;
    423     }
    424 
    425     startOfQueue = 0;
    426     endOfQueue = 0;
    427 
    428     replayingSavedEvents = false;
    429 }
    430 
    431 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    432 {
    433     if (argumentCount < 1)
    434         return JSValueMakeUndefined(context);
    435 
    436     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
    437 
    438     webkit_web_frame_layout(mainFrame);
    439 
    440     // handle modifier keys.
    441     int state = 0;
    442     if (argumentCount > 1) {
    443         JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
    444         if (modifiersArray) {
    445             for (int i = 0; i < JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); ++i) {
    446                 JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
    447                 JSStringRef string = JSValueToStringCopy(context, value, 0);
    448                 if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
    449                     state |= GDK_CONTROL_MASK;
    450                 else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
    451                     state |= GDK_SHIFT_MASK;
    452                 else if (JSStringIsEqualToUTF8CString(string, "altKey"))
    453                     state |= GDK_MOD1_MASK;
    454 
    455                 JSStringRelease(string);
    456             }
    457         }
    458     }
    459 
    460     // handle location argument.
    461     int location = DOM_KEY_LOCATION_STANDARD;
    462     if (argumentCount > 2)
    463         location = (int)JSValueToNumber(context, arguments[2], exception);
    464 
    465     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
    466     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
    467     int gdkKeySym = GDK_VoidSymbol;
    468     if (location == DOM_KEY_LOCATION_NUMPAD) {
    469         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
    470             gdkKeySym = GDK_KP_Left;
    471         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
    472             gdkKeySym = GDK_KP_Right;
    473         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
    474             gdkKeySym = GDK_KP_Up;
    475         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
    476             gdkKeySym = GDK_KP_Down;
    477         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
    478             gdkKeySym = GDK_KP_Page_Up;
    479         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
    480             gdkKeySym = GDK_KP_Page_Down;
    481         else if (JSStringIsEqualToUTF8CString(character, "home"))
    482             gdkKeySym = GDK_KP_Home;
    483         else if (JSStringIsEqualToUTF8CString(character, "end"))
    484             gdkKeySym = GDK_KP_End;
    485         else
    486             // Assume we only get arrow/pgUp/pgDn/home/end keys with
    487             // location=NUMPAD for now.
    488             g_assert_not_reached();
    489     } else {
    490         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
    491             gdkKeySym = GDK_Left;
    492         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
    493             gdkKeySym = GDK_Right;
    494         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
    495             gdkKeySym = GDK_Up;
    496         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
    497             gdkKeySym = GDK_Down;
    498         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
    499             gdkKeySym = GDK_Page_Up;
    500         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
    501             gdkKeySym = GDK_Page_Down;
    502         else if (JSStringIsEqualToUTF8CString(character, "home"))
    503             gdkKeySym = GDK_Home;
    504         else if (JSStringIsEqualToUTF8CString(character, "end"))
    505             gdkKeySym = GDK_End;
    506         else if (JSStringIsEqualToUTF8CString(character, "delete"))
    507             gdkKeySym = GDK_BackSpace;
    508         else if (JSStringIsEqualToUTF8CString(character, "F1"))
    509             gdkKeySym = GDK_F1;
    510         else if (JSStringIsEqualToUTF8CString(character, "F2"))
    511             gdkKeySym = GDK_F2;
    512         else if (JSStringIsEqualToUTF8CString(character, "F3"))
    513             gdkKeySym = GDK_F3;
    514         else if (JSStringIsEqualToUTF8CString(character, "F4"))
    515             gdkKeySym = GDK_F4;
    516         else if (JSStringIsEqualToUTF8CString(character, "F5"))
    517             gdkKeySym = GDK_F5;
    518         else if (JSStringIsEqualToUTF8CString(character, "F6"))
    519             gdkKeySym = GDK_F6;
    520         else if (JSStringIsEqualToUTF8CString(character, "F7"))
    521             gdkKeySym = GDK_F7;
    522         else if (JSStringIsEqualToUTF8CString(character, "F8"))
    523             gdkKeySym = GDK_F8;
    524         else if (JSStringIsEqualToUTF8CString(character, "F9"))
    525             gdkKeySym = GDK_F9;
    526         else if (JSStringIsEqualToUTF8CString(character, "F10"))
    527             gdkKeySym = GDK_F10;
    528         else if (JSStringIsEqualToUTF8CString(character, "F11"))
    529             gdkKeySym = GDK_F11;
    530         else if (JSStringIsEqualToUTF8CString(character, "F12"))
    531             gdkKeySym = GDK_F12;
    532         else {
    533             int charCode = JSStringGetCharactersPtr(character)[0];
    534             if (charCode == '\n' || charCode == '\r')
    535                 gdkKeySym = GDK_Return;
    536             else if (charCode == '\t')
    537                 gdkKeySym = GDK_Tab;
    538             else if (charCode == '\x8')
    539                 gdkKeySym = GDK_BackSpace;
    540             else {
    541                 gdkKeySym = gdk_unicode_to_keyval(charCode);
    542                 if (WTF::isASCIIUpper(charCode))
    543                     state |= GDK_SHIFT_MASK;
    544             }
    545         }
    546     }
    547     JSStringRelease(character);
    548 
    549     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    550     if (!view)
    551         return JSValueMakeUndefined(context);
    552 
    553     // create and send the event
    554     GdkEvent event;
    555     memset(&event, 0, sizeof(event));
    556     event.key.keyval = gdkKeySym;
    557     event.key.state = state;
    558     event.key.window = GTK_WIDGET(view)->window;
    559 
    560     gboolean return_val;
    561     event.key.type = GDK_KEY_PRESS;
    562     g_signal_emit_by_name(view, "key-press-event", &event.key, &return_val);
    563 
    564     event.key.type = GDK_KEY_RELEASE;
    565     g_signal_emit_by_name(view, "key-release-event", &event.key, &return_val);
    566 
    567     return JSValueMakeUndefined(context);
    568 }
    569 
    570 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    571 {
    572     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    573     if (!view)
    574         return JSValueMakeUndefined(context);
    575 
    576     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
    577     webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
    578 
    579     return JSValueMakeUndefined(context);
    580 }
    581 
    582 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    583 {
    584     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    585     if (!view)
    586         return JSValueMakeUndefined(context);
    587 
    588     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
    589     webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
    590 
    591     return JSValueMakeUndefined(context);
    592 }
    593 
    594 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    595 {
    596     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    597     if (!view)
    598         return JSValueMakeUndefined(context);
    599 
    600     webkit_web_view_zoom_in(view);
    601     return JSValueMakeUndefined(context);
    602 }
    603 
    604 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
    605 {
    606     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
    607     if (!view)
    608         return JSValueMakeUndefined(context);
    609 
    610     webkit_web_view_zoom_out(view);
    611     return JSValueMakeUndefined(context);
    612 }
    613 
    614 static JSStaticFunction staticFunctions[] = {
    615     { "mouseWheelTo", mouseWheelToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    616     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    617     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    618     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    619     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    620     { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    621     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    622     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    623     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    624     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    625     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    626     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
    627     { 0, 0, 0 }
    628 };
    629 
    630 static JSStaticValue staticValues[] = {
    631     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
    632     { 0, 0, 0, 0 }
    633 };
    634 
    635 static JSClassRef getClass(JSContextRef context)
    636 {
    637     static JSClassRef eventSenderClass = 0;
    638 
    639     if (!eventSenderClass) {
    640         JSClassDefinition classDefinition = {
    641                 0, 0, 0, 0, 0, 0,
    642                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    643         classDefinition.staticFunctions = staticFunctions;
    644         classDefinition.staticValues = staticValues;
    645 
    646         eventSenderClass = JSClassCreate(&classDefinition);
    647     }
    648 
    649     return eventSenderClass;
    650 }
    651 
    652 JSObjectRef makeEventSender(JSContextRef context)
    653 {
    654     down = false;
    655     dragMode = true;
    656     lastMousePositionX = lastMousePositionY = 0;
    657     lastClickPositionX = lastClickPositionY = 0;
    658 
    659     if (!replayingSavedEvents) {
    660         // This function can be called in the middle of a test, even
    661         // while replaying saved events. Resetting these while doing that
    662         // can break things.
    663         endOfQueue = 0;
    664         startOfQueue = 0;
    665     }
    666 
    667     return JSObjectMake(context, getClass(context), 0);
    668 }
    669