1/*
2 * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2006 Jonas Witt <jonas.witt@gmail.com>
4 * Copyright (C) 2006 Samuel Weinig <sam.weinig@gmail.com>
5 * Copyright (C) 2006 Alexey Proskuryakov <ap@nypop.com>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * 1.  Redistributions of source code must retain the above copyright
12 *     notice, this list of conditions and the following disclaimer.
13 * 2.  Redistributions in binary form must reproduce the above copyright
14 *     notice, this list of conditions and the following disclaimer in the
15 *     documentation and/or other materials provided with the distribution.
16 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17 *     its contributors may be used to endorse or promote products derived
18 *     from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32#import "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
44extern "C" void _NSNewKillRingSequence();
45
46enum MouseAction {
47    MouseDown,
48    MouseUp,
49    MouseDragged
50};
51
52// Match the DOM spec (sadly the DOM spec does not provide an enum)
53enum MouseButton {
54    LeftMouseButton = 0,
55    MiddleMouseButton = 1,
56    RightMouseButton = 2,
57    NoMouseButton = -1
58};
59
60NSPoint lastMousePosition;
61NSPoint lastClickPosition;
62int lastClickButton = NoMouseButton;
63NSArray *webkitDomEventNames;
64NSMutableArray *savedMouseEvents; // mouse events sent between mouseDown and mouseUp are stored here, and then executed at once.
65BOOL 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
222static 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
299static 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