| /* |
| * 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> |
| |
| @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]; |
| CFRelease(cgEvent); |
| |
| 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 |