blob: 93fafb9cda8ffb94cec420f2f7442b1ad410395b [file] [log] [blame]
/*
* 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) 2011 ProFUSION Embedded Systems
* Copyright (C) 2011 Samsung Electronics
*
* 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 "DumpRenderTreeChrome.h"
#include "JSStringUtils.h"
#include "NotImplemented.h"
#include "WebCoreSupport/DumpRenderTreeSupportEfl.h"
#include "ewk_private.h"
#include <EWebKit.h>
#include <JavaScriptCore/JSObjectRef.h>
#include <JavaScriptCore/JSRetainPtr.h>
#include <JavaScriptCore/JSStringRef.h>
#include <JavaScriptCore/OpaqueJSString.h>
#include <wtf/ASCIICType.h>
#include <wtf/Platform.h>
#include <wtf/text/CString.h>
static bool gDragMode;
static int gTimeOffset = 0;
static int gLastMousePositionX;
static int gLastMousePositionY;
static int gLastClickPositionX;
static int gLastClickPositionY;
static int gLastClickTimeOffset;
static int gLastClickButton;
static int gButtonCurrentlyDown;
static int gClickCount;
static const float zoomMultiplierRatio = 1.2f;
// Key event location code defined in DOM Level 3.
enum KeyLocationCode {
DomKeyLocationStandard,
DomKeyLocationLeft,
DomKeyLocationRight,
DomKeyLocationNumpad
};
enum EvasKeyModifier {
EvasKeyModifierNone = 0,
EvasKeyModifierControl = 1 << 0,
EvasKeyModifierShift = 1 << 1,
EvasKeyModifierAlt = 1 << 2,
EvasKeyModifierMeta = 1 << 3
};
enum EvasMouseButton {
EvasMouseButtonNone,
EvasMouseButtonLeft,
EvasMouseButtonMiddle,
EvasMouseButtonRight
};
enum EvasMouseEvent {
EvasMouseEventNone = 0,
EvasMouseEventDown = 1 << 0,
EvasMouseEventUp = 1 << 1,
EvasMouseEventMove = 1 << 2,
EvasMouseEventScrollUp = 1 << 3,
EvasMouseEventScrollDown = 1 << 4,
EvasMouseEventScrollLeft = 1 << 5,
EvasMouseEventScrollRight = 1 << 6,
EvasMouseEventClick = EvasMouseEventMove | EvasMouseEventDown | EvasMouseEventUp,
};
enum ZoomEvent {
ZoomIn,
ZoomOut
};
static void setEvasModifiers(Evas* evas, EvasKeyModifier modifiers)
{
static const char* modifierNames[] = { "Control", "Shift", "Alt", "Super" };
for (unsigned modifier = 0; modifier < 4; ++modifier) {
if (modifiers & (1 << modifier))
evas_key_modifier_on(evas, modifierNames[modifier]);
else
evas_key_modifier_off(evas, modifierNames[modifier]);
}
}
static EvasMouseButton translateMouseButtonNumber(int eventSenderButtonNumber)
{
static const EvasMouseButton translationTable[] = {
EvasMouseButtonLeft,
EvasMouseButtonMiddle,
EvasMouseButtonRight,
EvasMouseButtonMiddle // fast/events/mouse-click-events expects the 4th button to be treated as the middle button
};
static const unsigned translationTableSize = sizeof(translationTable) / sizeof(translationTable[0]);
if (eventSenderButtonNumber < translationTableSize)
return translationTable[eventSenderButtonNumber];
return EvasMouseButtonLeft;
}
static bool sendMouseEvent(Evas* evas, EvasMouseEvent event, int buttonNumber, EvasKeyModifier modifiers)
{
unsigned timeStamp = 0;
DumpRenderTreeSupportEfl::layoutFrame(browser->mainFrame());
setEvasModifiers(evas, modifiers);
if (event & EvasMouseEventMove)
evas_event_feed_mouse_move(evas, gLastMousePositionX, gLastMousePositionY, timeStamp++, 0);
if (event & EvasMouseEventDown)
evas_event_feed_mouse_down(evas, buttonNumber, EVAS_BUTTON_NONE, timeStamp++, 0);
if (event & EvasMouseEventUp)
evas_event_feed_mouse_up(evas, buttonNumber, EVAS_BUTTON_NONE, timeStamp++, 0);
const bool horizontal = !!(event & EvasMouseEventScrollLeft | event & EvasMouseEventScrollRight);
const bool vertical = !!(event & EvasMouseEventScrollUp | event & EvasMouseEventScrollDown);
if (vertical && horizontal) {
evas_event_feed_mouse_wheel(evas, 0, (event & EvasMouseEventScrollUp) ? 10 : -10, timeStamp, 0);
evas_event_feed_mouse_wheel(evas, 1, (event & EvasMouseEventScrollLeft) ? 10 : -10, timeStamp, 0);
} else if (vertical)
evas_event_feed_mouse_wheel(evas, 0, (event & EvasMouseEventScrollUp) ? 10 : -10, timeStamp, 0);
else if (horizontal)
evas_event_feed_mouse_wheel(evas, 1, (event & EvasMouseEventScrollLeft) ? 10 : -10, timeStamp, 0);
setEvasModifiers(evas, EvasKeyModifierNone);
return true;
}
static Eina_Bool sendClick(void*)
{
return !!sendMouseEvent(evas_object_evas_get(browser->mainFrame()), EvasMouseEventClick, EvasMouseButtonLeft, EvasKeyModifierNone);
}
static JSValueRef scheduleAsynchronousClickCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
ecore_idler_add(sendClick, 0);
return JSValueMakeUndefined(context);
}
static void updateClickCount(int button)
{
if (gLastClickPositionX != gLastMousePositionX
|| gLastClickPositionY != gLastMousePositionY
|| gLastClickButton != button
|| gTimeOffset - gLastClickTimeOffset >= 1)
gClickCount = 1;
else
gClickCount++;
}
static EvasKeyModifier modifierFromJSValue(JSContextRef context, const JSValueRef value)
{
JSRetainPtr<JSStringRef> jsKeyValue(Adopt, JSValueToStringCopy(context, value, 0));
if (equals(jsKeyValue, "ctrlKey") || equals(jsKeyValue, "addSelectionKey"))
return EvasKeyModifierControl;
if (equals(jsKeyValue, "shiftKey") || equals(jsKeyValue, "rangeSelectionKey"))
return EvasKeyModifierShift;
if (equals(jsKeyValue, "altKey"))
return EvasKeyModifierAlt;
if (equals(jsKeyValue, "metaKey"))
return EvasKeyModifierMeta;
return EvasKeyModifierNone;
}
static EvasKeyModifier modifiersFromJSValue(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 modifierFromJSValue(context, modifiers);
JSObjectRef modifiersArray = JSValueToObject(context, modifiers, 0);
if (!modifiersArray)
return EvasKeyModifierNone;
unsigned modifier = 0;
int modifiersCount = JSValueToNumber(context, JSObjectGetProperty(context, modifiersArray, JSStringCreateWithUTF8CString("length"), 0), 0);
for (int i = 0; i < modifiersCount; ++i)
modifier |= static_cast<unsigned>(modifierFromJSValue(context, JSObjectGetPropertyAtIndex(context, modifiersArray, i, 0)));
return static_cast<EvasKeyModifier>(modifier);
}
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));
if (exception && *exception)
return JSValueMakeUndefined(context);
}
button = translateMouseButtonNumber(button);
// If the same mouse button is already in the down position don't send another event as it may confuse Xvfb.
if (gButtonCurrentlyDown == button)
return JSValueMakeUndefined(context);
updateClickCount(button);
EvasKeyModifier modifiers = argumentCount >= 2 ? modifiersFromJSValue(context, arguments[1]) : EvasKeyModifierNone;
if (!sendMouseEvent(evas_object_evas_get(browser->mainFrame()), EvasMouseEventDown, button, modifiers))
return JSValueMakeUndefined(context);
gButtonCurrentlyDown = button;
return JSValueMakeUndefined(context);
}
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));
if (exception && *exception)
return JSValueMakeUndefined(context);
}
gLastClickPositionX = gLastMousePositionX;
gLastClickPositionY = gLastMousePositionY;
gLastClickButton = gButtonCurrentlyDown;
gLastClickTimeOffset = gTimeOffset;
gButtonCurrentlyDown = 0;
EvasKeyModifier modifiers = argumentCount >= 2 ? modifiersFromJSValue(context, arguments[1]) : EvasKeyModifierNone;
sendMouseEvent(evas_object_evas_get(browser->mainFrame()), EvasMouseEventUp, translateMouseButtonNumber(button), modifiers);
return JSValueMakeUndefined(context);
}
static JSValueRef mouseMoveToCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
if (argumentCount < 2)
return JSValueMakeUndefined(context);
gLastMousePositionX = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
if (exception && *exception)
return JSValueMakeUndefined(context);
gLastMousePositionY = static_cast<int>(JSValueToNumber(context, arguments[1], exception));
if (exception && *exception)
return JSValueMakeUndefined(context);
sendMouseEvent(evas_object_evas_get(browser->mainFrame()), EvasMouseEventMove, EvasMouseButtonNone, EvasKeyModifierNone);
return JSValueMakeUndefined(context);
}
static EvasMouseEvent evasMouseEventFromHorizontalAndVerticalOffsets(int horizontalOffset, int verticalOffset)
{
unsigned mouseEvent = 0;
if (verticalOffset > 0)
mouseEvent |= EvasMouseEventScrollUp;
else if (verticalOffset < 0)
mouseEvent |= EvasMouseEventScrollDown;
if (horizontalOffset > 0)
mouseEvent |= EvasMouseEventScrollRight;
else if (horizontalOffset < 0)
mouseEvent |= EvasMouseEventScrollLeft;
return static_cast<EvasMouseEvent>(mouseEvent);
}
static JSValueRef mouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
if (argumentCount < 2)
return JSValueMakeUndefined(context);
const int horizontal = static_cast<int>(JSValueToNumber(context, arguments[0], exception));
if (exception && *exception)
return JSValueMakeUndefined(context);
const int vertical = static_cast<int>(JSValueToNumber(context, arguments[1], exception));
if (exception && *exception)
return JSValueMakeUndefined(context);
sendMouseEvent(evas_object_evas_get(browser->mainFrame()), evasMouseEventFromHorizontalAndVerticalOffsets(horizontal, vertical), EvasMouseButtonNone, EvasKeyModifierNone);
return JSValueMakeUndefined(context);
}
static JSValueRef continuousMouseScrollByCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
return JSValueMakeUndefined(context);
}
static const char* keyPadNameFromJSValue(JSStringRef character)
{
if (equals(character, "leftArrow"))
return "KP_Left";
if (equals(character, "rightArrow"))
return "KP_Right";
if (equals(character, "upArrow"))
return "KP_Up";
if (equals(character, "downArrow"))
return "KP_Down";
if (equals(character, "pageUp"))
return "KP_Prior";
if (equals(character, "pageDown"))
return "KP_Next";
if (equals(character, "home"))
return "KP_Home";
if (equals(character, "end"))
return "KP_End";
if (equals(character, "insert"))
return "KP_Insert";
if (equals(character, "delete"))
return "KP_Delete";
return 0;
}
static const char* keyNameFromJSValue(JSStringRef character)
{
if (equals(character, "leftArrow"))
return "Left";
if (equals(character, "rightArrow"))
return "Right";
if (equals(character, "upArrow"))
return "Up";
if (equals(character, "downArrow"))
return "Down";
if (equals(character, "pageUp"))
return "Prior";
if (equals(character, "pageDown"))
return "Next";
if (equals(character, "home"))
return "Home";
if (equals(character, "end"))
return "End";
if (equals(character, "insert"))
return "Insert";
if (equals(character, "delete"))
return "Delete";
if (equals(character, "printScreen"))
return "Print";
if (equals(character, "menu"))
return "Menu";
if (equals(character, "F1"))
return "F1";
if (equals(character, "F2"))
return "F2";
if (equals(character, "F3"))
return "F3";
if (equals(character, "F4"))
return "F4";
if (equals(character, "F5"))
return "F5";
if (equals(character, "F6"))
return "F6";
if (equals(character, "F7"))
return "F7";
if (equals(character, "F8"))
return "F8";
if (equals(character, "F9"))
return "F9";
if (equals(character, "F10"))
return "F10";
if (equals(character, "F11"))
return "F11";
if (equals(character, "F12"))
return "F12";
int charCode = JSStringGetCharactersPtr(character)[0];
if (charCode == '\n' || charCode == '\r')
return "Return";
if (charCode == '\t')
return "Tab";
if (charCode == '\x8')
return "BackSpace";
return 0;
}
static JSValueRef keyDownCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
Evas_Object* view = ewk_frame_view_get(browser->mainFrame());
if (!view)
return JSValueMakeUndefined(context);
if (argumentCount < 1)
return JSValueMakeUndefined(context);
// handle location argument.
int location = DomKeyLocationStandard;
if (argumentCount > 2)
location = static_cast<int>(JSValueToNumber(context, arguments[2], exception));
JSRetainPtr<JSStringRef> character(Adopt, JSValueToStringCopy(context, arguments[0], exception));
if (exception && *exception)
return JSValueMakeUndefined(context);
// send the event
Evas* evas = evas_object_evas_get(view);
if (argumentCount >= 2)
setEvasModifiers(evas, modifiersFromJSValue(context, arguments[1]));
const CString cCharacter = character.get()->ustring().utf8();
const char* keyName = (location == DomKeyLocationNumpad) ? keyPadNameFromJSValue(character.get()) : keyNameFromJSValue(character.get());
if (!keyName)
keyName = cCharacter.data();
DumpRenderTreeSupportEfl::layoutFrame(browser->mainFrame());
evas_event_feed_key_down(evas, keyName, keyName, keyName, 0, 0, 0);
evas_event_feed_key_up(evas, keyName, keyName, keyName, 0, 1, 0);
setEvasModifiers(evas, EvasKeyModifierNone);
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);
Evas_Object* view = ewk_frame_view_get(browser->mainFrame());
if (!view)
return JSValueMakeUndefined(context);
float scaleFactor = JSValueToNumber(context, arguments[0], exception);
float x = JSValueToNumber(context, arguments[1], exception);
float y = JSValueToNumber(context, arguments[2], exception);
ewk_view_scale_set(view, scaleFactor, x, y);
return JSValueMakeUndefined(context);
}
static void textZoom(ZoomEvent zoomEvent)
{
Evas_Object* view = ewk_frame_view_get(browser->mainFrame());
if (!view)
return;
float zoomFactor = ewk_view_text_zoom_get(view);
if (zoomEvent == ZoomIn)
zoomFactor *= zoomMultiplierRatio;
else
zoomFactor /= zoomMultiplierRatio;
ewk_view_text_zoom_set(view, zoomFactor);
}
static void pageZoom(ZoomEvent zoomEvent)
{
Evas_Object* view = ewk_frame_view_get(browser->mainFrame());
if (!view)
return;
float zoomFactor = ewk_view_page_zoom_get(view);
if (zoomEvent == ZoomIn)
zoomFactor *= zoomMultiplierRatio;
else
zoomFactor /= zoomMultiplierRatio;
ewk_view_page_zoom_set(view, zoomFactor);
}
static JSValueRef textZoomInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
textZoom(ZoomIn);
return JSValueMakeUndefined(context);
}
static JSValueRef textZoomOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
textZoom(ZoomOut);
return JSValueMakeUndefined(context);
}
static JSValueRef zoomPageInCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
pageZoom(ZoomIn);
return JSValueMakeUndefined(context);
}
static JSValueRef zoomPageOutCallback(JSContextRef context, JSObjectRef function, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
{
pageZoom(ZoomOut);
return JSValueMakeUndefined(context);
}
static JSStaticFunction staticFunctions[] = {
{ "mouseScrollBy", mouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "continuousMouseScrollBy", continuousMouseScrollByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "mouseDown", mouseDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "mouseUp", mouseUpCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "mouseMoveTo", mouseMoveToCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "keyDown", keyDownCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "scheduleAsynchronousClick", scheduleAsynchronousClickCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "scalePageBy", scalePageByCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "textZoomIn", textZoomInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "textZoomOut", textZoomOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "zoomPageIn", zoomPageInCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ "zoomPageOut", zoomPageOutCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
{ 0, 0, 0 }
};
static JSStaticValue staticValues[] = {
{ 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) {
gDragMode = 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.
gTimeOffset += 1000;
gLastMousePositionX = gLastMousePositionY = 0;
gLastClickPositionX = gLastClickPositionY = 0;
gLastClickTimeOffset = 0;
gLastClickButton = 0;
gButtonCurrentlyDown = 0;
gClickCount = 0;
}
return JSObjectMake(context, getClass(context), 0);
}