diff options
author | suzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-27 21:17:57 +0000 |
---|---|---|
committer | suzhe@chromium.org <suzhe@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-27 21:17:57 +0000 |
commit | 853300a85cf5390d7e7e25fcf5acf391306c94f8 (patch) | |
tree | 00d89c84c2855562fed93e64f04a3180eb70d99b /chrome/browser/automation/ui_controls_mac.mm | |
parent | 7fdad3a542e60dd9426e103b813421f3d1b37b40 (diff) | |
download | chromium_src-853300a85cf5390d7e7e25fcf5acf391306c94f8.zip chromium_src-853300a85cf5390d7e7e25fcf5acf391306c94f8.tar.gz chromium_src-853300a85cf5390d7e7e25fcf5acf391306c94f8.tar.bz2 |
[Mac]Port browser_keyevents_browsertest.cc and browser_focus_uitest.cc to Mac.
This CL includes:
1. Implementation of ui_test_utils_mac.mm
2. Fix for ui_controls_mac.mm
3. Port browser_keyevents_browsertest.cc to Mac and add some new tests for Mac.
4. Partially port browser_focus_uitest.cc to Mac, now can be compiled and run
on Mac but some tests fail.
5. Add two functions into ui_test_utils.h: HideNativeWindow() and
ShowAndFocusNativeWindow(). The latter one shows a window and grabs the input
focus, which is useful for tests depending on fake keyboard/mouse events.
Because browser_keyevents_browsertests.cc and browser_focus_uitest.cc
belong to interactive_ui_tests, which is not available on Mac (see
http://crbug.com/21276), in order to test them on Mac, you may want to
move them into browser_tests locally. But it won't work on build and try
bots, because these tests must be run with screen unlocked.
This CL depends on CL: http://codereview.chromium.org/2973004
and http://codereview.chromium.org/2805075
BUG=22515 Keyboard handling needs unit tests
BUG=48671 interactive_ui_test: BrowserKeyEventsTest.NormalKeyEvents is flaky
BUG=48936 Browser window is opened inactivated when running an InProcessBrowserTest.
TEST=none
Review URL: http://codereview.chromium.org/2986004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@53840 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/automation/ui_controls_mac.mm')
-rw-r--r-- | chrome/browser/automation/ui_controls_mac.mm | 270 |
1 files changed, 205 insertions, 65 deletions
diff --git a/chrome/browser/automation/ui_controls_mac.mm b/chrome/browser/automation/ui_controls_mac.mm index 715135c1..d95dbc2 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 |