| /* |
| * 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 |