Swipe gesture tests don't work on macOS Sierra
[WebKit-https.git] / Tools / WebKitTestRunner / mac / EventSenderProxy.mm
1 /*
2  * Copyright (C) 2011, 2014-2015 Apple Inc. All rights reserved.
3  * Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
15  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
16  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
18  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
24  * THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #import "config.h"
28 #import "EventSenderProxy.h"
29
30 #import "PlatformWebView.h"
31 #import "StringFunctions.h"
32 #import "TestController.h"
33 #import "TestRunnerWKWebView.h"
34 #import <Carbon/Carbon.h>
35 #import <WebKit/WKString.h>
36 #import <WebKit/WKPagePrivate.h>
37 #import <WebKit/WKWebView.h>
38 #import <wtf/RetainPtr.h>
39 #import <wtf/mac/AppKitCompatibilityDeclarations.h>
40
41 @interface NSApplication (Details)
42 - (void)_setCurrentEvent:(NSEvent *)event;
43 @end
44
45 @interface NSEvent (ForTestRunner)
46 - (void)_postDelayed;
47 @end
48
49 @interface EventSenderSyntheticEvent : NSEvent {
50 @public
51     NSPoint _eventSender_locationInWindow;
52     NSPoint _eventSender_location;
53     NSInteger _eventSender_stage;
54     float _eventSender_pressure;
55     CGFloat _eventSender_stageTransition;
56     NSEventPhase _eventSender_phase;
57     NSEventPhase _eventSender_momentumPhase;
58     NSTimeInterval _eventSender_timestamp;
59     NSInteger _eventSender_eventNumber;
60     short _eventSender_subtype;
61     NSEventType _eventSender_type;
62     NSWindow *_eventSender_window;
63 }
64
65 - (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;
66 - (NSTimeInterval)timestamp;
67 @end
68
69 @implementation EventSenderSyntheticEvent
70
71 - (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
72 {
73     self = [super init];
74
75     if (!self)
76         return nil;
77
78     _eventSender_location = location;
79     _eventSender_locationInWindow = globalLocation;
80     _eventSender_stage = stage;
81     _eventSender_pressure = pressure;
82     _eventSender_stageTransition = stageTransition;
83     _eventSender_phase = phase;
84     _eventSender_timestamp = time;
85     _eventSender_eventNumber = eventNumber;
86     _eventSender_window = window;
87 #if defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
88     self->_type = NSEventTypePressure;
89     _eventSender_type = NSEventTypePressure;
90 #endif
91
92     return self;
93 }
94
95 - (CGFloat)stageTransition
96 {
97     return _eventSender_stageTransition;
98 }
99
100 - (NSTimeInterval)timestamp
101 {
102     return _eventSender_timestamp;
103 }
104
105 - (NSEventType)type
106 {
107     return _eventSender_type;
108 }
109
110 - (NSEventSubtype)subtype
111 {
112     return (NSEventSubtype)_eventSender_subtype;
113 }
114
115 - (NSPoint)locationInWindow
116 {
117     return _eventSender_location;
118 }
119
120 - (NSPoint)location
121 {
122     return _eventSender_locationInWindow;
123 }
124
125 - (NSInteger)stage
126 {
127     return _eventSender_stage;
128 }
129
130 - (float)pressure
131 {
132     return _eventSender_pressure;
133 }
134
135 - (NSEventPhase)phase
136 {
137     return _eventSender_phase;
138 }
139
140 - (NSEventPhase)momentumPhase
141 {
142     return _eventSender_momentumPhase;
143 }
144
145 - (NSInteger)eventNumber
146 {
147     return _eventSender_eventNumber;
148 }
149
150 - (BOOL)_isTouchesEnded
151 {
152     return false;
153 }
154
155 - (NSWindow *)window
156 {
157     return _eventSender_window;
158 }
159
160 @end
161
162 namespace WTR {
163
164 enum MouseAction {
165     MouseDown,
166     MouseUp,
167     MouseDragged
168 };
169
170 // Match the DOM spec (sadly the DOM spec does not provide an enum)
171 enum MouseButton {
172     LeftMouseButton = 0,
173     MiddleMouseButton = 1,
174     RightMouseButton = 2,
175     NoMouseButton = -1
176 };
177
178 struct KeyMappingEntry {
179     int macKeyCode;
180     int macNumpadKeyCode;
181     unichar character;
182     NSString* characterName;
183 };
184
185 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
186 {
187     switch (button) {
188     case LeftMouseButton:
189         switch (action) {
190         case MouseDown:
191             return NSEventTypeLeftMouseDown;
192         case MouseUp:
193             return NSEventTypeLeftMouseUp;
194         case MouseDragged:
195             return NSEventTypeLeftMouseDragged;
196         }
197     case RightMouseButton:
198         switch (action) {
199         case MouseDown:
200             return NSEventTypeRightMouseDown;
201         case MouseUp:
202             return NSEventTypeRightMouseUp;
203         case MouseDragged:
204             return NSEventTypeRightMouseDragged;
205         }
206     default:
207         switch (action) {
208         case MouseDown:
209             return NSEventTypeOtherMouseDown;
210         case MouseUp:
211             return NSEventTypeOtherMouseUp;
212         case MouseDragged:
213             return NSEventTypeOtherMouseDragged;
214         }
215     }
216     assert(0);
217     return static_cast<NSEventType>(0);
218 }
219
220 static int buildModifierFlags(WKEventModifiers modifiers)
221 {
222     int flags = 0;
223     if (modifiers & kWKEventModifiersControlKey)
224         flags |= NSEventModifierFlagControl;
225     if (modifiers & kWKEventModifiersShiftKey)
226         flags |= NSEventModifierFlagShift;
227     if (modifiers & kWKEventModifiersAltKey)
228         flags |= NSEventModifierFlagOption;
229     if (modifiers & kWKEventModifiersMetaKey)
230         flags |= NSEventModifierFlagCommand;
231     if (modifiers & kWKEventModifiersCapsLockKey)
232         flags |= NSEventModifierFlagCapsLock;
233     return flags;
234 }
235
236 static NSTimeInterval absoluteTimeForEventTime(double currentEventTime)
237 {
238     return GetCurrentEventTime() + currentEventTime;
239 }
240
241 EventSenderProxy::EventSenderProxy(TestController* testController)
242     : m_testController(testController)
243     , m_time(0)
244     , m_position()
245     , m_leftMouseButtonDown(false)
246     , m_clickCount(0)
247     , m_clickTime(0)
248     , m_clickPosition()
249     , m_clickButton(kWKEventMouseButtonNoButton)
250     , eventNumber(0)
251 {
252 }
253
254 EventSenderProxy::~EventSenderProxy()
255 {
256 }
257
258 void EventSenderProxy::updateClickCountForButton(int button)
259 {
260     if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
261         ++m_clickCount;
262         m_clickTime = m_time;
263         return;
264     }
265
266     m_clickCount = 1;
267     m_clickTime = m_time;
268     m_clickPosition = m_position;
269     m_clickButton = button;
270 }
271
272 void EventSenderProxy::mouseDown(unsigned buttonNumber, WKEventModifiers modifiers)
273 {
274     updateClickCountForButton(buttonNumber);
275
276     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
277     NSEvent *event = [NSEvent mouseEventWithType:eventType
278                                         location:NSMakePoint(m_position.x, m_position.y)
279                                    modifierFlags:buildModifierFlags(modifiers)
280                                        timestamp:absoluteTimeForEventTime(currentEventTime())
281                                     windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
282                                          context:[NSGraphicsContext currentContext] 
283                                      eventNumber:++eventNumber 
284                                       clickCount:m_clickCount 
285                                         pressure:0.0];
286
287     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
288     if (targetView) {
289         [NSApp _setCurrentEvent:event];
290         [targetView mouseDown:event];
291         [NSApp _setCurrentEvent:nil];
292         if (buttonNumber == LeftMouseButton)
293             m_leftMouseButtonDown = true;
294     }
295 }
296
297 void EventSenderProxy::mouseUp(unsigned buttonNumber, WKEventModifiers modifiers)
298 {
299     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
300     NSEvent *event = [NSEvent mouseEventWithType:eventType
301                                         location:NSMakePoint(m_position.x, m_position.y)
302                                    modifierFlags:buildModifierFlags(modifiers)
303                                        timestamp:absoluteTimeForEventTime(currentEventTime())
304                                     windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
305                                          context:[NSGraphicsContext currentContext] 
306                                      eventNumber:++eventNumber 
307                                       clickCount:m_clickCount 
308                                         pressure:0.0];
309
310     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
311     // FIXME: Silly hack to teach WKTR to respect capturing mouse events outside the WKView.
312     // The right solution is just to use NSApplication's built-in event sending methods, 
313     // instead of rolling our own algorithm for selecting an event target.
314     if (!targetView)
315         targetView = m_testController->mainWebView()->platformView();
316
317     ASSERT(targetView);
318     [NSApp _setCurrentEvent:event];
319     [targetView mouseUp:event];
320     [NSApp _setCurrentEvent:nil];
321     if (buttonNumber == LeftMouseButton)
322         m_leftMouseButtonDown = false;
323     m_clickTime = currentEventTime();
324     m_clickPosition = m_position;
325 }
326
327 #if defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
328 void EventSenderProxy::sendMouseDownToStartPressureEvents()
329 {
330     updateClickCountForButton(0);
331
332     NSEvent *event = [NSEvent mouseEventWithType:NSEventTypeLeftMouseDown
333         location:NSMakePoint(m_position.x, m_position.y)
334         modifierFlags:NSEventMaskPressure
335         timestamp:absoluteTimeForEventTime(currentEventTime())
336         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
337         context:[NSGraphicsContext currentContext]
338         eventNumber:++eventNumber
339         clickCount:m_clickCount
340         pressure:0.0];
341
342     [NSApp sendEvent:event];
343 }
344
345 static void handleForceEventSynchronously(NSEvent *event)
346 {
347     // Force events have to be pushed onto the queue, then popped off right away and handled synchronously in order
348     // to get the NSImmediateActionGestureRecognizer to do the right thing.
349     [event _postDelayed];
350     [NSApp sendEvent:[NSApp nextEventMatchingMask:NSEventMaskPressure untilDate:[NSDate dateWithTimeIntervalSinceNow:0.05] inMode:NSDefaultRunLoopMode dequeue:YES]];
351 }
352
353 RetainPtr<NSEvent> EventSenderProxy::beginPressureEvent(int stage)
354 {
355     RetainPtr<EventSenderSyntheticEvent> event = adoptNS([[EventSenderSyntheticEvent alloc] initPressureEventAtLocation:NSMakePoint(m_position.x, m_position.y)
356         globalLocation:([m_testController->mainWebView()->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
357         stage:stage
358         pressure:0.5
359         stageTransition:0
360         phase:NSEventPhaseBegan
361         time:absoluteTimeForEventTime(currentEventTime())
362         eventNumber:++eventNumber
363         window:[m_testController->mainWebView()->platformView() window]]);
364
365     return event;
366 }
367
368 RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int stage, float pressure, EventSenderProxy::PressureChangeDirection direction)
369 {
370     RetainPtr<EventSenderSyntheticEvent> event = adoptNS([[EventSenderSyntheticEvent alloc] initPressureEventAtLocation:NSMakePoint(m_position.x, m_position.y)
371         globalLocation:([m_testController->mainWebView()->platformWindow() convertRectToScreen:NSMakeRect(m_position.x, m_position.y, 1, 1)].origin)
372         stage:stage
373         pressure:pressure
374         stageTransition:direction == PressureChangeDirection::Increasing ? 0.5 : -0.5
375         phase:NSEventPhaseChanged
376         time:absoluteTimeForEventTime(currentEventTime())
377         eventNumber:++eventNumber
378         window:[m_testController->mainWebView()->platformView() window]]);
379
380     return event;
381 }
382
383 RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int stage, EventSenderProxy::PressureChangeDirection direction)
384 {
385     return pressureChangeEvent(stage, 0.5, direction);
386 }
387
388 void EventSenderProxy::mouseForceClick()
389 {
390     sendMouseDownToStartPressureEvents();
391
392     RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
393     RetainPtr<NSEvent> preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
394     RetainPtr<NSEvent> forceClick = pressureChangeEvent(2, PressureChangeDirection::Increasing);
395     RetainPtr<NSEvent> releasingPressure = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
396     NSEvent *mouseUp = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
397         location:NSMakePoint(m_position.x, m_position.y)
398         modifierFlags:0
399         timestamp:absoluteTimeForEventTime(currentEventTime())
400         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
401         context:[NSGraphicsContext currentContext]
402         eventNumber:++eventNumber
403         clickCount:m_clickCount
404         pressure:0.0];
405
406     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[preForceClick.get() locationInWindow]];
407     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
408     ASSERT(targetView);
409
410     // Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
411     // the change in stage that marks those moments.
412     handleForceEventSynchronously(beginPressure.get());
413     handleForceEventSynchronously(preForceClick.get());
414     handleForceEventSynchronously(forceClick.get());
415     handleForceEventSynchronously(releasingPressure.get());
416     [NSApp sendEvent:mouseUp];
417
418     [NSApp _setCurrentEvent:nil];
419 #pragma clang diagnostic push
420 #pragma clang diagnostic ignored "-Wnonnull"
421     // WKView caches the most recent pressure event, so send it a nil event to clear the cache.
422     [targetView pressureChangeWithEvent:nil];
423 #pragma clang diagnostic pop
424 }
425
426 void EventSenderProxy::startAndCancelMouseForceClick()
427 {
428     sendMouseDownToStartPressureEvents();
429
430     RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
431     RetainPtr<NSEvent> increasingPressure = pressureChangeEvent(1, PressureChangeDirection::Increasing);
432     RetainPtr<NSEvent> releasingPressure = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
433     NSEvent *mouseUp = [NSEvent mouseEventWithType:NSEventTypeLeftMouseUp
434         location:NSMakePoint(m_position.x, m_position.y)
435         modifierFlags:0
436         timestamp:absoluteTimeForEventTime(currentEventTime())
437         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
438         context:[NSGraphicsContext currentContext]
439         eventNumber:++eventNumber
440         clickCount:m_clickCount
441         pressure:0.0];
442
443     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure.get() locationInWindow]];
444     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
445     ASSERT(targetView);
446
447     // Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
448     // the change in stage that marks those moments.
449     handleForceEventSynchronously(beginPressure.get());
450     handleForceEventSynchronously(increasingPressure.get());
451     handleForceEventSynchronously(releasingPressure.get());
452     [NSApp sendEvent:mouseUp];
453
454     [NSApp _setCurrentEvent:nil];
455 #pragma clang diagnostic push
456 #pragma clang diagnostic ignored "-Wnonnull"
457     // WKView caches the most recent pressure event, so send it a nil event to clear the cache.
458     [targetView pressureChangeWithEvent:nil];
459 #pragma clang diagnostic pop
460 }
461
462 void EventSenderProxy::mouseForceDown()
463 {
464     sendMouseDownToStartPressureEvents();
465
466     RetainPtr<NSEvent> beginPressure = beginPressureEvent(1);
467     RetainPtr<NSEvent> preForceClick = pressureChangeEvent(1, PressureChangeDirection::Increasing);
468     RetainPtr<NSEvent> forceMouseDown = pressureChangeEvent(2, PressureChangeDirection::Increasing);
469
470     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
471     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
472     ASSERT(targetView);
473
474     // Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
475     // the change in stage that marks those moments.
476     handleForceEventSynchronously(beginPressure.get());
477     handleForceEventSynchronously(preForceClick.get());
478     [forceMouseDown _postDelayed];
479
480     [NSApp _setCurrentEvent:nil];
481 #pragma clang diagnostic push
482 #pragma clang diagnostic ignored "-Wnonnull"
483     // WKView caches the most recent pressure event, so send it a nil event to clear the cache.
484     [targetView pressureChangeWithEvent:nil];
485 #pragma clang diagnostic pop
486 }
487
488 void EventSenderProxy::mouseForceUp()
489 {
490     RetainPtr<NSEvent> beginPressure = beginPressureEvent(2);
491     RetainPtr<NSEvent> stageTwoEvent = pressureChangeEvent(2, PressureChangeDirection::Decreasing);
492     RetainPtr<NSEvent> stageOneEvent = pressureChangeEvent(1, PressureChangeDirection::Decreasing);
493
494     // Since AppKit does not implement forceup/down as mouse events, we need to send two pressure events to detect
495     // the change in stage that marks those moments.
496     [NSApp sendEvent:beginPressure.get()];
497     [NSApp sendEvent:stageTwoEvent.get()];
498     [NSApp sendEvent:stageOneEvent.get()];
499
500     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
501     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
502     ASSERT(targetView);
503
504     [NSApp _setCurrentEvent:nil];
505
506 #pragma clang diagnostic push
507 #pragma clang diagnostic ignored "-Wnonnull"
508 // WKView caches the most recent pressure event, so send it a nil event to clear the cache.
509     [targetView pressureChangeWithEvent:nil];
510 #pragma clang diagnostic pop
511 }
512
513 void EventSenderProxy::mouseForceChanged(float force)
514 {
515     int stage = force < 1 ? 1 : 2;
516     float pressure = force < 1 ? force : force - 1;
517     RetainPtr<NSEvent> beginPressure = beginPressureEvent(stage);
518     RetainPtr<NSEvent> pressureChangedEvent = pressureChangeEvent(stage, pressure, PressureChangeDirection::Increasing);
519
520     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[beginPressure locationInWindow]];
521     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
522     ASSERT(targetView);
523
524     [NSApp sendEvent:beginPressure.get()];
525     [NSApp sendEvent:pressureChangedEvent.get()];
526
527 #pragma clang diagnostic push
528 #pragma clang diagnostic ignored "-Wnonnull"
529     // WKView caches the most recent pressure event, so send it a nil event to clear the cache.
530     [targetView pressureChangeWithEvent:nil];
531 #pragma clang diagnostic pop
532 }
533 #else
534
535 #if PLATFORM(COCOA)
536 RetainPtr<NSEvent> EventSenderProxy::beginPressureEvent(int)
537 {
538     return nil;
539 }
540
541 RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int, PressureChangeDirection)
542 {
543     return nil;
544 }
545
546 RetainPtr<NSEvent> EventSenderProxy::pressureChangeEvent(int, float, PressureChangeDirection)
547 {
548     return nil;
549 }
550 #endif // PLATFORM(COCOA)
551
552 void EventSenderProxy::sendMouseDownToStartPressureEvents()
553 {
554 }
555
556 void EventSenderProxy::mouseForceDown()
557 {
558 }
559
560 void EventSenderProxy::mouseForceUp()
561 {
562 }
563
564 void EventSenderProxy::mouseForceChanged(float)
565 {
566 }
567
568 void EventSenderProxy::mouseForceClick()
569 {
570 }
571
572 void EventSenderProxy::startAndCancelMouseForceClick()
573 {
574 }
575 #endif // defined(__LP64__) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101003
576
577 void EventSenderProxy::mouseMoveTo(double x, double y)
578 {
579     NSView *view = m_testController->mainWebView()->platformView();
580     NSPoint newMousePosition = [view convertPoint:NSMakePoint(x, y) toView:nil];
581     bool isDrag = m_leftMouseButtonDown;
582     NSEvent *event = [NSEvent mouseEventWithType:(isDrag ? NSEventTypeLeftMouseDragged : NSEventTypeMouseMoved)
583                                         location:newMousePosition
584                                    modifierFlags:0 
585                                        timestamp:absoluteTimeForEventTime(currentEventTime())
586                                     windowNumber:view.window.windowNumber
587                                          context:[NSGraphicsContext currentContext] 
588                                      eventNumber:++eventNumber 
589                                       clickCount:(m_leftMouseButtonDown ? m_clickCount : 0) 
590                                         pressure:0];
591
592     CGEventRef cgEvent = event.CGEvent;
593     CGEventSetIntegerValueField(cgEvent, kCGMouseEventDeltaX, newMousePosition.x - m_position.x);
594     CGEventSetIntegerValueField(cgEvent, kCGMouseEventDeltaY, newMousePosition.y - m_position.y);
595     event = [NSEvent eventWithCGEvent:cgEvent];
596     m_position.x = newMousePosition.x;
597     m_position.y = newMousePosition.y;
598
599     NSPoint windowLocation = event.locationInWindow;
600     // Always target drags at the WKWebView to allow for drag-scrolling outside the view.
601     NSView *targetView = isDrag ? m_testController->mainWebView()->platformView() : [m_testController->mainWebView()->platformView() hitTest:windowLocation];
602     if (targetView) {
603         [NSApp _setCurrentEvent:event];
604         if (isDrag)
605             [targetView mouseDragged:event];
606         else
607             [targetView mouseMoved:event];
608         [NSApp _setCurrentEvent:nil];
609     } else
610         WTFLogAlways("mouseMoveTo failed to find a target view at %f,%f\n", windowLocation.x, windowLocation.y);
611 }
612
613 void EventSenderProxy::leapForward(int milliseconds)
614 {
615     m_time += milliseconds / 1000.0;
616 }
617
618 void EventSenderProxy::keyDown(WKStringRef key, WKEventModifiers modifiers, unsigned keyLocation)
619 {
620     NSString* character = [NSString stringWithCString:toSTD(key).c_str()
621                                    encoding:[NSString defaultCStringEncoding]];
622
623     NSString *eventCharacter = character;
624     unsigned short keyCode = 0;
625     if ([character isEqualToString:@"leftArrow"]) {
626         const unichar ch = NSLeftArrowFunctionKey;
627         eventCharacter = [NSString stringWithCharacters:&ch length:1];
628         keyCode = 0x7B;
629     } else if ([character isEqualToString:@"rightArrow"]) {
630         const unichar ch = NSRightArrowFunctionKey;
631         eventCharacter = [NSString stringWithCharacters:&ch length:1];
632         keyCode = 0x7C;
633     } else if ([character isEqualToString:@"upArrow"]) {
634         const unichar ch = NSUpArrowFunctionKey;
635         eventCharacter = [NSString stringWithCharacters:&ch length:1];
636         keyCode = 0x7E;
637     } else if ([character isEqualToString:@"downArrow"]) {
638         const unichar ch = NSDownArrowFunctionKey;
639         eventCharacter = [NSString stringWithCharacters:&ch length:1];
640         keyCode = 0x7D;
641     } else if ([character isEqualToString:@"pageUp"]) {
642         const unichar ch = NSPageUpFunctionKey;
643         eventCharacter = [NSString stringWithCharacters:&ch length:1];
644         keyCode = 0x74;
645     } else if ([character isEqualToString:@"pageDown"]) {
646         const unichar ch = NSPageDownFunctionKey;
647         eventCharacter = [NSString stringWithCharacters:&ch length:1];
648         keyCode = 0x79;
649     } else if ([character isEqualToString:@"home"]) {
650         const unichar ch = NSHomeFunctionKey;
651         eventCharacter = [NSString stringWithCharacters:&ch length:1];
652         keyCode = 0x73;
653     } else if ([character isEqualToString:@"end"]) {
654         const unichar ch = NSEndFunctionKey;
655         eventCharacter = [NSString stringWithCharacters:&ch length:1];
656         keyCode = 0x77;
657     } else if ([character isEqualToString:@"insert"]) {
658         const unichar ch = NSInsertFunctionKey;
659         eventCharacter = [NSString stringWithCharacters:&ch length:1];
660         keyCode = 0x72;
661     } else if ([character isEqualToString:@"delete"]) {
662         const unichar ch = NSDeleteFunctionKey;
663         eventCharacter = [NSString stringWithCharacters:&ch length:1];
664         keyCode = 0x75;
665     } else if ([character isEqualToString:@"printScreen"]) {
666         const unichar ch = NSPrintScreenFunctionKey;
667         eventCharacter = [NSString stringWithCharacters:&ch length:1];
668         keyCode = 0x0; // There is no known virtual key code for PrintScreen.
669     } else if ([character isEqualToString:@"cyrillicSmallLetterA"]) {
670         const unichar ch = 0x0430;
671         eventCharacter = [NSString stringWithCharacters:&ch length:1];
672         keyCode = 0x3; // Shares key with "F" on Russian layout.
673     } else if ([character isEqualToString:@"leftControl"]) {
674         const unichar ch = 0xFFE3;
675         eventCharacter = [NSString stringWithCharacters:&ch length:1];
676         keyCode = 0x3B;
677     } else if ([character isEqualToString:@"leftShift"]) {
678         const unichar ch = 0xFFE1;
679         eventCharacter = [NSString stringWithCharacters:&ch length:1];
680         keyCode = 0x38;
681     } else if ([character isEqualToString:@"leftAlt"]) {
682         const unichar ch = 0xFFE7;
683         eventCharacter = [NSString stringWithCharacters:&ch length:1];
684         keyCode = 0x3A;
685     } else if ([character isEqualToString:@"rightControl"]) {
686         const unichar ch = 0xFFE4;
687         eventCharacter = [NSString stringWithCharacters:&ch length:1];
688         keyCode = 0x3E;
689     } else if ([character isEqualToString:@"rightShift"]) {
690         const unichar ch = 0xFFE2;
691         eventCharacter = [NSString stringWithCharacters:&ch length:1];
692         keyCode = 0x3C;
693     } else if ([character isEqualToString:@"rightAlt"]) {
694         const unichar ch = 0xFFE8;
695         eventCharacter = [NSString stringWithCharacters:&ch length:1];
696         keyCode = 0x3D;
697     } else if ([character isEqualToString:@"escape"]) {
698         const unichar ch = 0x1B;
699         eventCharacter = [NSString stringWithCharacters:&ch length:1];
700         keyCode = 0x35;
701     }
702
703     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
704     // If the input string is a function-key name, set its key code.
705     for (unsigned i = 1; i <= 24; i++) {
706         if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
707             const unichar ch = NSF1FunctionKey + (i - 1);
708             eventCharacter = [NSString stringWithCharacters:&ch length:1];
709             switch (i) {
710                 case 1: keyCode = 0x7A; break;
711                 case 2: keyCode = 0x78; break;
712                 case 3: keyCode = 0x63; break;
713                 case 4: keyCode = 0x76; break;
714                 case 5: keyCode = 0x60; break;
715                 case 6: keyCode = 0x61; break;
716                 case 7: keyCode = 0x62; break;
717                 case 8: keyCode = 0x64; break;
718                 case 9: keyCode = 0x65; break;
719                 case 10: keyCode = 0x6D; break;
720                 case 11: keyCode = 0x67; break;
721                 case 12: keyCode = 0x6F; break;
722                 case 13: keyCode = 0x69; break;
723                 case 14: keyCode = 0x6B; break;
724                 case 15: keyCode = 0x71; break;
725                 case 16: keyCode = 0x6A; break;
726                 case 17: keyCode = 0x40; break;
727                 case 18: keyCode = 0x4F; break;
728                 case 19: keyCode = 0x50; break;
729                 case 20: keyCode = 0x5A; break;
730             }
731         }
732     }
733
734     // FIXME: No keyCode is set for most keys.
735     if ([character isEqualToString:@"\t"])
736         keyCode = 0x30;
737     else if ([character isEqualToString:@" "])
738         keyCode = 0x31;
739     else if ([character isEqualToString:@"\r"])
740         keyCode = 0x24;
741     else if ([character isEqualToString:@"\n"])
742         keyCode = 0x4C;
743     else if ([character isEqualToString:@"\x8"])
744         keyCode = 0x33;
745     else if ([character isEqualToString:@"a"])
746         keyCode = 0x00;
747     else if ([character isEqualToString:@"b"])
748         keyCode = 0x0B;
749     else if ([character isEqualToString:@"d"])
750         keyCode = 0x02;
751     else if ([character isEqualToString:@"e"])
752         keyCode = 0x0E;
753     else if ([character isEqualToString:@"\x1b"])
754         keyCode = 0x1B;
755
756     KeyMappingEntry table[] = {
757         {0x2F, 0x41, '.', nil},
758         {0,    0x43, '*', nil},
759         {0,    0x45, '+', nil},
760         {0,    0x47, NSClearLineFunctionKey, @"clear"},
761         {0x2C, 0x4B, '/', nil},
762         {0,    0x4C, 3, @"enter" },
763         {0x1B, 0x4E, '-', nil},
764         {0x18, 0x51, '=', nil},
765         {0x1D, 0x52, '0', nil},
766         {0x12, 0x53, '1', nil},
767         {0x13, 0x54, '2', nil},
768         {0x14, 0x55, '3', nil},
769         {0x15, 0x56, '4', nil},
770         {0x17, 0x57, '5', nil},
771         {0x16, 0x58, '6', nil},
772         {0x1A, 0x59, '7', nil},
773         {0x1C, 0x5B, '8', nil},
774         {0x19, 0x5C, '9', nil},
775     };
776     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) {
777         NSString* currentCharacterString = [NSString stringWithCharacters:&table[i].character length:1];
778         if ([character isEqualToString:currentCharacterString] || [character isEqualToString:table[i].characterName]) {
779             if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
780                 keyCode = table[i].macNumpadKeyCode;
781             else
782                 keyCode = table[i].macKeyCode;
783             eventCharacter = currentCharacterString;
784             break;
785         }
786     }
787
788     NSString *charactersIgnoringModifiers = eventCharacter;
789
790     int modifierFlags = 0;
791
792     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
793         modifierFlags |= NSEventModifierFlagShift;
794         charactersIgnoringModifiers = [character lowercaseString];
795     }
796
797     modifierFlags |= buildModifierFlags(modifiers);
798
799     if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
800         modifierFlags |= NSEventModifierFlagNumericPad;
801
802     NSEvent *event = [NSEvent keyEventWithType:NSEventTypeKeyDown
803                         location:NSMakePoint(5, 5)
804                         modifierFlags:modifierFlags
805                         timestamp:absoluteTimeForEventTime(currentEventTime())
806                         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
807                         context:[NSGraphicsContext currentContext]
808                         characters:eventCharacter
809                         charactersIgnoringModifiers:charactersIgnoringModifiers
810                         isARepeat:NO
811                         keyCode:keyCode];
812
813     [NSApp _setCurrentEvent:event];
814     [[m_testController->mainWebView()->platformWindow() firstResponder] keyDown:event];
815     [NSApp _setCurrentEvent:nil];
816
817     event = [NSEvent keyEventWithType:NSEventTypeKeyUp
818                         location:NSMakePoint(5, 5)
819                         modifierFlags:modifierFlags
820                         timestamp:absoluteTimeForEventTime(currentEventTime())
821                         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
822                         context:[NSGraphicsContext currentContext]
823                         characters:eventCharacter
824                         charactersIgnoringModifiers:charactersIgnoringModifiers
825                         isARepeat:NO
826                         keyCode:keyCode];
827
828     [NSApp _setCurrentEvent:event];
829     [[m_testController->mainWebView()->platformWindow() firstResponder] keyUp:event];
830     [NSApp _setCurrentEvent:nil];
831 }
832
833 void EventSenderProxy::mouseScrollBy(int x, int y)
834 {
835     RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
836
837     // Set the CGEvent location in flipped coords relative to the first screen, which
838     // compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
839     // no associated window. See <rdar://problem/17180591>.
840     CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] objectAtIndex:0] frame].size.height - m_position.y);
841     CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
842
843     NSEvent *event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
844     if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
845         [NSApp _setCurrentEvent:event];
846         [targetView scrollWheel:event];
847         [NSApp _setCurrentEvent:nil];
848     } else {
849         NSPoint location = [event locationInWindow];
850         WTFLogAlways("mouseScrollBy failed to find the target view at %f,%f\n", location.x, location.y);
851     }
852 }
853
854 void EventSenderProxy::continuousMouseScrollBy(int x, int y, bool paged)
855 {
856     WTFLogAlways("EventSenderProxy::continuousMouseScrollBy is not implemented\n");
857     return;
858 }
859
860 void EventSenderProxy::mouseScrollByWithWheelAndMomentumPhases(int x, int y, int phase, int momentum)
861 {
862     RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
863
864     // Set the CGEvent location in flipped coords relative to the first screen, which
865     // compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
866     // no associated window. See <rdar://problem/17180591>.
867     CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] objectAtIndex:0] frame].size.height - m_position.y);
868     CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
869
870     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventIsContinuous, 1);
871     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventScrollPhase, phase);
872     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventMomentumPhase, momentum);
873
874     NSEvent* event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
875
876     // Our event should have the correct settings:
877     if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
878         [NSApp _setCurrentEvent:event];
879         [targetView scrollWheel:event];
880         [NSApp _setCurrentEvent:nil];
881     } else {
882         NSPoint windowLocation = [event locationInWindow];
883         WTFLogAlways("mouseScrollByWithWheelAndMomentumPhases failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
884     }
885 }
886
887 } // namespace WTR