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