Index: chrome/browser/automation/ui_controls_mac.mm |
diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm |
index 715135c15e9649d8d2100fa9915dcd92dcf123a9..d95dbc22719547dbbfa72da2fb4020610e19222c 100644 |
--- a/chrome/browser/automation/ui_controls_mac.mm |
+++ b/chrome/browser/automation/ui_controls_mac.mm |
@@ -6,8 +6,11 @@ |
#import <Cocoa/Cocoa.h> |
#include <mach/mach_time.h> |
+#include <vector> |
+#include "base/keyboard_code_conversion_mac.h" |
#include "base/message_loop.h" |
+#include "chrome/browser/automation/ui_controls_internal.h" |
#include "chrome/browser/chrome_thread.h" |
// Implementation details: We use [NSApplication sendEvent:] instead |
@@ -22,6 +25,23 @@ |
// into the event queue. (I can post other kinds of tasks but can't |
// guarantee their order with regards to events). |
+// But [NSApplication sendEvent:] causes a problem when sending mouse click |
+// events. Because in order to handle mouse drag, when processing a mouse |
+// click event, the application may want to retrieve the next event |
+// synchronously by calling NSApplication's nextEventMatchingMask method. |
+// In this case, [NSApplication sendEvent:] causes deadlock. |
+// So we need to use [NSApplication postEvent:atStart:] for mouse click |
+// events. In order to notify the caller correctly after all events has been |
+// processed, we setup a task to watch for the event queue time to time and |
+// notify the caller as soon as there is no event in the queue. |
+// |
+// TODO(suzhe): |
+// 1. Investigate why using [NSApplication postEvent:atStart:] for keyboard |
+// events causes BrowserKeyEventsTest.CommandKeyEvents to fail. |
+// See http://crbug.com/49270 |
+// 2. On OSX 10.6, [NSEvent addLocalMonitorForEventsMatchingMask:handler:] may |
+// be used, so that we don't need to poll the event queue time to time. |
+ |
namespace { |
// From |
@@ -53,6 +73,148 @@ NSTimeInterval TimeIntervalSinceSystemStartup() { |
return UpTimeInNanoseconds() / 1000000000.0; |
} |
+// Creates and returns an autoreleased key event. |
+NSEvent* SynthesizeKeyEvent(NSWindow* window, |
+ bool keyDown, |
+ base::KeyboardCode keycode, |
+ NSUInteger flags) { |
+ unichar character; |
+ unichar characterIgnoringModifiers; |
+ int macKeycode = base::MacKeyCodeForWindowsKeyCode( |
+ keycode, flags, &character, &characterIgnoringModifiers); |
+ |
+ if (macKeycode < 0) |
+ return nil; |
+ |
+ NSString* charactersIgnoringModifiers = |
+ [[[NSString alloc] initWithCharacters:&characterIgnoringModifiers |
+ length:1] |
+ autorelease]; |
+ NSString* characters = |
+ [[[NSString alloc] initWithCharacters:&character length:1] autorelease]; |
+ |
+ NSEventType type = (keyDown ? NSKeyDown : NSKeyUp); |
+ |
+ // Modifier keys generate NSFlagsChanged event rather than |
+ // NSKeyDown/NSKeyUp events. |
+ if (keycode == base::VKEY_CONTROL || keycode == base::VKEY_SHIFT || |
+ keycode == base::VKEY_MENU || keycode == base::VKEY_COMMAND) |
+ type = NSFlagsChanged; |
+ |
+ // For events other than mouse moved, [event locationInWindow] is |
+ // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) |
+ // location should be fine. |
+ NSEvent* event = |
+ [NSEvent keyEventWithType:type |
+ location:NSMakePoint(0, 0) |
+ modifierFlags:flags |
+ timestamp:TimeIntervalSinceSystemStartup() |
+ windowNumber:[window windowNumber] |
+ context:nil |
+ characters:characters |
+ charactersIgnoringModifiers:charactersIgnoringModifiers |
+ isARepeat:NO |
+ keyCode:(unsigned short)macKeycode]; |
+ |
+ return event; |
+} |
+ |
+// Creates the proper sequence of autoreleased key events for a key down + up. |
+void SynthesizeKeyEventsSequence(NSWindow* window, |
+ base::KeyboardCode keycode, |
+ bool control, |
+ bool shift, |
+ bool alt, |
+ bool command, |
+ std::vector<NSEvent*>* events) { |
+ NSEvent* event = nil; |
+ NSUInteger flags = 0; |
+ if (control) { |
+ flags |= NSControlKeyMask; |
+ event = SynthesizeKeyEvent(window, true, base::VKEY_CONTROL, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (shift) { |
+ flags |= NSShiftKeyMask; |
+ event = SynthesizeKeyEvent(window, true, base::VKEY_SHIFT, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (alt) { |
+ flags |= NSAlternateKeyMask; |
+ event = SynthesizeKeyEvent(window, true, base::VKEY_MENU, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (command) { |
+ flags |= NSCommandKeyMask; |
+ event = SynthesizeKeyEvent(window, true, base::VKEY_COMMAND, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ |
+ event = SynthesizeKeyEvent(window, true, keycode, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ event = SynthesizeKeyEvent(window, false, keycode, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ |
+ if (command) { |
+ flags &= ~NSCommandKeyMask; |
+ event = SynthesizeKeyEvent(window, false, base::VKEY_COMMAND, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (alt) { |
+ flags &= ~NSAlternateKeyMask; |
+ event = SynthesizeKeyEvent(window, false, base::VKEY_MENU, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (shift) { |
+ flags &= ~NSShiftKeyMask; |
+ event = SynthesizeKeyEvent(window, false, base::VKEY_SHIFT, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+ if (control) { |
+ flags &= ~NSControlKeyMask; |
+ event = SynthesizeKeyEvent(window, false, base::VKEY_CONTROL, flags); |
+ DCHECK(event); |
+ events->push_back(event); |
+ } |
+} |
+ |
+// A task class to watch for the event queue. The specific task will be fired |
+// when there is no more event in the queue. |
+class EventQueueWatcher : public Task { |
+ public: |
+ EventQueueWatcher(Task* task) : task_(task) {} |
+ |
+ virtual ~EventQueueWatcher() {} |
+ |
+ virtual void Run() { |
+ NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask |
+ untilDate:nil |
+ inMode:NSDefaultRunLoopMode |
+ dequeue:NO]; |
+ // If there is still event in the queue, then we need to check again. |
+ if (event) |
+ MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task_)); |
+ else |
+ MessageLoop::current()->PostTask(FROM_HERE, task_); |
+ } |
+ |
+ private: |
+ Task* task_; |
+}; |
+ |
+// Stores the current mouse location on the screen. So that we can use it |
+// when firing keyboard and mouse click events. |
+NSPoint g_mouse_location = { 0, 0 }; |
+ |
} // anonymous namespace |
@@ -71,8 +233,6 @@ bool SendKeyPress(gfx::NativeWindow window, |
// Win and Linux implement a SendKeyPress() this as a |
// SendKeyPressAndRelease(), so we should as well (despite the name). |
-// |
-// TODO(jrg): handle "characters" better (e.g. apply shift?) |
bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, |
base::KeyboardCode key, |
bool control, |
@@ -81,55 +241,23 @@ bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, |
bool command, |
Task* task) { |
DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); |
- NSUInteger flags = 0; |
- if (control) |
- flags |= NSControlKeyMask; |
- if (shift) |
- flags |= NSShiftKeyMask; |
- if (alt) |
- flags |= NSAlternateKeyMask; |
- if (command) |
- flags |= NSCommandKeyMask; |
- unsigned char keycode = key; |
- NSString* charactersIgnoringModifiers = [[[NSString alloc] |
- initWithBytes:&keycode |
- length:1 |
- encoding:NSUTF8StringEncoding] |
- autorelease]; |
- NSString* characters = charactersIgnoringModifiers; |
- // For events other than mouse moved, [event locationInWindow] is |
- // UNDEFINED if the event is not NSMouseMoved. Thus, the (0,0) |
- // locaiton should be fine. |
- // First a key down... |
- NSEvent* event = |
- [NSEvent keyEventWithType:NSKeyDown |
- location:NSMakePoint(0,0) |
- modifierFlags:flags |
- timestamp:TimeIntervalSinceSystemStartup() |
- windowNumber:[window windowNumber] |
- context:nil |
- characters:characters |
- charactersIgnoringModifiers:charactersIgnoringModifiers |
- isARepeat:NO |
- keyCode:key]; |
- [[NSApplication sharedApplication] sendEvent:event]; |
- // Then a key up. |
- event = |
- [NSEvent keyEventWithType:NSKeyUp |
- location:NSMakePoint(0,0) |
- modifierFlags:flags |
- timestamp:TimeIntervalSinceSystemStartup() |
- windowNumber:[window windowNumber] |
- context:nil |
- characters:characters |
- charactersIgnoringModifiers:charactersIgnoringModifiers |
- isARepeat:NO |
- keyCode:key]; |
- [[NSApplication sharedApplication] sendEvent:event]; |
+ std::vector<NSEvent*> events; |
+ SynthesizeKeyEventsSequence( |
+ window, key, control, shift, alt, command, &events); |
+ |
+ // TODO(suzhe): Using [NSApplication postEvent:atStart:] here causes |
+ // BrowserKeyEventsTest.CommandKeyEvents to fail. See http://crbug.com/49270 |
+ // But using [NSApplication sendEvent:] should be safe for keyboard events, |
+ // because until now, no code wants to retrieve the next event when handling |
+ // a keyboard event. |
+ for (std::vector<NSEvent*>::iterator iter = events.begin(); |
+ iter != events.end(); ++iter) |
+ [[NSApplication sharedApplication] sendEvent:*iter]; |
if (task) |
- MessageLoop::current()->PostTask(FROM_HERE, task); |
+ MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
+ |
return true; |
} |
@@ -144,7 +272,8 @@ bool SendMouseMove(long x, long y) { |
bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { |
NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
CGFloat screenHeight = [[NSScreen mainScreen] frame].size.height; |
- NSPoint pointInWindow = NSMakePoint(x, screenHeight - y); // flip! |
+ g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! |
+ NSPoint pointInWindow = g_mouse_location; |
if (window) |
pointInWindow = [window convertScreenToBase:pointInWindow]; |
NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); |
@@ -160,8 +289,10 @@ bool SendMouseMoveNotifyWhenDone(long x, long y, Task* task) { |
clickCount:0 |
pressure:0.0]; |
[[NSApplication sharedApplication] postEvent:event atStart:NO]; |
+ |
if (task) |
- MessageLoop::current()->PostTask(FROM_HERE, task); |
+ MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
+ |
return true; |
} |
@@ -176,7 +307,6 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { |
return (SendMouseEventsNotifyWhenDone(type, DOWN, NULL) && |
SendMouseEventsNotifyWhenDone(type, UP, task)); |
} |
- |
NSEventType etype = 0; |
if (type == LEFT) { |
if (state == UP) { |
@@ -200,8 +330,7 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { |
return false; |
} |
NSWindow* window = [[NSApplication sharedApplication] keyWindow]; |
- NSPoint location = [NSEvent mouseLocation]; |
- NSPoint pointInWindow = location; |
+ NSPoint pointInWindow = g_mouse_location; |
if (window) |
pointInWindow = [window convertScreenToBase:pointInWindow]; |
@@ -213,11 +342,13 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, Task* task) { |
windowNumber:[window windowNumber] |
context:nil |
eventNumber:0 |
- clickCount:0 |
- pressure:0.0]; |
- [[NSApplication sharedApplication] sendEvent:event]; |
+ clickCount:1 |
+ pressure:(state == DOWN ? 1.0 : 0.0 )]; |
+ [[NSApplication sharedApplication] postEvent:event atStart:NO]; |
+ |
if (task) |
- MessageLoop::current()->PostTask(FROM_HERE, task); |
+ MessageLoop::current()->PostTask(FROM_HERE, new EventQueueWatcher(task)); |
+ |
return true; |
} |
@@ -225,18 +356,27 @@ bool SendMouseClick(MouseButton type) { |
return SendMouseEventsNotifyWhenDone(type, UP|DOWN, NULL); |
} |
-// This appears to only be used by a function in test/ui_test_utils.h: |
-// ui_test_utils::ClickOnView(). That is not implemented on Mac, so |
-// we don't need to implement MoveMouseToCenterAndPress(). I've |
-// suggested an implementation of ClickOnView() which would call Cocoa |
-// directly and not need this indirection, so this may not be needed, |
-// ever. |
void MoveMouseToCenterAndPress( |
- NSWindow* window, |
+ NSView* view, |
MouseButton button, |
int state, |
Task* task) { |
- NOTIMPLEMENTED(); |
+ DCHECK(view); |
+ NSWindow* window = [view window]; |
+ DCHECK(window); |
+ NSScreen* screen = [window screen]; |
+ DCHECK(screen); |
+ |
+ // Converts the center position of the view into the coordinates accepted |
+ // by SendMouseMoveNotifyWhenDone() method. |
+ NSRect bounds = [view bounds]; |
+ NSPoint center = NSMakePoint(NSMidX(bounds), NSMidY(bounds)); |
+ center = [view convertPoint:center toView:nil]; |
+ center = [window convertBaseToScreen:center]; |
+ center = NSMakePoint(center.x, [screen frame].size.height - center.y); |
+ |
+ SendMouseMoveNotifyWhenDone(center.x, center.y, |
+ new ClickTask(button, state, task)); |
} |
} // ui_controls |