blob: 89ce4ef126c15d92dcf76650ce6eef5b8923172e [file] [log] [blame]
/*
* Copyright (C) 2011, 2014-2015 Apple Inc. All rights reserved.
* Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. 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 INC. 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.
*/
#import "config.h"
#import "EventSenderProxy.h"
#import "CoreGraphicsTestSPI.h"
#import "PlatformWebView.h"
#import "StringFunctions.h"
#import "TestController.h"
#import "TestRunnerWKWebView.h"
#import "WebKitTestRunnerWindow.h"
#import <Carbon/Carbon.h>
#import <WebKit/WKString.h>
#import <WebKit/WKPagePrivate.h>
#import <WebKit/WKWebView.h>
#import <wtf/RetainPtr.h>
#import <wtf/mac/AppKitCompatibilityDeclarations.h>
@interface NSApplication (Details)
- (void)_setCurrentEvent:(NSEvent *)event;
@end
@interface NSEvent (ForTestRunner)
- (void)_postDelayed;
- (instancetype)_initWithCGEvent:(CGEventRef)event eventRef:(void *)eventRef;
@end
@interface EventSenderSyntheticEvent : NSEvent {
@public
NSPoint _eventSender_locationInWindow;
NSPoint _eventSender_location;
NSInteger _eventSender_stage;
float _eventSender_pressure;
CGFloat _eventSender_stageTransition;
NSEventPhase _eventSender_phase;
NSEventPhase _eventSender_momentumPhase;
NSTimeInterval _eventSender_timestamp;
NSInteger _eventSender_eventNumber;
short _eventSender_subtype;
NSEventType _eventSender_type;
NSWindow *_eventSender_window;
}
- (id)initPressureEventAtLocation:(NSPoint)location globalLocation:(NSPoint)globalLocation stage:(NSInteger)stage pressure:(float)pressure stageTransition:(float)stageTransition phase:(NSEventPhase)phase time:(NSTimeInterval)time eventNumber:(NSInteger)eventNumber window:(NSWindow *)window;
- (NSTimeInterval)timestamp;
@end
@implementation EventSenderSyntheticEvent
- (instancetype)initPressureEventAtLocation:(NSPoint)location globalLocation:(NSPoint)globalLocation stage:(NSInteger)stage pressure:(float)pressure stageTransition:(float)stageTransition phase:(NSEventPhase)phase time:(NSTimeInterval)time eventNumber:(NSInteger)eventNumber window:(NSWindow *)window
{
CGSGesturePhase gesturePhase;
switch (phase) {
case NSEventPhaseMayBegin:
gesturePhase = kCGSGesturePhaseMayBegin;
break;
case NSEventPhaseBegan:
gesturePhase = kCGSGesturePhaseBegan;
break;
case NSEventPhaseChanged:
gesturePhase = kCGSGesturePhaseChanged;
break;
case NSEventPhaseCancelled:
gesturePhase = kCGSGesturePhaseCancelled;
break;
case NSEventPhaseEnded:
gesturePhase = kCGSGesturePhaseEnded;
break;
case NSEventPhaseNone:
default:
gesturePhase = kCGSGesturePhaseNone;
break;
}
CGEventRef cgEvent = CGEventCreate(nullptr);
CGEventSetType(cgEvent, (CGEventType)kCGSEventGesture);
CGEventSetIntegerValueField(cgEvent, kCGEventGestureHIDType, 32);
CGEventSetIntegerValueField(cgEvent, kCGEventGesturePhase, gesturePhase);
CGEventSetDoubleValueField(cgEvent, kCGEventStagePressure, pressure);
CGEventSetDoubleValueField(cgEvent, kCGEventTransitionProgress, pressure);
CGEventSetIntegerValueField(cgEvent, kCGEventGestureStage, stageTransition);
CGEventSetIntegerValueField(cgEvent, kCGEventGestureBehavior, kCGSGestureBehaviorDeepPress);
self = [super _initWithCGEvent:cgEvent eventRef:nullptr];
if (!self)
return nil;
_eventSender_location = location;
_eventSender_locationInWindow = globalLocation;
_eventSender_stage = stage;
_eventSender_pressure = pressure;
_eventSender_stageTransition = stageTransition;
_eventSender_phase = phase;
_eventSender_timestamp = time;
_eventSender_eventNumber = eventNumber;
_eventSender_window = window;
_eventSender_type = NSEventTypePressure;
return self;
}
- (CGFloat)stageTransition
{
return _eventSender_stageTransition;
}
- (NSTimeInterval)timestamp
{
return _eventSender_timestamp;
}
- (NSEventType)type
{
return _eventSender_type;
}
- (NSEventSubtype)subtype
{
return (NSEventSubtype)_eventSender_subtype;
}
- (NSPoint)locationInWindow
{
return _eventSender_location;
}
- (NSPoint)location
{
return _eventSender_locationInWindow;
}
- (NSInteger)stage
{
return _eventSender_stage;
}
- (float)pressure
{
return _eventSender_pressure;
}
- (NSEventPhase)phase
{
return _eventSender_phase;
}
- (NSEventPhase)momentumPhase
{
return _eventSender_momentumPhase;
}
- (NSInteger)eventNumber
{
return _eventSender_eventNumber;
}
- (BOOL)_isTouchesEnded
{
return false;
}
- (NSWindow *)window
{
return _eventSender_window;
}
@end
namespace WTR {
enum MouseAction {
MouseDown,
MouseUp,
MouseDragged
};
// Match the DOM spec (sadly the DOM spec does not provide an enum)
enum MouseButton {
LeftMouseButton = 0,
MiddleMouseButton = 1,
RightMouseButton = 2,
NoMouseButton = -2
};
struct KeyMappingEntry {
int macKeyCode;
int macNumpadKeyCode;
unichar character;
NSString* characterName;
};
static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
{
switch (button) {
case LeftMouseButton:
switch (action) {
case MouseDown:
return NSEventTypeLeftMouseDown;
case MouseUp:
return NSEventTypeLeftMouseUp;
case MouseDragged:
return NSEventTypeLeftMouseDragged;
}
case RightMouseButton:
switch (action) {
case MouseDown:
return NSEventTypeRightMouseDown;
case MouseUp:
return NSEventTypeRightMouseUp;
case MouseDragged:
return NSEventTypeRightMouseDragged;
}
default:
switch (action) {
case MouseDown:
return NSEventTypeOtherMouseDown;
case MouseUp:
return NSEventTypeOtherMouseUp;
case MouseDragged:
return NSEventTypeOtherMouseDragged;
}
}
assert(0);
return static_cast<NSEventType>(0);
}
static int buildModifierFlags(WKEventModifiers modifiers)
{
int flags = 0;
if (modifiers & kWKEventModifiersControlKey)
flags |= NSEventModifierFlagControl;
if (modifiers & kWKEventModifiersShiftKey)
flags |= NSEventModifierFlagShift;
if (modifiers & kWKEventModifiersAltKey)
flags |= NSEventModifierFlagOption;
if (modifiers & kWKEventModifiersMetaKey)
flags |= NSEventModifierFlagCommand;
if (modifiers & kWKEventModifiersCapsLockKey)
flags |= NSEventModifierFlagCapsLock;
return flags;
}
static NSTimeInterval absoluteTimeForEventTime(double currentEventTime)
{
return GetCurrentEventTime() + currentEventTime;
}
EventSenderProxy::EventSenderProxy(TestController* testController)
: m_testController(testController)
, m_time(0)
, m_position()
, m_leftMouseButtonDown(false)
, m_clickCount(0)
, m_clickTime(0)
, m_clickPosition()
, m_clickButton(kWKEventMouseButtonNoButton)
, eventNumber(0)
{
}
EventSenderProxy::~EventSenderProxy()
{
}
void EventSenderProxy::updateClickCountForButton(int button)
{
if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
++m_clickCount;
m_clickTime = m_time;
return;
}
m_clickCount = 1;
m_clickTime = m_time;
m_clickPosition = m_position;
m_clickButton = button;
}
static NSUInteger swizzledEventPressedMouseButtons()
{
return TestController::singleton().eventSenderProxy()->mouseButtonsCurrentlyDown();
}
void EventSenderProxy::mouseDown(unsigned buttonNumber, WKEventModifiers modifiers)
{
m_mouseButtonsCurrentlyDown |= (1 << buttonNumber);
updateClickCountForButton(buttonNumber);
NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
NSEvent *event = [NSEvent mouseEventWithType:eventType
location:NSMakePoint(m_position.x, m_position.y)
modifierFlags:buildModifierFlags(modifiers)
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:m_clickCount
pressure:0.0];
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
if (targetView) {
auto eventPressedMouseButtonsSwizzler = makeUnique<ClassMethodSwizzler>([NSEvent class], @selector(pressedMouseButtons), reinterpret_cast<IMP>(swizzledEventPressedMouseButtons));
[NSApp _setCurrentEvent:event];
[targetView mouseDown:event];
[NSApp _setCurrentEvent:nil];
if (buttonNumber == LeftMouseButton)
m_leftMouseButtonDown = true;
}
}
void EventSenderProxy::mouseUp(unsigned buttonNumber, WKEventModifiers modifiers)
{
m_mouseButtonsCurrentlyDown &= ~(1 << buttonNumber);
NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
NSEvent *event = [NSEvent mouseEventWithType:eventType
location:NSMakePoint(m_position.x, m_position.y)
modifierFlags:buildModifierFlags(modifiers)
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:m_clickCount
pressure:0.0];
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
// FIXME: Silly hack to teach WKTR to respect capturing mouse events outside the WKView.
// The right solution is just to use NSApplication's built-in event sending methods,
// instead of rolling our own algorithm for selecting an event target.
if (!targetView)
targetView = m_testController->mainWebView()->platformView();
ASSERT(targetView);
auto eventPressedMouseButtonsSwizzler = makeUnique<ClassMethodSwizzler>([NSEvent class], @selector(pressedMouseButtons), reinterpret_cast<IMP>(swizzledEventPressedMouseButtons));
[NSApp _setCurrentEvent:event];
[targetView mouseUp:event];
[NSApp _setCurrentEvent:nil];
if (buttonNumber == LeftMouseButton)
m_leftMouseButtonDown = false;
m_clickTime = currentEventTime();
m_clickPosition = m_position;
}
void EventSenderProxy::sendMouseDownToStartPressureEvents()
{
updateClickCountForButton(0);
NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
location:NSMakePoint(m_position.x, m_position.y)
modifierFlags:NSEventMaskPressure
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:m_clickCount
pressure:0.0];
[NSApp sendEvent:event];
}
static void handleForceEventSynchronously(NSEvent *event)
{
// Force events have to be pushed onto the queue, then popped off right away and handled synchronously in order
// to get the NSImmediateActionGestureRecognizer to do the right thing.
[event _postDelayed];
[NSApp sendEvent:[NSApp nextEventMatchingMask:NSEventMaskPressure untilDate:[NSDate dateWithTimeIntervalSinceNow:0.05] inMode:NSDefaultRunLoopMode dequeue:YES]];
}
RetainPtr<NSEvent> EventSenderProxy::beginPressureEvent(int stage)
{
RetainPtr<EventSenderSyntheticEvent> event = adoptNS([[EventSenderSyntheticEvent alloc] initPressureEventAtLocation:NSMakePoint(m_position.x, m_position.y)
globalLocation:([m_testController->mainWebView()->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
stage:stage
pressure:0.5
stageTransition:0
phase:NSEventPhaseBegan
time:absoluteTimeForEventTime(currentEventTime())
eventNumber:++eventNumber
window:[m_testController->mainWebView()->platformView() window]]);
return event;
}
RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int stage, float pressure, EventSenderProxy::PressureChangeDirection direction)
{
RetainPtr<EventSenderSyntheticEvent> event = adoptNS([[EventSenderSyntheticEvent alloc] initPressureEventAtLocation:NSMakePoint(m_position.x, m_position.y)
globalLocation:([m_testController->mainWebView()->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
stage:stage
pressure:pressure
stageTransition:direction == PressureChangeDirection::Increasing ? 0.5 : -0.5
phase:NSEventPhaseChanged
time:absoluteTimeForEventTime(currentEventTime())
eventNumber:++eventNumber
window:[m_testController->mainWebView()->platformView() window]]);
return event;
}
RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int stage, EventSenderProxy::PressureChangeDirection direction)
{
return pressureChangeEvent(stage, 0.5, direction);
}
void EventSenderProxy::mouseForceClick()
{
sendMouseDownToStartPressureEvents();
RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
RetainPtr<NSEvent> preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
RetainPtr<NSEvent> forceClick = pressureChangeEvent(2, PressureChangeDirection::Increasing);
RetainPtr<NSEvent> releasingPressure = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
NSEvent *mouseUp = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
location:NSMakePoint(m_position.x, m_position.y)
modifierFlags:0
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:m_clickCount
pressure:0.0];
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[preForceClick.get() locationInWindow]];
targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
ASSERT(targetView);
// Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
// the change in stage that marks those moments.
handleForceEventSynchronously(beginPressure.get());
handleForceEventSynchronously(preForceClick.get());
handleForceEventSynchronously(forceClick.get());
handleForceEventSynchronously(releasingPressure.get());
[NSApp sendEvent:mouseUp];
[NSApp _setCurrentEvent:nil];
// WKView caches the most recent pressure event, so send it a nil event to clear the cache.
IGNORE_NULL_CHECK_WARNINGS_BEGIN
[targetView pressureChangeWithEvent:nil];
IGNORE_NULL_CHECK_WARNINGS_END
}
void EventSenderProxy::startAndCancelMouseForceClick()
{
sendMouseDownToStartPressureEvents();
RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
RetainPtr<NSEvent> increasingPressure = pressureChangeEvent(1, PressureChangeDirection::Increasing);
RetainPtr<NSEvent> releasingPressure = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
NSEvent *mouseUp = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
location:NSMakePoint(m_position.x, m_position.y)
modifierFlags:0
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:m_clickCount
pressure:0.0];
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure.get() locationInWindow]];
targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
ASSERT(targetView);
// Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
// the change in stage that marks those moments.
handleForceEventSynchronously(beginPressure.get());
handleForceEventSynchronously(increasingPressure.get());
handleForceEventSynchronously(releasingPressure.get());
[NSApp sendEvent:mouseUp];
[NSApp _setCurrentEvent:nil];
// WKView caches the most recent pressure event, so send it a nil event to clear the cache.
IGNORE_NULL_CHECK_WARNINGS_BEGIN
[targetView pressureChangeWithEvent:nil];
IGNORE_NULL_CHECK_WARNINGS_END
}
void EventSenderProxy::mouseForceDown()
{
sendMouseDownToStartPressureEvents();
RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
RetainPtr<NSEvent> preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
RetainPtr<NSEvent> forceMouseDown = pressureChangeEvent(2, PressureChangeDirection::Increasing);
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
ASSERT(targetView);
// Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
// the change in stage that marks those moments.
handleForceEventSynchronously(beginPressure.get());
handleForceEventSynchronously(preForceClick.get());
[forceMouseDown _postDelayed];
[NSApp _setCurrentEvent:nil];
// WKView caches the most recent pressure event, so send it a nil event to clear the cache.
IGNORE_NULL_CHECK_WARNINGS_BEGIN
[targetView pressureChangeWithEvent:nil];
IGNORE_NULL_CHECK_WARNINGS_END
}
void EventSenderProxy::mouseForceUp()
{
RetainPtr<NSEvent> beginPressure = beginPressureEvent(2);
RetainPtr<NSEvent> stageTwoEvent = pressureChangeEvent(2, PressureChangeDirection::Decreasing);
RetainPtr<NSEvent> stageOneEvent = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
// Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
// the change in stage that marks those moments.
[NSApp sendEvent:beginPressure.get()];
[NSApp sendEvent:stageTwoEvent.get()];
[NSApp sendEvent:stageOneEvent.get()];
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
ASSERT(targetView);
[NSApp _setCurrentEvent:nil];
// WKView caches the most recent pressure event, so send it a nil event to clear the cache.
IGNORE_NULL_CHECK_WARNINGS_BEGIN
[targetView pressureChangeWithEvent:nil];
IGNORE_NULL_CHECK_WARNINGS_END
}
void EventSenderProxy::mouseForceChanged(float force)
{
int stage = force < 1 ? 1 : 2;
float pressure = force < 1 ? force : force - 1;
RetainPtr<NSEvent> beginPressure = beginPressureEvent(stage);
RetainPtr<NSEvent> pressureChangedEvent = pressureChangeEvent(stage, pressure, PressureChangeDirection::Increasing);
NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
ASSERT(targetView);
[NSApp sendEvent:beginPressure.get()];
[NSApp sendEvent:pressureChangedEvent.get()];
// WKView caches the most recent pressure event, so send it a nil event to clear the cache.
IGNORE_NULL_CHECK_WARNINGS_BEGIN
[targetView pressureChangeWithEvent:nil];
IGNORE_NULL_CHECK_WARNINGS_END
}
void EventSenderProxy::mouseMoveTo(double x, double y)
{
NSView *view = m_testController->mainWebView()->platformView();
NSPoint newMousePosition = [view convertPoint:NSMakePoint(x, y) toView:nil];
bool isDrag = m_leftMouseButtonDown;
NSEvent *event = [NSEvent mouseEventWithType:(isDrag ? NSEventTypeLeftMouseDragged : NSEventTypeMouseMoved)
location:newMousePosition
modifierFlags:0
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:view.window.windowNumber
context:[NSGraphicsContext currentContext]
eventNumber:++eventNumber
clickCount:(m_leftMouseButtonDown ? m_clickCount : 0)
pressure:0];
CGEventRef cgEvent = event.CGEvent;
CGEventSetIntegerValueField(cgEvent, kCGMouseEventDeltaX, newMousePosition.x - m_position.x);
CGEventSetIntegerValueField(cgEvent, kCGMouseEventDeltaY, newMousePosition.y - m_position.y);
event = [NSEvent eventWithCGEvent:cgEvent];
m_position.x = newMousePosition.x;
m_position.y = newMousePosition.y;
NSPoint windowLocation = event.locationInWindow;
// Always target drags at the WKWebView to allow for drag-scrolling outside the view.
NSView *targetView = isDrag ? m_testController->mainWebView()->platformView() : [m_testController->mainWebView()->platformView() hitTest:windowLocation];
if (targetView) {
auto eventPressedMouseButtonsSwizzler = makeUnique<ClassMethodSwizzler>([NSEvent class], @selector(pressedMouseButtons), reinterpret_cast<IMP>(swizzledEventPressedMouseButtons));
[NSApp _setCurrentEvent:event];
if (isDrag)
[targetView mouseDragged:event];
else
[targetView mouseMoved:event];
[NSApp _setCurrentEvent:nil];
} else
WTFLogAlways("mouseMoveTo failed to find a target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
void EventSenderProxy::leapForward(int milliseconds)
{
m_time += milliseconds / 1000.0;
}
void EventSenderProxy::keyDown(WKStringRef key, WKEventModifiers modifiers, unsigned keyLocation)
{
NSString* character = [NSString stringWithCString:toSTD(key).c_str()
encoding:[NSString defaultCStringEncoding]];
NSString *eventCharacter = character;
unsigned short keyCode = 0;
if ([character isEqualToString:@"leftArrow"]) {
const unichar ch = NSLeftArrowFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x7B;
} else if ([character isEqualToString:@"rightArrow"]) {
const unichar ch = NSRightArrowFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x7C;
} else if ([character isEqualToString:@"upArrow"]) {
const unichar ch = NSUpArrowFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x7E;
} else if ([character isEqualToString:@"downArrow"]) {
const unichar ch = NSDownArrowFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x7D;
} else if ([character isEqualToString:@"pageUp"]) {
const unichar ch = NSPageUpFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x74;
} else if ([character isEqualToString:@"pageDown"]) {
const unichar ch = NSPageDownFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x79;
} else if ([character isEqualToString:@"home"]) {
const unichar ch = NSHomeFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x73;
} else if ([character isEqualToString:@"end"]) {
const unichar ch = NSEndFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x77;
} else if ([character isEqualToString:@"insert"]) {
const unichar ch = NSInsertFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x72;
} else if ([character isEqualToString:@"delete"]) {
const unichar ch = NSDeleteFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x75;
} else if ([character isEqualToString:@"printScreen"]) {
const unichar ch = NSPrintScreenFunctionKey;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x0; // There is no known virtual key code for PrintScreen.
} else if ([character isEqualToString:@"cyrillicSmallLetterA"]) {
const unichar ch = 0x0430;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3; // Shares key with "F" on Russian layout.
} else if ([character isEqualToString:@"leftControl"]) {
const unichar ch = 0xFFE3;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3B;
} else if ([character isEqualToString:@"leftShift"]) {
const unichar ch = 0xFFE1;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x38;
} else if ([character isEqualToString:@"leftAlt"]) {
const unichar ch = 0xFFE7;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3A;
} else if ([character isEqualToString:@"rightControl"]) {
const unichar ch = 0xFFE4;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3E;
} else if ([character isEqualToString:@"rightShift"]) {
const unichar ch = 0xFFE2;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3C;
} else if ([character isEqualToString:@"rightAlt"]) {
const unichar ch = 0xFFE8;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x3D;
} else if ([character isEqualToString:@"escape"]) {
const unichar ch = 0x1B;
eventCharacter = [NSString stringWithCharacters:&ch length:1];
keyCode = 0x35;
}
// Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
// If the input string is a function-key name, set its key code.
for (unsigned i = 1; i <= 24; i++) {
if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
const unichar ch = NSF1FunctionKey + (i - 1);
eventCharacter = [NSString stringWithCharacters:&ch length:1];
switch (i) {
case 1: keyCode = 0x7A; break;
case 2: keyCode = 0x78; break;
case 3: keyCode = 0x63; break;
case 4: keyCode = 0x76; break;
case 5: keyCode = 0x60; break;
case 6: keyCode = 0x61; break;
case 7: keyCode = 0x62; break;
case 8: keyCode = 0x64; break;
case 9: keyCode = 0x65; break;
case 10: keyCode = 0x6D; break;
case 11: keyCode = 0x67; break;
case 12: keyCode = 0x6F; break;
case 13: keyCode = 0x69; break;
case 14: keyCode = 0x6B; break;
case 15: keyCode = 0x71; break;
case 16: keyCode = 0x6A; break;
case 17: keyCode = 0x40; break;
case 18: keyCode = 0x4F; break;
case 19: keyCode = 0x50; break;
case 20: keyCode = 0x5A; break;
}
}
}
// FIXME: No keyCode is set for most keys.
if ([character isEqualToString:@"\t"])
keyCode = 0x30;
else if ([character isEqualToString:@" "])
keyCode = 0x31;
else if ([character isEqualToString:@"\r"])
keyCode = 0x24;
else if ([character isEqualToString:@"\n"])
keyCode = 0x4C;
else if ([character isEqualToString:@"\x8"])
keyCode = 0x33;
else if ([character isEqualToString:@"a"])
keyCode = 0x00;
else if ([character isEqualToString:@"b"])
keyCode = 0x0B;
else if ([character isEqualToString:@"d"])
keyCode = 0x02;
else if ([character isEqualToString:@"e"])
keyCode = 0x0E;
else if ([character isEqualToString:@"\x1b"])
keyCode = 0x1B;
KeyMappingEntry table[] = {
{0x2F, 0x41, '.', nil},
{0, 0x43, '*', nil},
{0, 0x45, '+', nil},
{0, 0x47, NSClearLineFunctionKey, @"clear"},
{0x2C, 0x4B, '/', nil},
{0, 0x4C, 3, @"enter" },
{0x1B, 0x4E, '-', nil},
{0x18, 0x51, '=', nil},
{0x1D, 0x52, '0', nil},
{0x12, 0x53, '1', nil},
{0x13, 0x54, '2', nil},
{0x14, 0x55, '3', nil},
{0x15, 0x56, '4', nil},
{0x17, 0x57, '5', nil},
{0x16, 0x58, '6', nil},
{0x1A, 0x59, '7', nil},
{0x1C, 0x5B, '8', nil},
{0x19, 0x5C, '9', nil},
};
for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) {
NSString* currentCharacterString = [NSString stringWithCharacters:&table[i].character length:1];
if ([character isEqualToString:currentCharacterString] || [character isEqualToString:table[i].characterName]) {
if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
keyCode = table[i].macNumpadKeyCode;
else
keyCode = table[i].macKeyCode;
eventCharacter = currentCharacterString;
break;
}
}
NSString *charactersIgnoringModifiers = eventCharacter;
int modifierFlags = 0;
if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
modifierFlags |= NSEventModifierFlagShift;
charactersIgnoringModifiers = [character lowercaseString];
}
modifierFlags |= buildModifierFlags(modifiers);
if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
modifierFlags |= NSEventModifierFlagNumericPad;
NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown
location:NSMakePoint(5, 5)
modifierFlags:modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:eventCharacter
charactersIgnoringModifiers:charactersIgnoringModifiers
isARepeat:NO
keyCode:keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyDown:event];
[NSApp _setCurrentEvent:nil];
event = [NSEvent keyEventWithType:NSEventTypeKeyUp
location:NSMakePoint(5, 5)
modifierFlags:modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:eventCharacter
charactersIgnoringModifiers:charactersIgnoringModifiers
isARepeat:NO
keyCode:keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyUp:event];
[NSApp _setCurrentEvent:nil];
}
void EventSenderProxy::mouseScrollBy(int x, int y)
{
RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
// Set the CGEvent location in flipped coords relative to the first screen, which
// compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
// no associated window. See <rdar://problem/17180591>.
CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] objectAtIndex:0] frame].size.height - m_position.y);
CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
NSEvent *event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
[NSApp _setCurrentEvent:event];
[targetView scrollWheel:event];
[NSApp _setCurrentEvent:nil];
} else {
NSPoint location = [event locationInWindow];
WTFLogAlways("mouseScrollBy failed to find the target view at %f,%f\n", location.x, location.y);
}
}
void EventSenderProxy::continuousMouseScrollBy(int x, int y, bool paged)
{
WTFLogAlways("EventSenderProxy::continuousMouseScrollBy is not implemented\n");
return;
}
void EventSenderProxy::mouseScrollByWithWheelAndMomentumPhases(int x, int y, int phase, int momentum)
{
RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
// Set the CGEvent location in flipped coords relative to the first screen, which
// compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
// no associated window. See <rdar://problem/17180591>.
CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] objectAtIndex:0] frame].size.height - m_position.y);
CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventIsContinuous, 1);
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventScrollPhase, phase);
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventMomentumPhase, momentum);
NSEvent* event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
// Our event should have the correct settings:
if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
[NSApp _setCurrentEvent:event];
[targetView scrollWheel:event];
[NSApp _setCurrentEvent:nil];
} else {
NSPoint windowLocation = [event locationInWindow];
WTFLogAlways("mouseScrollByWithWheelAndMomentumPhases failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
}
} // namespace WTR