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