Reviewed by Maciej.
[WebKit-https.git] / WebKitTools / DumpRenderTree / mac / EventSendingController.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2006 Jonas Witt <jonas.witt@gmail.com>
4  * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
5  * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1.  Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer. 
13  * 2.  Redistributions in binary form must reproduce the above copyright
14  *     notice, this list of conditions and the following disclaimer in the
15  *     documentation and/or other materials provided with the distribution. 
16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17  *     its contributors may be used to endorse or promote products derived
18  *     from this software without specific prior written permission. 
19  *
20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31
32 #import "EventSendingController.h"
33
34 #import "DumpRenderTree.h"
35 #import "DumpRenderTreeDraggingInfo.h"
36
37 #import <Carbon/Carbon.h>                           // for GetCurrentEventTime()
38 #import <WebKit/WebKit.h>
39 #import <WebKit/DOMPrivate.h>
40
41 extern "C" void _NSNewKillRingSequence();
42
43 NSPoint lastMousePosition;
44 NSArray *webkitDomEventNames;
45 NSMutableArray *savedMouseEvents; // mouse events sent between mouseDown and mouseUp are stored here, and then executed at once.
46 BOOL replayingSavedEvents;
47
48 @implementation EventSendingController
49
50 + (void)initialize
51 {
52     webkitDomEventNames = [[NSArray alloc] initWithObjects:
53         @"abort",
54         @"beforecopy",
55         @"beforecut",
56         @"beforepaste",
57         @"blur",
58         @"change",
59         @"click",
60         @"contextmenu",
61         @"copy",
62         @"cut",
63         @"dblclick",
64         @"drag",
65         @"dragend",
66         @"dragenter",
67         @"dragleave",
68         @"dragover",
69         @"dragstart",
70         @"drop",
71         @"error",
72         @"focus",
73         @"input",
74         @"keydown",
75         @"keypress",
76         @"keyup",
77         @"load",
78         @"mousedown",
79         @"mousemove",
80         @"mouseout",
81         @"mouseover",
82         @"mouseup",
83         @"mousewheel",
84         @"beforeunload",
85         @"paste",
86         @"readystatechange",
87         @"reset",
88         @"resize", 
89         @"scroll", 
90         @"search",
91         @"select",
92         @"selectstart",
93         @"submit", 
94         @"textInput", 
95         @"textzoomin",
96         @"textzoomout",
97         @"unload",
98         @"zoom",
99         nil];
100 }
101
102 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
103 {
104     if (aSelector == @selector(mouseDown)
105             || aSelector == @selector(mouseUp)
106             || aSelector == @selector(contextClick)
107             || aSelector == @selector(mouseMoveToX:Y:)
108             || aSelector == @selector(leapForward:)
109             || aSelector == @selector(keyDown:withModifiers:)
110             || aSelector == @selector(enableDOMUIEventLogging:)
111             || aSelector == @selector(fireKeyboardEventsToElement:)
112             || aSelector == @selector(clearKillRing)
113             || aSelector == @selector(textZoomIn)
114             || aSelector == @selector(textZoomOut))
115         return NO;
116     return YES;
117 }
118
119 + (BOOL)isKeyExcludedFromWebScript:(const char*)name
120 {
121     if (strcmp(name, "dragMode") == 0)
122         return NO;
123     return YES;
124 }
125
126 + (NSString *)webScriptNameForSelector:(SEL)aSelector
127 {
128     if (aSelector == @selector(mouseMoveToX:Y:))
129         return @"mouseMoveTo";
130     if (aSelector == @selector(leapForward:))
131         return @"leapForward";
132     if (aSelector == @selector(keyDown:withModifiers:))
133         return @"keyDown";
134     if (aSelector == @selector(enableDOMUIEventLogging:))
135         return @"enableDOMUIEventLogging";
136     if (aSelector == @selector(fireKeyboardEventsToElement:))
137         return @"fireKeyboardEventsToElement";
138     if (aSelector == @selector(setDragMode:))
139         return @"setDragMode";
140     return nil;
141 }
142
143 - (id)init
144 {
145     self = [super init];
146     if (self)
147         dragMode = YES;
148     return self;
149 }
150
151 - (void)dealloc
152 {
153     [super dealloc];
154 }
155
156 - (double)currentEventTime
157 {
158     return GetCurrentEventTime() + timeOffset;
159 }
160
161 - (void)leapForward:(int)milliseconds
162 {
163     if (dragMode && down && !replayingSavedEvents) {
164         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(leapForward:)]];
165         [invocation setTarget:self];
166         [invocation setSelector:@selector(leapForward:)];
167         [invocation setArgument:&milliseconds atIndex:2];
168         
169         [EventSendingController saveEvent:invocation];
170         
171         return;
172     }
173
174     timeOffset += milliseconds / 1000.0;
175 }
176
177 - (void)clearKillRing
178 {
179     _NSNewKillRingSequence();
180 }
181
182 - (void)mouseDown
183 {
184     [[[mainFrame frameView] documentView] layout];
185     if ([self currentEventTime] - lastClick >= 1)
186         clickCount = 1;
187     else
188         clickCount++;
189     NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseDown 
190                                         location:lastMousePosition 
191                                    modifierFlags:0 
192                                        timestamp:[self currentEventTime]
193                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
194                                          context:[NSGraphicsContext currentContext] 
195                                      eventNumber:++eventNumber 
196                                       clickCount:clickCount 
197                                         pressure:0.0];
198
199     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
200     if (subView) {
201         [subView mouseDown:event];
202         down = YES;
203     }
204 }
205
206 - (void)textZoomIn
207 {
208     [[mainFrame webView] makeTextLarger:self];
209 }
210
211 - (void)textZoomOut
212 {
213     [[mainFrame webView] makeTextSmaller:self];
214 }
215
216 - (void)mouseUp
217 {
218     if (dragMode && !replayingSavedEvents) {
219         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseUp)]];
220         [invocation setTarget:self];
221         [invocation setSelector:@selector(mouseUp)];
222         
223         [EventSendingController saveEvent:invocation];
224         [EventSendingController replaySavedEvents];
225
226         return;
227     }
228
229     [[[mainFrame frameView] documentView] layout];
230     NSEvent *event = [NSEvent mouseEventWithType:NSLeftMouseUp 
231                                         location:lastMousePosition 
232                                    modifierFlags:0 
233                                        timestamp:[self currentEventTime]
234                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
235                                          context:[NSGraphicsContext currentContext] 
236                                      eventNumber:++eventNumber 
237                                       clickCount:clickCount 
238                                         pressure:0.0];
239
240     NSView *targetView = [[mainFrame webView] hitTest:[event locationInWindow]];
241     // FIXME: Silly hack to teach DRT to respect capturing mouse events outside the WebView.
242     // The right solution is just to use NSApplication's built-in event sending methods, 
243     // instead of rolling our own algorithm for selecting an event target.
244     targetView = targetView ? targetView : [[mainFrame frameView] documentView];
245     assert(targetView);
246     [targetView mouseUp:event];
247     down = NO;
248     lastClick = [event timestamp];
249     if (draggingInfo) {
250         WebView *webView = [mainFrame webView];
251         
252         NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
253         
254         if (dragOperation != NSDragOperationNone)
255             [webView performDragOperation:draggingInfo];
256         else
257             [webView draggingExited:draggingInfo];
258         [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
259         [draggingInfo release];
260         draggingInfo = nil;
261     }
262 }
263
264 - (void)mouseMoveToX:(int)x Y:(int)y
265 {
266     if (dragMode && down && !replayingSavedEvents) {
267         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseMoveToX:Y:)]];
268         [invocation setTarget:self];
269         [invocation setSelector:@selector(mouseMoveToX:Y:)];
270         [invocation setArgument:&x atIndex:2];
271         [invocation setArgument:&y atIndex:3];
272         
273         [EventSendingController saveEvent:invocation];
274         
275         return;
276     }
277
278     NSView *view = [mainFrame webView];
279     lastMousePosition = [view convertPoint:NSMakePoint(x, [view frame].size.height - y) toView:nil];
280     NSEvent *event = [NSEvent mouseEventWithType:(down ? NSLeftMouseDragged : NSMouseMoved) 
281                                         location:lastMousePosition 
282                                    modifierFlags:0 
283                                        timestamp:[self currentEventTime]
284                                     windowNumber:[[view window] windowNumber] 
285                                          context:[NSGraphicsContext currentContext] 
286                                      eventNumber:++eventNumber 
287                                       clickCount:(down ? clickCount : 0) 
288                                         pressure:0.0];
289
290     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
291     if (subView) {
292         if (down) {
293             [subView mouseDragged:event];
294             if (draggingInfo) {
295                 [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
296                 [[mainFrame webView] draggingUpdated:draggingInfo];
297             }
298         } else
299             [subView mouseMoved:event];
300     }
301 }
302
303 - (void)contextClick
304 {
305     [[[mainFrame frameView] documentView] layout];
306     if ([self currentEventTime] - lastClick >= 1)
307         clickCount = 1;
308     else
309         clickCount++;
310     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown 
311                                         location:lastMousePosition 
312                                    modifierFlags:0 
313                                        timestamp:[self currentEventTime]
314                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
315                                          context:[NSGraphicsContext currentContext] 
316                                      eventNumber:++eventNumber 
317                                       clickCount:clickCount 
318                                         pressure:0.0];
319
320     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
321     if (subView)
322         [subView menuForEvent:event];
323 }
324
325 + (void)saveEvent:(NSInvocation *)event
326 {
327     if (!savedMouseEvents)
328         savedMouseEvents = [[NSMutableArray alloc] init];
329     [savedMouseEvents addObject:event];
330 }
331
332 + (void)replaySavedEvents
333 {
334     replayingSavedEvents = YES;
335     while ([savedMouseEvents count]) {
336         // if a drag is initiated, the remaining saved events will be dispatched from our dragging delegate
337         NSInvocation *invocation = [[[savedMouseEvents objectAtIndex:0] retain] autorelease];
338         [savedMouseEvents removeObjectAtIndex:0];
339         [invocation invoke];
340     }
341     replayingSavedEvents = NO;
342 }
343
344 + (void)clearSavedEvents
345 {
346     [savedMouseEvents release];
347     savedMouseEvents = nil;
348 }
349
350 - (void)keyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers
351 {
352     NSString *eventCharacter = character;
353     if ([character isEqualToString:@"leftArrow"]) {
354         const unichar ch = NSLeftArrowFunctionKey;
355         eventCharacter = [NSString stringWithCharacters:&ch length:1];
356     } else if ([character isEqualToString:@"rightArrow"]) {
357         const unichar ch = NSRightArrowFunctionKey;
358         eventCharacter = [NSString stringWithCharacters:&ch length:1];
359     } else if ([character isEqualToString:@"upArrow"]) {
360         const unichar ch = NSUpArrowFunctionKey;
361         eventCharacter = [NSString stringWithCharacters:&ch length:1];
362     } else if ([character isEqualToString:@"downArrow"]) {
363         const unichar ch = NSDownArrowFunctionKey;
364         eventCharacter = [NSString stringWithCharacters:&ch length:1];
365     } else if ([character isEqualToString:@"pageUp"]) {
366         const unichar ch = NSPageUpFunctionKey;
367         eventCharacter = [NSString stringWithCharacters:&ch length:1];
368     } else if ([character isEqualToString:@"pageDown"]) {
369         const unichar ch = NSPageDownFunctionKey;
370         eventCharacter = [NSString stringWithCharacters:&ch length:1];
371     } else if ([character isEqualToString:@"delete"]) {
372         const unichar ch = 0x7f;
373         eventCharacter = [NSString stringWithCharacters:&ch length:1];
374     }
375
376     NSString *charactersIgnoringModifiers = eventCharacter;
377
378     int modifierFlags = 0;
379
380     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
381         modifierFlags |= NSShiftKeyMask;
382         charactersIgnoringModifiers = [character lowercaseString];
383     }
384
385     if ([modifiers isKindOfClass:[WebScriptObject class]])
386         for (unsigned i = 0; [[modifiers webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
387             NSString *modifier = (NSString *)[modifiers webScriptValueAtIndex:i];
388             if ([modifier isEqual:@"ctrlKey"])
389                 modifierFlags |= NSControlKeyMask;
390             else if ([modifier isEqual:@"shiftKey"])
391                 modifierFlags |= NSShiftKeyMask;
392             else if ([modifier isEqual:@"altKey"])
393                 modifierFlags |= NSAlternateKeyMask;
394             else if ([modifier isEqual:@"metaKey"])
395                 modifierFlags |= NSCommandKeyMask;
396         }
397
398     [[[mainFrame frameView] documentView] layout];
399
400     NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
401                         location:NSMakePoint(5, 5)
402                         modifierFlags:modifierFlags
403                         timestamp:[self currentEventTime]
404                         windowNumber:[[[mainFrame webView] window] windowNumber]
405                         context:[NSGraphicsContext currentContext]
406                         characters:eventCharacter
407                         charactersIgnoringModifiers:charactersIgnoringModifiers
408                         isARepeat:NO
409                         keyCode:0];
410
411     [[[[mainFrame webView] window] firstResponder] keyDown:event];
412
413     event = [NSEvent keyEventWithType:NSKeyUp
414                         location:NSMakePoint(5, 5)
415                         modifierFlags:modifierFlags
416                         timestamp:[self currentEventTime]
417                         windowNumber:[[[mainFrame webView] window] windowNumber]
418                         context:[NSGraphicsContext currentContext]
419                         characters:eventCharacter
420                         charactersIgnoringModifiers:charactersIgnoringModifiers
421                         isARepeat:NO
422                         keyCode:0];
423
424     [[[[mainFrame webView] window] firstResponder] keyUp:event];
425 }
426
427 - (void)enableDOMUIEventLogging:(WebScriptObject *)node
428 {
429     NSEnumerator *eventEnumerator = [webkitDomEventNames objectEnumerator];
430     id eventName;
431     while ((eventName = [eventEnumerator nextObject])) {
432         [(id<DOMEventTarget>)node addEventListener:eventName listener:self useCapture:NO];
433     }
434 }
435
436 - (void)handleEvent:(DOMEvent *)event
437 {
438     DOMNode *target = [event target];
439
440     printf("event type:      %s\n", [[event type] UTF8String]);
441     printf("  target:        <%s>\n", [[[target nodeName] lowercaseString] UTF8String]);
442     
443     if ([event isKindOfClass:[DOMEvent class]]) {
444         printf("  eventPhase:    %d\n", [event eventPhase]);
445         printf("  bubbles:       %d\n", [event bubbles] ? 1 : 0);
446         printf("  cancelable:    %d\n", [event cancelable] ? 1 : 0);
447     }
448     
449     if ([event isKindOfClass:[DOMUIEvent class]]) {
450         printf("  detail:        %d\n", [(DOMUIEvent*)event detail]);
451         
452         DOMAbstractView *view = [(DOMUIEvent*)event view];
453         if (view) {
454             printf("  view:          OK");            
455             if ([view document])
456                 printf(" (document: OK)");
457             printf("\n");
458         }
459     }
460     
461     if ([event isKindOfClass:[DOMKeyboardEvent class]]) {
462         printf("  keyIdentifier: %s\n", [[(DOMKeyboardEvent*)event keyIdentifier] UTF8String]);
463         printf("  keyLocation:   %d\n", [(DOMKeyboardEvent*)event keyLocation]);
464         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
465                [(DOMKeyboardEvent*)event ctrlKey] ? 1 : 0, 
466                [(DOMKeyboardEvent*)event shiftKey] ? 1 : 0, 
467                [(DOMKeyboardEvent*)event altKey] ? 1 : 0, 
468                [(DOMKeyboardEvent*)event metaKey] ? 1 : 0);
469         printf("  keyCode:       %d\n", [(DOMKeyboardEvent*)event keyCode]);
470         printf("  charCode:      %d\n", [(DOMKeyboardEvent*)event charCode]);
471     }
472     
473     if ([event isKindOfClass:[DOMMouseEvent class]]) {
474         printf("  button:        %d\n", [(DOMMouseEvent*)event button]);
475         printf("  clientX:       %d\n", [(DOMMouseEvent*)event clientX]);
476         printf("  clientY:       %d\n", [(DOMMouseEvent*)event clientY]);
477         printf("  screenX:       %d\n", [(DOMMouseEvent*)event screenX]);
478         printf("  screenY:       %d\n", [(DOMMouseEvent*)event screenY]);
479         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
480                [(DOMMouseEvent*)event ctrlKey] ? 1 : 0, 
481                [(DOMMouseEvent*)event shiftKey] ? 1 : 0, 
482                [(DOMMouseEvent*)event altKey] ? 1 : 0, 
483                [(DOMMouseEvent*)event metaKey] ? 1 : 0);
484         id relatedTarget = [(DOMMouseEvent*)event relatedTarget];
485         if (relatedTarget) {
486             printf("  relatedTarget: %s", [[[relatedTarget class] description] UTF8String]);
487             if ([relatedTarget isKindOfClass:[DOMNode class]])
488                 printf(" (nodeName: %s)", [[(DOMNode*)relatedTarget nodeName] UTF8String]);
489             printf("\n");
490         }
491     }
492     
493     if ([event isKindOfClass:[DOMMutationEvent class]]) {
494         printf("  prevValue:     %s\n", [[(DOMMutationEvent*)event prevValue] UTF8String]);
495         printf("  newValue:      %s\n", [[(DOMMutationEvent*)event newValue] UTF8String]);
496         printf("  attrName:      %s\n", [[(DOMMutationEvent*)event attrName] UTF8String]);
497         printf("  attrChange:    %d\n", [(DOMMutationEvent*)event attrChange]);
498         DOMNode *relatedNode = [(DOMMutationEvent*)event relatedNode];
499         if (relatedNode) {
500             printf("  relatedNode:   %s (nodeName: %s)\n", 
501                    [[[relatedNode class] description] UTF8String],
502                    [[relatedNode nodeName] UTF8String]);
503         }
504     }
505     
506     if ([event isKindOfClass:[DOMWheelEvent class]]) {
507         printf("  clientX:       %d\n", [(DOMWheelEvent*)event clientX]);
508         printf("  clientY:       %d\n", [(DOMWheelEvent*)event clientY]);
509         printf("  screenX:       %d\n", [(DOMWheelEvent*)event screenX]);
510         printf("  screenY:       %d\n", [(DOMWheelEvent*)event screenY]);
511         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
512                [(DOMWheelEvent*)event ctrlKey] ? 1 : 0, 
513                [(DOMWheelEvent*)event shiftKey] ? 1 : 0, 
514                [(DOMWheelEvent*)event altKey] ? 1 : 0, 
515                [(DOMWheelEvent*)event metaKey] ? 1 : 0);
516         printf("  isHorizontal:  %d\n", [(DOMWheelEvent*)event isHorizontal] ? 1 : 0);
517         printf("  wheelDelta:    %d\n", [(DOMWheelEvent*)event wheelDelta]);
518     }
519 }
520
521 // FIXME: It's not good to have a test hard-wired into this controller like this.
522 // Instead we need to get testing framework based on the Objective-C bindings
523 // to work well enough that we can test that way instead.
524 - (void)fireKeyboardEventsToElement:(WebScriptObject *)element {
525     
526     if (![element isKindOfClass:[DOMHTMLElement class]]) {
527         return;
528     }
529     
530     DOMHTMLElement *target = (DOMHTMLElement*)element;
531     DOMDocument *document = [target ownerDocument];
532     
533     // Keyboard Event 1
534     
535     DOMEvent *domEvent = [document createEvent:@"KeyboardEvent"];
536     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keydown" 
537                                          canBubble:YES
538                                         cancelable:YES
539                                               view:[document defaultView]
540                                      keyIdentifier:@"U+000041" 
541                                        keyLocation:0
542                                            ctrlKey:YES
543                                             altKey:NO
544                                           shiftKey:NO
545                                            metaKey:NO];
546     [target dispatchEvent:domEvent];  
547         
548     // Keyboard Event 2
549     
550     domEvent = [document createEvent:@"KeyboardEvent"];
551     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keypress" 
552                                          canBubble:YES
553                                         cancelable:YES
554                                               view:[document defaultView]
555                                      keyIdentifier:@"U+000045" 
556                                        keyLocation:1
557                                            ctrlKey:NO
558                                             altKey:YES
559                                           shiftKey:NO
560                                            metaKey:NO];
561     [target dispatchEvent:domEvent];    
562     
563     // Keyboard Event 3
564     
565     domEvent = [document createEvent:@"KeyboardEvent"];
566     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keyup" 
567                                          canBubble:YES
568                                         cancelable:YES
569                                               view:[document defaultView]
570                                      keyIdentifier:@"U+000056" 
571                                        keyLocation:0
572                                            ctrlKey:NO
573                                             altKey:NO
574                                           shiftKey:NO
575                                            metaKey:NO];
576     [target dispatchEvent:domEvent];   
577     
578 }
579
580 @end