Chromium Code Reviews| Index: ui/base/test/ui_controls_mac.mm | 
| diff --git a/ui/base/test/ui_controls_mac.mm b/ui/base/test/ui_controls_mac.mm | 
| index b7615b6c2fd78eab1ddbb87a351cf33f47c4efd1..9446beccb03271c7b326c98d030c05544f9870c9 100644 | 
| --- a/ui/base/test/ui_controls_mac.mm | 
| +++ b/ui/base/test/ui_controls_mac.mm | 
| @@ -9,10 +9,17 @@ | 
| #include "base/bind.h" | 
| #include "base/callback.h" | 
| +#import "base/mac/foundation_util.h" | 
| +#import "base/mac/scoped_nsobject.h" | 
| +#import "base/mac/scoped_objc_class_swizzler.h" | 
| #include "base/message_loop/message_loop.h" | 
| +#include "base/threading/thread_task_runner_handle.h" | 
| #include "ui/base/cocoa/cocoa_base_utils.h" | 
| +#include "ui/display/screen.h" | 
| #include "ui/events/keycodes/keyboard_code_conversion_mac.h" | 
| #import "ui/events/test/cocoa_test_event_utils.h" | 
| +#include "ui/gfx/geometry/point.h" | 
| +#import "ui/gfx/mac/coordinate_conversion.h" | 
| // Implementation details: We use [NSApplication sendEvent:] instead | 
| // of [NSApplication postEvent:atStart:] so that the event gets sent | 
| @@ -48,10 +55,23 @@ using cocoa_test_event_utils::TimeIntervalSinceSystemStartup; | 
| namespace { | 
| +// When enabled, all simulated mouse events will be posted to | 
| +// the WindowServer, and the actual mouse will move on the screen. | 
| +bool g_use_cgevents = false; | 
| + | 
| // 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 }; | 
| +// If enabled overrides results returned by +[NSEvent mouseLocation] and | 
| +// -[NSWindow mouseLocationOutsideOfEventStream] | 
| +NSPoint g_mouse_location_override = {0, 0}; | 
| +bool g_mouse_location_override_enabled = false; | 
| + | 
| +// Stores the current pressed mouse buttons. Indexed by | 
| +// ui_controls::MouseButton. | 
| +bool g_mouse_button_down[3] = {false, false, false}; | 
| + | 
| bool g_ui_controls_enabled = false; | 
| // Creates the proper sequence of autoreleased key events for a key down + up. | 
| @@ -122,9 +142,18 @@ void SynthesizeKeyEventsSequence(NSWindow* window, | 
| } | 
| } | 
| +void RunTaskInTaskRunner( | 
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner, | 
| + const base::Closure& task) { | 
| + task_runner->PostTask(FROM_HERE, task); | 
| +} | 
| + | 
| // A helper function to watch for the event queue. The specific task will be | 
| // fired when there is no more event in the queue. | 
| void EventQueueWatcher(const base::Closure& task) { | 
| + DCHECK_EQ(dispatch_get_current_queue(), dispatch_get_main_queue()) | 
| + << "It should be run on the UI thread, as otherwise it will always " | 
| + "report there are no pending events"; | 
| NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask | 
| untilDate:nil | 
| inMode:NSDefaultRunLoopMode | 
| @@ -173,6 +202,187 @@ NSWindow* WindowAtCurrentMouseLocation() { | 
| } // namespace | 
| +// Since CGEvents take some time to reach -[NSApplication sendEvent:] we use | 
| +// this class to wait for the events to start being processed before sending | 
| +// finish notifications. | 
| +class EventMonitor { | 
| 
 
tapted
2016/05/23 07:29:28
This should be in the anonymous namespace
 
themblsha
2016/05/26 15:13:25
Done.
 
 | 
| + public: | 
| + void NotifyWhenEventIsProcessed(NSEvent* event, const base::Closure& task) { | 
| + if (g_use_cgevents) { | 
| + CHECK([[NSApplication sharedApplication] isActive]) | 
| + << "If we generate CGEvents and the application is not active, we'll " | 
| + "deadlock waiting for NSMouseMoved events. Use " | 
| + "ui_test_utils::BringBrowserWindowToFront() to activate the " | 
| + "browser window prior to generating CGEvents."; | 
| + } | 
| + | 
| + base::AutoLock auto_lock(lock_); | 
| + tasks_.emplace_back(Task(event, task)); | 
| + } | 
| + | 
| + void ProcessingEvent(NSEvent* event) { | 
| + base::AutoLock auto_lock(lock_); | 
| + auto it = std::find_if( | 
| + tasks_.begin(), tasks_.end(), | 
| + [&event](const Task& task) { return task.MatchesEvent(event); }); | 
| + if (it != tasks_.end()) { | 
| + it->Run(); | 
| + tasks_.erase(it); | 
| + } | 
| + } | 
| + | 
| + static EventMonitor* Instance() { | 
| + static EventMonitor* monitor = nullptr; | 
| + if (!monitor) { | 
| + monitor = new EventMonitor(); | 
| + } | 
| + return monitor; | 
| + } | 
| + | 
| + private: | 
| + class Task { | 
| + public: | 
| + Task(NSEvent* event, const base::Closure& task) | 
| + : task_runner_(base::ThreadTaskRunnerHandle::Get()), | 
| + event_([event retain]), | 
| + finish_closure_(task) {} | 
| + | 
| + bool MatchesEvent(NSEvent* event) const { | 
| + // When moving the window using BridgedNativeWidget::RunMoveLoop the | 
| + // locationInWindow can be a little inconsistent with what we expect. | 
| + // Seems that only comparing event type is fine. | 
| + return | 
| + [event_ type] == [event type] && [event_ subtype] == [event subtype]; | 
| + } | 
| + | 
| + void Run() { | 
| + // We get here before the event is actually processed. Run the | 
| + // EventQueueWatcher on the main thread in order to wait for all events to | 
| + // finish processing. | 
| + base::MessageLoop::current()->PostTask( | 
| + FROM_HERE, base::Bind(&EventQueueWatcher, | 
| + base::Bind(&RunTaskInTaskRunner, task_runner_, | 
| + finish_closure_))); | 
| + } | 
| + | 
| + private: | 
| + // Events could be spawned on background threads. Be sure to invoke the | 
| + // |finish_closure_| on an appropriate thread. | 
| + scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | 
| + base::scoped_nsobject<NSEvent> event_; | 
| + base::Closure finish_closure_; | 
| + }; | 
| + | 
| + EventMonitor() | 
| + : send_event_swizzler_( | 
| + new base::mac::ScopedObjCClassSwizzler([NSApplication class], | 
| + @selector(sendEvent:), | 
| + @selector(cr_sendEvent:))) {} | 
| + | 
| + std::vector<Task> tasks_; | 
| + | 
| + // synchronizes access to the |tasks_| in case we spawn the events on a | 
| + // different thread | 
| + base::Lock lock_; | 
| + | 
| + std::unique_ptr<base::mac::ScopedObjCClassSwizzler> send_event_swizzler_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(EventMonitor); | 
| +}; | 
| + | 
| +// Donates testing implementations of NSApplication methods. We can't simply | 
| +// use -[NSEvent addLocalMonitorForEventsMatchingMask:handler:], as other event | 
| +// monitors could have precedence, and they could filter the events before we | 
| +// can see them. But since nobody swizzles -[NSApplication sendEvent:] we should | 
| +// be safe. | 
| +@interface NSApplication (TestingDonor) | 
| +@end | 
| + | 
| +@implementation NSApplication (TestingDonor) | 
| +- (void)cr_sendEvent:(NSEvent*)event { | 
| + // Invoke the finish handler before the event is processed, since we can get | 
| + // stuck in BridgedNativeWidget::RunMoveLoop and would never see the event | 
| + // otherwise. | 
| + EventMonitor::Instance()->ProcessingEvent(event); | 
| + | 
| + [self cr_sendEvent:event]; | 
| +} | 
| +@end | 
| + | 
| +// Donates testing implementations of NSEvent methods. | 
| +@interface FakeNSEventTestingDonor : NSObject | 
| +@end | 
| + | 
| +@implementation FakeNSEventTestingDonor | 
| ++ (NSPoint)mouseLocation { | 
| + if (g_mouse_location_override_enabled) | 
| + return g_mouse_location_override; | 
| + | 
| + return g_mouse_location; | 
| +} | 
| + | 
| ++ (NSUInteger)pressedMouseButtons { | 
| + NSUInteger result = 0; | 
| + const int buttons[3] = { | 
| + ui_controls::LEFT, ui_controls::RIGHT, ui_controls::MIDDLE}; | 
| + for (unsigned int i = 0; i < arraysize(buttons); ++i) { | 
| + if (g_mouse_button_down[buttons[i]]) | 
| + result |= (1 << i); | 
| + } | 
| + return result; | 
| +} | 
| +@end | 
| + | 
| +// Donates testing implementations of NSWindow methods. | 
| +@interface FakeNSWindowTestingDonor : NSObject | 
| +@end | 
| + | 
| +@implementation FakeNSWindowTestingDonor | 
| +- (NSPoint)mouseLocationOutsideOfEventStream { | 
| + NSWindow* window = base::mac::ObjCCastStrict<NSWindow>(self); | 
| + if (g_mouse_location_override_enabled) | 
| + return ui::ConvertPointFromWindowToScreen(window, | 
| + g_mouse_location_override); | 
| + | 
| + return ui::ConvertPointFromWindowToScreen(window, g_mouse_location); | 
| +} | 
| +@end | 
| + | 
| +namespace { | 
| +class NSEventSwizzler { | 
| + public: | 
| + static void Install() { | 
| + static NSEventSwizzler* swizzler = nullptr; | 
| + if (!swizzler) { | 
| + swizzler = new NSEventSwizzler(); | 
| + } | 
| + } | 
| + | 
| + protected: | 
| + NSEventSwizzler() | 
| + : mouse_location_swizzler_(new base::mac::ScopedObjCClassSwizzler( | 
| + [NSEvent class], | 
| + [FakeNSEventTestingDonor class], | 
| + @selector(mouseLocation))), | 
| + pressed_mouse_buttons_swizzler_(new base::mac::ScopedObjCClassSwizzler( | 
| + [NSEvent class], | 
| + [FakeNSEventTestingDonor class], | 
| + @selector(pressedMouseButtons))), | 
| + mouse_location_outside_of_event_stream_swizzler_( | 
| + new base::mac::ScopedObjCClassSwizzler( | 
| + [NSWindow class], | 
| + [FakeNSWindowTestingDonor class], | 
| + @selector(mouseLocationOutsideOfEventStream))) {} | 
| + | 
| + private: | 
| + std::unique_ptr<base::mac::ScopedObjCClassSwizzler> mouse_location_swizzler_; | 
| + std::unique_ptr<base::mac::ScopedObjCClassSwizzler> | 
| + pressed_mouse_buttons_swizzler_; | 
| + std::unique_ptr<base::mac::ScopedObjCClassSwizzler> | 
| + mouse_location_outside_of_event_stream_swizzler_; | 
| +}; | 
| +} // namespace | 
| + | 
| namespace ui_controls { | 
| void EnableUIControls() { | 
| @@ -204,6 +414,7 @@ bool SendKeyPressNotifyWhenDone(gfx::NativeWindow window, | 
| bool alt, | 
| bool command, | 
| const base::Closure& task) { | 
| + DCHECK(!g_use_cgevents) << "Not implemented"; | 
| CHECK(g_ui_controls_enabled); | 
| DCHECK(base::MessageLoopForUI::IsCurrent()); | 
| @@ -239,9 +450,8 @@ bool SendMouseMove(long x, long y) { | 
| // platforms. E.g. (0,0) is upper-left. | 
| bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | 
| CHECK(g_ui_controls_enabled); | 
| - CGFloat screenHeight = | 
| - [[[NSScreen screens] firstObject] frame].size.height; | 
| - g_mouse_location = NSMakePoint(x, screenHeight - y); // flip! | 
| + g_mouse_location = gfx::ScreenPointToNSPoint(gfx::Point(x, y)); // flip! | 
| + NSEventSwizzler::Install(); | 
| NSWindow* window = WindowAtCurrentMouseLocation(); | 
| @@ -250,21 +460,37 @@ bool SendMouseMoveNotifyWhenDone(long x, long y, const base::Closure& task) { | 
| pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 
| NSTimeInterval timestamp = TimeIntervalSinceSystemStartup(); | 
| + NSEventType event_type = NSMouseMoved; | 
| + if (g_mouse_button_down[LEFT]) { | 
| + event_type = NSLeftMouseDragged; | 
| + } else if (g_mouse_button_down[RIGHT]) { | 
| + event_type = NSRightMouseDragged; | 
| + } else if (g_mouse_button_down[MIDDLE]) { | 
| + event_type = NSOtherMouseDragged; | 
| + } | 
| + | 
| NSEvent* event = | 
| - [NSEvent mouseEventWithType:NSMouseMoved | 
| + [NSEvent mouseEventWithType:event_type | 
| location:pointInWindow | 
| modifierFlags:0 | 
| timestamp:timestamp | 
| windowNumber:[window windowNumber] | 
| context:nil | 
| eventNumber:0 | 
| - clickCount:0 | 
| - pressure:0.0]; | 
| - [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 
| + clickCount:(event_type == NSMouseMoved ? 0 : 1) | 
| + pressure:(event_type == NSMouseMoved ? 0.0 : 1.0)]; | 
| if (!task.is_null()) { | 
| - base::MessageLoop::current()->PostTask( | 
| - FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 
| + EventMonitor::Instance()->NotifyWhenEventIsProcessed(event, task); | 
| + } | 
| + | 
| + gfx::Point gp = gfx::ScreenPointFromNSPoint(g_mouse_location); | 
| + CGWarpMouseCursorPosition(CGPointMake(gp.x(), gp.y())); | 
| + | 
| + if (g_use_cgevents) { | 
| + CGEventPost(kCGSessionEventTap, [event CGEvent]); | 
| + } else { | 
| + [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 
| } | 
| return true; | 
| @@ -284,35 +510,40 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, | 
| return (SendMouseEventsNotifyWhenDone(type, DOWN, base::Closure()) && | 
| SendMouseEventsNotifyWhenDone(type, UP, task)); | 
| } | 
| - NSEventType etype = NSLeftMouseDown; | 
| + NSEventType event_type = NSLeftMouseDown; | 
| if (type == LEFT) { | 
| if (state == UP) { | 
| - etype = NSLeftMouseUp; | 
| + event_type = NSLeftMouseUp; | 
| } else { | 
| - etype = NSLeftMouseDown; | 
| + event_type = NSLeftMouseDown; | 
| } | 
| } else if (type == MIDDLE) { | 
| if (state == UP) { | 
| - etype = NSOtherMouseUp; | 
| + event_type = NSOtherMouseUp; | 
| } else { | 
| - etype = NSOtherMouseDown; | 
| + event_type = NSOtherMouseDown; | 
| } | 
| } else if (type == RIGHT) { | 
| if (state == UP) { | 
| - etype = NSRightMouseUp; | 
| + event_type = NSRightMouseUp; | 
| } else { | 
| - etype = NSRightMouseDown; | 
| + event_type = NSRightMouseDown; | 
| } | 
| } else { | 
| + NOTREACHED(); | 
| return false; | 
| } | 
| + g_mouse_button_down[type] = state == DOWN; | 
| + | 
| NSWindow* window = WindowAtCurrentMouseLocation(); | 
| NSPoint pointInWindow = g_mouse_location; | 
| if (window) | 
| pointInWindow = ui::ConvertPointFromScreenToWindow(window, pointInWindow); | 
| + NSEventSwizzler::Install(); | 
| + | 
| NSEvent* event = | 
| - [NSEvent mouseEventWithType:etype | 
| + [NSEvent mouseEventWithType:event_type | 
| location:pointInWindow | 
| modifierFlags:0 | 
| timestamp:TimeIntervalSinceSystemStartup() | 
| @@ -321,11 +552,15 @@ bool SendMouseEventsNotifyWhenDone(MouseButton type, int state, | 
| eventNumber:0 | 
| clickCount:1 | 
| pressure:(state == DOWN ? 1.0 : 0.0 )]; | 
| - [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 
| if (!task.is_null()) { | 
| - base::MessageLoop::current()->PostTask( | 
| - FROM_HERE, base::Bind(&EventQueueWatcher, task)); | 
| + EventMonitor::Instance()->NotifyWhenEventIsProcessed(event, task); | 
| + } | 
| + | 
| + if (g_use_cgevents) { | 
| + CGEventPost(kCGSessionEventTap, [event CGEvent]); | 
| + } else { | 
| + [[NSApplication sharedApplication] postEvent:event atStart:NO]; | 
| } | 
| return true; | 
| @@ -345,4 +580,21 @@ bool IsFullKeyboardAccessEnabled() { | 
| return [NSApp isFullKeyboardAccessEnabled]; | 
| } | 
| +void SetSendMouseEventsAsCGEvents(bool enable_cgevents) { | 
| + g_use_cgevents = enable_cgevents; | 
| +} | 
| + | 
| +bool SendMouseEventsAsCGEvents() { | 
| + return g_use_cgevents; | 
| +} | 
| + | 
| +void NotifyWhenEventIsProcessed(NSEvent* event, const base::Closure& task) { | 
| + EventMonitor::Instance()->NotifyWhenEventIsProcessed(event, task); | 
| +} | 
| + | 
| +void SetMousePositionOverride(bool enable_override, const gfx::Point& p) { | 
| + g_mouse_location_override_enabled = enable_override; | 
| + g_mouse_location_override = gfx::ScreenPointToNSPoint(p); // flip! | 
| +} | 
| + | 
| } // namespace ui_controls |