WebKitTools/DumpRenderTree/gtk/EventSender.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     1 /*
       
     2  * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
       
     3  * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
       
     4  * Copyright (C) 2009 Holger Hans Peter Freyther
       
     5  *
       
     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 // FIXME: Implement support for synthesizing drop events.
       
    49 
       
    50 extern "C" {
       
    51     extern void webkit_web_frame_layout(WebKitWebFrame* frame);
       
    52 }
       
    53 
       
    54 static bool dragMode;
       
    55 static int timeOffset = 0;
       
    56 
       
    57 static int lastMousePositionX;
       
    58 static int lastMousePositionY;
       
    59 static int lastClickPositionX;
       
    60 static int lastClickPositionY;
       
    61 static int lastClickTimeOffset;
       
    62 static int lastClickButton;
       
    63 static int buttonCurrentlyDown;
       
    64 static int clickCount;
       
    65 
       
    66 struct DelayedMessage {
       
    67     GdkEvent event;
       
    68     gulong delay;
       
    69 };
       
    70 
       
    71 static DelayedMessage msgQueue[1024];
       
    72 
       
    73 static unsigned endOfQueue;
       
    74 static unsigned startOfQueue;
       
    75 
       
    76 static const float zoomMultiplierRatio = 1.2f;
       
    77 
       
    78 // Key event location code defined in DOM Level 3.
       
    79 enum KeyLocationCode {
       
    80     DOM_KEY_LOCATION_STANDARD      = 0x00,
       
    81     DOM_KEY_LOCATION_LEFT          = 0x01,
       
    82     DOM_KEY_LOCATION_RIGHT         = 0x02,
       
    83     DOM_KEY_LOCATION_NUMPAD        = 0x03
       
    84 };
       
    85 
       
    86 static void sendOrQueueEvent(GdkEvent, bool = true);
       
    87 static void dispatchEvent(GdkEvent event);
       
    88 static guint getStateFlags();
       
    89 
       
    90 #if !GTK_CHECK_VERSION(2, 17, 3)
       
    91 static void gdk_window_get_root_coords(GdkWindow* window, gint x, gint y, gint* rootX, gint* rootY)
       
    92 {
       
    93     gdk_window_get_root_origin(window, rootX, rootY);
       
    94     *rootX = *rootX + x;
       
    95     *rootY = *rootY + y;
       
    96 }
       
    97 #endif
       
    98 
       
    99 #if !GTK_CHECK_VERSION(2, 14, 0)
       
   100 static GdkWindow* gtk_widget_get_window(GtkWidget* widget)
       
   101 {
       
   102     g_return_val_if_fail(GTK_IS_WIDGET(widget), 0);
       
   103     return widget->window;
       
   104 }
       
   105 #endif
       
   106 
       
   107 static JSValueRef getDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef* exception)
       
   108 {
       
   109     return JSValueMakeBoolean(context, dragMode);
       
   110 }
       
   111 
       
   112 static bool setDragModeCallback(JSContextRef context, JSObjectRef object, JSStringRef propertyName, JSValueRef value, JSValueRef* exception)
       
   113 {
       
   114     dragMode = JSValueToBoolean(context, value);
       
   115     return true;
       
   116 }
       
   117 
       
   118 static JSValueRef leapForwardCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   119 {
       
   120     if (argumentCount > 0) {
       
   121         msgQueue[endOfQueue].delay = JSValueToNumber(context, arguments[0], exception);
       
   122         timeOffset += msgQueue[endOfQueue].delay;
       
   123         ASSERT(!exception || !*exception);
       
   124     }
       
   125 
       
   126     return JSValueMakeUndefined(context);
       
   127 }
       
   128 
       
   129 bool prepareMouseButtonEvent(GdkEvent* event, int eventSenderButtonNumber)
       
   130 {
       
   131     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   132     if (!view)
       
   133         return false;
       
   134 
       
   135     // The logic for mapping EventSender button numbers to GDK button
       
   136     // numbers originates from the Windows EventSender.
       
   137     int gdkButtonNumber = 3;
       
   138     if (eventSenderButtonNumber >= 0 && eventSenderButtonNumber <= 2)
       
   139         gdkButtonNumber = eventSenderButtonNumber + 1;
       
   140 
       
   141     // fast/events/mouse-click-events expects the 4th button
       
   142     // to be event.button = 1, so send a middle-button event.
       
   143     else if (eventSenderButtonNumber == 3)
       
   144         gdkButtonNumber = 2;
       
   145 
       
   146     memset(event, 0, sizeof(event));
       
   147     event->button.button = gdkButtonNumber;
       
   148     event->button.x = lastMousePositionX;
       
   149     event->button.y = lastMousePositionY;
       
   150     event->button.window = gtk_widget_get_window(GTK_WIDGET(view));
       
   151     event->button.device = gdk_device_get_core_pointer();
       
   152     event->button.state = getStateFlags();
       
   153     event->button.time = GDK_CURRENT_TIME;
       
   154 
       
   155     int xRoot, yRoot;
       
   156     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
       
   157     event->button.x_root = xRoot;
       
   158     event->button.y_root = yRoot;
       
   159 
       
   160     return true;
       
   161 }
       
   162 
       
   163 static JSValueRef contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   164 {
       
   165     GdkEvent event;
       
   166     if (!prepareMouseButtonEvent(&event, 2))
       
   167         return JSValueMakeUndefined(context);
       
   168 
       
   169     event.type = GDK_BUTTON_PRESS;
       
   170     sendOrQueueEvent(event);
       
   171     event.type = GDK_BUTTON_RELEASE;
       
   172     sendOrQueueEvent(event);
       
   173 
       
   174     return JSValueMakeUndefined(context);
       
   175 }
       
   176 
       
   177 static void updateClickCount(int button)
       
   178 {
       
   179     if (lastClickPositionX != lastMousePositionX
       
   180         || lastClickPositionY != lastMousePositionY
       
   181         || lastClickButton != button
       
   182         || timeOffset - lastClickTimeOffset >= 1)
       
   183         clickCount = 1;
       
   184     else
       
   185         clickCount++;
       
   186 }
       
   187 
       
   188 static JSValueRef mouseDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   189 {
       
   190     int button = 0;
       
   191     if (argumentCount == 1) {
       
   192         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
       
   193         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   194     }
       
   195 
       
   196     GdkEvent event;
       
   197     if (!prepareMouseButtonEvent(&event, button))
       
   198         return JSValueMakeUndefined(context);
       
   199 
       
   200     buttonCurrentlyDown = event.button.button;
       
   201 
       
   202     // Normally GDK will send both GDK_BUTTON_PRESS and GDK_2BUTTON_PRESS for
       
   203     // the second button press during double-clicks. WebKit GTK+ selectively
       
   204     // ignores the first GDK_BUTTON_PRESS of that pair using gdk_event_peek.
       
   205     // Since our events aren't ever going onto the GDK event queue, WebKit won't
       
   206     // be able to filter out the first GDK_BUTTON_PRESS, so we just don't send
       
   207     // it here. Eventually this code should probably figure out a way to get all
       
   208     // appropriate events onto the event queue and this work-around should be
       
   209     // removed.
       
   210     updateClickCount(event.button.button);
       
   211     if (clickCount == 2)
       
   212         event.type = GDK_2BUTTON_PRESS;
       
   213     else if (clickCount == 3)
       
   214         event.type = GDK_3BUTTON_PRESS;
       
   215     else
       
   216         event.type = GDK_BUTTON_PRESS;
       
   217 
       
   218     sendOrQueueEvent(event);
       
   219     return JSValueMakeUndefined(context);
       
   220 }
       
   221 
       
   222 static guint getStateFlags()
       
   223 {
       
   224     if (buttonCurrentlyDown == 1)
       
   225         return GDK_BUTTON1_MASK;
       
   226     if (buttonCurrentlyDown == 2)
       
   227         return GDK_BUTTON2_MASK;
       
   228     if (buttonCurrentlyDown == 3)
       
   229         return GDK_BUTTON3_MASK;
       
   230     return 0;
       
   231 }
       
   232 
       
   233 static JSValueRef mouseUpCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   234 {
       
   235     int button = 0;
       
   236     if (argumentCount == 1) {
       
   237         button = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
       
   238         g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   239     }
       
   240 
       
   241     GdkEvent event;
       
   242     if (!prepareMouseButtonEvent(&event, button))
       
   243         return JSValueMakeUndefined(context);
       
   244 
       
   245     lastClickPositionX = lastMousePositionX;
       
   246     lastClickPositionY = lastMousePositionY;
       
   247     lastClickButton = buttonCurrentlyDown;
       
   248     lastClickTimeOffset = timeOffset;
       
   249     buttonCurrentlyDown = 0;
       
   250 
       
   251     event.type = GDK_BUTTON_RELEASE;
       
   252     sendOrQueueEvent(event);
       
   253     return JSValueMakeUndefined(context);
       
   254 }
       
   255 
       
   256 static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   257 {
       
   258     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   259     if (!view)
       
   260         return JSValueMakeUndefined(context);
       
   261 
       
   262     if (argumentCount < 2)
       
   263         return JSValueMakeUndefined(context);
       
   264 
       
   265     lastMousePositionX = (int)JSValueToNumber(context, arguments[0], exception);
       
   266     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   267     lastMousePositionY = (int)JSValueToNumber(context, arguments[1], exception);
       
   268     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   269 
       
   270     GdkEvent event;
       
   271     memset(&event, 0, sizeof(event));
       
   272     event.type = GDK_MOTION_NOTIFY;
       
   273     event.motion.x = lastMousePositionX;
       
   274     event.motion.y = lastMousePositionY;
       
   275 
       
   276     event.motion.time = GDK_CURRENT_TIME;
       
   277     event.motion.window = gtk_widget_get_window(GTK_WIDGET(view));
       
   278     event.motion.device = gdk_device_get_core_pointer();
       
   279     event.motion.state = getStateFlags();
       
   280 
       
   281     int xRoot, yRoot;
       
   282     gdk_window_get_root_coords(gtk_widget_get_window(GTK_WIDGET(view)), lastMousePositionX, lastMousePositionY, &xRoot, &yRoot);
       
   283     event.motion.x_root = xRoot;
       
   284     event.motion.y_root = yRoot;
       
   285 
       
   286     sendOrQueueEvent(event, false);
       
   287     return JSValueMakeUndefined(context);
       
   288 }
       
   289 
       
   290 static JSValueRef mouseWheelToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   291 {
       
   292     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   293     if (!view)
       
   294         return JSValueMakeUndefined(context);
       
   295 
       
   296     if (argumentCount < 2)
       
   297         return JSValueMakeUndefined(context);
       
   298 
       
   299     int horizontal = (int)JSValueToNumber(context, arguments[0], exception);
       
   300     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   301     int vertical = (int)JSValueToNumber(context, arguments[1], exception);
       
   302     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   303 
       
   304     // GTK+ doesn't support multiple direction scrolls in the same event!
       
   305     g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
       
   306 
       
   307     GdkEvent event;
       
   308     event.type = GDK_SCROLL;
       
   309     event.scroll.x = lastMousePositionX;
       
   310     event.scroll.y = lastMousePositionY;
       
   311     event.scroll.time = GDK_CURRENT_TIME;
       
   312     event.scroll.window = gtk_widget_get_window(GTK_WIDGET(view));
       
   313 
       
   314     if (horizontal < 0)
       
   315         event.scroll.direction = GDK_SCROLL_LEFT;
       
   316     else if (horizontal > 0)
       
   317         event.scroll.direction = GDK_SCROLL_RIGHT;
       
   318     else if (vertical < 0)
       
   319         event.scroll.direction = GDK_SCROLL_UP;
       
   320     else if (vertical > 0)
       
   321         event.scroll.direction = GDK_SCROLL_DOWN;
       
   322     else
       
   323         g_assert_not_reached();
       
   324 
       
   325     sendOrQueueEvent(event);
       
   326     return JSValueMakeUndefined(context);
       
   327 }
       
   328 
       
   329 static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   330 {
       
   331     if (argumentCount < 1)
       
   332         return JSValueMakeUndefined(context);
       
   333 
       
   334     // FIXME: Implement this completely once WebCore has complete drag and drop support
       
   335     return JSValueMakeUndefined(context);
       
   336 }
       
   337 
       
   338 static void sendOrQueueEvent(GdkEvent event, bool shouldReplaySavedEvents)
       
   339 {
       
   340     // Mouse move events are queued if the previous event was queued or if a
       
   341     // delay was set up by leapForward().
       
   342     if ((dragMode && buttonCurrentlyDown) || endOfQueue != startOfQueue || msgQueue[endOfQueue].delay) {
       
   343         msgQueue[endOfQueue++].event = event;
       
   344 
       
   345         if (shouldReplaySavedEvents)
       
   346             replaySavedEvents();
       
   347 
       
   348         return;
       
   349     }
       
   350 
       
   351     dispatchEvent(event);
       
   352 }
       
   353 
       
   354 static void dispatchEvent(GdkEvent event)
       
   355 {
       
   356     webkit_web_frame_layout(mainFrame);
       
   357     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   358     if (!view)
       
   359         return;
       
   360 
       
   361     gtk_main_do_event(&event);
       
   362 }
       
   363 
       
   364 void replaySavedEvents()
       
   365 {
       
   366     // FIXME: Eventually we may need to have more sophisticated logic to
       
   367     // track drag-and-drop operations.
       
   368 
       
   369     // First send all the events that are ready to be sent
       
   370     while (startOfQueue < endOfQueue) {
       
   371         if (msgQueue[startOfQueue].delay) {
       
   372             g_usleep(msgQueue[startOfQueue].delay * 1000);
       
   373             msgQueue[startOfQueue].delay = 0;
       
   374         }
       
   375 
       
   376         dispatchEvent(msgQueue[startOfQueue++].event);
       
   377     }
       
   378 
       
   379     startOfQueue = 0;
       
   380     endOfQueue = 0;
       
   381 }
       
   382 
       
   383 static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   384 {
       
   385     if (argumentCount < 1)
       
   386         return JSValueMakeUndefined(context);
       
   387 
       
   388     static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
       
   389 
       
   390     webkit_web_frame_layout(mainFrame);
       
   391 
       
   392     // handle modifier keys.
       
   393     int state = 0;
       
   394     if (argumentCount > 1) {
       
   395         JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
       
   396         if (modifiersArray) {
       
   397             for (int i = 0; i < JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); ++i) {
       
   398                 JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
       
   399                 JSStringRef string = JSValueToStringCopy(context, value, 0);
       
   400                 if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
       
   401                     state |= GDK_CONTROL_MASK;
       
   402                 else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
       
   403                     state |= GDK_SHIFT_MASK;
       
   404                 else if (JSStringIsEqualToUTF8CString(string, "altKey"))
       
   405                     state |= GDK_MOD1_MASK;
       
   406 
       
   407                 JSStringRelease(string);
       
   408             }
       
   409         }
       
   410     }
       
   411 
       
   412     // handle location argument.
       
   413     int location = DOM_KEY_LOCATION_STANDARD;
       
   414     if (argumentCount > 2)
       
   415         location = (int)JSValueToNumber(context, arguments[2], exception);
       
   416 
       
   417     JSStringRef character = JSValueToStringCopy(context, arguments[0], exception);
       
   418     g_return_val_if_fail((!exception || !*exception), JSValueMakeUndefined(context));
       
   419     int gdkKeySym = GDK_VoidSymbol;
       
   420     if (location == DOM_KEY_LOCATION_NUMPAD) {
       
   421         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
       
   422             gdkKeySym = GDK_KP_Left;
       
   423         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
       
   424             gdkKeySym = GDK_KP_Right;
       
   425         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
       
   426             gdkKeySym = GDK_KP_Up;
       
   427         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
       
   428             gdkKeySym = GDK_KP_Down;
       
   429         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
       
   430             gdkKeySym = GDK_KP_Page_Up;
       
   431         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
       
   432             gdkKeySym = GDK_KP_Page_Down;
       
   433         else if (JSStringIsEqualToUTF8CString(character, "home"))
       
   434             gdkKeySym = GDK_KP_Home;
       
   435         else if (JSStringIsEqualToUTF8CString(character, "end"))
       
   436             gdkKeySym = GDK_KP_End;
       
   437         else
       
   438             // Assume we only get arrow/pgUp/pgDn/home/end keys with
       
   439             // location=NUMPAD for now.
       
   440             g_assert_not_reached();
       
   441     } else {
       
   442         if (JSStringIsEqualToUTF8CString(character, "leftArrow"))
       
   443             gdkKeySym = GDK_Left;
       
   444         else if (JSStringIsEqualToUTF8CString(character, "rightArrow"))
       
   445             gdkKeySym = GDK_Right;
       
   446         else if (JSStringIsEqualToUTF8CString(character, "upArrow"))
       
   447             gdkKeySym = GDK_Up;
       
   448         else if (JSStringIsEqualToUTF8CString(character, "downArrow"))
       
   449             gdkKeySym = GDK_Down;
       
   450         else if (JSStringIsEqualToUTF8CString(character, "pageUp"))
       
   451             gdkKeySym = GDK_Page_Up;
       
   452         else if (JSStringIsEqualToUTF8CString(character, "pageDown"))
       
   453             gdkKeySym = GDK_Page_Down;
       
   454         else if (JSStringIsEqualToUTF8CString(character, "home"))
       
   455             gdkKeySym = GDK_Home;
       
   456         else if (JSStringIsEqualToUTF8CString(character, "end"))
       
   457             gdkKeySym = GDK_End;
       
   458         else if (JSStringIsEqualToUTF8CString(character, "delete"))
       
   459             gdkKeySym = GDK_Delete;
       
   460         else if (JSStringIsEqualToUTF8CString(character, "F1"))
       
   461             gdkKeySym = GDK_F1;
       
   462         else if (JSStringIsEqualToUTF8CString(character, "F2"))
       
   463             gdkKeySym = GDK_F2;
       
   464         else if (JSStringIsEqualToUTF8CString(character, "F3"))
       
   465             gdkKeySym = GDK_F3;
       
   466         else if (JSStringIsEqualToUTF8CString(character, "F4"))
       
   467             gdkKeySym = GDK_F4;
       
   468         else if (JSStringIsEqualToUTF8CString(character, "F5"))
       
   469             gdkKeySym = GDK_F5;
       
   470         else if (JSStringIsEqualToUTF8CString(character, "F6"))
       
   471             gdkKeySym = GDK_F6;
       
   472         else if (JSStringIsEqualToUTF8CString(character, "F7"))
       
   473             gdkKeySym = GDK_F7;
       
   474         else if (JSStringIsEqualToUTF8CString(character, "F8"))
       
   475             gdkKeySym = GDK_F8;
       
   476         else if (JSStringIsEqualToUTF8CString(character, "F9"))
       
   477             gdkKeySym = GDK_F9;
       
   478         else if (JSStringIsEqualToUTF8CString(character, "F10"))
       
   479             gdkKeySym = GDK_F10;
       
   480         else if (JSStringIsEqualToUTF8CString(character, "F11"))
       
   481             gdkKeySym = GDK_F11;
       
   482         else if (JSStringIsEqualToUTF8CString(character, "F12"))
       
   483             gdkKeySym = GDK_F12;
       
   484         else {
       
   485             int charCode = JSStringGetCharactersPtr(character)[0];
       
   486             if (charCode == '\n' || charCode == '\r')
       
   487                 gdkKeySym = GDK_Return;
       
   488             else if (charCode == '\t')
       
   489                 gdkKeySym = GDK_Tab;
       
   490             else if (charCode == '\x8')
       
   491                 gdkKeySym = GDK_BackSpace;
       
   492             else {
       
   493                 gdkKeySym = gdk_unicode_to_keyval(charCode);
       
   494                 if (WTF::isASCIIUpper(charCode))
       
   495                     state |= GDK_SHIFT_MASK;
       
   496             }
       
   497         }
       
   498     }
       
   499     JSStringRelease(character);
       
   500 
       
   501     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   502     if (!view)
       
   503         return JSValueMakeUndefined(context);
       
   504 
       
   505     // create and send the event
       
   506     GdkEvent event;
       
   507     memset(&event, 0, sizeof(event));
       
   508     event.key.keyval = gdkKeySym;
       
   509     event.key.state = state;
       
   510     event.key.window = gtk_widget_get_window(GTK_WIDGET(view));
       
   511 
       
   512     // When synthesizing an event, an invalid hardware_keycode value
       
   513     // can cause it to be badly processed by Gtk+.
       
   514     GdkKeymapKey* keys;
       
   515     gint n_keys;
       
   516     if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys, &n_keys)) {
       
   517         event.key.hardware_keycode = keys[0].keycode;
       
   518         g_free(keys);
       
   519     }
       
   520 
       
   521     event.key.type = GDK_KEY_PRESS;
       
   522     dispatchEvent(event);
       
   523 
       
   524     event.key.type = GDK_KEY_RELEASE;
       
   525     dispatchEvent(event);
       
   526 
       
   527     return JSValueMakeUndefined(context);
       
   528 }
       
   529 
       
   530 static void zoomIn(gboolean fullContentsZoom)
       
   531 {
       
   532     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   533     if (!view)
       
   534         return;
       
   535 
       
   536     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
       
   537     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
       
   538     webkit_web_view_set_zoom_level(view, currentZoom * zoomMultiplierRatio);
       
   539 }
       
   540 
       
   541 static void zoomOut(gboolean fullContentsZoom)
       
   542 {
       
   543     WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
       
   544     if (!view)
       
   545         return;
       
   546 
       
   547     webkit_web_view_set_full_content_zoom(view, fullContentsZoom);
       
   548     gfloat currentZoom = webkit_web_view_get_zoom_level(view);
       
   549     webkit_web_view_set_zoom_level(view, currentZoom / zoomMultiplierRatio);
       
   550 }
       
   551 
       
   552 static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   553 {
       
   554     zoomIn(FALSE);
       
   555     return JSValueMakeUndefined(context);
       
   556 }
       
   557 
       
   558 static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   559 {
       
   560     zoomOut(FALSE);
       
   561     return JSValueMakeUndefined(context);
       
   562 }
       
   563 
       
   564 static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   565 {
       
   566     zoomIn(TRUE);
       
   567     return JSValueMakeUndefined(context);
       
   568 }
       
   569 
       
   570 static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
       
   571 {
       
   572     zoomOut(TRUE);
       
   573     return JSValueMakeUndefined(context);
       
   574 }
       
   575 
       
   576 static JSStaticFunction staticFunctions[] = {
       
   577     { "mouseWheelTo", mouseWheelToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   578     { "contextClick", contextClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   579     { "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   580     { "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   581     { "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   582     { "beginDragWithFiles", beginDragWithFilesCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   583     { "leapForward", leapForwardCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   584     { "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   585     { "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   586     { "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   587     { "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   588     { "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
       
   589     { 0, 0, 0 }
       
   590 };
       
   591 
       
   592 static JSStaticValue staticValues[] = {
       
   593     { "dragMode", getDragModeCallback, setDragModeCallback, kJSPropertyAttributeNone },
       
   594     { 0, 0, 0, 0 }
       
   595 };
       
   596 
       
   597 static JSClassRef getClass(JSContextRef context)
       
   598 {
       
   599     static JSClassRef eventSenderClass = 0;
       
   600 
       
   601     if (!eventSenderClass) {
       
   602         JSClassDefinition classDefinition = {
       
   603                 0, 0, 0, 0, 0, 0,
       
   604                 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
       
   605         classDefinition.staticFunctions = staticFunctions;
       
   606         classDefinition.staticValues = staticValues;
       
   607 
       
   608         eventSenderClass = JSClassCreate(&classDefinition);
       
   609     }
       
   610 
       
   611     return eventSenderClass;
       
   612 }
       
   613 
       
   614 JSObjectRef makeEventSender(JSContextRef context, bool isTopFrame)
       
   615 {
       
   616     if (isTopFrame) {
       
   617         dragMode = true;
       
   618 
       
   619         // Fly forward in time one second when the main frame loads. This will
       
   620         // ensure that when a test begins clicking in the same location as
       
   621         // a previous test, those clicks won't be interpreted as continuations
       
   622         // of the previous test's click sequences.
       
   623         timeOffset += 1000;
       
   624 
       
   625         lastMousePositionX = lastMousePositionY = 0;
       
   626         lastClickPositionX = lastClickPositionY = 0;
       
   627         lastClickTimeOffset = 0;
       
   628         lastClickButton = 0;
       
   629         buttonCurrentlyDown = 0;
       
   630         clickCount = 0;
       
   631 
       
   632         endOfQueue = 0;
       
   633         startOfQueue = 0;
       
   634     }
       
   635 
       
   636     return JSObjectMake(context, getClass(context), 0);
       
   637 }