--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebKitTools/DumpRenderTree/gtk/EventSender.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
+ * Copyright (C) 2009 Zan Dobersek <zandobersek@gmail.com>
+ * Copyright (C) 2009 Holger Hans Peter Freyther
+ *
+ * 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 <JavaScriptCore/JSObjectRef.h>
+#include <JavaScriptCore/JSRetainPtr.h>
+#include <JavaScriptCore/JSStringRef.h>
+#include <webkit/webkitwebframe.h>
+#include <webkit/webkitwebview.h>
+#include <wtf/ASCIICType.h>
+#include <wtf/Platform.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkkeysyms.h>
+#include <string.h>
+
+// FIXME: Implement support for synthesizing drop events.
+
+extern "C" {
+ extern void webkit_web_frame_layout(WebKitWebFrame* frame);
+}
+
+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 int buttonCurrentlyDown;
+static int clickCount;
+
+struct DelayedMessage {
+ GdkEvent event;
+ gulong delay;
+};
+
+static DelayedMessage msgQueue[1024];
+
+static unsigned endOfQueue;
+static unsigned startOfQueue;
+
+static const float zoomMultiplierRatio = 1.2f;
+
+// 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();
+
+#if !GTK_CHECK_VERSION(2, 17, 3)
+static void gdk_window_get_root_coords(GdkWindow* window, gint x, gint y, gint* rootX, gint* rootY)
+{
+ gdk_window_get_root_origin(window, rootX, rootY);
+ *rootX = *rootX + x;
+ *rootY = *rootY + y;
+}
+#endif
+
+#if !GTK_CHECK_VERSION(2, 14, 0)
+static GdkWindow* gtk_widget_get_window(GtkWidget* widget)
+{
+ g_return_val_if_fail(GTK_IS_WIDGET(widget), 0);
+ return widget->window;
+}
+#endif
+
+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)
+{
+ 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;
+
+ memset(event, 0, sizeof(event));
+ event->button.button = gdkButtonNumber;
+ event->button.x = lastMousePositionX;
+ event->button.y = lastMousePositionY;
+ event->button.window = gtk_widget_get_window(GTK_WIDGET(view));
+ event->button.device = gdk_device_get_core_pointer();
+ event->button.state = getStateFlags();
+ event->button.time = GDK_CURRENT_TIME;
+
+ 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 contextClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ GdkEvent event;
+ if (!prepareMouseButtonEvent(&event, 2))
+ return JSValueMakeUndefined(context);
+
+ event.type = GDK_BUTTON_PRESS;
+ sendOrQueueEvent(event);
+ event.type = GDK_BUTTON_RELEASE;
+ sendOrQueueEvent(event);
+
+ return JSValueMakeUndefined(context);
+}
+
+static void updateClickCount(int button)
+{
+ if (lastClickPositionX != lastMousePositionX
+ || lastClickPositionY != lastMousePositionY
+ || lastClickButton != button
+ || timeOffset - lastClickTimeOffset >= 1)
+ clickCount = 1;
+ else
+ clickCount++;
+}
+
+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));
+ }
+
+ GdkEvent event;
+ if (!prepareMouseButtonEvent(&event, button))
+ 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;
+ else
+ event.type = GDK_BUTTON_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));
+ }
+
+ GdkEvent event;
+ if (!prepareMouseButtonEvent(&event, button))
+ return JSValueMakeUndefined(context);
+
+ lastClickPositionX = lastMousePositionX;
+ lastClickPositionY = lastMousePositionY;
+ lastClickButton = buttonCurrentlyDown;
+ lastClickTimeOffset = timeOffset;
+ buttonCurrentlyDown = 0;
+
+ event.type = GDK_BUTTON_RELEASE;
+ 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;
+ 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 = gtk_widget_get_window(GTK_WIDGET(view));
+ event.motion.device = gdk_device_get_core_pointer();
+ event.motion.state = getStateFlags();
+
+ 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 mouseWheelToCallback(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));
+
+ // GTK+ doesn't support multiple direction scrolls in the same event!
+ g_return_val_if_fail((!vertical || !horizontal), JSValueMakeUndefined(context));
+
+ GdkEvent event;
+ event.type = 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));
+
+ if (horizontal < 0)
+ event.scroll.direction = GDK_SCROLL_LEFT;
+ else if (horizontal > 0)
+ event.scroll.direction = GDK_SCROLL_RIGHT;
+ else if (vertical < 0)
+ event.scroll.direction = GDK_SCROLL_UP;
+ else if (vertical > 0)
+ event.scroll.direction = GDK_SCROLL_DOWN;
+ else
+ g_assert_not_reached();
+
+ sendOrQueueEvent(event);
+ return JSValueMakeUndefined(context);
+}
+
+static JSValueRef beginDragWithFilesCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ if (argumentCount < 1)
+ return JSValueMakeUndefined(context);
+
+ // FIXME: Implement this completely once WebCore has complete drag and drop support
+ 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)
+{
+ webkit_web_frame_layout(mainFrame);
+ WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
+ if (!view)
+ return;
+
+ gtk_main_do_event(&event);
+}
+
+void replaySavedEvents()
+{
+ // FIXME: Eventually we may need to have more sophisticated logic to
+ // track drag-and-drop operations.
+
+ // 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 JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
+{
+ if (argumentCount < 1)
+ return JSValueMakeUndefined(context);
+
+ static const JSStringRef lengthProperty = JSStringCreateWithUTF8CString("length");
+
+ webkit_web_frame_layout(mainFrame);
+
+ // handle modifier keys.
+ int state = 0;
+ if (argumentCount > 1) {
+ JSObjectRef modifiersArray = JSValueToObject(context, arguments[1], exception);
+ if (modifiersArray) {
+ for (int i = 0; i < JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, lengthProperty, 0), 0); ++i) {
+ JSValueRef value = JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0);
+ JSStringRef string = JSValueToStringCopy(context, value, 0);
+ if (JSStringIsEqualToUTF8CString(string, "ctrlKey"))
+ state |= GDK_CONTROL_MASK;
+ else if (JSStringIsEqualToUTF8CString(string, "shiftKey"))
+ state |= GDK_SHIFT_MASK;
+ else if (JSStringIsEqualToUTF8CString(string, "altKey"))
+ state |= GDK_MOD1_MASK;
+
+ JSStringRelease(string);
+ }
+ }
+ }
+
+ // 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), JSValueMakeUndefined(context));
+ 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
+ // Assume we only get arrow/pgUp/pgDn/home/end keys with
+ // location=NUMPAD for now.
+ 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, "delete"))
+ gdkKeySym = GDK_Delete;
+ 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 {
+ 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))
+ state |= GDK_SHIFT_MASK;
+ }
+ }
+ }
+ JSStringRelease(character);
+
+ WebKitWebView* view = webkit_web_frame_get_web_view(mainFrame);
+ if (!view)
+ return JSValueMakeUndefined(context);
+
+ // create and send the event
+ GdkEvent event;
+ memset(&event, 0, sizeof(event));
+ event.key.keyval = gdkKeySym;
+ event.key.state = state;
+ event.key.window = gtk_widget_get_window(GTK_WIDGET(view));
+
+ // When synthesizing an event, an invalid hardware_keycode value
+ // can cause it to be badly processed by Gtk+.
+ GdkKeymapKey* keys;
+ gint n_keys;
+ if (gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(), gdkKeySym, &keys, &n_keys)) {
+ event.key.hardware_keycode = keys[0].keycode;
+ g_free(keys);
+ }
+
+ event.key.type = GDK_KEY_PRESS;
+ dispatchEvent(event);
+
+ event.key.type = GDK_KEY_RELEASE;
+ dispatchEvent(event);
+
+ 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 JSStaticFunction staticFunctions[] = {
+ { "mouseWheelTo", mouseWheelToCallback, 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 },
+ { 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;
+ }
+
+ return JSObjectMake(context, getClass(context), 0);
+}