/* * 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 "PlatformWebView.h" #import "StringFunctions.h" #import "TestController.h" #import "TestRunnerWKWebView.h" #import #import #import #import #import #import @interface NSApplication (Details) - (void)_setCurrentEvent:(NSEvent *)event; @end @interface NSEvent (ForTestRunner) - (void)_postDelayed; @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 - (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 { self = [super init]; 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; #if defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003 self->_type = NSEventTypePressure; _eventSender_type = NSEventTypePressure; #endif 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 = -1 }; 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(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; } void EventSenderProxy::mouseDown(unsigned buttonNumber, WKEventModifiers modifiers) { 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) { [NSApp _setCurrentEvent:event]; [targetView mouseDown:event]; [NSApp _setCurrentEvent:nil]; if (buttonNumber == LeftMouseButton) m_leftMouseButtonDown = true; } } void EventSenderProxy::mouseUp(unsigned buttonNumber, WKEventModifiers modifiers) { 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); [NSApp _setCurrentEvent:event]; [targetView mouseUp:event]; [NSApp _setCurrentEvent:nil]; if (buttonNumber == LeftMouseButton) m_leftMouseButtonDown = false; m_clickTime = currentEventTime(); m_clickPosition = m_position; } #if defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003 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 EventSenderProxy::beginPressureEvent(int stage) { RetainPtr 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 EventSenderProxy::pressureChangeEvent(int stage, float pressure, EventSenderProxy::PressureChangeDirection direction) { RetainPtr 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 EventSenderProxy::pressureChangeEvent(int stage, EventSenderProxy::PressureChangeDirection direction) { return pressureChangeEvent(stage, 0.5, direction); } void EventSenderProxy::mouseForceClick() { sendMouseDownToStartPressureEvents(); RetainPtr beginPressure = beginPressureEvent(1); RetainPtr preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing); RetainPtr forceClick = pressureChangeEvent(2, PressureChangeDirection::Increasing); RetainPtr 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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // WKView caches the most recent pressure event, so send it a nil event to clear the cache. [targetView pressureChangeWithEvent:nil]; #pragma clang diagnostic pop } void EventSenderProxy::startAndCancelMouseForceClick() { sendMouseDownToStartPressureEvents(); RetainPtr beginPressure = beginPressureEvent(1); RetainPtr increasingPressure = pressureChangeEvent(1, PressureChangeDirection::Increasing); RetainPtr 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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // WKView caches the most recent pressure event, so send it a nil event to clear the cache. [targetView pressureChangeWithEvent:nil]; #pragma clang diagnostic pop } void EventSenderProxy::mouseForceDown() { sendMouseDownToStartPressureEvents(); RetainPtr beginPressure = beginPressureEvent(1); RetainPtr preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing); RetainPtr 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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // WKView caches the most recent pressure event, so send it a nil event to clear the cache. [targetView pressureChangeWithEvent:nil]; #pragma clang diagnostic pop } void EventSenderProxy::mouseForceUp() { RetainPtr beginPressure = beginPressureEvent(2); RetainPtr stageTwoEvent = pressureChangeEvent(2, PressureChangeDirection::Decreasing); RetainPtr 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]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // WKView caches the most recent pressure event, so send it a nil event to clear the cache. [targetView pressureChangeWithEvent:nil]; #pragma clang diagnostic pop } void EventSenderProxy::mouseForceChanged(float force) { int stage = force < 1 ? 1 : 2; float pressure = force < 1 ? force : force - 1; RetainPtr beginPressure = beginPressureEvent(stage); RetainPtr 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()]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" // WKView caches the most recent pressure event, so send it a nil event to clear the cache. [targetView pressureChangeWithEvent:nil]; #pragma clang diagnostic pop } #else #if PLATFORM(COCOA) RetainPtr EventSenderProxy::beginPressureEvent(int) { return nil; } RetainPtr EventSenderProxy::pressureChangeEvent(int, PressureChangeDirection) { return nil; } RetainPtr EventSenderProxy::pressureChangeEvent(int, float, PressureChangeDirection) { return nil; } #endif // PLATFORM(COCOA) void EventSenderProxy::sendMouseDownToStartPressureEvents() { } void EventSenderProxy::mouseForceDown() { } void EventSenderProxy::mouseForceUp() { } void EventSenderProxy::mouseForceChanged(float) { } void EventSenderProxy::mouseForceClick() { } void EventSenderProxy::startAndCancelMouseForceClick() { } #endif // defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003 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) { [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 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 . 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 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 . 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