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