2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2006 Jonas Witt <jonas.witt@gmail.com>
4 * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
5 * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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.
33 #import "EventSendingController.h"
35 #import "DumpRenderTree.h"
36 #import "DumpRenderTreeDraggingInfo.h"
37 #import "DumpRenderTreeFileDraggingSource.h"
39 #import <WebKit/DOMPrivate.h>
40 #import <WebKit/WebKit.h>
41 #import <WebKit/WebViewPrivate.h>
44 #import <Carbon/Carbon.h> // for GetCurrentEventTime()
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>
56 extern "C" void _NSNewKillRingSequence();
58 @interface NSApplication (Details)
59 - (void)_setCurrentEvent:(NSEvent *)event;
69 // Match the DOM spec (sadly the DOM spec does not provide an enum)
72 MiddleMouseButton = 1,
77 struct KeyMappingEntry {
81 NSString* characterName;
84 NSPoint lastMousePosition;
85 NSPoint lastClickPosition;
86 int lastClickButton = NoMouseButton;
87 NSArray *webkitDomEventNames;
88 NSMutableArray *savedMouseEvents; // mouse events sent between mouseDown and mouseUp are stored here, and then executed at once.
89 BOOL replayingSavedEvents;
93 @interface SyntheticTouch : NSObject {
100 @property (nonatomic) CGPoint location;
101 @property (nonatomic) UITouchPhase phase;
102 @property (nonatomic) unsigned identifier;
104 + (id)touchWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier;
105 - (id)initWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier;
108 @implementation SyntheticTouch
110 @synthesize location = _location;
111 @synthesize phase = _phase;
112 @synthesize identifier = _identifier;
114 + (id)touchWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier
116 return [[[SyntheticTouch alloc] initWithLocation:location phase:phase identifier:identifier] autorelease];
119 - (id)initWithLocation:(CGPoint)location phase:(UITouchPhase)phase identifier:(unsigned)identifier
121 if ((self = [super init])) {
122 _location = location;
124 _identifier = identifier;
128 @end // SyntheticTouch
131 @implementation EventSendingController
135 webkitDomEventNames = [[NSArray alloc] initWithObjects:
185 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
187 if (aSelector == @selector(beginDragWithFiles:)
188 || aSelector == @selector(clearKillRing)
189 || aSelector == @selector(contextClick)
190 || aSelector == @selector(enableDOMUIEventLogging:)
191 || aSelector == @selector(fireKeyboardEventsToElement:)
192 || aSelector == @selector(keyDown:withModifiers:withLocation:)
193 || aSelector == @selector(leapForward:)
194 || aSelector == @selector(mouseDown:withModifiers:)
195 || aSelector == @selector(mouseMoveToX:Y:)
196 || aSelector == @selector(mouseUp:withModifiers:)
197 || aSelector == @selector(scheduleAsynchronousClick)
198 || aSelector == @selector(scheduleAsynchronousKeyDown:withModifiers:withLocation:)
199 || aSelector == @selector(textZoomIn)
200 || aSelector == @selector(textZoomOut)
201 || aSelector == @selector(zoomPageIn)
202 || aSelector == @selector(zoomPageOut)
203 || aSelector == @selector(scalePageBy:atX:andY:)
204 || aSelector == @selector(mouseScrollByX:andY:)
205 || aSelector == @selector(continuousMouseScrollByX:andY:)
207 || aSelector == @selector(addTouchAtX:y:)
208 || aSelector == @selector(updateTouchAtIndex:x:y:)
209 || aSelector == @selector(cancelTouchAtIndex:)
210 || aSelector == @selector(clearTouchPoints)
211 || aSelector == @selector(markAllTouchesAsStationary)
212 || aSelector == @selector(releaseTouchAtIndex:)
213 || aSelector == @selector(setTouchModifier:value:)
214 || aSelector == @selector(touchStart)
215 || aSelector == @selector(touchMove)
216 || aSelector == @selector(touchEnd)
217 || aSelector == @selector(touchCancel)
224 + (BOOL)isKeyExcludedFromWebScript:(const char*)name
226 if (strcmp(name, "dragMode") == 0)
231 + (NSString *)webScriptNameForSelector:(SEL)aSelector
233 if (aSelector == @selector(beginDragWithFiles:))
234 return @"beginDragWithFiles";
235 if (aSelector == @selector(contextClick))
236 return @"contextClick";
237 if (aSelector == @selector(enableDOMUIEventLogging:))
238 return @"enableDOMUIEventLogging";
239 if (aSelector == @selector(fireKeyboardEventsToElement:))
240 return @"fireKeyboardEventsToElement";
241 if (aSelector == @selector(keyDown:withModifiers:withLocation:))
243 if (aSelector == @selector(scheduleAsynchronousKeyDown:withModifiers:withLocation:))
244 return @"scheduleAsynchronousKeyDown";
245 if (aSelector == @selector(leapForward:))
246 return @"leapForward";
247 if (aSelector == @selector(mouseDown:withModifiers:))
249 if (aSelector == @selector(mouseUp:withModifiers:))
251 if (aSelector == @selector(mouseMoveToX:Y:))
252 return @"mouseMoveTo";
253 if (aSelector == @selector(setDragMode:))
254 return @"setDragMode";
255 if (aSelector == @selector(mouseScrollByX:andY:))
256 return @"mouseScrollBy";
257 if (aSelector == @selector(continuousMouseScrollByX:andY:))
258 return @"continuousMouseScrollBy";
259 if (aSelector == @selector(scalePageBy:atX:andY:))
260 return @"scalePageBy";
262 if (aSelector == @selector(addTouchAtX:y:))
263 return @"addTouchPoint";
264 if (aSelector == @selector(updateTouchAtIndex:x:y:))
265 return @"updateTouchPoint";
266 if (aSelector == @selector(cancelTouchAtIndex:))
267 return @"cancelTouchPoint";
268 if (aSelector == @selector(clearTouchPoints))
269 return @"clearTouchPoints";
270 if (aSelector == @selector(markAllTouchesAsStationary))
271 return @"markAllTouchesAsStationary";
272 if (aSelector == @selector(releaseTouchAtIndex:))
273 return @"releaseTouchPoint";
274 if (aSelector == @selector(setTouchModifier:value:))
275 return @"setTouchModifier";
276 if (aSelector == @selector(touchStart))
277 return @"touchStart";
278 if (aSelector == @selector(touchMove))
280 if (aSelector == @selector(touchEnd))
282 if (aSelector == @selector(touchCancel))
283 return @"touchCancel";
304 - (double)currentEventTime
307 return GetCurrentEventTime() + timeOffset;
309 return GSCurrentEventTimestamp() + timeOffset;
313 - (void)leapForward:(int)milliseconds
315 if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
316 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(leapForward:)]];
317 [invocation setTarget:self];
318 [invocation setSelector:@selector(leapForward:)];
319 [invocation setArgument:&milliseconds atIndex:2];
321 [EventSendingController saveEvent:invocation];
326 timeOffset += milliseconds / 1000.0;
329 - (void)clearKillRing
332 _NSNewKillRingSequence();
337 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
340 case LeftMouseButton:
343 return NSLeftMouseDown;
345 return NSLeftMouseUp;
347 return NSLeftMouseDragged;
349 case RightMouseButton:
352 return NSRightMouseDown;
354 return NSRightMouseUp;
356 return NSRightMouseDragged;
361 return NSOtherMouseDown;
363 return NSOtherMouseUp;
365 return NSOtherMouseDragged;
369 return static_cast<NSEventType>(0);
372 - (void)beginDragWithFiles:(WebScriptObject*)jsFilePaths
374 assert(!draggingInfo);
375 assert([jsFilePaths isKindOfClass:[WebScriptObject class]]);
377 NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
378 [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
380 NSURL *currentTestURL = [NSURL URLWithString:[[mainFrame webView] mainFrameURL]];
382 NSMutableArray *filePaths = [NSMutableArray array];
383 for (unsigned i = 0; [[jsFilePaths webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
384 NSString *filePath = (NSString *)[jsFilePaths webScriptValueAtIndex:i];
385 // Have NSURL encode the name so that we handle '?' in file names correctly.
386 NSURL *fileURL = [NSURL fileURLWithPath:filePath];
387 NSURL *absoluteFileURL = [NSURL URLWithString:[fileURL relativeString] relativeToURL:currentTestURL];
388 [filePaths addObject:[absoluteFileURL path]];
391 [pboard setPropertyList:filePaths forType:NSFilenamesPboardType];
392 assert([pboard propertyListForType:NSFilenamesPboardType]); // setPropertyList will silently fail on error, assert that it didn't fail
394 // Provide a source, otherwise [DumpRenderTreeDraggingInfo draggingSourceOperationMask] defaults to NSDragOperationNone
395 DumpRenderTreeFileDraggingSource *source = [[[DumpRenderTreeFileDraggingSource alloc] init] autorelease];
396 draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:nil offset:NSZeroSize pasteboard:pboard source:source];
397 [[mainFrame webView] draggingEntered:draggingInfo];
399 dragMode = NO; // dragMode saves events and then replays them later. We don't need/want that.
400 leftMouseButtonDown = YES; // Make the rest of eventSender think a drag is in progress
402 #endif // !PLATFORM(IOS)
404 - (void)updateClickCountForButton:(int)buttonNumber
406 if (([self currentEventTime] - lastClick >= 1) ||
407 !NSEqualPoints(lastMousePosition, lastClickPosition) ||
408 lastClickButton != buttonNumber) {
410 lastClickButton = buttonNumber;
415 static int modifierFlags(const NSString* modifierName)
418 const int controlKeyMask = NSControlKeyMask;
419 const int shiftKeyMask = NSShiftKeyMask;
420 const int alternateKeyMask = NSAlternateKeyMask;
421 const int commandKeyMask = NSCommandKeyMask;
423 const int controlKeyMask = WebEventFlagMaskControl;
424 const int shiftKeyMask = WebEventFlagMaskShift;
425 const int alternateKeyMask = WebEventFlagMaskAlternate;
426 const int commandKeyMask = WebEventFlagMaskCommand;
430 if ([modifierName isEqual:@"ctrlKey"])
431 flags |= controlKeyMask;
432 else if ([modifierName isEqual:@"shiftKey"] || [modifierName isEqual:@"rangeSelectionKey"])
433 flags |= shiftKeyMask;
434 else if ([modifierName isEqual:@"altKey"])
435 flags |= alternateKeyMask;
436 else if ([modifierName isEqual:@"metaKey"] || [modifierName isEqual:@"addSelectionKey"])
437 flags |= commandKeyMask;
442 static int buildModifierFlags(const WebScriptObject* modifiers)
445 if ([modifiers isKindOfClass:[NSString class]])
446 return modifierFlags((NSString*)modifiers);
447 else if (![modifiers isKindOfClass:[WebScriptObject class]])
449 for (unsigned i = 0; [[modifiers webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
450 NSString* modifierName = (NSString*)[modifiers webScriptValueAtIndex:i];
451 flags |= modifierFlags(modifierName);
456 - (void)mouseDown:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
458 [[[mainFrame frameView] documentView] layout];
459 [self updateClickCountForButton:buttonNumber];
462 NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
463 NSEvent *event = [NSEvent mouseEventWithType:eventType
464 location:lastMousePosition
465 modifierFlags:buildModifierFlags(modifiers)
466 timestamp:[self currentEventTime]
467 windowNumber:[[[mainFrame webView] window] windowNumber]
468 context:[NSGraphicsContext currentContext]
469 eventNumber:++eventNumber
470 clickCount:clickCount
473 WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseDown
474 timeStamp:[self currentEventTime]
475 location:lastMousePosition];
478 NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
481 [NSApp _setCurrentEvent:event];
483 [subView mouseDown:event];
485 [NSApp _setCurrentEvent:nil];
487 if (buttonNumber == LeftMouseButton)
488 leftMouseButtonDown = YES;
496 - (void)mouseDown:(int)buttonNumber
498 [self mouseDown:buttonNumber withModifiers:nil];
503 [[mainFrame webView] makeTextLarger:self];
508 [[mainFrame webView] makeTextSmaller:self];
513 [[mainFrame webView] zoomPageIn:self];
518 [[mainFrame webView] zoomPageOut:self];
521 - (void)scalePageBy:(float)scale atX:(float)x andY:(float)y
524 // -[WebView _scaleWebView:] is Mac-specific API, and calls functions that
525 // assert to not be used in iOS.
526 [[mainFrame webView] _scaleWebView:scale atOrigin:NSMakePoint(x, y)];
530 - (void)mouseUp:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
532 if (dragMode && !replayingSavedEvents) {
533 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseUp:withModifiers:)]];
534 [invocation setTarget:self];
535 [invocation setSelector:@selector(mouseUp:withModifiers:)];
536 [invocation setArgument:&buttonNumber atIndex:2];
537 [invocation setArgument:&modifiers atIndex:3];
539 [EventSendingController saveEvent:invocation];
540 [EventSendingController replaySavedEvents];
545 [[[mainFrame frameView] documentView] layout];
547 NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
548 NSEvent *event = [NSEvent mouseEventWithType:eventType
549 location:lastMousePosition
550 modifierFlags:buildModifierFlags(modifiers)
551 timestamp:[self currentEventTime]
552 windowNumber:[[[mainFrame webView] window] windowNumber]
553 context:[NSGraphicsContext currentContext]
554 eventNumber:++eventNumber
555 clickCount:clickCount
558 WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseUp
559 timeStamp:[self currentEventTime]
560 location:lastMousePosition];
563 NSView *targetView = [[mainFrame webView] hitTest:[event locationInWindow]];
564 // FIXME: Silly hack to teach DRT to respect capturing mouse events outside the WebView.
565 // The right solution is just to use NSApplication's built-in event sending methods,
566 // instead of rolling our own algorithm for selecting an event target.
567 targetView = targetView ? targetView : [[mainFrame frameView] documentView];
570 [NSApp _setCurrentEvent:event];
572 [targetView mouseUp:event];
574 [NSApp _setCurrentEvent:nil];
576 if (buttonNumber == LeftMouseButton)
577 leftMouseButtonDown = NO;
578 lastClick = [event timestamp];
579 lastClickPosition = lastMousePosition;
582 WebView *webView = [mainFrame webView];
584 NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
586 if (dragOperation != NSDragOperationNone)
587 [webView performDragOperation:draggingInfo];
589 [webView draggingExited:draggingInfo];
590 // Per NSDragging.h: draggingSources may not implement draggedImage:endedAt:operation:
591 if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:endedAt:operation:)])
592 [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
593 [draggingInfo release];
603 - (void)mouseUp:(int)buttonNumber
605 [self mouseUp:buttonNumber withModifiers:nil];
608 - (void)mouseMoveToX:(int)x Y:(int)y
610 if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
611 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseMoveToX:Y:)]];
612 [invocation setTarget:self];
613 [invocation setSelector:@selector(mouseMoveToX:Y:)];
614 [invocation setArgument:&x atIndex:2];
615 [invocation setArgument:&y atIndex:3];
617 [EventSendingController saveEvent:invocation];
621 NSView *view = [mainFrame webView];
623 lastMousePosition = [view convertPoint:NSMakePoint(x, [view frame].size.height - y) toView:nil];
624 NSEvent *event = [NSEvent mouseEventWithType:(leftMouseButtonDown ? NSLeftMouseDragged : NSMouseMoved)
625 location:lastMousePosition
627 timestamp:[self currentEventTime]
628 windowNumber:[[view window] windowNumber]
629 context:[NSGraphicsContext currentContext]
630 eventNumber:++eventNumber
631 clickCount:(leftMouseButtonDown ? clickCount : 0)
634 lastMousePosition = [view convertPoint:NSMakePoint(x, y) toView:nil];
635 WebEvent *event = [[WebEvent alloc] initWithMouseEventType:WebEventMouseMoved
636 timeStamp:[self currentEventTime]
637 location:lastMousePosition];
638 #endif // !PLATFORM(IOS)
640 NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
643 [NSApp _setCurrentEvent:event];
645 if (leftMouseButtonDown) {
648 // Per NSDragging.h: draggingSources may not implement draggedImage:movedTo:
649 if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:movedTo:)])
650 [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
651 [[mainFrame webView] draggingUpdated:draggingInfo];
653 [subView mouseDragged:event];
656 [subView mouseMoved:event];
658 [NSApp _setCurrentEvent:nil];
667 - (void)mouseScrollByX:(int)x andY:(int)y continuously:(BOOL)c
670 CGScrollEventUnit unit = c?kCGScrollEventUnitPixel:kCGScrollEventUnitLine;
671 CGEventRef cgScrollEvent = CGEventCreateScrollWheelEvent(NULL, unit, 2, y, x);
673 // CGEvent locations are in global display coordinates.
674 CGPoint lastGlobalMousePosition = {
676 [[NSScreen mainScreen] frame].size.height - lastMousePosition.y
678 CGEventSetLocation(cgScrollEvent, lastGlobalMousePosition);
680 NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgScrollEvent];
681 CFRelease(cgScrollEvent);
683 NSView *subView = [[mainFrame webView] hitTest:[scrollEvent locationInWindow]];
685 [NSApp _setCurrentEvent:scrollEvent];
686 [subView scrollWheel:scrollEvent];
687 [NSApp _setCurrentEvent:nil];
692 - (void)continuousMouseScrollByX:(int)x andY:(int)y
694 [self mouseScrollByX:x andY:y continuously:YES];
697 - (void)mouseScrollByX:(int)x andY:(int)y
699 [self mouseScrollByX:x andY:y continuously:NO];
702 - (NSArray *)contextClick
705 [[[mainFrame frameView] documentView] layout];
706 [self updateClickCountForButton:RightMouseButton];
708 NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
709 location:lastMousePosition
711 timestamp:[self currentEventTime]
712 windowNumber:[[[mainFrame webView] window] windowNumber]
713 context:[NSGraphicsContext currentContext]
714 eventNumber:++eventNumber
715 clickCount:clickCount
718 NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
719 NSMutableArray *menuItemStrings = [NSMutableArray array];
722 [NSApp _setCurrentEvent:event];
723 NSMenu* menu = [subView menuForEvent:event];
724 [NSApp _setCurrentEvent:nil];
726 for (int i = 0; i < [menu numberOfItems]; ++i) {
727 NSMenuItem* menuItem = [menu itemAtIndex:i];
728 if (!strcmp("Inspect Element", [[menuItem title] UTF8String]))
731 if ([menuItem isSeparatorItem])
732 [menuItemStrings addObject:@"<separator>"];
734 [menuItemStrings addObject:[menuItem title]];
738 return menuItemStrings;
744 - (void)scheduleAsynchronousClick
746 [self performSelector:@selector(mouseDown:) withObject:nil afterDelay:0];
747 [self performSelector:@selector(mouseUp:) withObject:nil afterDelay:0];
750 + (void)saveEvent:(NSInvocation *)event
752 if (!savedMouseEvents)
753 savedMouseEvents = [[NSMutableArray alloc] init];
754 [savedMouseEvents addObject:event];
757 + (void)replaySavedEvents
759 replayingSavedEvents = YES;
760 while ([savedMouseEvents count]) {
761 // if a drag is initiated, the remaining saved events will be dispatched from our dragging delegate
762 NSInvocation *invocation = [[[savedMouseEvents objectAtIndex:0] retain] autorelease];
763 [savedMouseEvents removeObjectAtIndex:0];
766 replayingSavedEvents = NO;
769 + (void)clearSavedEvents
771 [savedMouseEvents release];
772 savedMouseEvents = nil;
775 - (void)keyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
777 NSString *eventCharacter = character;
778 unsigned short keyCode = 0;
779 if ([character isEqualToString:@"leftArrow"]) {
780 const unichar ch = NSLeftArrowFunctionKey;
781 eventCharacter = [NSString stringWithCharacters:&ch length:1];
783 } else if ([character isEqualToString:@"rightArrow"]) {
784 const unichar ch = NSRightArrowFunctionKey;
785 eventCharacter = [NSString stringWithCharacters:&ch length:1];
787 } else if ([character isEqualToString:@"upArrow"]) {
788 const unichar ch = NSUpArrowFunctionKey;
789 eventCharacter = [NSString stringWithCharacters:&ch length:1];
791 } else if ([character isEqualToString:@"downArrow"]) {
792 const unichar ch = NSDownArrowFunctionKey;
793 eventCharacter = [NSString stringWithCharacters:&ch length:1];
795 } else if ([character isEqualToString:@"pageUp"]) {
796 const unichar ch = NSPageUpFunctionKey;
797 eventCharacter = [NSString stringWithCharacters:&ch length:1];
799 } else if ([character isEqualToString:@"pageDown"]) {
800 const unichar ch = NSPageDownFunctionKey;
801 eventCharacter = [NSString stringWithCharacters:&ch length:1];
803 } else if ([character isEqualToString:@"home"]) {
804 const unichar ch = NSHomeFunctionKey;
805 eventCharacter = [NSString stringWithCharacters:&ch length:1];
807 } else if ([character isEqualToString:@"end"]) {
808 const unichar ch = NSEndFunctionKey;
809 eventCharacter = [NSString stringWithCharacters:&ch length:1];
811 } else if ([character isEqualToString:@"insert"]) {
812 const unichar ch = NSInsertFunctionKey;
813 eventCharacter = [NSString stringWithCharacters:&ch length:1];
815 } else if ([character isEqualToString:@"delete"]) {
816 const unichar ch = NSDeleteFunctionKey;
817 eventCharacter = [NSString stringWithCharacters:&ch length:1];
819 } else if ([character isEqualToString:@"printScreen"]) {
820 const unichar ch = NSPrintScreenFunctionKey;
821 eventCharacter = [NSString stringWithCharacters:&ch length:1];
822 keyCode = 0x0; // There is no known virtual key code for PrintScreen.
823 } else if ([character isEqualToString:@"cyrillicSmallLetterA"]) {
824 const unichar ch = 0x0430;
825 eventCharacter = [NSString stringWithCharacters:&ch length:1];
826 keyCode = 0x3; // Shares key with "F" on Russian layout.
827 } else if ([character isEqualToString:@"leftControl"]) {
828 const unichar ch = 0xFFE3;
829 eventCharacter = [NSString stringWithCharacters:&ch length:1];
831 } else if ([character isEqualToString:@"leftShift"]) {
832 const unichar ch = 0xFFE1;
833 eventCharacter = [NSString stringWithCharacters:&ch length:1];
835 } else if ([character isEqualToString:@"leftAlt"]) {
836 const unichar ch = 0xFFE7;
837 eventCharacter = [NSString stringWithCharacters:&ch length:1];
839 } else if ([character isEqualToString:@"rightControl"]) {
840 const unichar ch = 0xFFE4;
841 eventCharacter = [NSString stringWithCharacters:&ch length:1];
843 } else if ([character isEqualToString:@"rightShift"]) {
844 const unichar ch = 0xFFE2;
845 eventCharacter = [NSString stringWithCharacters:&ch length:1];
847 } else if ([character isEqualToString:@"rightAlt"]) {
848 const unichar ch = 0xFFE8;
849 eventCharacter = [NSString stringWithCharacters:&ch length:1];
853 // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
854 // If the input string is a function-key name, set its key code.
855 for (unsigned i = 1; i <= 24; i++) {
856 if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
857 const unichar ch = NSF1FunctionKey + (i - 1);
858 eventCharacter = [NSString stringWithCharacters:&ch length:1];
860 case 1: keyCode = 0x7A; break;
861 case 2: keyCode = 0x78; break;
862 case 3: keyCode = 0x63; break;
863 case 4: keyCode = 0x76; break;
864 case 5: keyCode = 0x60; break;
865 case 6: keyCode = 0x61; break;
866 case 7: keyCode = 0x62; break;
867 case 8: keyCode = 0x64; break;
868 case 9: keyCode = 0x65; break;
869 case 10: keyCode = 0x6D; break;
870 case 11: keyCode = 0x67; break;
871 case 12: keyCode = 0x6F; break;
872 case 13: keyCode = 0x69; break;
873 case 14: keyCode = 0x6B; break;
874 case 15: keyCode = 0x71; break;
875 case 16: keyCode = 0x6A; break;
876 case 17: keyCode = 0x40; break;
877 case 18: keyCode = 0x4F; break;
878 case 19: keyCode = 0x50; break;
879 case 20: keyCode = 0x5A; break;
884 // FIXME: No keyCode is set for most keys.
885 if ([character isEqualToString:@"\t"])
887 else if ([character isEqualToString:@" "])
889 else if ([character isEqualToString:@"\r"])
891 else if ([character isEqualToString:@"\n"])
893 else if ([character isEqualToString:@"\x8"])
895 else if ([character isEqualToString:@"a"])
897 else if ([character isEqualToString:@"b"])
899 else if ([character isEqualToString:@"d"])
901 else if ([character isEqualToString:@"e"])
904 KeyMappingEntry table[] = {
905 {0x2F, 0x41, '.', nil},
908 {0, 0x47, NSClearLineFunctionKey, @"clear"},
909 {0x2C, 0x4B, '/', nil},
910 {0, 0x4C, 3, @"enter" },
911 {0x1B, 0x4E, '-', nil},
912 {0x18, 0x51, '=', nil},
913 {0x1D, 0x52, '0', nil},
914 {0x12, 0x53, '1', nil},
915 {0x13, 0x54, '2', nil},
916 {0x14, 0x55, '3', nil},
917 {0x15, 0x56, '4', nil},
918 {0x17, 0x57, '5', nil},
919 {0x16, 0x58, '6', nil},
920 {0x1A, 0x59, '7', nil},
921 {0x1C, 0x5B, '8', nil},
922 {0x19, 0x5C, '9', nil},
924 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(table); ++i) {
925 NSString* currentCharacterString = [NSString stringWithCharacters:&table[i].character length:1];
926 if ([character isEqualToString:currentCharacterString] || [character isEqualToString:table[i].characterName]) {
927 if (location == DOM_KEY_LOCATION_NUMPAD)
928 keyCode = table[i].macNumpadKeyCode;
930 keyCode = table[i].macKeyCode;
931 eventCharacter = currentCharacterString;
936 NSString *charactersIgnoringModifiers = eventCharacter;
938 int modifierFlags = 0;
940 if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
942 modifierFlags |= NSShiftKeyMask;
944 modifierFlags |= WebEventFlagMaskAlphaShift;
946 charactersIgnoringModifiers = [character lowercaseString];
949 modifierFlags |= buildModifierFlags(modifiers);
951 if (location == DOM_KEY_LOCATION_NUMPAD)
952 modifierFlags |= NSNumericPadKeyMask;
954 [[[mainFrame frameView] documentView] layout];
957 NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
958 location:NSMakePoint(5, 5)
959 modifierFlags:modifierFlags
960 timestamp:[self currentEventTime]
961 windowNumber:[[[mainFrame webView] window] windowNumber]
962 context:[NSGraphicsContext currentContext]
963 characters:eventCharacter
964 charactersIgnoringModifiers:charactersIgnoringModifiers
968 WebEvent *event = [[WebEvent alloc] initWithKeyEventType:WebEventKeyDown
969 timeStamp:[self currentEventTime]
970 characters:eventCharacter
971 charactersIgnoringModifiers:charactersIgnoringModifiers
972 modifiers:(WebEventFlags)modifierFlags
975 keyCode:[character characterAtIndex:0]
976 isTabKey:([character characterAtIndex:0] == '\t')
977 characterSet:WebEventCharacterSetASCII];
981 [NSApp _setCurrentEvent:event];
983 [[[[mainFrame webView] window] firstResponder] keyDown:event];
985 [NSApp _setCurrentEvent:nil];
989 event = [NSEvent keyEventWithType:NSKeyUp
990 location:NSMakePoint(5, 5)
991 modifierFlags:modifierFlags
992 timestamp:[self currentEventTime]
993 windowNumber:[[[mainFrame webView] window] windowNumber]
994 context:[NSGraphicsContext currentContext]
995 characters:eventCharacter
996 charactersIgnoringModifiers:charactersIgnoringModifiers
1001 event = [[WebEvent alloc] initWithKeyEventType:WebEventKeyUp
1002 timeStamp:[self currentEventTime]
1003 characters:eventCharacter
1004 charactersIgnoringModifiers:charactersIgnoringModifiers
1005 modifiers:(WebEventFlags)modifierFlags
1008 keyCode:[character characterAtIndex:0]
1009 isTabKey:([character characterAtIndex:0] == '\t')
1010 characterSet:WebEventCharacterSetASCII];
1014 [NSApp _setCurrentEvent:event];
1016 [[[[mainFrame webView] window] firstResponder] keyUp:event];
1018 [NSApp _setCurrentEvent:nil];
1026 - (void)keyDownWrapper:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
1028 [self keyDown:character withModifiers:modifiers withLocation:location];
1031 - (void)scheduleAsynchronousKeyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)location
1033 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(keyDownWrapper:withModifiers:withLocation:)]];
1034 [invocation retainArguments];
1035 [invocation setTarget:self];
1036 [invocation setSelector:@selector(keyDownWrapper:withModifiers:withLocation:)];
1037 [invocation setArgument:&character atIndex:2];
1038 [invocation setArgument:&modifiers atIndex:3];
1039 [invocation setArgument:&location atIndex:4];
1040 [invocation performSelector:@selector(invoke) withObject:nil afterDelay:0];
1043 - (void)enableDOMUIEventLogging:(WebScriptObject *)node
1045 NSEnumerator *eventEnumerator = [webkitDomEventNames objectEnumerator];
1047 while ((eventName = [eventEnumerator nextObject])) {
1048 [(id<DOMEventTarget>)node addEventListener:eventName listener:self useCapture:NO];
1052 - (void)handleEvent:(DOMEvent *)event
1054 DOMNode *target = [event target];
1056 printf("event type: %s\n", [[event type] UTF8String]);
1057 printf(" target: <%s>\n", [[[target nodeName] lowercaseString] UTF8String]);
1059 if ([event isKindOfClass:[DOMEvent class]]) {
1060 printf(" eventPhase: %d\n", [event eventPhase]);
1061 printf(" bubbles: %d\n", [event bubbles] ? 1 : 0);
1062 printf(" cancelable: %d\n", [event cancelable] ? 1 : 0);
1065 if ([event isKindOfClass:[DOMUIEvent class]]) {
1066 printf(" detail: %d\n", [(DOMUIEvent*)event detail]);
1068 DOMAbstractView *view = [(DOMUIEvent*)event view];
1070 printf(" view: OK");
1071 if ([view document])
1072 printf(" (document: OK)");
1077 if ([event isKindOfClass:[DOMKeyboardEvent class]]) {
1078 printf(" keyIdentifier: %s\n", [[(DOMKeyboardEvent*)event keyIdentifier] UTF8String]);
1079 printf(" keyLocation: %d\n", [(DOMKeyboardEvent*)event location]);
1080 printf(" modifier keys: c:%d s:%d a:%d m:%d\n",
1081 [(DOMKeyboardEvent*)event ctrlKey] ? 1 : 0,
1082 [(DOMKeyboardEvent*)event shiftKey] ? 1 : 0,
1083 [(DOMKeyboardEvent*)event altKey] ? 1 : 0,
1084 [(DOMKeyboardEvent*)event metaKey] ? 1 : 0);
1085 printf(" keyCode: %d\n", [(DOMKeyboardEvent*)event keyCode]);
1086 printf(" charCode: %d\n", [(DOMKeyboardEvent*)event charCode]);
1089 if ([event isKindOfClass:[DOMMouseEvent class]]) {
1090 printf(" button: %d\n", [(DOMMouseEvent*)event button]);
1091 printf(" clientX: %d\n", [(DOMMouseEvent*)event clientX]);
1092 printf(" clientY: %d\n", [(DOMMouseEvent*)event clientY]);
1093 printf(" screenX: %d\n", [(DOMMouseEvent*)event screenX]);
1094 printf(" screenY: %d\n", [(DOMMouseEvent*)event screenY]);
1095 printf(" modifier keys: c:%d s:%d a:%d m:%d\n",
1096 [(DOMMouseEvent*)event ctrlKey] ? 1 : 0,
1097 [(DOMMouseEvent*)event shiftKey] ? 1 : 0,
1098 [(DOMMouseEvent*)event altKey] ? 1 : 0,
1099 [(DOMMouseEvent*)event metaKey] ? 1 : 0);
1100 id relatedTarget = [(DOMMouseEvent*)event relatedTarget];
1101 if (relatedTarget) {
1102 printf(" relatedTarget: %s", [[[relatedTarget class] description] UTF8String]);
1103 if ([relatedTarget isKindOfClass:[DOMNode class]])
1104 printf(" (nodeName: %s)", [[(DOMNode*)relatedTarget nodeName] UTF8String]);
1109 if ([event isKindOfClass:[DOMMutationEvent class]]) {
1110 printf(" prevValue: %s\n", [[(DOMMutationEvent*)event prevValue] UTF8String]);
1111 printf(" newValue: %s\n", [[(DOMMutationEvent*)event newValue] UTF8String]);
1112 printf(" attrName: %s\n", [[(DOMMutationEvent*)event attrName] UTF8String]);
1113 printf(" attrChange: %d\n", [(DOMMutationEvent*)event attrChange]);
1114 DOMNode *relatedNode = [(DOMMutationEvent*)event relatedNode];
1116 printf(" relatedNode: %s (nodeName: %s)\n",
1117 [[[relatedNode class] description] UTF8String],
1118 [[relatedNode nodeName] UTF8String]);
1122 if ([event isKindOfClass:[DOMWheelEvent class]]) {
1123 printf(" clientX: %d\n", [(DOMWheelEvent*)event clientX]);
1124 printf(" clientY: %d\n", [(DOMWheelEvent*)event clientY]);
1125 printf(" screenX: %d\n", [(DOMWheelEvent*)event screenX]);
1126 printf(" screenY: %d\n", [(DOMWheelEvent*)event screenY]);
1127 printf(" modifier keys: c:%d s:%d a:%d m:%d\n",
1128 [(DOMWheelEvent*)event ctrlKey] ? 1 : 0,
1129 [(DOMWheelEvent*)event shiftKey] ? 1 : 0,
1130 [(DOMWheelEvent*)event altKey] ? 1 : 0,
1131 [(DOMWheelEvent*)event metaKey] ? 1 : 0);
1132 printf(" isHorizontal: %d\n", [(DOMWheelEvent*)event isHorizontal] ? 1 : 0);
1133 printf(" wheelDelta: %d\n", [(DOMWheelEvent*)event wheelDelta]);
1137 // FIXME: It's not good to have a test hard-wired into this controller like this.
1138 // Instead we need to get testing framework based on the Objective-C bindings
1139 // to work well enough that we can test that way instead.
1140 - (void)fireKeyboardEventsToElement:(WebScriptObject *)element {
1142 if (![element isKindOfClass:[DOMHTMLElement class]])
1145 DOMHTMLElement *target = (DOMHTMLElement*)element;
1146 DOMDocument *document = [target ownerDocument];
1150 DOMEvent *domEvent = [document createEvent:@"KeyboardEvent"];
1151 [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keydown"
1154 view:[document defaultView]
1155 keyIdentifier:@"U+000041"
1161 [target dispatchEvent:domEvent];
1165 domEvent = [document createEvent:@"KeyboardEvent"];
1166 [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keypress"
1169 view:[document defaultView]
1170 keyIdentifier:@"U+000045"
1176 [target dispatchEvent:domEvent];
1180 domEvent = [document createEvent:@"KeyboardEvent"];
1181 [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keyup"
1184 view:[document defaultView]
1185 keyIdentifier:@"U+000056"
1191 [target dispatchEvent:domEvent];
1196 - (void)addTouchAtX:(int)x y:(int)y
1199 touches = [[NSMutableArray alloc] init];
1201 [touches addObject:[SyntheticTouch touchWithLocation:CGPointMake(x, y) phase:UITouchPhaseBegan identifier:currentTouchIdentifier++]];
1204 - (void)cancelTouchAtIndex:(unsigned)index
1206 if (index < [touches count])
1207 [[touches objectAtIndex:index] setPhase:UITouchPhaseCancelled];
1210 - (void)clearTouchPoints
1212 [touches removeAllObjects];
1215 - (void)releaseTouchAtIndex:(unsigned)index
1217 if (index < [touches count]) {
1218 SyntheticTouch *touch = [touches objectAtIndex:index];
1219 [touch setPhase:UITouchPhaseEnded];
1223 - (void)markAllTouchesAsStationary
1225 for (SyntheticTouch *touch in touches)
1226 [touch setPhase:UITouchPhaseStationary];
1229 - (void)updateTouchAtIndex:(unsigned)index x:(int)x y:(int)y
1231 if (index < [touches count]) {
1232 SyntheticTouch *touch = [touches objectAtIndex:index];
1233 [touch setPhase:UITouchPhaseMoved];
1234 [touch setLocation:CGPointMake(x, y)];
1238 - (void)setTouchModifier:(NSString*)modifierName value:(BOOL)flag
1240 unsigned modifier = 0;
1242 if ([modifierName isEqualToString:@"alt"])
1243 modifier = WebEventFlagMaskAlternate;
1244 else if ([modifierName isEqualToString:@"shift"])
1245 modifier = WebEventFlagMaskShift;
1246 else if ([modifierName isEqualToString:@"meta"])
1247 modifier = WebEventFlagMaskCommand;
1248 else if ([modifierName isEqualToString:@"ctrl"])
1249 modifier = WebEventFlagMaskControl;
1255 nextEventFlags |= modifier;
1257 nextEventFlags &= ~modifier;
1260 - (void)sentTouchEventOfType:(WebEventType)type
1262 NSMutableArray *touchLocations = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1263 NSMutableArray *touchIdentifiers = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1264 NSMutableArray *touchPhases = [[NSMutableArray alloc] initWithCapacity:[touches count]];
1266 CGPoint centroid = CGPointZero;
1267 NSUInteger touchesDownCount = 0;
1269 for (SyntheticTouch *currTouch in touches) {
1270 [touchLocations addObject:[NSValue valueWithCGPoint:currTouch.location]];
1271 [touchIdentifiers addObject:[NSNumber numberWithUnsignedInt:currTouch.identifier]];
1272 [touchPhases addObject:[NSNumber numberWithUnsignedInt:currTouch.phase]];
1274 if ((currTouch.phase == UITouchPhaseEnded) || (currTouch.phase == UITouchPhaseCancelled))
1277 centroid.x += currTouch.location.x;
1278 centroid.y += currTouch.location.y;
1283 if (touchesDownCount > 0)
1284 centroid = CGPointMake(centroid.x / touchesDownCount, centroid.y / touchesDownCount);
1286 centroid = CGPointZero;
1288 WebEvent *event = [[WebEvent alloc] initWithTouchEventType:type
1289 timeStamp:[self currentEventTime]
1291 modifiers:(WebEventFlags)nextEventFlags
1292 touchCount:[touches count]
1293 touchLocations:touchLocations
1294 touchIdentifiers:touchIdentifiers
1295 touchPhases:touchPhases
1296 isGesture:(touchesDownCount > 1)
1299 // Ensure that layout is up-to-date so that hit-testing through WAKViews works correctly.
1300 [mainFrame updateLayout];
1301 [[[mainFrame webView] window] sendEventSynchronously:event];
1304 [touchLocations release];
1305 [touchIdentifiers release];
1306 [touchPhases release];
1313 [self sentTouchEventOfType:WebEventTouchBegin];
1318 [self sentTouchEventOfType:WebEventTouchChange];
1323 [self sentTouchEventOfType:WebEventTouchEnd];
1325 NSMutableArray *touchesToRemove = [[NSMutableArray alloc] init];
1326 for (SyntheticTouch *currTouch in touches) {
1327 if (currTouch.phase == UITouchPhaseEnded)
1328 [touchesToRemove addObject:currTouch];
1331 [touches removeObjectsInArray:touchesToRemove];
1332 [touchesToRemove release];
1337 [self sentTouchEventOfType:WebEventTouchCancel];
1339 NSMutableArray *touchesToRemove = [[NSMutableArray alloc] init];
1340 for (SyntheticTouch *currTouch in touches) {
1341 if (currTouch.phase == UITouchPhaseCancelled)
1342 [touchesToRemove addObject:currTouch];
1345 [touches removeObjectsInArray:touchesToRemove];
1346 [touchesToRemove release];