blob: 7e7585e699663e5bdc8284e4e3540818742102ab [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 "ModifierKeys.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 <pal/spi/cocoa/IOKitSPI.h>
#import <wtf/RetainPtr.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_magnification;
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;
- (id)initMagnifyEventAtLocation:(NSPoint)location globalLocation:(NSPoint)globalLocation magnification:(CGFloat)magnification phase:(NSEventPhase)phase time:(NSTimeInterval)time eventNumber:(NSInteger)eventNumber window:(NSWindow *)window;
- (NSTimeInterval)timestamp;
@end
static CGSGesturePhase EventSenderCGGesturePhaseFromNSEventPhase(NSEventPhase phase)
{
switch (phase) {
case NSEventPhaseMayBegin:
return kCGSGesturePhaseMayBegin;
case NSEventPhaseBegan:
return kCGSGesturePhaseBegan;
case NSEventPhaseChanged:
return kCGSGesturePhaseChanged;
case NSEventPhaseCancelled:
return kCGSGesturePhaseCancelled;
case NSEventPhaseEnded:
return kCGSGesturePhaseEnded;
case NSEventPhaseNone:
default:
return kCGSGesturePhaseNone;
}
}
@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
{
auto cgEvent = adoptCF(CGEventCreate(nullptr));
CGEventSetType(cgEvent.get(), (CGEventType)kCGSEventGesture);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGestureHIDType, kIOHIDEventTypeForce);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGesturePhase, EventSenderCGGesturePhaseFromNSEventPhase(phase));
CGEventSetDoubleValueField(cgEvent.get(), kCGEventStagePressure, pressure);
CGEventSetDoubleValueField(cgEvent.get(), kCGEventTransitionProgress, pressure);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGestureStage, stageTransition);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGestureBehavior, kCGSGestureBehaviorDeepPress);
self = [super _initWithCGEvent:cgEvent.get() 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;
}
- (id)initMagnifyEventAtLocation:(NSPoint)location globalLocation:(NSPoint)globalLocation magnification:(CGFloat)magnification phase:(NSEventPhase)phase time:(NSTimeInterval)time eventNumber:(NSInteger)eventNumber window:(NSWindow *)window
{
auto cgEvent = adoptCF(CGEventCreate(nullptr));
CGEventSetType(cgEvent.get(), (CGEventType)kCGSEventGesture);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGestureHIDType, kIOHIDEventTypeZoom);
CGEventSetIntegerValueField(cgEvent.get(), kCGEventGesturePhase, EventSenderCGGesturePhaseFromNSEventPhase(phase));
CGEventSetDoubleValueField(cgEvent.get(), kCGEventGestureZoomValue, magnification);
if (!(self = [super _initWithCGEvent:cgEvent.get() eventRef:nullptr]))
return nil;
_eventSender_location = location;
_eventSender_locationInWindow = globalLocation;
_eventSender_magnification = magnification;
_eventSender_phase = phase;
_eventSender_timestamp = time;
_eventSender_eventNumber = eventNumber;
_eventSender_window = window;
_eventSender_type = NSEventTypeMagnify;
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;
}
- (CGFloat)magnification
{
return _eventSender_magnification;
}
- (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
};
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)
{
}
EventSenderProxy::~EventSenderProxy() = default;
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, WKStringRef pointerType)
{
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:++m_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, WKStringRef pointerType)
{
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:++m_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:++m_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)
{
auto 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:++m_eventNumber
window:[m_testController->mainWebView()->platformView() window]]);
return event;
}
RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int stage, float pressure, EventSenderProxy::PressureChangeDirection direction)
{
auto 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:++m_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();
auto beginPressure = beginPressureEvent(1);
auto preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
auto forceClick = pressureChangeEvent(2, PressureChangeDirection::Increasing);
auto 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:++m_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();
auto beginPressure = beginPressureEvent(1);
auto increasingPressure = pressureChangeEvent(1, PressureChangeDirection::Increasing);
auto 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:++m_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();
auto beginPressure = beginPressureEvent(1);
auto preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
auto 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()
{
auto beginPressure = beginPressureEvent(2);
auto stageTwoEvent = pressureChangeEvent(2, PressureChangeDirection::Decreasing);
auto 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;
auto beginPressure = beginPressureEvent(stage);
auto 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, WKStringRef pointerType)
{
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:++m_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)
{
RetainPtr<ModifierKeys> modifierKeys = [ModifierKeys modifierKeysWithKey:toWTFString(key) modifiers:buildModifierFlags(modifiers) keyLocation:keyLocation];
NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown
location:NSMakePoint(5, 5)
modifierFlags:modifierKeys->modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:modifierKeys->eventCharacter.get()
charactersIgnoringModifiers:modifierKeys->charactersIgnoringModifiers.get()
isARepeat:NO
keyCode:modifierKeys->keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyDown:event];
[NSApp _setCurrentEvent:nil];
event = [NSEvent keyEventWithType:NSEventTypeKeyUp
location:NSMakePoint(5, 5)
modifierFlags:modifierKeys->modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:modifierKeys->eventCharacter.get()
charactersIgnoringModifiers:modifierKeys->charactersIgnoringModifiers.get()
isARepeat:NO
keyCode:modifierKeys->keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyUp:event];
[NSApp _setCurrentEvent:nil];
}
void EventSenderProxy::rawKeyDown(WKStringRef key, WKEventModifiers modifiers, unsigned keyLocation)
{
RetainPtr<ModifierKeys> modifierKeys = [ModifierKeys modifierKeysWithKey:toWTFString(key) modifiers:buildModifierFlags(modifiers) keyLocation:keyLocation];
NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown
location:NSMakePoint(5, 5)
modifierFlags:modifierKeys->modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:modifierKeys->eventCharacter.get()
charactersIgnoringModifiers:modifierKeys->charactersIgnoringModifiers.get()
isARepeat:NO
keyCode:modifierKeys->keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyDown:event];
[NSApp _setCurrentEvent:nil];
}
void EventSenderProxy::rawKeyUp(WKStringRef key, WKEventModifiers modifiers, unsigned keyLocation)
{
RetainPtr<ModifierKeys> modifierKeys = [ModifierKeys modifierKeysWithKey:toWTFString(key) modifiers:buildModifierFlags(modifiers) keyLocation:keyLocation];
NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyUp
location:NSMakePoint(5, 5)
modifierFlags:modifierKeys->modifierFlags
timestamp:absoluteTimeForEventTime(currentEventTime())
windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
context:[NSGraphicsContext currentContext]
characters:modifierKeys->eventCharacter.get()
charactersIgnoringModifiers:modifierKeys->charactersIgnoringModifiers.get()
isARepeat:NO
keyCode:modifierKeys->keyCode];
[NSApp _setCurrentEvent:event];
[[m_testController->mainWebView()->platformWindow() firstResponder] keyUp:event];
[NSApp _setCurrentEvent:nil];
}
void EventSenderProxy::mouseScrollBy(int x, int y)
{
auto cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent2(0, kCGScrollEventUnitLine, 2, y, x, 0));
// 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)
{
auto cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent2(0, kCGScrollEventUnitLine, 2, y, x, 0));
// 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);
}
}
static CGGesturePhase cgScrollPhaseFromPhase(EventSenderProxy::WheelEventPhase phase)
{
switch (phase) {
case EventSenderProxy::WheelEventPhase::None:
return kCGGesturePhaseNone;
case EventSenderProxy::WheelEventPhase::Began:
return kCGGesturePhaseBegan;
case EventSenderProxy::WheelEventPhase::Changed:
return kCGGesturePhaseChanged;
case EventSenderProxy::WheelEventPhase::Ended:
return kCGGesturePhaseEnded;
case EventSenderProxy::WheelEventPhase::Cancelled:
return kCGGesturePhaseCancelled;
case EventSenderProxy::WheelEventPhase::MayBegin:
return kCGGesturePhaseMayBegin;
}
ASSERT_NOT_REACHED();
return kCGGesturePhaseNone;
}
static CGMomentumScrollPhase cgMomentumPhaseFromPhase(EventSenderProxy::WheelEventPhase phase)
{
switch (phase) {
case EventSenderProxy::WheelEventPhase::None:
return kCGMomentumScrollPhaseNone;
case EventSenderProxy::WheelEventPhase::Began:
return kCGMomentumScrollPhaseBegin;
case EventSenderProxy::WheelEventPhase::Changed:
return kCGMomentumScrollPhaseContinue;
case EventSenderProxy::WheelEventPhase::Ended:
return kCGMomentumScrollPhaseEnd;
case EventSenderProxy::WheelEventPhase::Cancelled:
case EventSenderProxy::WheelEventPhase::MayBegin:
break;
}
ASSERT_NOT_REACHED();
return kCGMomentumScrollPhaseNone;
}
void EventSenderProxy::sendWheelEvent(EventTimestamp timestamp, double windowX, double windowY, double deltaX, double deltaY, WheelEventPhase phase, WheelEventPhase momentumPhase)
{
constexpr uint32_t wheelCount = 2;
auto cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent2(nullptr, kCGScrollEventUnitPixel, wheelCount, deltaY, deltaX, 0));
CGEventSetTimestamp(cgScrollEvent.get(), timestamp);
// 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 flippedWindowMousePosition = CGPointMake(windowX, [[[NSScreen screens] objectAtIndex:0] frame].size.height - windowY);
CGEventSetLocation(cgScrollEvent.get(), flippedWindowMousePosition);
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventIsContinuous, 1);
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventScrollPhase, cgScrollPhaseFromPhase(phase));
CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventMomentumPhase, cgMomentumPhaseFromPhase(momentumPhase));
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("EventSenderProxy::sendWheelEvent failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
}
#if ENABLE(MAC_GESTURE_EVENTS)
void EventSenderProxy::scaleGestureStart(double scale)
{
auto* mainWebView = m_testController->mainWebView();
NSView *platformView = mainWebView->platformView();
auto event = adoptNS([[EventSenderSyntheticEvent alloc] initMagnifyEventAtLocation:NSMakePoint(m_position.x, m_position.y)
globalLocation:([mainWebView->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
magnification:scale
phase:NSEventPhaseBegan
time:absoluteTimeForEventTime(currentEventTime())
eventNumber:++m_eventNumber
window:platformView.window]);
if (NSView *targetView = [platformView hitTest:[event locationInWindow]]) {
[NSApp _setCurrentEvent:event.get()];
[targetView magnifyWithEvent:event.get()];
[NSApp _setCurrentEvent:nil];
} else {
NSPoint windowLocation = [event locationInWindow];
WTFLogAlways("gestureStart failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
}
void EventSenderProxy::scaleGestureChange(double scale)
{
auto* mainWebView = m_testController->mainWebView();
NSView *platformView = mainWebView->platformView();
auto event = adoptNS([[EventSenderSyntheticEvent alloc] initMagnifyEventAtLocation:NSMakePoint(m_position.x, m_position.y)
globalLocation:([mainWebView->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
magnification:scale
phase:NSEventPhaseChanged
time:absoluteTimeForEventTime(currentEventTime())
eventNumber:++m_eventNumber
window:platformView.window]);
if (NSView *targetView = [platformView hitTest:[event locationInWindow]]) {
[NSApp _setCurrentEvent:event.get()];
[targetView magnifyWithEvent:event.get()];
[NSApp _setCurrentEvent:nil];
} else {
NSPoint windowLocation = [event locationInWindow];
WTFLogAlways("gestureStart failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
}
void EventSenderProxy::scaleGestureEnd(double scale)
{
auto* mainWebView = m_testController->mainWebView();
NSView *platformView = mainWebView->platformView();
auto event = adoptNS([[EventSenderSyntheticEvent alloc] initMagnifyEventAtLocation:NSMakePoint(m_position.x, m_position.y)
globalLocation:([mainWebView->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
magnification:scale
phase:NSEventPhaseEnded
time:absoluteTimeForEventTime(currentEventTime())
eventNumber:++m_eventNumber
window:platformView.window]);
if (NSView *targetView = [platformView hitTest:[event locationInWindow]]) {
[NSApp _setCurrentEvent:event.get()];
[targetView magnifyWithEvent:event.get()];
[NSApp _setCurrentEvent:nil];
} else {
NSPoint windowLocation = [event locationInWindow];
WTFLogAlways("gestureStart failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
}
}
#endif // ENABLE(MAC_GESTURE_EVENTS)
} // namespace WTR