Latched scrolling tests are flakey on Mavericks
[WebKit-https.git] / Tools / WebKitTestRunner / mac / EventSenderProxy.mm
1 /*
2  * Copyright (C) 2011, 2014 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 <Carbon/Carbon.h>
34 #import <WebKit/WKString.h>
35 #import <wtf/RetainPtr.h>
36
37 @interface NSApplication (Details)
38 - (void)_setCurrentEvent:(NSEvent *)event;
39 @end
40
41 namespace WTR {
42
43 enum MouseAction {
44     MouseDown,
45     MouseUp,
46     MouseDragged
47 };
48
49 // Match the DOM spec (sadly the DOM spec does not provide an enum)
50 enum MouseButton {
51     LeftMouseButton = 0,
52     MiddleMouseButton = 1,
53     RightMouseButton = 2,
54     NoMouseButton = -1
55 };
56
57 struct KeyMappingEntry {
58     int macKeyCode;
59     int macNumpadKeyCode;
60     unichar character;
61     NSString* characterName;
62 };
63
64 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
65 {
66     switch (button) {
67         case LeftMouseButton:
68             switch (action) {
69                 case MouseDown:
70                     return NSLeftMouseDown;
71                 case MouseUp:
72                     return NSLeftMouseUp;
73                 case MouseDragged:
74                     return NSLeftMouseDragged;
75             }
76         case RightMouseButton:
77             switch (action) {
78                 case MouseDown:
79                     return NSRightMouseDown;
80                 case MouseUp:
81                     return NSRightMouseUp;
82                 case MouseDragged:
83                     return NSRightMouseDragged;
84             }
85         default:
86             switch (action) {
87                 case MouseDown:
88                     return NSOtherMouseDown;
89                 case MouseUp:
90                     return NSOtherMouseUp;
91                 case MouseDragged:
92                     return NSOtherMouseDragged;
93             }
94     }
95     assert(0);
96     return static_cast<NSEventType>(0);
97 }
98
99 static int buildModifierFlags(WKEventModifiers modifiers)
100 {
101     int flags = 0;
102     if (modifiers & kWKEventModifiersControlKey)
103         flags |= NSControlKeyMask;
104     if (modifiers & kWKEventModifiersShiftKey)
105         flags |= NSShiftKeyMask;
106     if (modifiers & kWKEventModifiersAltKey)
107         flags |= NSAlternateKeyMask;
108     if (modifiers & kWKEventModifiersMetaKey)
109         flags |= NSCommandKeyMask;
110     return flags;
111 }
112
113 static NSTimeInterval absoluteTimeForEventTime(double currentEventTime)
114 {
115     return GetCurrentEventTime() + currentEventTime;
116 }
117
118 EventSenderProxy::EventSenderProxy(TestController* testController)
119     : m_testController(testController)
120     , m_time(0)
121     , m_position()
122     , m_leftMouseButtonDown(false)
123     , m_clickCount(0)
124     , m_clickTime(0)
125     , m_clickPosition()
126     , m_clickButton(kWKEventMouseButtonNoButton)
127     , eventNumber(0)
128 {
129 }
130
131 EventSenderProxy::~EventSenderProxy()
132 {
133 }
134
135 void EventSenderProxy::updateClickCountForButton(int button)
136 {
137     if (m_time - m_clickTime < 1 && m_position == m_clickPosition && button == m_clickButton) {
138         ++m_clickCount;
139         m_clickTime = m_time;
140         return;
141     }
142
143     m_clickCount = 1;
144     m_clickTime = m_time;
145     m_clickPosition = m_position;
146     m_clickButton = button;
147 }
148
149 void EventSenderProxy::mouseDown(unsigned buttonNumber, WKEventModifiers modifiers)
150 {
151     updateClickCountForButton(buttonNumber);
152
153     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
154     NSEvent *event = [NSEvent mouseEventWithType:eventType
155                                         location:NSMakePoint(m_position.x, m_position.y)
156                                    modifierFlags:buildModifierFlags(modifiers)
157                                        timestamp:absoluteTimeForEventTime(currentEventTime())
158                                     windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
159                                          context:[NSGraphicsContext currentContext] 
160                                      eventNumber:++eventNumber 
161                                       clickCount:m_clickCount 
162                                         pressure:0.0];
163
164     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
165     if (targetView) {
166         [NSApp _setCurrentEvent:event];
167         [targetView mouseDown:event];
168         [NSApp _setCurrentEvent:nil];
169         if (buttonNumber == LeftMouseButton)
170             m_leftMouseButtonDown = true;
171     }
172 }
173
174 void EventSenderProxy::mouseUp(unsigned buttonNumber, WKEventModifiers modifiers)
175 {
176     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
177     NSEvent *event = [NSEvent mouseEventWithType:eventType
178                                         location:NSMakePoint(m_position.x, m_position.y)
179                                    modifierFlags:buildModifierFlags(modifiers)
180                                        timestamp:absoluteTimeForEventTime(currentEventTime())
181                                     windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
182                                          context:[NSGraphicsContext currentContext] 
183                                      eventNumber:++eventNumber 
184                                       clickCount:m_clickCount 
185                                         pressure:0.0];
186
187     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
188     // FIXME: Silly hack to teach WKTR to respect capturing mouse events outside the WKView.
189     // The right solution is just to use NSApplication's built-in event sending methods, 
190     // instead of rolling our own algorithm for selecting an event target.
191     targetView = targetView ? targetView : m_testController->mainWebView()->platformView();
192     ASSERT(targetView);
193     [NSApp _setCurrentEvent:event];
194     [targetView mouseUp:event];
195     [NSApp _setCurrentEvent:nil];
196     if (buttonNumber == LeftMouseButton)
197         m_leftMouseButtonDown = false;
198     m_clickTime = currentEventTime();
199     m_clickPosition = m_position;
200 }
201
202 void EventSenderProxy::mouseMoveTo(double x, double y)
203 {
204     NSView *view = m_testController->mainWebView()->platformView();
205     NSPoint position = [view convertPoint:NSMakePoint(x, y) toView:nil];
206     m_position.x = position.x;
207     m_position.y = position.y;
208     NSEvent *event = [NSEvent mouseEventWithType:(m_leftMouseButtonDown ? NSLeftMouseDragged : NSMouseMoved)
209                                         location:position
210                                    modifierFlags:0 
211                                        timestamp:absoluteTimeForEventTime(currentEventTime())
212                                     windowNumber:[[view window] windowNumber] 
213                                          context:[NSGraphicsContext currentContext] 
214                                      eventNumber:++eventNumber 
215                                       clickCount:(m_leftMouseButtonDown ? m_clickCount : 0) 
216                                         pressure:0.0];
217
218     NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]];
219     if (targetView) {
220         [NSApp _setCurrentEvent:event];
221         [targetView mouseMoved:event];
222         [NSApp _setCurrentEvent:nil];
223     } else {
224         CGPoint windowLocation = [event locationInWindow];
225         WTFLogAlways("mouseMoveTo failed to find a target view at %f,%f\n", windowLocation.x, windowLocation.y);
226     }
227 }
228
229 void EventSenderProxy::leapForward(int milliseconds)
230 {
231     m_time += milliseconds / 1000.0;
232 }
233
234 void EventSenderProxy::keyDown(WKStringRef key, WKEventModifiers modifiers, unsigned keyLocation)
235 {
236     NSString* character = [NSString stringWithCString:toSTD(key).c_str() 
237                                    encoding:[NSString defaultCStringEncoding]];
238
239     NSString *eventCharacter = character;
240     unsigned short keyCode = 0;
241     if ([character isEqualToString:@"leftArrow"]) {
242         const unichar ch = NSLeftArrowFunctionKey;
243         eventCharacter = [NSString stringWithCharacters:&ch length:1];
244         keyCode = 0x7B;
245     } else if ([character isEqualToString:@"rightArrow"]) {
246         const unichar ch = NSRightArrowFunctionKey;
247         eventCharacter = [NSString stringWithCharacters:&ch length:1];
248         keyCode = 0x7C;
249     } else if ([character isEqualToString:@"upArrow"]) {
250         const unichar ch = NSUpArrowFunctionKey;
251         eventCharacter = [NSString stringWithCharacters:&ch length:1];
252         keyCode = 0x7E;
253     } else if ([character isEqualToString:@"downArrow"]) {
254         const unichar ch = NSDownArrowFunctionKey;
255         eventCharacter = [NSString stringWithCharacters:&ch length:1];
256         keyCode = 0x7D;
257     } else if ([character isEqualToString:@"pageUp"]) {
258         const unichar ch = NSPageUpFunctionKey;
259         eventCharacter = [NSString stringWithCharacters:&ch length:1];
260         keyCode = 0x74;
261     } else if ([character isEqualToString:@"pageDown"]) {
262         const unichar ch = NSPageDownFunctionKey;
263         eventCharacter = [NSString stringWithCharacters:&ch length:1];
264         keyCode = 0x79;
265     } else if ([character isEqualToString:@"home"]) {
266         const unichar ch = NSHomeFunctionKey;
267         eventCharacter = [NSString stringWithCharacters:&ch length:1];
268         keyCode = 0x73;
269     } else if ([character isEqualToString:@"end"]) {
270         const unichar ch = NSEndFunctionKey;
271         eventCharacter = [NSString stringWithCharacters:&ch length:1];
272         keyCode = 0x77;
273     } else if ([character isEqualToString:@"insert"]) {
274         const unichar ch = NSInsertFunctionKey;
275         eventCharacter = [NSString stringWithCharacters:&ch length:1];
276         keyCode = 0x72;
277     } else if ([character isEqualToString:@"delete"]) {
278         const unichar ch = NSDeleteFunctionKey;
279         eventCharacter = [NSString stringWithCharacters:&ch length:1];
280         keyCode = 0x75;
281     } else if ([character isEqualToString:@"printScreen"]) {
282         const unichar ch = NSPrintScreenFunctionKey;
283         eventCharacter = [NSString stringWithCharacters:&ch length:1];
284         keyCode = 0x0; // There is no known virtual key code for PrintScreen.
285     } else if ([character isEqualToString:@"cyrillicSmallLetterA"]) {
286         const unichar ch = 0x0430;
287         eventCharacter = [NSString stringWithCharacters:&ch length:1];
288         keyCode = 0x3; // Shares key with "F" on Russian layout.
289     } else if ([character isEqualToString:@"leftControl"]) {
290         const unichar ch = 0xFFE3;
291         eventCharacter = [NSString stringWithCharacters:&ch length:1];
292         keyCode = 0x3B;
293     } else if ([character isEqualToString:@"leftShift"]) {
294         const unichar ch = 0xFFE1;
295         eventCharacter = [NSString stringWithCharacters:&ch length:1];
296         keyCode = 0x38;
297     } else if ([character isEqualToString:@"leftAlt"]) {
298         const unichar ch = 0xFFE7;
299         eventCharacter = [NSString stringWithCharacters:&ch length:1];
300         keyCode = 0x3A;
301     } else if ([character isEqualToString:@"rightControl"]) {
302         const unichar ch = 0xFFE4;
303         eventCharacter = [NSString stringWithCharacters:&ch length:1];
304         keyCode = 0x3E;
305     } else if ([character isEqualToString:@"rightShift"]) {
306         const unichar ch = 0xFFE2;
307         eventCharacter = [NSString stringWithCharacters:&ch length:1];
308         keyCode = 0x3C;
309     } else if ([character isEqualToString:@"rightAlt"]) {
310         const unichar ch = 0xFFE8;
311         eventCharacter = [NSString stringWithCharacters:&ch length:1];
312         keyCode = 0x3D;
313     }
314
315     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
316     // If the input string is a function-key name, set its key code.
317     for (unsigned i = 1; i <= 24; i++) {
318         if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
319             const unichar ch = NSF1FunctionKey + (i - 1);
320             eventCharacter = [NSString stringWithCharacters:&ch length:1];
321             switch (i) {
322                 case 1: keyCode = 0x7A; break;
323                 case 2: keyCode = 0x78; break;
324                 case 3: keyCode = 0x63; break;
325                 case 4: keyCode = 0x76; break;
326                 case 5: keyCode = 0x60; break;
327                 case 6: keyCode = 0x61; break;
328                 case 7: keyCode = 0x62; break;
329                 case 8: keyCode = 0x64; break;
330                 case 9: keyCode = 0x65; break;
331                 case 10: keyCode = 0x6D; break;
332                 case 11: keyCode = 0x67; break;
333                 case 12: keyCode = 0x6F; break;
334                 case 13: keyCode = 0x69; break;
335                 case 14: keyCode = 0x6B; break;
336                 case 15: keyCode = 0x71; break;
337                 case 16: keyCode = 0x6A; break;
338                 case 17: keyCode = 0x40; break;
339                 case 18: keyCode = 0x4F; break;
340                 case 19: keyCode = 0x50; break;
341                 case 20: keyCode = 0x5A; break;
342             }
343         }
344     }
345
346     // FIXME: No keyCode is set for most keys.
347     if ([character isEqualToString:@"\t"])
348         keyCode = 0x30;
349     else if ([character isEqualToString:@" "])
350         keyCode = 0x31;
351     else if ([character isEqualToString:@"\r"])
352         keyCode = 0x24;
353     else if ([character isEqualToString:@"\n"])
354         keyCode = 0x4C;
355     else if ([character isEqualToString:@"\x8"])
356         keyCode = 0x33;
357     else if ([character isEqualToString:@"a"])
358         keyCode = 0x00;
359     else if ([character isEqualToString:@"b"])
360         keyCode = 0x0B;
361     else if ([character isEqualToString:@"d"])
362         keyCode = 0x02;
363     else if ([character isEqualToString:@"e"])
364         keyCode = 0x0E;
365
366     KeyMappingEntry table[] = {
367         {0x2F, 0x41, '.', nil},
368         {0,    0x43, '*', nil},
369         {0,    0x45, '+', nil},
370         {0,    0x47, NSClearLineFunctionKey, @"clear"},
371         {0x2C, 0x4B, '/', nil},
372         {0,    0x4C, 3, @"enter" },
373         {0x1B, 0x4E, '-', nil},
374         {0x18, 0x51, '=', nil},
375         {0x1D, 0x52, '0', nil},
376         {0x12, 0x53, '1', nil},
377         {0x13, 0x54, '2', nil},
378         {0x14, 0x55, '3', nil},
379         {0x15, 0x56, '4', nil},
380         {0x17, 0x57, '5', nil},
381         {0x16, 0x58, '6', nil},
382         {0x1A, 0x59, '7', nil},
383         {0x1C, 0x5B, '8', nil},
384         {0x19, 0x5C, '9', nil},
385     };
386     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) {
387         NSString* currentCharacterString = [NSString stringWithCharacters:&table[i].character length:1];
388         if ([character isEqualToString:currentCharacterString] || [character isEqualToString:table[i].characterName]) {
389             if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
390                 keyCode = table[i].macNumpadKeyCode;
391             else
392                 keyCode = table[i].macKeyCode;
393             eventCharacter = currentCharacterString;
394             break;
395         }
396     }
397
398     NSString *charactersIgnoringModifiers = eventCharacter;
399
400     int modifierFlags = 0;
401
402     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
403         modifierFlags |= NSShiftKeyMask;
404         charactersIgnoringModifiers = [character lowercaseString];
405     }
406
407     modifierFlags |= buildModifierFlags(modifiers);
408
409     if (keyLocation == 0x03 /*DOM_KEY_LOCATION_NUMPAD*/)
410         modifierFlags |= NSNumericPadKeyMask;
411
412     // FIXME: [[[mainFrame frameView] documentView] layout];
413
414     NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
415                         location:NSMakePoint(5, 5)
416                         modifierFlags:modifierFlags
417                         timestamp:absoluteTimeForEventTime(currentEventTime())
418                         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
419                         context:[NSGraphicsContext currentContext]
420                         characters:eventCharacter
421                         charactersIgnoringModifiers:charactersIgnoringModifiers
422                         isARepeat:NO
423                         keyCode:keyCode];
424
425     [NSApp _setCurrentEvent:event];
426     [[m_testController->mainWebView()->platformWindow() firstResponder] keyDown:event];
427     [NSApp _setCurrentEvent:nil];
428
429     event = [NSEvent keyEventWithType:NSKeyUp
430                         location:NSMakePoint(5, 5)
431                         modifierFlags:modifierFlags
432                         timestamp:absoluteTimeForEventTime(currentEventTime())
433                         windowNumber:[m_testController->mainWebView()->platformWindow() windowNumber]
434                         context:[NSGraphicsContext currentContext]
435                         characters:eventCharacter
436                         charactersIgnoringModifiers:charactersIgnoringModifiers
437                         isARepeat:NO
438                         keyCode:keyCode];
439
440     [NSApp _setCurrentEvent:event];
441     [[m_testController->mainWebView()->platformWindow() firstResponder] keyUp:event];
442     [NSApp _setCurrentEvent:nil];
443 }
444
445 void EventSenderProxy::mouseScrollBy(int x, int y)
446 {
447     RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
448
449     // Set the CGEvent location in flipped coords relative to the first screen, which
450     // compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
451     // no associated window. See <rdar://problem/17180591>.
452     CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] firstObject] frame].size.height - m_position.y);
453     CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
454
455     NSEvent *event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
456     if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
457         [NSApp _setCurrentEvent:event];
458         [targetView scrollWheel:event];
459         [NSApp _setCurrentEvent:nil];
460     } else {
461         NSPoint location = [event locationInWindow];
462         WTFLogAlways("mouseScrollByWithWheelAndMomentumPhases failed to find the target view at %f,%f\n", location.x, location.y);
463     }
464 }
465
466 void EventSenderProxy::continuousMouseScrollBy(int x, int y, bool paged)
467 {
468     WTFLogAlways("EventSenderProxy::continuousMouseScrollBy is not implemented\n");
469     return;
470 }
471
472 #if MAC_OS_X_VERSION_MAX_ALLOWED < 1090
473 const uint32_t kCGScrollWheelEventMomentumPhase = 123;
474 #endif
475
476 void EventSenderProxy::mouseScrollByWithWheelAndMomentumPhases(int x, int y, int phase, int momentum)
477 {
478     RetainPtr<CGEventRef> cgScrollEvent = adoptCF(CGEventCreateScrollWheelEvent(0, kCGScrollEventUnitLine, 2, y, x));
479
480     // Set the CGEvent location in flipped coords relative to the first screen, which
481     // compensates for the behavior of +[NSEvent eventWithCGEvent:] when the event has
482     // no associated window. See <rdar://problem/17180591>.
483     CGPoint lastGlobalMousePosition = CGPointMake(m_position.x, [[[NSScreen screens] firstObject] frame].size.height - m_position.y);
484     CGEventSetLocation(cgScrollEvent.get(), lastGlobalMousePosition);
485
486     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventIsContinuous, 1);
487     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventScrollPhase, phase);
488     CGEventSetIntegerValueField(cgScrollEvent.get(), kCGScrollWheelEventMomentumPhase, momentum);
489
490     NSEvent* event = [NSEvent eventWithCGEvent:cgScrollEvent.get()];
491
492     // Our event should have the correct settings:
493     if (NSView *targetView = [m_testController->mainWebView()->platformView() hitTest:[event locationInWindow]]) {
494         [NSApp _setCurrentEvent:event];
495         [targetView scrollWheel:event];
496         [NSApp _setCurrentEvent:nil];
497     } else {
498         CGPoint windowLocation = [event locationInWindow];
499         WTFLogAlways("mouseScrollByWithWheelAndMomentumPhases failed to find the target view at %f,%f\n", windowLocation.x, windowLocation.y);
500     }
501 }
502
503 } // namespace WTR