Home | History | Annotate | Download | only in mac
      1 /*
      2  * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
      3  * Copyright (C) 2006 Jonas Witt <jonas.witt (at) gmail.com>
      4  * Copyright (C) 2006 Samuel Weinig <sam.weinig (at) gmail.com>
      5  * Copyright (C) 2006 Alexey Proskuryakov <ap (at) nypop.com>
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  *
     11  * 1.  Redistributions of source code must retain the above copyright
     12  *     notice, this list of conditions and the following disclaimer.
     13  * 2.  Redistributions in binary form must reproduce the above copyright
     14  *     notice, this list of conditions and the following disclaimer in the
     15  *     documentation and/or other materials provided with the distribution.
     16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     17  *     its contributors may be used to endorse or promote products derived
     18  *     from this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 #import "config.h"
     33 #import "EventSendingController.h"
     34 
     35 #import "DumpRenderTree.h"
     36 #import "DumpRenderTreeDraggingInfo.h"
     37 #import "DumpRenderTreeFileDraggingSource.h"
     38 
     39 #import <Carbon/Carbon.h>                           // for GetCurrentEventTime()
     40 #import <WebKit/DOMPrivate.h>
     41 #import <WebKit/WebKit.h>
     42 #import <WebKit/WebViewPrivate.h>
     43 
     44 extern "C" void _NSNewKillRingSequence();
     45 
     46 enum MouseAction {
     47     MouseDown,
     48     MouseUp,
     49     MouseDragged
     50 };
     51 
     52 // Match the DOM spec (sadly the DOM spec does not provide an enum)
     53 enum MouseButton {
     54     LeftMouseButton = 0,
     55     MiddleMouseButton = 1,
     56     RightMouseButton = 2,
     57     NoMouseButton = -1
     58 };
     59 
     60 NSPoint lastMousePosition;
     61 NSPoint lastClickPosition;
     62 int lastClickButton = NoMouseButton;
     63 NSArray *webkitDomEventNames;
     64 NSMutableArray *savedMouseEvents; // mouse events sent between mouseDown and mouseUp are stored here, and then executed at once.
     65 BOOL replayingSavedEvents;
     66 
     67 @implementation EventSendingController
     68 
     69 + (void)initialize
     70 {
     71     webkitDomEventNames = [[NSArray alloc] initWithObjects:
     72         @"abort",
     73         @"beforecopy",
     74         @"beforecut",
     75         @"beforepaste",
     76         @"blur",
     77         @"change",
     78         @"click",
     79         @"contextmenu",
     80         @"copy",
     81         @"cut",
     82         @"dblclick",
     83         @"drag",
     84         @"dragend",
     85         @"dragenter",
     86         @"dragleave",
     87         @"dragover",
     88         @"dragstart",
     89         @"drop",
     90         @"error",
     91         @"focus",
     92         @"input",
     93         @"keydown",
     94         @"keypress",
     95         @"keyup",
     96         @"load",
     97         @"mousedown",
     98         @"mousemove",
     99         @"mouseout",
    100         @"mouseover",
    101         @"mouseup",
    102         @"mousewheel",
    103         @"beforeunload",
    104         @"paste",
    105         @"readystatechange",
    106         @"reset",
    107         @"resize",
    108         @"scroll",
    109         @"search",
    110         @"select",
    111         @"selectstart",
    112         @"submit",
    113         @"textInput",
    114         @"textzoomin",
    115         @"textzoomout",
    116         @"unload",
    117         @"zoom",
    118         nil];
    119 }
    120 
    121 + (BOOL)isSelectorExcludedFromWebScript:(SEL)aSelector
    122 {
    123     if (aSelector == @selector(beginDragWithFiles:)
    124             || aSelector == @selector(clearKillRing)
    125             || aSelector == @selector(contextClick)
    126             || aSelector == @selector(enableDOMUIEventLogging:)
    127             || aSelector == @selector(fireKeyboardEventsToElement:)
    128             || aSelector == @selector(keyDown:withModifiers:withLocation:)
    129             || aSelector == @selector(leapForward:)
    130             || aSelector == @selector(mouseDown:withModifiers:)
    131             || aSelector == @selector(mouseMoveToX:Y:)
    132             || aSelector == @selector(mouseUp:withModifiers:)
    133             || aSelector == @selector(scheduleAsynchronousClick)
    134             || aSelector == @selector(textZoomIn)
    135             || aSelector == @selector(textZoomOut)
    136             || aSelector == @selector(zoomPageIn)
    137             || aSelector == @selector(zoomPageOut)
    138             || aSelector == @selector(scalePageBy:atX:andY:)
    139             || aSelector == @selector(mouseScrollByX:andY:)
    140             || aSelector == @selector(continuousMouseScrollByX:andY:))
    141         return NO;
    142     return YES;
    143 }
    144 
    145 + (BOOL)isKeyExcludedFromWebScript:(const char*)name
    146 {
    147     if (strcmp(name, "dragMode") == 0)
    148         return NO;
    149     return YES;
    150 }
    151 
    152 + (NSString *)webScriptNameForSelector:(SEL)aSelector
    153 {
    154     if (aSelector == @selector(beginDragWithFiles:))
    155         return @"beginDragWithFiles";
    156     if (aSelector == @selector(contextClick))
    157         return @"contextClick";
    158     if (aSelector == @selector(enableDOMUIEventLogging:))
    159         return @"enableDOMUIEventLogging";
    160     if (aSelector == @selector(fireKeyboardEventsToElement:))
    161         return @"fireKeyboardEventsToElement";
    162     if (aSelector == @selector(keyDown:withModifiers:withLocation:))
    163         return @"keyDown";
    164     if (aSelector == @selector(leapForward:))
    165         return @"leapForward";
    166     if (aSelector == @selector(mouseDown:withModifiers:))
    167         return @"mouseDown";
    168     if (aSelector == @selector(mouseUp:withModifiers:))
    169         return @"mouseUp";
    170     if (aSelector == @selector(mouseMoveToX:Y:))
    171         return @"mouseMoveTo";
    172     if (aSelector == @selector(setDragMode:))
    173         return @"setDragMode";
    174     if (aSelector == @selector(mouseScrollByX:andY:))
    175         return @"mouseScrollBy";
    176     if (aSelector == @selector(continuousMouseScrollByX:andY:))
    177         return @"continuousMouseScrollBy";
    178     if (aSelector == @selector(scalePageBy:atX:andY:))
    179         return @"scalePageBy";
    180     return nil;
    181 }
    182 
    183 - (id)init
    184 {
    185     self = [super init];
    186     if (self)
    187         dragMode = YES;
    188     return self;
    189 }
    190 
    191 - (void)dealloc
    192 {
    193     [super dealloc];
    194 }
    195 
    196 - (double)currentEventTime
    197 {
    198     return GetCurrentEventTime() + timeOffset;
    199 }
    200 
    201 - (void)leapForward:(int)milliseconds
    202 {
    203     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
    204         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(leapForward:)]];
    205         [invocation setTarget:self];
    206         [invocation setSelector:@selector(leapForward:)];
    207         [invocation setArgument:&milliseconds atIndex:2];
    208 
    209         [EventSendingController saveEvent:invocation];
    210 
    211         return;
    212     }
    213 
    214     timeOffset += milliseconds / 1000.0;
    215 }
    216 
    217 - (void)clearKillRing
    218 {
    219     _NSNewKillRingSequence();
    220 }
    221 
    222 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
    223 {
    224     switch (button) {
    225         case LeftMouseButton:
    226             switch (action) {
    227                 case MouseDown:
    228                     return NSLeftMouseDown;
    229                 case MouseUp:
    230                     return NSLeftMouseUp;
    231                 case MouseDragged:
    232                     return NSLeftMouseDragged;
    233             }
    234         case RightMouseButton:
    235             switch (action) {
    236                 case MouseDown:
    237                     return NSRightMouseDown;
    238                 case MouseUp:
    239                     return NSRightMouseUp;
    240                 case MouseDragged:
    241                     return NSRightMouseDragged;
    242             }
    243         default:
    244             switch (action) {
    245                 case MouseDown:
    246                     return NSOtherMouseDown;
    247                 case MouseUp:
    248                     return NSOtherMouseUp;
    249                 case MouseDragged:
    250                     return NSOtherMouseDragged;
    251             }
    252     }
    253     assert(0);
    254     return static_cast<NSEventType>(0);
    255 }
    256 
    257 - (void)beginDragWithFiles:(WebScriptObject*)jsFilePaths
    258 {
    259     assert(!draggingInfo);
    260     assert([jsFilePaths isKindOfClass:[WebScriptObject class]]);
    261 
    262     NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
    263     [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
    264 
    265     NSURL *currentTestURL = [NSURL URLWithString:[[mainFrame webView] mainFrameURL]];
    266 
    267     NSMutableArray *filePaths = [NSMutableArray array];
    268     for (unsigned i = 0; [[jsFilePaths webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
    269         NSString *filePath = (NSString *)[jsFilePaths webScriptValueAtIndex:i];
    270         // Have NSURL encode the name so that we handle '?' in file names correctly.
    271         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
    272         NSURL *absoluteFileURL = [NSURL URLWithString:[fileURL relativeString]  relativeToURL:currentTestURL];
    273         [filePaths addObject:[absoluteFileURL path]];
    274     }
    275 
    276     [pboard setPropertyList:filePaths forType:NSFilenamesPboardType];
    277     assert([pboard propertyListForType:NSFilenamesPboardType]); // setPropertyList will silently fail on error, assert that it didn't fail
    278 
    279     // Provide a source, otherwise [DumpRenderTreeDraggingInfo draggingSourceOperationMask] defaults to NSDragOperationNone
    280     DumpRenderTreeFileDraggingSource *source = [[[DumpRenderTreeFileDraggingSource alloc] init] autorelease];
    281     draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:nil offset:NSZeroSize pasteboard:pboard source:source];
    282     [[mainFrame webView] draggingEntered:draggingInfo];
    283 
    284     dragMode = NO; // dragMode saves events and then replays them later.  We don't need/want that.
    285     leftMouseButtonDown = YES; // Make the rest of eventSender think a drag is in progress
    286 }
    287 
    288 - (void)updateClickCountForButton:(int)buttonNumber
    289 {
    290     if (([self currentEventTime] - lastClick >= 1) ||
    291         !NSEqualPoints(lastMousePosition, lastClickPosition) ||
    292         lastClickButton != buttonNumber) {
    293         clickCount = 1;
    294         lastClickButton = buttonNumber;
    295     } else
    296         clickCount++;
    297 }
    298 
    299 static int buildModifierFlags(const WebScriptObject* modifiers)
    300 {
    301     int flags = 0;
    302     if (![modifiers isKindOfClass:[WebScriptObject class]])
    303         return flags;
    304     for (unsigned i = 0; [[modifiers webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
    305         NSString* modifierName = (NSString*)[modifiers webScriptValueAtIndex:i];
    306         if ([modifierName isEqual:@"ctrlKey"])
    307             flags |= NSControlKeyMask;
    308         else if ([modifierName isEqual:@"shiftKey"] || [modifierName isEqual:@"rangeSelectionKey"])
    309             flags |= NSShiftKeyMask;
    310         else if ([modifierName isEqual:@"altKey"])
    311             flags |= NSAlternateKeyMask;
    312         else if ([modifierName isEqual:@"metaKey"] || [modifierName isEqual:@"addSelectionKey"])
    313             flags |= NSCommandKeyMask;
    314     }
    315     return flags;
    316 }
    317 
    318 - (void)mouseDown:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
    319 {
    320     [[[mainFrame frameView] documentView] layout];
    321     [self updateClickCountForButton:buttonNumber];
    322 
    323     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
    324     NSEvent *event = [NSEvent mouseEventWithType:eventType
    325                                         location:lastMousePosition
    326                                    modifierFlags:buildModifierFlags(modifiers)
    327                                        timestamp:[self currentEventTime]
    328                                     windowNumber:[[[mainFrame webView] window] windowNumber]
    329                                          context:[NSGraphicsContext currentContext]
    330                                      eventNumber:++eventNumber
    331                                       clickCount:clickCount
    332                                         pressure:0.0];
    333 
    334     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
    335     if (subView) {
    336         [subView mouseDown:event];
    337         if (buttonNumber == LeftMouseButton)
    338             leftMouseButtonDown = YES;
    339     }
    340 }
    341 
    342 - (void)mouseDown:(int)buttonNumber
    343 {
    344     [self mouseDown:buttonNumber withModifiers:nil];
    345 }
    346 
    347 - (void)textZoomIn
    348 {
    349     [[mainFrame webView] makeTextLarger:self];
    350 }
    351 
    352 - (void)textZoomOut
    353 {
    354     [[mainFrame webView] makeTextSmaller:self];
    355 }
    356 
    357 - (void)zoomPageIn
    358 {
    359     [[mainFrame webView] zoomPageIn:self];
    360 }
    361 
    362 - (void)zoomPageOut
    363 {
    364     [[mainFrame webView] zoomPageOut:self];
    365 }
    366 
    367 - (void)scalePageBy:(float)scale atX:(float)x andY:(float)y
    368 {
    369     [[mainFrame webView] _scaleWebView:scale atOrigin:NSMakePoint(x, y)];
    370 }
    371 
    372 - (void)mouseUp:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
    373 {
    374     if (dragMode && !replayingSavedEvents) {
    375         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseUp:withModifiers:)]];
    376         [invocation setTarget:self];
    377         [invocation setSelector:@selector(mouseUp:withModifiers:)];
    378         [invocation setArgument:&buttonNumber atIndex:2];
    379         [invocation setArgument:&modifiers atIndex:3];
    380 
    381         [EventSendingController saveEvent:invocation];
    382         [EventSendingController replaySavedEvents];
    383 
    384         return;
    385     }
    386 
    387     [[[mainFrame frameView] documentView] layout];
    388     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
    389     NSEvent *event = [NSEvent mouseEventWithType:eventType
    390                                         location:lastMousePosition
    391                                    modifierFlags:buildModifierFlags(modifiers)
    392                                        timestamp:[self currentEventTime]
    393                                     windowNumber:[[[mainFrame webView] window] windowNumber]
    394                                          context:[NSGraphicsContext currentContext]
    395                                      eventNumber:++eventNumber
    396                                       clickCount:clickCount
    397                                         pressure:0.0];
    398 
    399     NSView *targetView = [[mainFrame webView] hitTest:[event locationInWindow]];
    400     // FIXME: Silly hack to teach DRT to respect capturing mouse events outside the WebView.
    401     // The right solution is just to use NSApplication's built-in event sending methods,
    402     // instead of rolling our own algorithm for selecting an event target.
    403     targetView = targetView ? targetView : [[mainFrame frameView] documentView];
    404     assert(targetView);
    405     [targetView mouseUp:event];
    406     if (buttonNumber == LeftMouseButton)
    407         leftMouseButtonDown = NO;
    408     lastClick = [event timestamp];
    409     lastClickPosition = lastMousePosition;
    410     if (draggingInfo) {
    411         WebView *webView = [mainFrame webView];
    412 
    413         NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
    414 
    415         if (dragOperation != NSDragOperationNone)
    416             [webView performDragOperation:draggingInfo];
    417         else
    418             [webView draggingExited:draggingInfo];
    419         // Per NSDragging.h: draggingSources may not implement draggedImage:endedAt:operation:
    420         if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:endedAt:operation:)])
    421             [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
    422         [draggingInfo release];
    423         draggingInfo = nil;
    424     }
    425 }
    426 
    427 - (void)mouseUp:(int)buttonNumber
    428 {
    429     [self mouseUp:buttonNumber withModifiers:nil];
    430 }
    431 
    432 - (void)mouseMoveToX:(int)x Y:(int)y
    433 {
    434     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
    435         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseMoveToX:Y:)]];
    436         [invocation setTarget:self];
    437         [invocation setSelector:@selector(mouseMoveToX:Y:)];
    438         [invocation setArgument:&x atIndex:2];
    439         [invocation setArgument:&y atIndex:3];
    440 
    441         [EventSendingController saveEvent:invocation];
    442         return;
    443     }
    444 
    445     NSView *view = [mainFrame webView];
    446     lastMousePosition = [view convertPoint:NSMakePoint(x, [view frame].size.height - y) toView:nil];
    447     NSEvent *event = [NSEvent mouseEventWithType:(leftMouseButtonDown ? NSLeftMouseDragged : NSMouseMoved)
    448                                         location:lastMousePosition
    449                                    modifierFlags:0
    450                                        timestamp:[self currentEventTime]
    451                                     windowNumber:[[view window] windowNumber]
    452                                          context:[NSGraphicsContext currentContext]
    453                                      eventNumber:++eventNumber
    454                                       clickCount:(leftMouseButtonDown ? clickCount : 0)
    455                                         pressure:0.0];
    456 
    457     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
    458     if (subView) {
    459         if (leftMouseButtonDown) {
    460             if (draggingInfo) {
    461                 // Per NSDragging.h: draggingSources may not implement draggedImage:movedTo:
    462                 if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:movedTo:)])
    463                     [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
    464                 [[mainFrame webView] draggingUpdated:draggingInfo];
    465             } else
    466                 [subView mouseDragged:event];
    467         } else
    468             [subView mouseMoved:event];
    469     }
    470 }
    471 
    472 - (void)mouseScrollByX:(int)x andY:(int)y continuously:(BOOL)c
    473 {
    474     // CGEventCreateScrollWheelEvent() was introduced in 10.5
    475 #if !defined(BUILDING_ON_TIGER)
    476     CGScrollEventUnit unit = c?kCGScrollEventUnitPixel:kCGScrollEventUnitLine;
    477     CGEventRef cgScrollEvent = CGEventCreateScrollWheelEvent(NULL, unit, 2, y, x);
    478 
    479     // CGEvent locations are in global display coordinates.
    480     CGPoint lastGlobalMousePosition = {
    481         lastMousePosition.x,
    482         [[NSScreen mainScreen] frame].size.height - lastMousePosition.y
    483     };
    484     CGEventSetLocation(cgScrollEvent, lastGlobalMousePosition);
    485 
    486     NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgScrollEvent];
    487     CFRelease(cgScrollEvent);
    488 
    489     NSView *subView = [[mainFrame webView] hitTest:[scrollEvent locationInWindow]];
    490     if (subView)
    491         [subView scrollWheel:scrollEvent];
    492 #endif
    493 }
    494 
    495 - (void)continuousMouseScrollByX:(int)x andY:(int)y
    496 {
    497     [self mouseScrollByX:x andY:y continuously:YES];
    498 }
    499 
    500 - (void)mouseScrollByX:(int)x andY:(int)y
    501 {
    502     [self mouseScrollByX:x andY:y continuously:NO];
    503 }
    504 
    505 - (NSArray *)contextClick
    506 {
    507     [[[mainFrame frameView] documentView] layout];
    508     [self updateClickCountForButton:RightMouseButton];
    509 
    510     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
    511                                         location:lastMousePosition
    512                                    modifierFlags:0
    513                                        timestamp:[self currentEventTime]
    514                                     windowNumber:[[[mainFrame webView] window] windowNumber]
    515                                          context:[NSGraphicsContext currentContext]
    516                                      eventNumber:++eventNumber
    517                                       clickCount:clickCount
    518                                         pressure:0.0];
    519 
    520     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
    521     NSMutableArray *menuItemStrings = [NSMutableArray array];
    522 
    523     if (subView) {
    524         NSMenu* menu = [subView menuForEvent:event];
    525 
    526         for (int i = 0; i < [menu numberOfItems]; ++i) {
    527             NSMenuItem* menuItem = [menu itemAtIndex:i];
    528             if (!strcmp("Inspect Element", [[menuItem title] UTF8String]))
    529                 continue;
    530 
    531             if ([menuItem isSeparatorItem])
    532                 [menuItemStrings addObject:@"<separator>"];
    533             else
    534                 [menuItemStrings addObject:[menuItem title]];
    535         }
    536     }
    537 
    538     return menuItemStrings;
    539 }
    540 
    541 - (void)scheduleAsynchronousClick
    542 {
    543     [self performSelector:@selector(mouseDown:) withObject:nil afterDelay:0];
    544     [self performSelector:@selector(mouseUp:) withObject:nil afterDelay:0];
    545 }
    546 
    547 + (void)saveEvent:(NSInvocation *)event
    548 {
    549     if (!savedMouseEvents)
    550         savedMouseEvents = [[NSMutableArray alloc] init];
    551     [savedMouseEvents addObject:event];
    552 }
    553 
    554 + (void)replaySavedEvents
    555 {
    556     replayingSavedEvents = YES;
    557     while ([savedMouseEvents count]) {
    558         // if a drag is initiated, the remaining saved events will be dispatched from our dragging delegate
    559         NSInvocation *invocation = [[[savedMouseEvents objectAtIndex:0] retain] autorelease];
    560         [savedMouseEvents removeObjectAtIndex:0];
    561         [invocation invoke];
    562     }
    563     replayingSavedEvents = NO;
    564 }
    565 
    566 + (void)clearSavedEvents
    567 {
    568     [savedMouseEvents release];
    569     savedMouseEvents = nil;
    570 }
    571 
    572 - (void)keyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)keyLocation
    573 {
    574     NSString *eventCharacter = character;
    575     unsigned short keyCode = 0;
    576     if ([character isEqualToString:@"leftArrow"]) {
    577         const unichar ch = NSLeftArrowFunctionKey;
    578         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    579         keyCode = 0x7B;
    580     } else if ([character isEqualToString:@"rightArrow"]) {
    581         const unichar ch = NSRightArrowFunctionKey;
    582         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    583         keyCode = 0x7C;
    584     } else if ([character isEqualToString:@"upArrow"]) {
    585         const unichar ch = NSUpArrowFunctionKey;
    586         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    587         keyCode = 0x7E;
    588     } else if ([character isEqualToString:@"downArrow"]) {
    589         const unichar ch = NSDownArrowFunctionKey;
    590         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    591         keyCode = 0x7D;
    592     } else if ([character isEqualToString:@"pageUp"]) {
    593         const unichar ch = NSPageUpFunctionKey;
    594         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    595         keyCode = 0x74;
    596     } else if ([character isEqualToString:@"pageDown"]) {
    597         const unichar ch = NSPageDownFunctionKey;
    598         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    599         keyCode = 0x79;
    600     } else if ([character isEqualToString:@"home"]) {
    601         const unichar ch = NSHomeFunctionKey;
    602         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    603         keyCode = 0x73;
    604     } else if ([character isEqualToString:@"end"]) {
    605         const unichar ch = NSEndFunctionKey;
    606         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    607         keyCode = 0x77;
    608     } else if ([character isEqualToString:@"insert"]) {
    609         const unichar ch = NSInsertFunctionKey;
    610         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    611         keyCode = 0x72;
    612     } else if ([character isEqualToString:@"delete"]) {
    613         const unichar ch = NSDeleteFunctionKey;
    614         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    615         keyCode = 0x75;
    616     } else if ([character isEqualToString:@"printScreen"]) {
    617         const unichar ch = NSPrintScreenFunctionKey;
    618         eventCharacter = [NSString stringWithCharacters:&ch length:1];
    619         keyCode = 0x0; // There is no known virtual key code for PrintScreen.
    620     }
    621 
    622     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
    623     // If the input string is a function-key name, set its key code.
    624     for (unsigned i = 1; i <= 24; i++) {
    625         if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
    626             const unichar ch = NSF1FunctionKey + (i - 1);
    627             eventCharacter = [NSString stringWithCharacters:&ch length:1];
    628             switch (i) {
    629                 case 1: keyCode = 0x7A; break;
    630                 case 2: keyCode = 0x78; break;
    631                 case 3: keyCode = 0x63; break;
    632                 case 4: keyCode = 0x76; break;
    633                 case 5: keyCode = 0x60; break;
    634                 case 6: keyCode = 0x61; break;
    635                 case 7: keyCode = 0x62; break;
    636                 case 8: keyCode = 0x64; break;
    637                 case 9: keyCode = 0x65; break;
    638                 case 10: keyCode = 0x6D; break;
    639                 case 11: keyCode = 0x67; break;
    640                 case 12: keyCode = 0x6F; break;
    641                 case 13: keyCode = 0x69; break;
    642                 case 14: keyCode = 0x6B; break;
    643                 case 15: keyCode = 0x71; break;
    644                 case 16: keyCode = 0x6A; break;
    645                 case 17: keyCode = 0x40; break;
    646                 case 18: keyCode = 0x4F; break;
    647                 case 19: keyCode = 0x50; break;
    648                 case 20: keyCode = 0x5A; break;
    649             }
    650         }
    651     }
    652 
    653     // FIXME: No keyCode is set for most keys.
    654     if ([character isEqualToString:@"\t"])
    655         keyCode = 0x30;
    656     else if ([character isEqualToString:@" "])
    657         keyCode = 0x31;
    658     else if ([character isEqualToString:@"\r"])
    659         keyCode = 0x24;
    660     else if ([character isEqualToString:@"\n"])
    661         keyCode = 0x4C;
    662     else if ([character isEqualToString:@"\x8"])
    663         keyCode = 0x33;
    664     else if ([character isEqualToString:@"7"])
    665         keyCode = 0x1A;
    666     else if ([character isEqualToString:@"5"])
    667         keyCode = 0x17;
    668     else if ([character isEqualToString:@"9"])
    669         keyCode = 0x19;
    670     else if ([character isEqualToString:@"0"])
    671         keyCode = 0x1D;
    672     else if ([character isEqualToString:@"a"])
    673         keyCode = 0x00;
    674     else if ([character isEqualToString:@"b"])
    675         keyCode = 0x0B;
    676     else if ([character isEqualToString:@"d"])
    677         keyCode = 0x02;
    678     else if ([character isEqualToString:@"e"])
    679         keyCode = 0x0E;
    680 
    681     NSString *charactersIgnoringModifiers = eventCharacter;
    682 
    683     int modifierFlags = 0;
    684 
    685     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
    686         modifierFlags |= NSShiftKeyMask;
    687         charactersIgnoringModifiers = [character lowercaseString];
    688     }
    689 
    690     modifierFlags |= buildModifierFlags(modifiers);
    691 
    692     if (keyLocation == DOM_KEY_LOCATION_NUMPAD)
    693         modifierFlags |= NSNumericPadKeyMask;
    694 
    695     [[[mainFrame frameView] documentView] layout];
    696 
    697     NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
    698                         location:NSMakePoint(5, 5)
    699                         modifierFlags:modifierFlags
    700                         timestamp:[self currentEventTime]
    701                         windowNumber:[[[mainFrame webView] window] windowNumber]
    702                         context:[NSGraphicsContext currentContext]
    703                         characters:eventCharacter
    704                         charactersIgnoringModifiers:charactersIgnoringModifiers
    705                         isARepeat:NO
    706                         keyCode:keyCode];
    707 
    708     [[[[mainFrame webView] window] firstResponder] keyDown:event];
    709 
    710     event = [NSEvent keyEventWithType:NSKeyUp
    711                         location:NSMakePoint(5, 5)
    712                         modifierFlags:modifierFlags
    713                         timestamp:[self currentEventTime]
    714                         windowNumber:[[[mainFrame webView] window] windowNumber]
    715                         context:[NSGraphicsContext currentContext]
    716                         characters:eventCharacter
    717                         charactersIgnoringModifiers:charactersIgnoringModifiers
    718                         isARepeat:NO
    719                         keyCode:keyCode];
    720 
    721     [[[[mainFrame webView] window] firstResponder] keyUp:event];
    722 }
    723 
    724 - (void)enableDOMUIEventLogging:(WebScriptObject *)node
    725 {
    726     NSEnumerator *eventEnumerator = [webkitDomEventNames objectEnumerator];
    727     id eventName;
    728     while ((eventName = [eventEnumerator nextObject])) {
    729         [(id<DOMEventTarget>)node addEventListener:eventName listener:self useCapture:NO];
    730     }
    731 }
    732 
    733 - (void)handleEvent:(DOMEvent *)event
    734 {
    735     DOMNode *target = [event target];
    736 
    737     printf("event type:      %s\n", [[event type] UTF8String]);
    738     printf("  target:        <%s>\n", [[[target nodeName] lowercaseString] UTF8String]);
    739 
    740     if ([event isKindOfClass:[DOMEvent class]]) {
    741         printf("  eventPhase:    %d\n", [event eventPhase]);
    742         printf("  bubbles:       %d\n", [event bubbles] ? 1 : 0);
    743         printf("  cancelable:    %d\n", [event cancelable] ? 1 : 0);
    744     }
    745 
    746     if ([event isKindOfClass:[DOMUIEvent class]]) {
    747         printf("  detail:        %d\n", [(DOMUIEvent*)event detail]);
    748 
    749         DOMAbstractView *view = [(DOMUIEvent*)event view];
    750         if (view) {
    751             printf("  view:          OK");
    752             if ([view document])
    753                 printf(" (document: OK)");
    754             printf("\n");
    755         }
    756     }
    757 
    758     if ([event isKindOfClass:[DOMKeyboardEvent class]]) {
    759         printf("  keyIdentifier: %s\n", [[(DOMKeyboardEvent*)event keyIdentifier] UTF8String]);
    760         printf("  keyLocation:   %d\n", [(DOMKeyboardEvent*)event keyLocation]);
    761         printf("  modifier keys: c:%d s:%d a:%d m:%d\n",
    762                [(DOMKeyboardEvent*)event ctrlKey] ? 1 : 0,
    763                [(DOMKeyboardEvent*)event shiftKey] ? 1 : 0,
    764                [(DOMKeyboardEvent*)event altKey] ? 1 : 0,
    765                [(DOMKeyboardEvent*)event metaKey] ? 1 : 0);
    766         printf("  keyCode:       %d\n", [(DOMKeyboardEvent*)event keyCode]);
    767         printf("  charCode:      %d\n", [(DOMKeyboardEvent*)event charCode]);
    768     }
    769 
    770     if ([event isKindOfClass:[DOMMouseEvent class]]) {
    771         printf("  button:        %d\n", [(DOMMouseEvent*)event button]);
    772         printf("  clientX:       %d\n", [(DOMMouseEvent*)event clientX]);
    773         printf("  clientY:       %d\n", [(DOMMouseEvent*)event clientY]);
    774         printf("  screenX:       %d\n", [(DOMMouseEvent*)event screenX]);
    775         printf("  screenY:       %d\n", [(DOMMouseEvent*)event screenY]);
    776         printf("  modifier keys: c:%d s:%d a:%d m:%d\n",
    777                [(DOMMouseEvent*)event ctrlKey] ? 1 : 0,
    778                [(DOMMouseEvent*)event shiftKey] ? 1 : 0,
    779                [(DOMMouseEvent*)event altKey] ? 1 : 0,
    780                [(DOMMouseEvent*)event metaKey] ? 1 : 0);
    781         id relatedTarget = [(DOMMouseEvent*)event relatedTarget];
    782         if (relatedTarget) {
    783             printf("  relatedTarget: %s", [[[relatedTarget class] description] UTF8String]);
    784             if ([relatedTarget isKindOfClass:[DOMNode class]])
    785                 printf(" (nodeName: %s)", [[(DOMNode*)relatedTarget nodeName] UTF8String]);
    786             printf("\n");
    787         }
    788     }
    789 
    790     if ([event isKindOfClass:[DOMMutationEvent class]]) {
    791         printf("  prevValue:     %s\n", [[(DOMMutationEvent*)event prevValue] UTF8String]);
    792         printf("  newValue:      %s\n", [[(DOMMutationEvent*)event newValue] UTF8String]);
    793         printf("  attrName:      %s\n", [[(DOMMutationEvent*)event attrName] UTF8String]);
    794         printf("  attrChange:    %d\n", [(DOMMutationEvent*)event attrChange]);
    795         DOMNode *relatedNode = [(DOMMutationEvent*)event relatedNode];
    796         if (relatedNode) {
    797             printf("  relatedNode:   %s (nodeName: %s)\n",
    798                    [[[relatedNode class] description] UTF8String],
    799                    [[relatedNode nodeName] UTF8String]);
    800         }
    801     }
    802 
    803     if ([event isKindOfClass:[DOMWheelEvent class]]) {
    804         printf("  clientX:       %d\n", [(DOMWheelEvent*)event clientX]);
    805         printf("  clientY:       %d\n", [(DOMWheelEvent*)event clientY]);
    806         printf("  screenX:       %d\n", [(DOMWheelEvent*)event screenX]);
    807         printf("  screenY:       %d\n", [(DOMWheelEvent*)event screenY]);
    808         printf("  modifier keys: c:%d s:%d a:%d m:%d\n",
    809                [(DOMWheelEvent*)event ctrlKey] ? 1 : 0,
    810                [(DOMWheelEvent*)event shiftKey] ? 1 : 0,
    811                [(DOMWheelEvent*)event altKey] ? 1 : 0,
    812                [(DOMWheelEvent*)event metaKey] ? 1 : 0);
    813         printf("  isHorizontal:  %d\n", [(DOMWheelEvent*)event isHorizontal] ? 1 : 0);
    814         printf("  wheelDelta:    %d\n", [(DOMWheelEvent*)event wheelDelta]);
    815     }
    816 }
    817 
    818 // FIXME: It's not good to have a test hard-wired into this controller like this.
    819 // Instead we need to get testing framework based on the Objective-C bindings
    820 // to work well enough that we can test that way instead.
    821 - (void)fireKeyboardEventsToElement:(WebScriptObject *)element {
    822 
    823     if (![element isKindOfClass:[DOMHTMLElement class]])
    824         return;
    825 
    826     DOMHTMLElement *target = (DOMHTMLElement*)element;
    827     DOMDocument *document = [target ownerDocument];
    828 
    829     // Keyboard Event 1
    830 
    831     DOMEvent *domEvent = [document createEvent:@"KeyboardEvent"];
    832     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keydown"
    833                                          canBubble:YES
    834                                         cancelable:YES
    835                                               view:[document defaultView]
    836                                      keyIdentifier:@"U+000041"
    837                                        keyLocation:0
    838                                            ctrlKey:YES
    839                                             altKey:NO
    840                                           shiftKey:NO
    841                                            metaKey:NO];
    842     [target dispatchEvent:domEvent];
    843 
    844     // Keyboard Event 2
    845 
    846     domEvent = [document createEvent:@"KeyboardEvent"];
    847     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keypress"
    848                                          canBubble:YES
    849                                         cancelable:YES
    850                                               view:[document defaultView]
    851                                      keyIdentifier:@"U+000045"
    852                                        keyLocation:1
    853                                            ctrlKey:NO
    854                                             altKey:YES
    855                                           shiftKey:NO
    856                                            metaKey:NO];
    857     [target dispatchEvent:domEvent];
    858 
    859     // Keyboard Event 3
    860 
    861     domEvent = [document createEvent:@"KeyboardEvent"];
    862     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keyup"
    863                                          canBubble:YES
    864                                         cancelable:YES
    865                                               view:[document defaultView]
    866                                      keyIdentifier:@"U+000056"
    867                                        keyLocation:0
    868                                            ctrlKey:NO
    869                                             altKey:NO
    870                                           shiftKey:NO
    871                                            metaKey:NO];
    872     [target dispatchEvent:domEvent];
    873 
    874 }
    875 
    876 @end
    877