4be480e94f827323268a82818d60574651c75631
[WebKit-https.git] / Tools / DumpRenderTree / mac / EventSendingController.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008, 2014 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 "config.h"
33 #import "EventSendingController.h"
34
35 #import "DumpRenderTree.h"
36 #import "DumpRenderTreeDraggingInfo.h"
37 #import "DumpRenderTreeFileDraggingSource.h"
38
39 #import <WebKit/DOMPrivate.h>
40 #import <WebKit/WebKit.h>
41 #import <WebKit/WebViewPrivate.h>
42
43 #if !PLATFORM(IOS)
44 #import <Carbon/Carbon.h>                           // for GetCurrentEventTime()
45 #endif
46
47 #if PLATFORM(IOS)
48 #import <GraphicsServices/GraphicsServices.h>       // for GSCurrentEventTimestamp()
49 #import <WebKit/KeyEventCodesIOS.h>
50 #import <WebKit/WAKWindow.h>
51 #import <WebKit/WebEvent.h>
52 #import <UIKit/UIKit.h>
53 #endif
54
55 #if !PLATFORM(IOS)
56 extern "C" void _NSNewKillRingSequence();
57
58 @interface NSApplication (Details)
59 - (void)_setCurrentEvent:(NSEvent *)event;
60 @end
61 #endif
62
63 enum MouseAction {
64     MouseDown,
65     MouseUp,
66     MouseDragged
67 };
68
69 // Match the DOM spec (sadly the DOM spec does not provide an enum)
70 enum MouseButton {
71     LeftMouseButton = 0,
72     MiddleMouseButton = 1,
73     RightMouseButton = 2,
74     NoMouseButton = -1
75 };
76
77
78 struct KeyMappingEntry {
79     int macKeyCode;
80     int macNumpadKeyCode;
81     unichar character;
82     NSString* characterName;
83 };
84
85 NSPoint lastMousePosition;
86 NSPoint lastClickPosition;
87 int lastClickButton = NoMouseButton;
88 NSArray *webkitDomEventNames;
89 NSMutableArray *savedMouseEvents; // mouse events sent between mouseDown and mouseUp are stored here, and then executed at once.
90 BOOL replayingSavedEvents;
91
92
93 #if PLATFORM(IOS)
94 @interface SyntheticTouch : NSObject {
95 @public
96     CGPoint _location;
97     UITouchPhase _phase;
98     unsigned _identifier;
99 };
100
101 @property (nonatomic) CGPoint location;
102 @property (nonatomic) UITouchPhase phase;
103 @property (nonatomic) unsigned identifier;
104
105 + (id)touchWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier;
106 - (id)initWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier;
107 @end
108
109 @implementation SyntheticTouch
110
111 @synthesize location = _location;
112 @synthesize phase = _phase;
113 @synthesize identifier = _identifier;
114
115 + (id)touchWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier
116 {
117     return [[[SyntheticTouch alloc] initWithLocation:location phase:phase identifier:identifier] autorelease];
118 }
119
120 - (id)initWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier
121 {
122     if ((self = [super init])) {
123         _location = location;
124         _phase = phase;
125         _identifier = identifier;
126     }
127     return self;
128 }
129 @end // SyntheticTouch
130 #endif
131
132 @implementation EventSendingController
133
134 + (void)initialize
135 {
136     webkitDomEventNames = [[NSArray alloc] initWithObjects:
137         @"abort",
138         @"beforecopy",
139         @"beforecut",
140         @"beforepaste",
141         @"blur",
142         @"change",
143         @"click",
144         @"contextmenu",
145         @"copy",
146         @"cut",
147         @"dblclick",
148         @"drag",
149         @"dragend",
150         @"dragenter",
151         @"dragleave",
152         @"dragover",
153         @"dragstart",
154         @"drop",
155         @"error",
156         @"focus",
157         @"input",
158         @"keydown",
159         @"keypress",
160         @"keyup",
161         @"load",
162         @"mousedown",
163         @"mousemove",
164         @"mouseout",
165         @"mouseover",
166         @"mouseup",
167         @"mousewheel",
168         @"beforeunload",
169         @"paste",
170         @"readystatechange",
171         @"reset",
172         @"resize", 
173         @"scroll", 
174         @"search",
175         @"select",
176         @"selectstart",
177         @"submit", 
178         @"textInput", 
179         @"textzoomin",
180         @"textzoomout",
181         @"unload",
182         @"zoom",
183         nil];
184 }
185
186 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
187 {
188     if (aSelector == @selector(beginDragWithFiles:)
189             || aSelector == @selector(clearKillRing)
190             || aSelector == @selector(contextClick)
191             || aSelector == @selector(enableDOMUIEventLogging:)
192             || aSelector == @selector(fireKeyboardEventsToElement:)
193             || aSelector == @selector(keyDown:withModifiers:withLocation:)
194             || aSelector == @selector(leapForward:)
195             || aSelector == @selector(mouseDown:withModifiers:)
196             || aSelector == @selector(mouseMoveToX:Y:)
197             || aSelector == @selector(mouseUp:withModifiers:)
198             || aSelector == @selector(scheduleAsynchronousClick)
199             || aSelector == @selector(scheduleAsynchronousKeyDown:withModifiers:withLocation:)
200             || aSelector == @selector(textZoomIn)
201             || aSelector == @selector(textZoomOut)
202             || aSelector == @selector(zoomPageIn)
203             || aSelector == @selector(zoomPageOut)
204             || aSelector == @selector(scalePageBy:atX:andY:)
205             || aSelector == @selector(mouseScrollByX:andY:)
206             || aSelector == @selector(mouseScrollByX:andY:withWheel:andMomentumPhases:)
207             || aSelector == @selector(continuousMouseScrollByX:andY:)
208 #if PLATFORM(IOS)
209             || aSelector == @selector(addTouchAtX:y:)
210             || aSelector == @selector(updateTouchAtIndex:x:y:)
211             || aSelector == @selector(cancelTouchAtIndex:)
212             || aSelector == @selector(clearTouchPoints)
213             || aSelector == @selector(markAllTouchesAsStationary)
214             || aSelector == @selector(releaseTouchAtIndex:)
215             || aSelector == @selector(setTouchModifier:value:)
216             || aSelector == @selector(touchStart)
217             || aSelector == @selector(touchMove)
218             || aSelector == @selector(touchEnd)
219             || aSelector == @selector(touchCancel)
220 #endif            
221             )
222         return NO;
223     return YES;
224 }
225
226 + (BOOL)isKeyExcludedFromWebScript:(const char*)name
227 {
228     if (strcmp(name, "dragMode") == 0)
229         return NO;
230     return YES;
231 }
232
233 + (NSString *)webScriptNameForSelector:(SEL)aSelector
234 {
235     if (aSelector == @selector(beginDragWithFiles:))
236         return @"beginDragWithFiles";
237     if (aSelector == @selector(contextClick))
238         return @"contextClick";
239     if (aSelector == @selector(enableDOMUIEventLogging:))
240         return @"enableDOMUIEventLogging";
241     if (aSelector == @selector(fireKeyboardEventsToElement:))
242         return @"fireKeyboardEventsToElement";
243     if (aSelector == @selector(keyDown:withModifiers:withLocation:))
244         return @"keyDown";
245     if (aSelector == @selector(scheduleAsynchronousKeyDown:withModifiers:withLocation:))
246         return @"scheduleAsynchronousKeyDown";
247     if (aSelector == @selector(leapForward:))
248         return @"leapForward";
249     if (aSelector == @selector(mouseDown:withModifiers:))
250         return @"mouseDown";
251     if (aSelector == @selector(mouseUp:withModifiers:))
252         return @"mouseUp";
253     if (aSelector == @selector(mouseMoveToX:Y:))
254         return @"mouseMoveTo";
255     if (aSelector == @selector(setDragMode:))
256         return @"setDragMode";
257     if (aSelector == @selector(mouseScrollByX:andY:))
258         return @"mouseScrollBy";
259     if (aSelector == @selector(mouseScrollByX:andY:withWheel:andMomentumPhases:))
260         return @"mouseScrollByWithWheelAndMomentumPhases";
261     if (aSelector == @selector(continuousMouseScrollByX:andY:))
262         return @"continuousMouseScrollBy";
263     if (aSelector == @selector(scalePageBy:atX:andY:))
264         return @"scalePageBy";
265 #if PLATFORM(IOS)
266     if (aSelector == @selector(addTouchAtX:y:))
267         return @"addTouchPoint";
268     if (aSelector == @selector(updateTouchAtIndex:x:y:))
269         return @"updateTouchPoint";
270     if (aSelector == @selector(cancelTouchAtIndex:))
271         return @"cancelTouchPoint";
272     if (aSelector == @selector(clearTouchPoints))
273         return @"clearTouchPoints";
274     if (aSelector == @selector(markAllTouchesAsStationary))
275         return @"markAllTouchesAsStationary";
276     if (aSelector == @selector(releaseTouchAtIndex:))
277         return @"releaseTouchPoint";
278     if (aSelector == @selector(setTouchModifier:value:))
279         return @"setTouchModifier";
280     if (aSelector == @selector(touchStart))
281         return @"touchStart";
282     if (aSelector == @selector(touchMove))
283         return @"touchMove";
284     if (aSelector == @selector(touchEnd))
285         return @"touchEnd";
286     if (aSelector == @selector(touchCancel))
287         return @"touchCancel";
288 #endif
289     return nil;
290 }
291
292 - (id)init
293 {
294     self = [super init];
295     if (self)
296         dragMode = YES;
297     return self;
298 }
299
300 - (void)dealloc
301 {
302 #if PLATFORM(IOS)
303     [touches release];
304 #endif
305     [super dealloc];
306 }
307
308 - (double)currentEventTime
309 {
310 #if !PLATFORM(IOS)
311     return GetCurrentEventTime() + timeOffset;
312 #else
313     return GSCurrentEventTimestamp() + timeOffset;
314 #endif
315 }
316
317 - (void)leapForward:(int)milliseconds
318 {
319     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
320         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(leapForward:)]];
321         [invocation setTarget:self];
322         [invocation setSelector:@selector(leapForward:)];
323         [invocation setArgument:&milliseconds atIndex:2];
324         
325         [EventSendingController saveEvent:invocation];
326         
327         return;
328     }
329
330     timeOffset += milliseconds / 1000.0;
331 }
332
333 - (void)clearKillRing
334 {
335 #if !PLATFORM(IOS)
336     _NSNewKillRingSequence();
337 #endif
338 }
339
340 #if !PLATFORM(IOS)
341 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
342 {
343     switch (button) {
344         case LeftMouseButton:
345             switch (action) {
346                 case MouseDown:
347                     return NSLeftMouseDown;
348                 case MouseUp:
349                     return NSLeftMouseUp;
350                 case MouseDragged:
351                     return NSLeftMouseDragged;
352             }
353         case RightMouseButton:
354             switch (action) {
355                 case MouseDown:
356                     return NSRightMouseDown;
357                 case MouseUp:
358                     return NSRightMouseUp;
359                 case MouseDragged:
360                     return NSRightMouseDragged;
361             }
362         default:
363             switch (action) {
364                 case MouseDown:
365                     return NSOtherMouseDown;
366                 case MouseUp:
367                     return NSOtherMouseUp;
368                 case MouseDragged:
369                     return NSOtherMouseDragged;
370             }
371     }
372     assert(0);
373     return static_cast<NSEventType>(0);
374 }
375
376 - (void)beginDragWithFiles:(WebScriptObject*)jsFilePaths
377 {
378     assert(!draggingInfo);
379     assert([jsFilePaths isKindOfClass:[WebScriptObject class]]);
380
381     NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
382     [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
383
384     NSURL *currentTestURL = [NSURL URLWithString:[[mainFrame webView] mainFrameURL]];
385
386     NSMutableArray *filePaths = [NSMutableArray array];
387     for (unsigned i = 0; [[jsFilePaths webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
388         NSString *filePath = (NSString *)[jsFilePaths webScriptValueAtIndex:i];
389         // Have NSURL encode the name so that we handle '?' in file names correctly.
390         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
391         NSURL *absoluteFileURL = [NSURL URLWithString:[fileURL relativeString]  relativeToURL:currentTestURL];
392         [filePaths addObject:[absoluteFileURL path]];
393     }
394
395     [pboard setPropertyList:filePaths forType:NSFilenamesPboardType];
396     assert([pboard propertyListForType:NSFilenamesPboardType]); // setPropertyList will silently fail on error, assert that it didn't fail
397
398     // Provide a source, otherwise [DumpRenderTreeDraggingInfo draggingSourceOperationMask] defaults to NSDragOperationNone
399     DumpRenderTreeFileDraggingSource *source = [[[DumpRenderTreeFileDraggingSource alloc] init] autorelease];
400     draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:nil offset:NSZeroSize pasteboard:pboard source:source];
401     [[mainFrame webView] draggingEntered:draggingInfo];
402
403     dragMode = NO; // dragMode saves events and then replays them later.  We don't need/want that.
404     leftMouseButtonDown = YES; // Make the rest of eventSender think a drag is in progress
405 }
406 #endif // !PLATFORM(IOS)
407
408 - (void)updateClickCountForButton:(int)buttonNumber
409 {
410     if (([self currentEventTime] - lastClick >= 1) ||
411         !NSEqualPoints(lastMousePosition, lastClickPosition) ||
412         lastClickButton != buttonNumber) {
413         clickCount = 1;
414         lastClickButton = buttonNumber;
415     } else
416         clickCount++;
417 }
418
419 static int modifierFlags(const NSString* modifierName)
420 {
421 #if !PLATFORM(IOS)
422     const int controlKeyMask = NSControlKeyMask;
423     const int shiftKeyMask = NSShiftKeyMask;
424     const int alternateKeyMask = NSAlternateKeyMask;
425     const int commandKeyMask = NSCommandKeyMask;
426 #else
427     const int controlKeyMask = WebEventFlagMaskControl;
428     const int shiftKeyMask = WebEventFlagMaskShift;
429     const int alternateKeyMask = WebEventFlagMaskAlternate;
430     const int commandKeyMask = WebEventFlagMaskCommand;
431 #endif
432
433     int flags = 0;
434     if ([modifierName isEqual:@"ctrlKey"])
435         flags |= controlKeyMask;
436     else if ([modifierName isEqual:@"shiftKey"] || [modifierName isEqual:@"rangeSelectionKey"])
437         flags |= shiftKeyMask;
438     else if ([modifierName isEqual:@"altKey"])
439         flags |= alternateKeyMask;
440     else if ([modifierName isEqual:@"metaKey"] || [modifierName isEqual:@"addSelectionKey"])
441         flags |= commandKeyMask;
442
443     return flags;
444 }
445
446 static int buildModifierFlags(const WebScriptObject* modifiers)
447 {
448     int flags = 0;
449     if ([modifiers isKindOfClass:[NSString class]])
450         return modifierFlags((NSString*)modifiers);
451     else if (![modifiers isKindOfClass:[WebScriptObject class]])
452         return flags;
453     for (unsigned i = 0; [[modifiers webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
454         NSString* modifierName = (NSString*)[modifiers webScriptValueAtIndex:i];
455         flags |= modifierFlags(modifierName);
456     }
457     return flags;
458 }
459
460 - (void)mouseDown:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
461 {
462     [[[mainFrame frameView] documentView] layout];
463     [self updateClickCountForButton:buttonNumber];
464     
465 #if !PLATFORM(IOS)
466     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
467     NSEvent *event = [NSEvent mouseEventWithType:eventType
468                                         location:lastMousePosition 
469                                    modifierFlags:buildModifierFlags(modifiers)
470                                        timestamp:[self currentEventTime]
471                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
472                                          context:[NSGraphicsContext currentContext] 
473                                      eventNumber:++eventNumber 
474                                       clickCount:clickCount 
475                                         pressure:0.0];
476 #else
477     WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseDown
478                                                      timeStamp:[self currentEventTime]
479                                                       location:lastMousePosition];
480 #endif
481
482     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
483     if (subView) {
484 #if !PLATFORM(IOS)
485         [NSApp _setCurrentEvent:event];
486 #endif
487         [subView mouseDown:event];
488 #if !PLATFORM(IOS)
489         [NSApp _setCurrentEvent:nil];
490 #endif
491         if (buttonNumber == LeftMouseButton)
492             leftMouseButtonDown = YES;
493     }
494
495 #if PLATFORM(IOS)
496     [event release];
497 #endif
498 }
499
500 - (void)mouseDown:(int)buttonNumber
501 {
502     [self mouseDown:buttonNumber withModifiers:nil];
503 }
504
505 - (void)textZoomIn
506 {
507     [[mainFrame webView] makeTextLarger:self];
508 }
509
510 - (void)textZoomOut
511 {
512     [[mainFrame webView] makeTextSmaller:self];
513 }
514
515 - (void)zoomPageIn
516 {
517     [[mainFrame webView] zoomPageIn:self];
518 }
519
520 - (void)zoomPageOut
521 {
522     [[mainFrame webView] zoomPageOut:self];
523 }
524
525 - (void)scalePageBy:(float)scale atX:(float)x andY:(float)y
526 {
527 #if !PLATFORM(IOS)
528     // -[WebView _scaleWebView:] is Mac-specific API, and calls functions that
529     // assert to not be used in iOS.
530     [[mainFrame webView] _scaleWebView:scale atOrigin:NSMakePoint(x, y)];
531 #endif
532 }
533
534 - (void)mouseUp:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
535 {
536     if (dragMode && !replayingSavedEvents) {
537         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseUp:withModifiers:)]];
538         [invocation setTarget:self];
539         [invocation setSelector:@selector(mouseUp:withModifiers:)];
540         [invocation setArgument:&buttonNumber atIndex:2];
541         [invocation setArgument:&modifiers atIndex:3];
542         
543         [EventSendingController saveEvent:invocation];
544         [EventSendingController replaySavedEvents];
545
546         return;
547     }
548
549     [[[mainFrame frameView] documentView] layout];
550 #if !PLATFORM(IOS)
551     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
552     NSEvent *event = [NSEvent mouseEventWithType:eventType
553                                         location:lastMousePosition 
554                                    modifierFlags:buildModifierFlags(modifiers)
555                                        timestamp:[self currentEventTime]
556                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
557                                          context:[NSGraphicsContext currentContext] 
558                                      eventNumber:++eventNumber 
559                                       clickCount:clickCount 
560                                         pressure:0.0];
561 #else
562     WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseUp
563                                                      timeStamp:[self currentEventTime]
564                                                       location:lastMousePosition];
565 #endif
566
567     NSView *targetView = [[mainFrame webView] hitTest:[event locationInWindow]];
568     // FIXME: Silly hack to teach DRT to respect capturing mouse events outside the WebView.
569     // The right solution is just to use NSApplication's built-in event sending methods, 
570     // instead of rolling our own algorithm for selecting an event target.
571     targetView = targetView ? targetView : [[mainFrame frameView] documentView];
572     assert(targetView);
573 #if !PLATFORM(IOS)
574     [NSApp _setCurrentEvent:event];
575 #endif
576     [targetView mouseUp:event];
577 #if !PLATFORM(IOS)
578     [NSApp _setCurrentEvent:nil];
579 #endif
580     if (buttonNumber == LeftMouseButton)
581         leftMouseButtonDown = NO;
582     lastClick = [event timestamp];
583     lastClickPosition = lastMousePosition;
584 #if !PLATFORM(IOS)
585     if (draggingInfo) {
586         WebView *webView = [mainFrame webView];
587         
588         NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
589         
590         if (dragOperation != NSDragOperationNone)
591             [webView performDragOperation:draggingInfo];
592         else
593             [webView draggingExited:draggingInfo];
594         // Per NSDragging.h: draggingSources may not implement draggedImage:endedAt:operation:
595         if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:endedAt:operation:)])
596             [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
597         [draggingInfo release];
598         draggingInfo = nil;
599     }
600 #endif
601
602 #if PLATFORM(IOS)
603     [event release];
604 #endif
605 }
606
607 - (void)mouseUp:(int)buttonNumber
608 {
609     [self mouseUp:buttonNumber withModifiers:nil];
610 }
611
612 - (void)mouseMoveToX:(int)x Y:(int)y
613 {
614     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
615         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseMoveToX:Y:)]];
616         [invocation setTarget:self];
617         [invocation setSelector:@selector(mouseMoveToX:Y:)];
618         [invocation setArgument:&x atIndex:2];
619         [invocation setArgument:&y atIndex:3];
620         
621         [EventSendingController saveEvent:invocation];
622         return;
623     }
624
625     NSView *view = [mainFrame webView];
626 #if !PLATFORM(IOS)
627     lastMousePosition = [view convertPoint:NSMakePoint(x, [view frame].size.height - y) toView:nil];
628     NSEvent *event = [NSEvent mouseEventWithType:(leftMouseButtonDown ? NSLeftMouseDragged : NSMouseMoved)
629                                         location:lastMousePosition 
630                                    modifierFlags:0 
631                                        timestamp:[self currentEventTime]
632                                     windowNumber:[[view window] windowNumber] 
633                                          context:[NSGraphicsContext currentContext] 
634                                      eventNumber:++eventNumber 
635                                       clickCount:(leftMouseButtonDown ? clickCount : 0) 
636                                         pressure:0.0];
637 #else
638     lastMousePosition = [view convertPoint:NSMakePoint(x, y) toView:nil];
639     WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseMoved
640                                                      timeStamp:[self currentEventTime]
641                                                       location:lastMousePosition];
642 #endif // !PLATFORM(IOS)
643
644     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
645     if (subView) {
646 #if !PLATFORM(IOS)
647         [NSApp _setCurrentEvent:event];
648 #endif
649         if (leftMouseButtonDown) {
650 #if !PLATFORM(IOS)
651             if (draggingInfo) {
652                 // Per NSDragging.h: draggingSources may not implement draggedImage:movedTo:
653                 if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:movedTo:)])
654                     [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
655                 [[mainFrame webView] draggingUpdated:draggingInfo];
656             } else
657                 [subView mouseDragged:event];
658 #endif
659         } else
660             [subView mouseMoved:event];
661 #if !PLATFORM(IOS)
662         [NSApp _setCurrentEvent:nil];
663 #endif
664     }
665
666 #if PLATFORM(IOS)
667     [event release];
668 #endif
669 }
670
671 - (void)mouseScrollByX:(int)x andY:(int)y continuously:(BOOL)c
672 {
673 #if !PLATFORM(IOS)
674     CGScrollEventUnit unit = c?kCGScrollEventUnitPixel:kCGScrollEventUnitLine;
675     CGEventRef cgScrollEvent = CGEventCreateScrollWheelEvent(NULL, unit, 2, y, x);
676     
677     // CGEvent locations are in global display coordinates.
678     CGPoint lastGlobalMousePosition = {
679         lastMousePosition.x,
680         [[NSScreen mainScreen] frame].size.height - lastMousePosition.y
681     };
682     CGEventSetLocation(cgScrollEvent, lastGlobalMousePosition);
683
684     NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgScrollEvent];
685     CFRelease(cgScrollEvent);
686
687     NSView *subView = [[mainFrame webView] hitTest:[scrollEvent locationInWindow]];
688     if (subView) {
689         [NSApp _setCurrentEvent:scrollEvent];
690         [subView scrollWheel:scrollEvent];
691         [NSApp _setCurrentEvent:nil];
692     }
693 #endif
694 }
695
696 - (void)continuousMouseScrollByX:(int)x andY:(int)y
697 {
698     [self mouseScrollByX:x andY:y continuously:YES];
699 }
700
701 - (void)mouseScrollByX:(int)x andY:(int)y
702 {
703     [self mouseScrollByX:x andY:y continuously:NO];
704 }
705
706 #if !PLATFORM(IOS) && __MAC_OS_X_VERSION_MIN_REQUIRED <= 1090
707 const uint32_t kCGScrollWheelEventMomentumPhase = 123;
708 #endif
709
710 - (void)mouseScrollByX:(int)x andY:(int)y withWheel:(NSString*)phaseName andMomentumPhases:(NSString*)momentumName
711 {
712 #if !PLATFORM(IOS)
713     uint32_t phase = 0;
714     if ([phaseName isEqualToString: @"none"])
715         phase = 0;
716     else if ([phaseName isEqualToString: @"began"])
717         phase = 1; // kCGScrollPhaseBegan
718     else if ([phaseName isEqualToString: @"changed"])
719         phase = 2; // kCGScrollPhaseChanged;
720     else if ([phaseName isEqualToString: @"ended"])
721         phase = 4; // kCGScrollPhaseEnded;
722     else if ([phaseName isEqualToString: @"cancelled"])
723         phase = 8; // kCGScrollPhaseCancelled;
724     else if ([phaseName isEqualToString: @"maybegin"])
725         phase = 128; // kCGScrollPhaseMayBegin;
726
727     uint32_t momentum = 0;
728     if ([momentumName isEqualToString: @"none"])
729         momentum = 0; // kCGMomentumScrollPhaseNone;
730     else if ([momentumName isEqualToString:@"begin"])
731         momentum = 1; // kCGMomentumScrollPhaseBegin;
732     else if ([momentumName isEqualToString:@"continue"])
733         momentum = 2; // kCGMomentumScrollPhaseContinue;
734     else if ([momentumName isEqualToString:@"end"])
735         momentum = 3; // kCGMomentumScrollPhaseEnd;
736
737     CGEventRef cgScrollEvent = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 2, y, x);
738
739     // CGEvent locations are in global display coordinates.
740     CGPoint lastGlobalMousePosition = CGPointMake(lastMousePosition.x, [[NSScreen mainScreen] frame].size.height - lastMousePosition.y);
741     CGEventSetLocation(cgScrollEvent, lastGlobalMousePosition);
742     CGEventSetIntegerValueField(cgScrollEvent, kCGScrollWheelEventScrollPhase, phase);
743     CGEventSetIntegerValueField(cgScrollEvent, kCGScrollWheelEventMomentumPhase, momentum);
744     
745     NSEvent* scrollEvent = [NSEvent eventWithCGEvent:cgScrollEvent];
746     CFRelease(cgScrollEvent);
747
748     if (NSView* targetView = [[mainFrame webView] hitTest:[scrollEvent locationInWindow]]) {
749         [NSApp _setCurrentEvent:scrollEvent];
750         [targetView scrollWheel:scrollEvent];
751         [NSApp _setCurrentEvent:nil];
752     }
753 #endif
754 }
755
756 - (NSArray *)contextClick
757 {
758 #if !PLATFORM(IOS)
759     [[[mainFrame frameView] documentView] layout];
760     [self updateClickCountForButton:RightMouseButton];
761
762     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
763                                         location:lastMousePosition 
764                                    modifierFlags:0 
765                                        timestamp:[self currentEventTime]
766                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
767                                          context:[NSGraphicsContext currentContext] 
768                                      eventNumber:++eventNumber 
769                                       clickCount:clickCount 
770                                         pressure:0.0];
771
772     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
773     NSMutableArray *menuItemStrings = [NSMutableArray array];
774     
775     if (subView) {
776         [NSApp _setCurrentEvent:event];
777         NSMenu* menu = [subView menuForEvent:event];
778         [NSApp _setCurrentEvent:nil];
779
780         for (int i = 0; i < [menu numberOfItems]; ++i) {
781             NSMenuItem* menuItem = [menu itemAtIndex:i];
782             if (!strcmp("Inspect Element", [[menuItem title] UTF8String]))
783                 continue;
784
785             if ([menuItem isSeparatorItem])
786                 [menuItemStrings addObject:@"<separator>"];
787             else
788                 [menuItemStrings addObject:[menuItem title]];
789         }
790     }
791     
792     return menuItemStrings;
793 #else
794     return nil;
795 #endif
796 }
797
798 - (void)scheduleAsynchronousClick
799 {
800     [self performSelector:@selector(mouseDown:) withObject:nil afterDelay:0];
801     [self performSelector:@selector(mouseUp:) withObject:nil afterDelay:0];
802 }
803
804 + (void)saveEvent:(NSInvocation *)event
805 {
806     if (!savedMouseEvents)
807         savedMouseEvents = [[NSMutableArray alloc] init];
808     [savedMouseEvents addObject:event];
809 }
810
811 + (void)replaySavedEvents
812 {
813     replayingSavedEvents = YES;
814     while ([savedMouseEvents count]) {
815         // if a drag is initiated, the remaining saved events will be dispatched from our dragging delegate
816         NSInvocation *invocation = [[[savedMouseEvents objectAtIndex:0] retain] autorelease];
817         [savedMouseEvents removeObjectAtIndex:0];
818         [invocation invoke];
819     }
820     replayingSavedEvents = NO;
821 }
822
823 + (void)clearSavedEvents
824 {
825     [savedMouseEvents release];
826     savedMouseEvents = nil;
827 }
828
829 - (void)keyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
830 {
831     NSString *eventCharacter = character;
832     unsigned short keyCode = 0;
833     if ([character isEqualToString:@"leftArrow"]) {
834         const unichar ch = NSLeftArrowFunctionKey;
835         eventCharacter = [NSString stringWithCharacters:&ch length:1];
836         keyCode = 0x7B;
837     } else if ([character isEqualToString:@"rightArrow"]) {
838         const unichar ch = NSRightArrowFunctionKey;
839         eventCharacter = [NSString stringWithCharacters:&ch length:1];
840         keyCode = 0x7C;
841     } else if ([character isEqualToString:@"upArrow"]) {
842         const unichar ch = NSUpArrowFunctionKey;
843         eventCharacter = [NSString stringWithCharacters:&ch length:1];
844         keyCode = 0x7E;
845     } else if ([character isEqualToString:@"downArrow"]) {
846         const unichar ch = NSDownArrowFunctionKey;
847         eventCharacter = [NSString stringWithCharacters:&ch length:1];
848         keyCode = 0x7D;
849     } else if ([character isEqualToString:@"pageUp"]) {
850         const unichar ch = NSPageUpFunctionKey;
851         eventCharacter = [NSString stringWithCharacters:&ch length:1];
852         keyCode = 0x74;
853     } else if ([character isEqualToString:@"pageDown"]) {
854         const unichar ch = NSPageDownFunctionKey;
855         eventCharacter = [NSString stringWithCharacters:&ch length:1];
856         keyCode = 0x79;
857     } else if ([character isEqualToString:@"home"]) {
858         const unichar ch = NSHomeFunctionKey;
859         eventCharacter = [NSString stringWithCharacters:&ch length:1];
860         keyCode = 0x73;
861     } else if ([character isEqualToString:@"end"]) {
862         const unichar ch = NSEndFunctionKey;
863         eventCharacter = [NSString stringWithCharacters:&ch length:1];
864         keyCode = 0x77;
865     } else if ([character isEqualToString:@"insert"]) {
866         const unichar ch = NSInsertFunctionKey;
867         eventCharacter = [NSString stringWithCharacters:&ch length:1];
868         keyCode = 0x72;
869     } else if ([character isEqualToString:@"delete"]) {
870         const unichar ch = NSDeleteFunctionKey;
871         eventCharacter = [NSString stringWithCharacters:&ch length:1];
872         keyCode = 0x75;
873     } else if ([character isEqualToString:@"printScreen"]) {
874         const unichar ch = NSPrintScreenFunctionKey;
875         eventCharacter = [NSString stringWithCharacters:&ch length:1];
876         keyCode = 0x0; // There is no known virtual key code for PrintScreen.
877     } else if ([character isEqualToString:@"cyrillicSmallLetterA"]) {
878         const unichar ch = 0x0430;
879         eventCharacter = [NSString stringWithCharacters:&ch length:1];
880         keyCode = 0x3; // Shares key with "F" on Russian layout.
881     } else if ([character isEqualToString:@"leftControl"]) {
882         const unichar ch = 0xFFE3;
883         eventCharacter = [NSString stringWithCharacters:&ch length:1];
884         keyCode = 0x3B;
885     } else if ([character isEqualToString:@"leftShift"]) {
886         const unichar ch = 0xFFE1;
887         eventCharacter = [NSString stringWithCharacters:&ch length:1];
888         keyCode = 0x38;
889     } else if ([character isEqualToString:@"leftAlt"]) {
890         const unichar ch = 0xFFE7;
891         eventCharacter = [NSString stringWithCharacters:&ch length:1];
892         keyCode = 0x3A;
893     } else if ([character isEqualToString:@"rightControl"]) {
894         const unichar ch = 0xFFE4;
895         eventCharacter = [NSString stringWithCharacters:&ch length:1];
896         keyCode = 0x3E;
897     } else if ([character isEqualToString:@"rightShift"]) {
898         const unichar ch = 0xFFE2;
899         eventCharacter = [NSString stringWithCharacters:&ch length:1];
900         keyCode = 0x3C;
901     } else if ([character isEqualToString:@"rightAlt"]) {
902         const unichar ch = 0xFFE8;
903         eventCharacter = [NSString stringWithCharacters:&ch length:1];
904         keyCode = 0x3D;
905     }
906
907     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
908     // If the input string is a function-key name, set its key code.
909     for (unsigned i = 1; i <= 24; i++) {
910         if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
911             const unichar ch = NSF1FunctionKey + (i - 1);
912             eventCharacter = [NSString stringWithCharacters:&ch length:1];
913             switch (i) {
914                 case 1: keyCode = 0x7A; break;
915                 case 2: keyCode = 0x78; break;
916                 case 3: keyCode = 0x63; break;
917                 case 4: keyCode = 0x76; break;
918                 case 5: keyCode = 0x60; break;
919                 case 6: keyCode = 0x61; break;
920                 case 7: keyCode = 0x62; break;
921                 case 8: keyCode = 0x64; break;
922                 case 9: keyCode = 0x65; break;
923                 case 10: keyCode = 0x6D; break;
924                 case 11: keyCode = 0x67; break;
925                 case 12: keyCode = 0x6F; break;
926                 case 13: keyCode = 0x69; break;
927                 case 14: keyCode = 0x6B; break;
928                 case 15: keyCode = 0x71; break;
929                 case 16: keyCode = 0x6A; break;
930                 case 17: keyCode = 0x40; break;
931                 case 18: keyCode = 0x4F; break;
932                 case 19: keyCode = 0x50; break;
933                 case 20: keyCode = 0x5A; break;
934             }
935         }
936     }
937
938     // FIXME: No keyCode is set for most keys.
939     if ([character isEqualToString:@"\t"])
940         keyCode = 0x30;
941     else if ([character isEqualToString:@" "])
942         keyCode = 0x31;
943     else if ([character isEqualToString:@"\r"])
944         keyCode = 0x24;
945     else if ([character isEqualToString:@"\n"])
946         keyCode = 0x4C;
947     else if ([character isEqualToString:@"\x8"])
948         keyCode = 0x33;
949     else if ([character isEqualToString:@"a"])
950         keyCode = 0x00;
951     else if ([character isEqualToString:@"b"])
952         keyCode = 0x0B;
953     else if ([character isEqualToString:@"d"])
954         keyCode = 0x02;
955     else if ([character isEqualToString:@"e"])
956         keyCode = 0x0E;
957
958     KeyMappingEntry table[] = {
959         {0x2F, 0x41, '.', nil},
960         {0,    0x43, '*', nil},
961         {0,    0x45, '+', nil},
962         {0,    0x47, NSClearLineFunctionKey, @"clear"},
963         {0x2C, 0x4B, '/', nil},
964         {0,    0x4C, 3, @"enter" },
965         {0x1B, 0x4E, '-', nil},
966         {0x18, 0x51, '=', nil},
967         {0x1D, 0x52, '0', nil},
968         {0x12, 0x53, '1', nil},
969         {0x13, 0x54, '2', nil},
970         {0x14, 0x55, '3', nil},
971         {0x15, 0x56, '4', nil},
972         {0x17, 0x57, '5', nil},
973         {0x16, 0x58, '6', nil},
974         {0x1A, 0x59, '7', nil},
975         {0x1C, 0x5B, '8', nil},
976         {0x19, 0x5C, '9', nil},
977     };
978     for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) {
979         NSString* currentCharacterString = [NSString stringWithCharacters:&table[i].character length:1];
980         if ([character isEqualToString:currentCharacterString] || [character isEqualToString:table[i].characterName]) {
981             if (location == DOM_KEY_LOCATION_NUMPAD)
982                 keyCode = table[i].macNumpadKeyCode;
983             else
984                 keyCode = table[i].macKeyCode;
985             eventCharacter = currentCharacterString;
986             break;
987         }
988     }
989
990     NSString *charactersIgnoringModifiers = eventCharacter;
991
992     int modifierFlags = 0;
993
994     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
995 #if !PLATFORM(IOS)
996         modifierFlags |= NSShiftKeyMask;
997 #else
998         modifierFlags |= WebEventFlagMaskAlphaShift;
999 #endif
1000         charactersIgnoringModifiers = [character lowercaseString];
1001     }
1002
1003     modifierFlags |= buildModifierFlags(modifiers);
1004
1005     if (location == DOM_KEY_LOCATION_NUMPAD)
1006         modifierFlags |= NSNumericPadKeyMask;
1007
1008     [[[mainFrame frameView] documentView] layout];
1009
1010 #if !PLATFORM(IOS)
1011     NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
1012                         location:NSMakePoint(5, 5)
1013                         modifierFlags:modifierFlags
1014                         timestamp:[self currentEventTime]
1015                         windowNumber:[[[mainFrame webView] window] windowNumber]
1016                         context:[NSGraphicsContext currentContext]
1017                         characters:eventCharacter
1018                         charactersIgnoringModifiers:charactersIgnoringModifiers
1019                         isARepeat:NO
1020                         keyCode:keyCode];
1021 #else
1022     WebEvent *event = [[WebEvent alloc] initWithKeyEventType:WebEventKeyDown
1023                         timeStamp:[self currentEventTime]
1024                         characters:eventCharacter
1025                         charactersIgnoringModifiers:charactersIgnoringModifiers
1026                         modifiers:(WebEventFlags)modifierFlags
1027                         isRepeating:NO
1028                         isPopupVariant:NO
1029                         keyCode:[character characterAtIndex:0]
1030                         isTabKey:([character characterAtIndex:0] == '\t')
1031                         characterSet:WebEventCharacterSetASCII];
1032 #endif
1033
1034 #if !PLATFORM(IOS)
1035     [NSApp _setCurrentEvent:event];
1036 #endif
1037     [[[[mainFrame webView] window] firstResponder] keyDown:event];
1038 #if !PLATFORM(IOS)
1039     [NSApp _setCurrentEvent:nil];
1040 #endif
1041
1042 #if !PLATFORM(IOS)
1043     event = [NSEvent keyEventWithType:NSKeyUp
1044                         location:NSMakePoint(5, 5)
1045                         modifierFlags:modifierFlags
1046                         timestamp:[self currentEventTime]
1047                         windowNumber:[[[mainFrame webView] window] windowNumber]
1048                         context:[NSGraphicsContext currentContext]
1049                         characters:eventCharacter
1050                         charactersIgnoringModifiers:charactersIgnoringModifiers
1051                         isARepeat:NO
1052                         keyCode:keyCode];
1053 #else
1054     [event release];
1055     event = [[WebEvent alloc] initWithKeyEventType:WebEventKeyUp
1056                         timeStamp:[self currentEventTime]
1057                         characters:eventCharacter
1058                         charactersIgnoringModifiers:charactersIgnoringModifiers
1059                         modifiers:(WebEventFlags)modifierFlags
1060                         isRepeating:NO
1061                         isPopupVariant:NO
1062                         keyCode:[character characterAtIndex:0]
1063                         isTabKey:([character characterAtIndex:0] == '\t')
1064                         characterSet:WebEventCharacterSetASCII];
1065 #endif
1066
1067 #if !PLATFORM(IOS)
1068     [NSApp _setCurrentEvent:event];
1069 #endif
1070     [[[[mainFrame webView] window] firstResponder] keyUp:event];
1071 #if !PLATFORM(IOS)
1072     [NSApp _setCurrentEvent:nil];
1073 #endif
1074
1075 #if PLATFORM(IOS)
1076     [event release];
1077 #endif
1078 }
1079
1080 - (void)keyDownWrapper:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
1081 {
1082     [self keyDown:character withModifiers:modifiers withLocation:location];
1083 }
1084
1085 - (void)scheduleAsynchronousKeyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
1086 {
1087     NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(keyDownWrapper:withModifiers:withLocation:)]];
1088     [invocation retainArguments];
1089     [invocation setTarget:self];
1090     [invocation setSelector:@selector(keyDownWrapper:withModifiers:withLocation:)];
1091     [invocation setArgument:&character atIndex:2];
1092     [invocation setArgument:&modifiers atIndex:3];
1093     [invocation setArgument:&location atIndex:4];
1094     [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0];
1095 }
1096
1097 - (void)enableDOMUIEventLogging:(WebScriptObject *)node
1098 {
1099     NSEnumerator *eventEnumerator = [webkitDomEventNames objectEnumerator];
1100     id eventName;
1101     while ((eventName = [eventEnumerator nextObject])) {
1102         [(id<DOMEventTarget>)node addEventListener:eventName listener:self useCapture:NO];
1103     }
1104 }
1105
1106 - (void)handleEvent:(DOMEvent *)event
1107 {
1108     DOMNode *target = [event target];
1109
1110     printf("event type:      %s\n", [[event type] UTF8String]);
1111     printf("  target:        <%s>\n", [[[target nodeName] lowercaseString] UTF8String]);
1112     
1113     if ([event isKindOfClass:[DOMEvent class]]) {
1114         printf("  eventPhase:    %d\n", [event eventPhase]);
1115         printf("  bubbles:       %d\n", [event bubbles] ? 1 : 0);
1116         printf("  cancelable:    %d\n", [event cancelable] ? 1 : 0);
1117     }
1118     
1119     if ([event isKindOfClass:[DOMUIEvent class]]) {
1120         printf("  detail:        %d\n", [(DOMUIEvent*)event detail]);
1121         
1122         DOMAbstractView *view = [(DOMUIEvent*)event view];
1123         if (view) {
1124             printf("  view:          OK");            
1125             if ([view document])
1126                 printf(" (document: OK)");
1127             printf("\n");
1128         }
1129     }
1130     
1131     if ([event isKindOfClass:[DOMKeyboardEvent class]]) {
1132         printf("  keyIdentifier: %s\n", [[(DOMKeyboardEvent*)event keyIdentifier] UTF8String]);
1133         printf("  keyLocation:   %d\n", [(DOMKeyboardEvent*)event location]);
1134         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
1135                [(DOMKeyboardEvent*)event ctrlKey] ? 1 : 0, 
1136                [(DOMKeyboardEvent*)event shiftKey] ? 1 : 0, 
1137                [(DOMKeyboardEvent*)event altKey] ? 1 : 0, 
1138                [(DOMKeyboardEvent*)event metaKey] ? 1 : 0);
1139         printf("  keyCode:       %d\n", [(DOMKeyboardEvent*)event keyCode]);
1140         printf("  charCode:      %d\n", [(DOMKeyboardEvent*)event charCode]);
1141     }
1142     
1143     if ([event isKindOfClass:[DOMMouseEvent class]]) {
1144         printf("  button:        %d\n", [(DOMMouseEvent*)event button]);
1145         printf("  clientX:       %d\n", [(DOMMouseEvent*)event clientX]);
1146         printf("  clientY:       %d\n", [(DOMMouseEvent*)event clientY]);
1147         printf("  screenX:       %d\n", [(DOMMouseEvent*)event screenX]);
1148         printf("  screenY:       %d\n", [(DOMMouseEvent*)event screenY]);
1149         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
1150                [(DOMMouseEvent*)event ctrlKey] ? 1 : 0, 
1151                [(DOMMouseEvent*)event shiftKey] ? 1 : 0, 
1152                [(DOMMouseEvent*)event altKey] ? 1 : 0, 
1153                [(DOMMouseEvent*)event metaKey] ? 1 : 0);
1154         id relatedTarget = [(DOMMouseEvent*)event relatedTarget];
1155         if (relatedTarget) {
1156             printf("  relatedTarget: %s", [[[relatedTarget class] description] UTF8String]);
1157             if ([relatedTarget isKindOfClass:[DOMNode class]])
1158                 printf(" (nodeName: %s)", [[(DOMNode*)relatedTarget nodeName] UTF8String]);
1159             printf("\n");
1160         }
1161     }
1162     
1163     if ([event isKindOfClass:[DOMMutationEvent class]]) {
1164         printf("  prevValue:     %s\n", [[(DOMMutationEvent*)event prevValue] UTF8String]);
1165         printf("  newValue:      %s\n", [[(DOMMutationEvent*)event newValue] UTF8String]);
1166         printf("  attrName:      %s\n", [[(DOMMutationEvent*)event attrName] UTF8String]);
1167         printf("  attrChange:    %d\n", [(DOMMutationEvent*)event attrChange]);
1168         DOMNode *relatedNode = [(DOMMutationEvent*)event relatedNode];
1169         if (relatedNode) {
1170             printf("  relatedNode:   %s (nodeName: %s)\n", 
1171                    [[[relatedNode class] description] UTF8String],
1172                    [[relatedNode nodeName] UTF8String]);
1173         }
1174     }
1175     
1176     if ([event isKindOfClass:[DOMWheelEvent class]]) {
1177         printf("  clientX:       %d\n", [(DOMWheelEvent*)event clientX]);
1178         printf("  clientY:       %d\n", [(DOMWheelEvent*)event clientY]);
1179         printf("  screenX:       %d\n", [(DOMWheelEvent*)event screenX]);
1180         printf("  screenY:       %d\n", [(DOMWheelEvent*)event screenY]);
1181         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
1182                [(DOMWheelEvent*)event ctrlKey] ? 1 : 0, 
1183                [(DOMWheelEvent*)event shiftKey] ? 1 : 0, 
1184                [(DOMWheelEvent*)event altKey] ? 1 : 0, 
1185                [(DOMWheelEvent*)event metaKey] ? 1 : 0);
1186         printf("  isHorizontal:  %d\n", [(DOMWheelEvent*)event isHorizontal] ? 1 : 0);
1187         printf("  wheelDelta:    %d\n", [(DOMWheelEvent*)event wheelDelta]);
1188     }
1189 }
1190
1191 // FIXME: It's not good to have a test hard-wired into this controller like this.
1192 // Instead we need to get testing framework based on the Objective-C bindings
1193 // to work well enough that we can test that way instead.
1194 - (void)fireKeyboardEventsToElement:(WebScriptObject *)element {
1195     
1196     if (![element isKindOfClass:[DOMHTMLElement class]])
1197         return;
1198     
1199     DOMHTMLElement *target = (DOMHTMLElement*)element;
1200     DOMDocument *document = [target ownerDocument];
1201     
1202     // Keyboard Event 1
1203     
1204     DOMEvent *domEvent = [document createEvent:@"KeyboardEvent"];
1205     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keydown" 
1206                                          canBubble:YES
1207                                         cancelable:YES
1208                                               view:[document defaultView]
1209                                      keyIdentifier:@"U+000041" 
1210                                        location:0
1211                                            ctrlKey:YES
1212                                             altKey:NO
1213                                           shiftKey:NO
1214                                            metaKey:NO];
1215     [target dispatchEvent:domEvent];  
1216         
1217     // Keyboard Event 2
1218     
1219     domEvent = [document createEvent:@"KeyboardEvent"];
1220     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keypress" 
1221                                          canBubble:YES
1222                                         cancelable:YES
1223                                               view:[document defaultView]
1224                                      keyIdentifier:@"U+000045" 
1225                                        location:1
1226                                            ctrlKey:NO
1227                                             altKey:YES
1228                                           shiftKey:NO
1229                                            metaKey:NO];
1230     [target dispatchEvent:domEvent];    
1231     
1232     // Keyboard Event 3
1233     
1234     domEvent = [document createEvent:@"KeyboardEvent"];
1235     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keyup" 
1236                                          canBubble:YES
1237                                         cancelable:YES
1238                                               view:[document defaultView]
1239                                      keyIdentifier:@"U+000056" 
1240                                        location:0
1241                                            ctrlKey:NO
1242                                             altKey:NO
1243                                           shiftKey:NO
1244                                            metaKey:NO];
1245     [target dispatchEvent:domEvent];   
1246     
1247 }
1248
1249 #if PLATFORM(IOS)
1250 - (void)addTouchAtX:(int)x y:(int)y
1251 {
1252     if (!touches)
1253         touches = [[NSMutableArray alloc] init];
1254
1255     [touches addObject:[SyntheticTouch touchWithLocation:CGPointMake(x, y) phase:UITouchPhaseBegan identifier:currentTouchIdentifier++]];
1256 }
1257
1258 - (void)cancelTouchAtIndex:(unsigned)index
1259 {
1260     if (index < [touches count])
1261         [[touches objectAtIndex:index] setPhase:UITouchPhaseCancelled];
1262 }
1263
1264 - (void)clearTouchPoints
1265 {
1266     [touches removeAllObjects];
1267 }
1268
1269 - (void)releaseTouchAtIndex:(unsigned)index
1270 {
1271     if (index < [touches count]) {
1272         SyntheticTouch *touch = [touches objectAtIndex:index];
1273         [touch setPhase:UITouchPhaseEnded];
1274     }
1275 }
1276
1277 - (void)markAllTouchesAsStationary
1278 {
1279     for (SyntheticTouch *touch in touches)
1280         [touch setPhase:UITouchPhaseStationary];
1281 }
1282
1283 - (void)updateTouchAtIndex:(unsigned)index x:(int)x y:(int)y
1284 {
1285     if (index < [touches count]) {
1286         SyntheticTouch *touch = [touches objectAtIndex:index];
1287         [touch setPhase:UITouchPhaseMoved];
1288         [touch setLocation:CGPointMake(x, y)];
1289     }
1290 }
1291
1292 - (void)setTouchModifier:(NSString*)modifierName value:(BOOL)flag
1293 {
1294     unsigned modifier = 0;
1295     
1296     if ([modifierName isEqualToString:@"alt"])
1297         modifier = WebEventFlagMaskAlternate;
1298     else if ([modifierName isEqualToString:@"shift"])
1299         modifier = WebEventFlagMaskShift;
1300     else if ([modifierName isEqualToString:@"meta"])
1301         modifier = WebEventFlagMaskCommand;
1302     else if ([modifierName isEqualToString:@"ctrl"])
1303         modifier = WebEventFlagMaskControl;
1304
1305     if (!modifier)
1306         return;
1307
1308     if (flag)
1309         nextEventFlags |= modifier;
1310     else
1311         nextEventFlags &= ~modifier;
1312 }
1313
1314 - (void)sentTouchEventOfType:(WebEventType)type
1315 {
1316     NSMutableArray *touchLocations = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1317     NSMutableArray *touchIdentifiers = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1318     NSMutableArray *touchPhases = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1319     
1320     CGPoint centroid = CGPointZero;
1321     NSUInteger touchesDownCount = 0;
1322
1323     for (SyntheticTouch *currTouch in touches) {
1324         [touchLocations addObject:[NSValue valueWithCGPoint:currTouch.location]];
1325         [touchIdentifiers addObject:[NSNumber numberWithUnsignedInt:currTouch.identifier]];
1326         [touchPhases addObject:[NSNumber numberWithUnsignedInt:currTouch.phase]];
1327
1328         if ((currTouch.phase == UITouchPhaseEnded) || (currTouch.phase == UITouchPhaseCancelled))
1329             continue;
1330         
1331         centroid.x += currTouch.location.x;
1332         centroid.y += currTouch.location.y;
1333
1334         touchesDownCount++;
1335     }
1336
1337     if (touchesDownCount > 0)
1338         centroid = CGPointMake(centroid.x / touchesDownCount, centroid.y / touchesDownCount);
1339     else
1340         centroid = CGPointZero;
1341
1342     WebEvent *event = [[WebEvent alloc] initWithTouchEventType:type
1343                                         timeStamp:[self currentEventTime]
1344                                         location:centroid
1345                                         modifiers:(WebEventFlags)nextEventFlags
1346                                         touchCount:[touches count]
1347                                         touchLocations:touchLocations
1348                                         touchIdentifiers:touchIdentifiers
1349                                         touchPhases:touchPhases
1350                                         isGesture:(touchesDownCount > 1)
1351                                         gestureScale:1
1352                                         gestureRotation:0];
1353     // Ensure that layout is up-to-date so that hit-testing through WAKViews works correctly.
1354     [mainFrame updateLayout];
1355     [[[mainFrame webView] window] sendEventSynchronously:event];
1356     [event release];
1357     
1358     [touchLocations release];
1359     [touchIdentifiers release];
1360     [touchPhases release];
1361     
1362     nextEventFlags = 0;
1363 }
1364
1365 - (void)touchStart
1366 {
1367     [self sentTouchEventOfType:WebEventTouchBegin];
1368 }
1369
1370 - (void)touchMove
1371 {
1372     [self sentTouchEventOfType:WebEventTouchChange];
1373 }
1374
1375 - (void)touchEnd
1376 {
1377     [self sentTouchEventOfType:WebEventTouchEnd];
1378
1379     NSMutableArray *touchesToRemove = [[NSMutableArray alloc] init];
1380     for (SyntheticTouch *currTouch in touches) {
1381         if (currTouch.phase == UITouchPhaseEnded)
1382             [touchesToRemove addObject:currTouch];
1383     }
1384
1385     [touches removeObjectsInArray:touchesToRemove];
1386     [touchesToRemove release];
1387 }
1388
1389 - (void)touchCancel
1390 {
1391     [self sentTouchEventOfType:WebEventTouchCancel];
1392
1393     NSMutableArray *touchesToRemove = [[NSMutableArray alloc] init];
1394     for (SyntheticTouch *currTouch in touches) {
1395         if (currTouch.phase == UITouchPhaseCancelled)
1396             [touchesToRemove addObject:currTouch];
1397     }
1398
1399     [touches removeObjectsInArray:touchesToRemove];
1400     [touchesToRemove release];
1401 }
1402 #endif
1403
1404 @end