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