Clean up ChunkedUpdateDrawingAreaProxy
[WebKit-https.git] / WebKitTools / DumpRenderTree / mac / EventSendingController.mm
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
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(mouseScrollByX:andY:)
139             || aSelector == @selector(continuousMouseScrollByX:andY:))
140         return NO;
141     return YES;
142 }
143
144 + (BOOL)isKeyExcludedFromWebScript:(const char*)name
145 {
146     if (strcmp(name, "dragMode") == 0)
147         return NO;
148     return YES;
149 }
150
151 + (NSString *)webScriptNameForSelector:(SEL)aSelector
152 {
153     if (aSelector == @selector(beginDragWithFiles:))
154         return @"beginDragWithFiles";
155     if (aSelector == @selector(contextClick))
156         return @"contextClick";
157     if (aSelector == @selector(enableDOMUIEventLogging:))
158         return @"enableDOMUIEventLogging";
159     if (aSelector == @selector(fireKeyboardEventsToElement:))
160         return @"fireKeyboardEventsToElement";
161     if (aSelector == @selector(keyDown:withModifiers:withLocation:))
162         return @"keyDown";
163     if (aSelector == @selector(leapForward:))
164         return @"leapForward";
165     if (aSelector == @selector(mouseDown:withModifiers:))
166         return @"mouseDown";
167     if (aSelector == @selector(mouseUp:withModifiers:))
168         return @"mouseUp";
169     if (aSelector == @selector(mouseMoveToX:Y:))
170         return @"mouseMoveTo";
171     if (aSelector == @selector(setDragMode:))
172         return @"setDragMode";
173     if (aSelector == @selector(mouseScrollByX:andY:))
174         return @"mouseScrollBy";
175     if (aSelector == @selector(continuousMouseScrollByX:andY:))
176         return @"continuousMouseScrollBy";
177     return nil;
178 }
179
180 - (id)init
181 {
182     self = [super init];
183     if (self)
184         dragMode = YES;
185     return self;
186 }
187
188 - (void)dealloc
189 {
190     [super dealloc];
191 }
192
193 - (double)currentEventTime
194 {
195     return GetCurrentEventTime() + timeOffset;
196 }
197
198 - (void)leapForward:(int)milliseconds
199 {
200     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
201         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(leapForward:)]];
202         [invocation setTarget:self];
203         [invocation setSelector:@selector(leapForward:)];
204         [invocation setArgument:&milliseconds atIndex:2];
205         
206         [EventSendingController saveEvent:invocation];
207         
208         return;
209     }
210
211     timeOffset += milliseconds / 1000.0;
212 }
213
214 - (void)clearKillRing
215 {
216     _NSNewKillRingSequence();
217 }
218
219 static NSEventType eventTypeForMouseButtonAndAction(int button, MouseAction action)
220 {
221     switch (button) {
222         case LeftMouseButton:
223             switch (action) {
224                 case MouseDown:
225                     return NSLeftMouseDown;
226                 case MouseUp:
227                     return NSLeftMouseUp;
228                 case MouseDragged:
229                     return NSLeftMouseDragged;
230             }
231         case RightMouseButton:
232             switch (action) {
233                 case MouseDown:
234                     return NSRightMouseDown;
235                 case MouseUp:
236                     return NSRightMouseUp;
237                 case MouseDragged:
238                     return NSRightMouseDragged;
239             }
240         default:
241             switch (action) {
242                 case MouseDown:
243                     return NSOtherMouseDown;
244                 case MouseUp:
245                     return NSOtherMouseUp;
246                 case MouseDragged:
247                     return NSOtherMouseDragged;
248             }
249     }
250     assert(0);
251     return static_cast<NSEventType>(0);
252 }
253
254 - (void)beginDragWithFiles:(WebScriptObject*)jsFilePaths
255 {
256     assert(!draggingInfo);
257     assert([jsFilePaths isKindOfClass:[WebScriptObject class]]);
258
259     NSPasteboard *pboard = [NSPasteboard pasteboardWithUniqueName];
260     [pboard declareTypes:[NSArray arrayWithObject:NSFilenamesPboardType] owner:nil];
261
262     NSURL *currentTestURL = [NSURL URLWithString:[[mainFrame webView] mainFrameURL]];
263
264     NSMutableArray *filePaths = [NSMutableArray array];
265     for (unsigned i = 0; [[jsFilePaths webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
266         NSString *filePath = (NSString *)[jsFilePaths webScriptValueAtIndex:i];
267         // Have NSURL encode the name so that we handle '?' in file names correctly.
268         NSURL *fileURL = [NSURL fileURLWithPath:filePath];
269         NSURL *absoluteFileURL = [NSURL URLWithString:[fileURL relativeString]  relativeToURL:currentTestURL];
270         [filePaths addObject:[absoluteFileURL path]];
271     }
272
273     [pboard setPropertyList:filePaths forType:NSFilenamesPboardType];
274     assert([pboard propertyListForType:NSFilenamesPboardType]); // setPropertyList will silently fail on error, assert that it didn't fail
275
276     // Provide a source, otherwise [DumpRenderTreeDraggingInfo draggingSourceOperationMask] defaults to NSDragOperationNone
277     DumpRenderTreeFileDraggingSource *source = [[[DumpRenderTreeFileDraggingSource alloc] init] autorelease];
278     draggingInfo = [[DumpRenderTreeDraggingInfo alloc] initWithImage:nil offset:NSZeroSize pasteboard:pboard source:source];
279     [[mainFrame webView] draggingEntered:draggingInfo];
280
281     dragMode = NO; // dragMode saves events and then replays them later.  We don't need/want that.
282     leftMouseButtonDown = YES; // Make the rest of eventSender think a drag is in progress
283 }
284
285 - (void)updateClickCountForButton:(int)buttonNumber
286 {
287     if (([self currentEventTime] - lastClick >= 1) ||
288         !NSEqualPoints(lastMousePosition, lastClickPosition) ||
289         lastClickButton != buttonNumber) {
290         clickCount = 1;
291         lastClickButton = buttonNumber;
292     } else
293         clickCount++;
294 }
295
296 static int buildModifierFlags(const WebScriptObject* modifiers)
297 {
298     int flags = 0;
299     if (![modifiers isKindOfClass:[WebScriptObject class]])
300         return flags;
301     for (unsigned i = 0; [[modifiers webScriptValueAtIndex:i] isKindOfClass:[NSString class]]; i++) {
302         NSString* modifierName = (NSString*)[modifiers webScriptValueAtIndex:i];
303         if ([modifierName isEqual:@"ctrlKey"])
304             flags |= NSControlKeyMask;
305         else if ([modifierName isEqual:@"shiftKey"] || [modifierName isEqual:@"rangeSelectionKey"])
306             flags |= NSShiftKeyMask;
307         else if ([modifierName isEqual:@"altKey"])
308             flags |= NSAlternateKeyMask;
309         else if ([modifierName isEqual:@"metaKey"] || [modifierName isEqual:@"addSelectionKey"])
310             flags |= NSCommandKeyMask;
311     }
312     return flags;
313 }
314
315 - (void)mouseDown:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
316 {
317     [[[mainFrame frameView] documentView] layout];
318     [self updateClickCountForButton:buttonNumber];
319     
320     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseDown);
321     NSEvent *event = [NSEvent mouseEventWithType:eventType
322                                         location:lastMousePosition 
323                                    modifierFlags:buildModifierFlags(modifiers)
324                                        timestamp:[self currentEventTime]
325                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
326                                          context:[NSGraphicsContext currentContext] 
327                                      eventNumber:++eventNumber 
328                                       clickCount:clickCount 
329                                         pressure:0.0];
330
331     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
332     if (subView) {
333         [subView mouseDown:event];
334         if (buttonNumber == LeftMouseButton)
335             leftMouseButtonDown = YES;
336     }
337 }
338
339 - (void)mouseDown:(int)buttonNumber
340 {
341     [self mouseDown:buttonNumber withModifiers:nil];
342 }
343
344 - (void)textZoomIn
345 {
346     [[mainFrame webView] makeTextLarger:self];
347 }
348
349 - (void)textZoomOut
350 {
351     [[mainFrame webView] makeTextSmaller:self];
352 }
353
354 - (void)zoomPageIn
355 {
356     [[mainFrame webView] zoomPageIn:self];
357 }
358
359 - (void)zoomPageOut
360 {
361     [[mainFrame webView] zoomPageOut:self];
362 }
363
364 - (void)mouseUp:(int)buttonNumber withModifiers:(WebScriptObject*)modifiers
365 {
366     if (dragMode && !replayingSavedEvents) {
367         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseUp:withModifiers:)]];
368         [invocation setTarget:self];
369         [invocation setSelector:@selector(mouseUp:withModifiers:)];
370         [invocation setArgument:&buttonNumber atIndex:2];
371         [invocation setArgument:&modifiers atIndex:3];
372         
373         [EventSendingController saveEvent:invocation];
374         [EventSendingController replaySavedEvents];
375
376         return;
377     }
378
379     [[[mainFrame frameView] documentView] layout];
380     NSEventType eventType = eventTypeForMouseButtonAndAction(buttonNumber, MouseUp);
381     NSEvent *event = [NSEvent mouseEventWithType:eventType
382                                         location:lastMousePosition 
383                                    modifierFlags:buildModifierFlags(modifiers)
384                                        timestamp:[self currentEventTime]
385                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
386                                          context:[NSGraphicsContext currentContext] 
387                                      eventNumber:++eventNumber 
388                                       clickCount:clickCount 
389                                         pressure:0.0];
390
391     NSView *targetView = [[mainFrame webView] hitTest:[event locationInWindow]];
392     // FIXME: Silly hack to teach DRT to respect capturing mouse events outside the WebView.
393     // The right solution is just to use NSApplication's built-in event sending methods, 
394     // instead of rolling our own algorithm for selecting an event target.
395     targetView = targetView ? targetView : [[mainFrame frameView] documentView];
396     assert(targetView);
397     [targetView mouseUp:event];
398     if (buttonNumber == LeftMouseButton)
399         leftMouseButtonDown = NO;
400     lastClick = [event timestamp];
401     lastClickPosition = lastMousePosition;
402     if (draggingInfo) {
403         WebView *webView = [mainFrame webView];
404         
405         NSDragOperation dragOperation = [webView draggingUpdated:draggingInfo];
406         
407         if (dragOperation != NSDragOperationNone)
408             [webView performDragOperation:draggingInfo];
409         else
410             [webView draggingExited:draggingInfo];
411         // Per NSDragging.h: draggingSources may not implement draggedImage:endedAt:operation:
412         if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:endedAt:operation:)])
413             [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] endedAt:lastMousePosition operation:dragOperation];
414         [draggingInfo release];
415         draggingInfo = nil;
416     }
417 }
418
419 - (void)mouseUp:(int)buttonNumber
420 {
421     [self mouseUp:buttonNumber withModifiers:nil];
422 }
423
424 - (void)mouseMoveToX:(int)x Y:(int)y
425 {
426     if (dragMode && leftMouseButtonDown && !replayingSavedEvents) {
427         NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[EventSendingController instanceMethodSignatureForSelector:@selector(mouseMoveToX:Y:)]];
428         [invocation setTarget:self];
429         [invocation setSelector:@selector(mouseMoveToX:Y:)];
430         [invocation setArgument:&x atIndex:2];
431         [invocation setArgument:&y atIndex:3];
432         
433         [EventSendingController saveEvent:invocation];
434         return;
435     }
436
437     NSView *view = [mainFrame webView];
438     lastMousePosition = [view convertPoint:NSMakePoint(x, [view frame].size.height - y) toView:nil];
439     NSEvent *event = [NSEvent mouseEventWithType:(leftMouseButtonDown ? NSLeftMouseDragged : NSMouseMoved)
440                                         location:lastMousePosition 
441                                    modifierFlags:0 
442                                        timestamp:[self currentEventTime]
443                                     windowNumber:[[view window] windowNumber] 
444                                          context:[NSGraphicsContext currentContext] 
445                                      eventNumber:++eventNumber 
446                                       clickCount:(leftMouseButtonDown ? clickCount : 0) 
447                                         pressure:0.0];
448
449     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
450     if (subView) {
451         if (leftMouseButtonDown) {
452             if (draggingInfo) {
453                 // Per NSDragging.h: draggingSources may not implement draggedImage:movedTo:
454                 if ([[draggingInfo draggingSource] respondsToSelector:@selector(draggedImage:movedTo:)])
455                     [[draggingInfo draggingSource] draggedImage:[draggingInfo draggedImage] movedTo:lastMousePosition];
456                 [[mainFrame webView] draggingUpdated:draggingInfo];
457             } else
458                 [subView mouseDragged:event];
459         } else
460             [subView mouseMoved:event];
461     }
462 }
463
464 - (void)mouseScrollByX:(int)x andY:(int)y continuously:(BOOL)c
465 {
466     // CGEventCreateScrollWheelEvent() was introduced in 10.5
467 #if !defined(BUILDING_ON_TIGER)
468     CGScrollEventUnit unit = c?kCGScrollEventUnitPixel:kCGScrollEventUnitLine;
469     CGEventRef cgScrollEvent = CGEventCreateScrollWheelEvent(NULL, unit, 2, y, x);
470     
471     // CGEvent locations are in global display coordinates.
472     CGPoint lastGlobalMousePosition = {
473         lastMousePosition.x,
474         [[NSScreen mainScreen] frame].size.height - lastMousePosition.y
475     };
476     CGEventSetLocation(cgScrollEvent, lastGlobalMousePosition);
477
478     NSEvent *scrollEvent = [NSEvent eventWithCGEvent:cgScrollEvent];
479     CFRelease(cgScrollEvent);
480
481     NSView *subView = [[mainFrame webView] hitTest:[scrollEvent locationInWindow]];
482     if (subView)
483         [subView scrollWheel:scrollEvent];
484 #endif
485 }
486
487 - (void)continuousMouseScrollByX:(int)x andY:(int)y
488 {
489     [self mouseScrollByX:x andY:y continuously:YES];
490 }
491
492 - (void)mouseScrollByX:(int)x andY:(int)y
493 {
494     [self mouseScrollByX:x andY:y continuously:NO];
495 }
496
497 - (NSArray *)contextClick
498 {
499     [[[mainFrame frameView] documentView] layout];
500     [self updateClickCountForButton:RightMouseButton];
501
502     NSEvent *event = [NSEvent mouseEventWithType:NSRightMouseDown
503                                         location:lastMousePosition 
504                                    modifierFlags:0 
505                                        timestamp:[self currentEventTime]
506                                     windowNumber:[[[mainFrame webView] window] windowNumber] 
507                                          context:[NSGraphicsContext currentContext] 
508                                      eventNumber:++eventNumber 
509                                       clickCount:clickCount 
510                                         pressure:0.0];
511
512     NSView *subView = [[mainFrame webView] hitTest:[event locationInWindow]];
513     NSMutableArray *menuItemStrings = [NSMutableArray array];
514     
515     if (subView) {
516         NSMenu* menu = [subView menuForEvent:event];
517
518         for (int i = 0; i < [menu numberOfItems]; ++i) {
519             NSMenuItem* menuItem = [menu itemAtIndex:i];
520             if (!strcmp("Inspect Element", [[menuItem title] UTF8String]))
521                 continue;
522
523             if ([menuItem isSeparatorItem])
524                 [menuItemStrings addObject:@"<separator>"];
525             else
526                 [menuItemStrings addObject:[menuItem title]];
527         }
528     }
529     
530     return menuItemStrings;
531 }
532
533 - (void)scheduleAsynchronousClick
534 {
535     [self performSelector:@selector(mouseDown:) withObject:nil afterDelay:0];
536     [self performSelector:@selector(mouseUp:) withObject:nil afterDelay:0];
537 }
538
539 + (void)saveEvent:(NSInvocation *)event
540 {
541     if (!savedMouseEvents)
542         savedMouseEvents = [[NSMutableArray alloc] init];
543     [savedMouseEvents addObject:event];
544 }
545
546 + (void)replaySavedEvents
547 {
548     replayingSavedEvents = YES;
549     while ([savedMouseEvents count]) {
550         // if a drag is initiated, the remaining saved events will be dispatched from our dragging delegate
551         NSInvocation *invocation = [[[savedMouseEvents objectAtIndex:0] retain] autorelease];
552         [savedMouseEvents removeObjectAtIndex:0];
553         [invocation invoke];
554     }
555     replayingSavedEvents = NO;
556 }
557
558 + (void)clearSavedEvents
559 {
560     [savedMouseEvents release];
561     savedMouseEvents = nil;
562 }
563
564 - (void)keyDown:(NSString *)character withModifiers:(WebScriptObject *)modifiers withLocation:(unsigned long)keyLocation
565 {
566     NSString *eventCharacter = character;
567     unsigned short keyCode = 0;
568     if ([character isEqualToString:@"leftArrow"]) {
569         const unichar ch = NSLeftArrowFunctionKey;
570         eventCharacter = [NSString stringWithCharacters:&ch length:1];
571         keyCode = 0x7B;
572     } else if ([character isEqualToString:@"rightArrow"]) {
573         const unichar ch = NSRightArrowFunctionKey;
574         eventCharacter = [NSString stringWithCharacters:&ch length:1];
575         keyCode = 0x7C;
576     } else if ([character isEqualToString:@"upArrow"]) {
577         const unichar ch = NSUpArrowFunctionKey;
578         eventCharacter = [NSString stringWithCharacters:&ch length:1];
579         keyCode = 0x7E;
580     } else if ([character isEqualToString:@"downArrow"]) {
581         const unichar ch = NSDownArrowFunctionKey;
582         eventCharacter = [NSString stringWithCharacters:&ch length:1];
583         keyCode = 0x7D;
584     } else if ([character isEqualToString:@"pageUp"]) {
585         const unichar ch = NSPageUpFunctionKey;
586         eventCharacter = [NSString stringWithCharacters:&ch length:1];
587         keyCode = 0x74;
588     } else if ([character isEqualToString:@"pageDown"]) {
589         const unichar ch = NSPageDownFunctionKey;
590         eventCharacter = [NSString stringWithCharacters:&ch length:1];
591         keyCode = 0x79;
592     } else if ([character isEqualToString:@"home"]) {
593         const unichar ch = NSHomeFunctionKey;
594         eventCharacter = [NSString stringWithCharacters:&ch length:1];
595         keyCode = 0x73;
596     } else if ([character isEqualToString:@"end"]) {
597         const unichar ch = NSEndFunctionKey;
598         eventCharacter = [NSString stringWithCharacters:&ch length:1];
599         keyCode = 0x77;
600     } else if ([character isEqualToString:@"insert"]) {
601         const unichar ch = NSInsertFunctionKey;
602         eventCharacter = [NSString stringWithCharacters:&ch length:1];
603         keyCode = 0x72;
604     } else if ([character isEqualToString:@"delete"]) {
605         const unichar ch = NSDeleteFunctionKey;
606         eventCharacter = [NSString stringWithCharacters:&ch length:1];
607         keyCode = 0x75;
608     } else if ([character isEqualToString:@"printScreen"]) {
609         const unichar ch = NSPrintScreenFunctionKey;
610         eventCharacter = [NSString stringWithCharacters:&ch length:1];
611         keyCode = 0x0; // There is no known virtual key code for PrintScreen.
612     }
613
614     // Compare the input string with the function-key names defined by the DOM spec (i.e. "F1",...,"F24").
615     // If the input string is a function-key name, set its key code.
616     for (unsigned i = 1; i <= 24; i++) {
617         if ([character isEqualToString:[NSString stringWithFormat:@"F%u", i]]) {
618             const unichar ch = NSF1FunctionKey + (i - 1);
619             eventCharacter = [NSString stringWithCharacters:&ch length:1];
620             switch (i) {
621                 case 1: keyCode = 0x7A; break;
622                 case 2: keyCode = 0x78; break;
623                 case 3: keyCode = 0x63; break;
624                 case 4: keyCode = 0x76; break;
625                 case 5: keyCode = 0x60; break;
626                 case 6: keyCode = 0x61; break;
627                 case 7: keyCode = 0x62; break;
628                 case 8: keyCode = 0x64; break;
629                 case 9: keyCode = 0x65; break;
630                 case 10: keyCode = 0x6D; break;
631                 case 11: keyCode = 0x67; break;
632                 case 12: keyCode = 0x6F; break;
633                 case 13: keyCode = 0x69; break;
634                 case 14: keyCode = 0x6B; break;
635                 case 15: keyCode = 0x71; break;
636                 case 16: keyCode = 0x6A; break;
637                 case 17: keyCode = 0x40; break;
638                 case 18: keyCode = 0x4F; break;
639                 case 19: keyCode = 0x50; break;
640                 case 20: keyCode = 0x5A; break;
641             }
642         }
643     }
644
645     // FIXME: No keyCode is set for most keys.
646     if ([character isEqualToString:@"\t"])
647         keyCode = 0x30;
648     else if ([character isEqualToString:@" "])
649         keyCode = 0x31;
650     else if ([character isEqualToString:@"\r"])
651         keyCode = 0x24;
652     else if ([character isEqualToString:@"\n"])
653         keyCode = 0x4C;
654     else if ([character isEqualToString:@"\x8"])
655         keyCode = 0x33;
656     else if ([character isEqualToString:@"7"])
657         keyCode = 0x1A;
658     else if ([character isEqualToString:@"5"])
659         keyCode = 0x17;
660     else if ([character isEqualToString:@"9"])
661         keyCode = 0x19;
662     else if ([character isEqualToString:@"0"])
663         keyCode = 0x1D;
664     else if ([character isEqualToString:@"a"])
665         keyCode = 0x00;
666     else if ([character isEqualToString:@"b"])
667         keyCode = 0x0B;
668     else if ([character isEqualToString:@"d"])
669         keyCode = 0x02;
670     else if ([character isEqualToString:@"e"])
671         keyCode = 0x0E;
672
673     NSString *charactersIgnoringModifiers = eventCharacter;
674
675     int modifierFlags = 0;
676
677     if ([character length] == 1 && [character characterAtIndex:0] >= 'A' && [character characterAtIndex:0] <= 'Z') {
678         modifierFlags |= NSShiftKeyMask;
679         charactersIgnoringModifiers = [character lowercaseString];
680     }
681
682     modifierFlags |= buildModifierFlags(modifiers);
683
684     if (keyLocation == DOM_KEY_LOCATION_NUMPAD)
685         modifierFlags |= NSNumericPadKeyMask;
686
687     [[[mainFrame frameView] documentView] layout];
688
689     NSEvent *event = [NSEvent keyEventWithType:NSKeyDown
690                         location:NSMakePoint(5, 5)
691                         modifierFlags:modifierFlags
692                         timestamp:[self currentEventTime]
693                         windowNumber:[[[mainFrame webView] window] windowNumber]
694                         context:[NSGraphicsContext currentContext]
695                         characters:eventCharacter
696                         charactersIgnoringModifiers:charactersIgnoringModifiers
697                         isARepeat:NO
698                         keyCode:keyCode];
699
700     [[[[mainFrame webView] window] firstResponder] keyDown:event];
701
702     event = [NSEvent keyEventWithType:NSKeyUp
703                         location:NSMakePoint(5, 5)
704                         modifierFlags:modifierFlags
705                         timestamp:[self currentEventTime]
706                         windowNumber:[[[mainFrame webView] window] windowNumber]
707                         context:[NSGraphicsContext currentContext]
708                         characters:eventCharacter
709                         charactersIgnoringModifiers:charactersIgnoringModifiers
710                         isARepeat:NO
711                         keyCode:keyCode];
712
713     [[[[mainFrame webView] window] firstResponder] keyUp:event];
714 }
715
716 - (void)enableDOMUIEventLogging:(WebScriptObject *)node
717 {
718     NSEnumerator *eventEnumerator = [webkitDomEventNames objectEnumerator];
719     id eventName;
720     while ((eventName = [eventEnumerator nextObject])) {
721         [(id<DOMEventTarget>)node addEventListener:eventName listener:self useCapture:NO];
722     }
723 }
724
725 - (void)handleEvent:(DOMEvent *)event
726 {
727     DOMNode *target = [event target];
728
729     printf("event type:      %s\n", [[event type] UTF8String]);
730     printf("  target:        <%s>\n", [[[target nodeName] lowercaseString] UTF8String]);
731     
732     if ([event isKindOfClass:[DOMEvent class]]) {
733         printf("  eventPhase:    %d\n", [event eventPhase]);
734         printf("  bubbles:       %d\n", [event bubbles] ? 1 : 0);
735         printf("  cancelable:    %d\n", [event cancelable] ? 1 : 0);
736     }
737     
738     if ([event isKindOfClass:[DOMUIEvent class]]) {
739         printf("  detail:        %d\n", [(DOMUIEvent*)event detail]);
740         
741         DOMAbstractView *view = [(DOMUIEvent*)event view];
742         if (view) {
743             printf("  view:          OK");            
744             if ([view document])
745                 printf(" (document: OK)");
746             printf("\n");
747         }
748     }
749     
750     if ([event isKindOfClass:[DOMKeyboardEvent class]]) {
751         printf("  keyIdentifier: %s\n", [[(DOMKeyboardEvent*)event keyIdentifier] UTF8String]);
752         printf("  keyLocation:   %d\n", [(DOMKeyboardEvent*)event keyLocation]);
753         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
754                [(DOMKeyboardEvent*)event ctrlKey] ? 1 : 0, 
755                [(DOMKeyboardEvent*)event shiftKey] ? 1 : 0, 
756                [(DOMKeyboardEvent*)event altKey] ? 1 : 0, 
757                [(DOMKeyboardEvent*)event metaKey] ? 1 : 0);
758         printf("  keyCode:       %d\n", [(DOMKeyboardEvent*)event keyCode]);
759         printf("  charCode:      %d\n", [(DOMKeyboardEvent*)event charCode]);
760     }
761     
762     if ([event isKindOfClass:[DOMMouseEvent class]]) {
763         printf("  button:        %d\n", [(DOMMouseEvent*)event button]);
764         printf("  clientX:       %d\n", [(DOMMouseEvent*)event clientX]);
765         printf("  clientY:       %d\n", [(DOMMouseEvent*)event clientY]);
766         printf("  screenX:       %d\n", [(DOMMouseEvent*)event screenX]);
767         printf("  screenY:       %d\n", [(DOMMouseEvent*)event screenY]);
768         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
769                [(DOMMouseEvent*)event ctrlKey] ? 1 : 0, 
770                [(DOMMouseEvent*)event shiftKey] ? 1 : 0, 
771                [(DOMMouseEvent*)event altKey] ? 1 : 0, 
772                [(DOMMouseEvent*)event metaKey] ? 1 : 0);
773         id relatedTarget = [(DOMMouseEvent*)event relatedTarget];
774         if (relatedTarget) {
775             printf("  relatedTarget: %s", [[[relatedTarget class] description] UTF8String]);
776             if ([relatedTarget isKindOfClass:[DOMNode class]])
777                 printf(" (nodeName: %s)", [[(DOMNode*)relatedTarget nodeName] UTF8String]);
778             printf("\n");
779         }
780     }
781     
782     if ([event isKindOfClass:[DOMMutationEvent class]]) {
783         printf("  prevValue:     %s\n", [[(DOMMutationEvent*)event prevValue] UTF8String]);
784         printf("  newValue:      %s\n", [[(DOMMutationEvent*)event newValue] UTF8String]);
785         printf("  attrName:      %s\n", [[(DOMMutationEvent*)event attrName] UTF8String]);
786         printf("  attrChange:    %d\n", [(DOMMutationEvent*)event attrChange]);
787         DOMNode *relatedNode = [(DOMMutationEvent*)event relatedNode];
788         if (relatedNode) {
789             printf("  relatedNode:   %s (nodeName: %s)\n", 
790                    [[[relatedNode class] description] UTF8String],
791                    [[relatedNode nodeName] UTF8String]);
792         }
793     }
794     
795     if ([event isKindOfClass:[DOMWheelEvent class]]) {
796         printf("  clientX:       %d\n", [(DOMWheelEvent*)event clientX]);
797         printf("  clientY:       %d\n", [(DOMWheelEvent*)event clientY]);
798         printf("  screenX:       %d\n", [(DOMWheelEvent*)event screenX]);
799         printf("  screenY:       %d\n", [(DOMWheelEvent*)event screenY]);
800         printf("  modifier keys: c:%d s:%d a:%d m:%d\n", 
801                [(DOMWheelEvent*)event ctrlKey] ? 1 : 0, 
802                [(DOMWheelEvent*)event shiftKey] ? 1 : 0, 
803                [(DOMWheelEvent*)event altKey] ? 1 : 0, 
804                [(DOMWheelEvent*)event metaKey] ? 1 : 0);
805         printf("  isHorizontal:  %d\n", [(DOMWheelEvent*)event isHorizontal] ? 1 : 0);
806         printf("  wheelDelta:    %d\n", [(DOMWheelEvent*)event wheelDelta]);
807     }
808 }
809
810 // FIXME: It's not good to have a test hard-wired into this controller like this.
811 // Instead we need to get testing framework based on the Objective-C bindings
812 // to work well enough that we can test that way instead.
813 - (void)fireKeyboardEventsToElement:(WebScriptObject *)element {
814     
815     if (![element isKindOfClass:[DOMHTMLElement class]])
816         return;
817     
818     DOMHTMLElement *target = (DOMHTMLElement*)element;
819     DOMDocument *document = [target ownerDocument];
820     
821     // Keyboard Event 1
822     
823     DOMEvent *domEvent = [document createEvent:@"KeyboardEvent"];
824     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keydown" 
825                                          canBubble:YES
826                                         cancelable:YES
827                                               view:[document defaultView]
828                                      keyIdentifier:@"U+000041" 
829                                        keyLocation:0
830                                            ctrlKey:YES
831                                             altKey:NO
832                                           shiftKey:NO
833                                            metaKey:NO];
834     [target dispatchEvent:domEvent];  
835         
836     // Keyboard Event 2
837     
838     domEvent = [document createEvent:@"KeyboardEvent"];
839     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keypress" 
840                                          canBubble:YES
841                                         cancelable:YES
842                                               view:[document defaultView]
843                                      keyIdentifier:@"U+000045" 
844                                        keyLocation:1
845                                            ctrlKey:NO
846                                             altKey:YES
847                                           shiftKey:NO
848                                            metaKey:NO];
849     [target dispatchEvent:domEvent];    
850     
851     // Keyboard Event 3
852     
853     domEvent = [document createEvent:@"KeyboardEvent"];
854     [(DOMKeyboardEvent*)domEvent initKeyboardEvent:@"keyup" 
855                                          canBubble:YES
856                                         cancelable:YES
857                                               view:[document defaultView]
858                                      keyIdentifier:@"U+000056" 
859                                        keyLocation:0
860                                            ctrlKey:NO
861                                             altKey:NO
862                                           shiftKey:NO
863                                            metaKey:NO];
864     [target dispatchEvent:domEvent];   
865     
866 }
867
868 @end