ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / ui / views / test / event_generator_delegate_mac.mm
blob32878cd3a947c0a28846c70fe1833e393148c4a8
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import <Cocoa/Cocoa.h>
7 #import "base/mac/scoped_nsobject.h"
8 #import "base/mac/scoped_objc_class_swizzler.h"
9 #include "base/memory/singleton.h"
10 #include "ui/events/event_processor.h"
11 #include "ui/events/event_target.h"
12 #include "ui/events/event_target_iterator.h"
13 #include "ui/events/event_targeter.h"
14 #import "ui/events/test/cocoa_test_event_utils.h"
15 #include "ui/events/test/event_generator.h"
16 #include "ui/gfx/mac/coordinate_conversion.h"
18 namespace {
20 // Singleton to provide state for swizzled Objective C methods.
21 ui::test::EventGenerator* g_active_generator = NULL;
23 // Set (and always cleared) in EmulateSendEvent() to provide an answer for
24 // [NSApp currentEvent].
25 NSEvent* g_current_event = nil;
27 }  // namespace
29 @interface NSEventDonor : NSObject
30 @end
32 @interface NSApplicationDonor : NSObject
33 @end
35 namespace {
37 NSPoint ConvertRootPointToTarget(NSWindow* target,
38                                  const gfx::Point& point_in_root) {
39   // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can
40   // reposition the window on screen and make things flaky. Initially, just
41   // assume that the contentRect of |target| is at the top-left corner of the
42   // screen.
43   NSRect content_rect = [target contentRectForFrameRect:[target frame]];
44   return NSMakePoint(point_in_root.x(),
45                      NSHeight(content_rect) - point_in_root.y());
48 // Inverse of ui::EventFlagsFromModifiers().
49 NSUInteger EventFlagsToModifiers(int flags) {
50   NSUInteger modifiers = 0;
51   modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0;
52   modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0;
53   modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0;
54   modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0;
55   modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0;
56   // ui::EF_*_MOUSE_BUTTON not handled here.
57   // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped.
58   return modifiers;
61 // Picks the corresponding mouse event type for the buttons set in |flags|.
62 NSEventType PickMouseEventType(int flags,
63                                NSEventType left,
64                                NSEventType right,
65                                NSEventType other) {
66   if (flags & ui::EF_LEFT_MOUSE_BUTTON)
67     return left;
68   if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
69     return right;
70   return other;
73 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set
74 // using the inverse of ui::EventFlagsFromNSEventWithModifiers().
75 NSEventType EventTypeToNative(ui::EventType ui_event_type,
76                               int flags,
77                               NSUInteger* modifiers) {
78   if (modifiers)
79     *modifiers = EventFlagsToModifiers(flags);
80   switch (ui_event_type) {
81     case ui::ET_UNKNOWN:
82       return 0;
83     case ui::ET_KEY_PRESSED:
84       return NSKeyDown;
85     case ui::ET_KEY_RELEASED:
86       return NSKeyUp;
87     case ui::ET_MOUSE_PRESSED:
88       return PickMouseEventType(flags,
89                                 NSLeftMouseDown,
90                                 NSRightMouseDown,
91                                 NSOtherMouseDown);
92     case ui::ET_MOUSE_RELEASED:
93       return PickMouseEventType(flags,
94                                 NSLeftMouseUp,
95                                 NSRightMouseUp,
96                                 NSOtherMouseUp);
97     case ui::ET_MOUSE_DRAGGED:
98       return PickMouseEventType(flags,
99                                 NSLeftMouseDragged,
100                                 NSRightMouseDragged,
101                                 NSOtherMouseDragged);
102     case ui::ET_MOUSE_MOVED:
103       return NSMouseMoved;
104     case ui::ET_MOUSEWHEEL:
105       return NSScrollWheel;
106     case ui::ET_MOUSE_ENTERED:
107       return NSMouseEntered;
108     case ui::ET_MOUSE_EXITED:
109       return NSMouseExited;
110     case ui::ET_SCROLL_FLING_START:
111       return NSEventTypeSwipe;
112     default:
113       NOTREACHED();
114       return 0;
115   }
118 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:].
119 // sendEvent is a black box which (among other things) will try to peek at the
120 // event queue and can block indefinitely.
121 void EmulateSendEvent(NSWindow* window, NSEvent* event) {
122   base::AutoReset<NSEvent*> reset(&g_current_event, event);
123   NSResponder* responder = [window firstResponder];
124   switch ([event type]) {
125     case NSKeyDown:
126       [responder keyDown:event];
127       return;
128     case NSKeyUp:
129       [responder keyUp:event];
130       return;
131   }
133   // For mouse events, NSWindow will use -[NSView hitTest:] for the initial
134   // mouseDown, and then keep track of the NSView returned. The toolkit-views
135   // RootView does this too. So, for tests, assume tracking will be done there,
136   // and the NSWindow's contentView is wrapping a views::internal::RootView.
137   responder = [window contentView];
138   switch ([event type]) {
139     case NSLeftMouseDown:
140       [responder mouseDown:event];
141       break;
142     case NSRightMouseDown:
143       [responder rightMouseDown:event];
144       break;
145     case NSOtherMouseDown:
146       [responder otherMouseDown:event];
147       break;
148     case NSLeftMouseUp:
149       [responder mouseUp:event];
150       break;
151     case NSRightMouseUp:
152       [responder rightMouseUp:event];
153       break;
154     case NSOtherMouseUp:
155       [responder otherMouseUp:event];
156       break;
157     case NSLeftMouseDragged:
158       [responder mouseDragged:event];
159       break;
160     case NSRightMouseDragged:
161       [responder rightMouseDragged:event];
162       break;
163     case NSOtherMouseDragged:
164       [responder otherMouseDragged:event];
165       break;
166     case NSMouseMoved:
167       // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that
168       // NSTrackingAreas have been appropriately installed on |responder|.
169       [responder mouseMoved:event];
170       break;
171     case NSScrollWheel:
172       [responder scrollWheel:event];
173       break;
174     case NSMouseEntered:
175     case NSMouseExited:
176       // With the assumptions in NSMouseMoved, it doesn't make sense for the
177       // generator to handle entered/exited separately. It's the responsibility
178       // of views::internal::RootView to convert the moved events into entered
179       // and exited events for the individual views.
180       NOTREACHED();
181       break;
182     case NSEventTypeSwipe:
183       // NSEventTypeSwipe events can't be generated using public interfaces on
184       // NSEvent, so this will need to be handled at a higher level.
185       NOTREACHED();
186       break;
187     default:
188       NOTREACHED();
189   }
192 NSEvent* CreateMouseEventInWindow(NSWindow* window,
193                                   ui::EventType event_type,
194                                   const gfx::Point& point_in_root,
195                                   int flags) {
196   NSUInteger click_count = 0;
197   if (event_type == ui::ET_MOUSE_PRESSED ||
198       event_type == ui::ET_MOUSE_RELEASED) {
199     if (flags & ui::EF_IS_TRIPLE_CLICK)
200       click_count = 3;
201     else if (flags & ui::EF_IS_DOUBLE_CLICK)
202       click_count = 2;
203     else
204       click_count = 1;
205   }
206   NSPoint point = ConvertRootPointToTarget(window, point_in_root);
207   NSUInteger modifiers = 0;
208   NSEventType type = EventTypeToNative(event_type, flags, &modifiers);
209   return [NSEvent mouseEventWithType:type
210                             location:point
211                        modifierFlags:modifiers
212                            timestamp:0
213                         windowNumber:[window windowNumber]
214                              context:nil
215                          eventNumber:0
216                           clickCount:click_count
217                             pressure:1.0];
220 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything
221 // defined inline is just a stub. Interesting overrides are defined below the
222 // class.
223 class EventGeneratorDelegateMac : public ui::EventTarget,
224                                   public ui::EventSource,
225                                   public ui::EventProcessor,
226                                   public ui::EventTargeter,
227                                   public ui::test::EventGeneratorDelegate {
228  public:
229   static EventGeneratorDelegateMac* GetInstance() {
230     return Singleton<EventGeneratorDelegateMac>::get();
231   }
233   IMP CurrentEventMethod() {
234     return swizzle_current_event_->GetOriginalImplementation();
235   }
237   // Overridden from ui::EventTarget:
238   bool CanAcceptEvent(const ui::Event& event) override { return true; }
239   ui::EventTarget* GetParentTarget() override { return NULL; }
240   scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override;
241   ui::EventTargeter* GetEventTargeter() override { return this; }
243   // Overridden from ui::EventHandler (via ui::EventTarget):
244   void OnMouseEvent(ui::MouseEvent* event) override;
245   void OnKeyEvent(ui::KeyEvent* event) override;
247   // Overridden from ui::EventSource:
248   ui::EventProcessor* GetEventProcessor() override { return this; }
250   // Overridden from ui::EventProcessor:
251   ui::EventTarget* GetRootTarget() override { return this; }
253   // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor):
254   bool CanDispatchToTarget(EventTarget* target) override { return true; }
256   // Overridden from ui::test::EventGeneratorDelegate:
257   void SetContext(ui::test::EventGenerator* owner,
258                   gfx::NativeWindow root_window,
259                   gfx::NativeWindow window) override;
260   ui::EventTarget* GetTargetAt(const gfx::Point& location) override {
261     return this;
262   }
263   ui::EventSource* GetEventSource(ui::EventTarget* target) override {
264     return this;
265   }
266   gfx::Point CenterOfTarget(const ui::EventTarget* target) const override;
267   gfx::Point CenterOfWindow(gfx::NativeWindow window) const override;
269   void ConvertPointFromTarget(const ui::EventTarget* target,
270                               gfx::Point* point) const override {}
271   void ConvertPointToTarget(const ui::EventTarget* target,
272                             gfx::Point* point) const override {}
273   void ConvertPointFromHost(const ui::EventTarget* hosted_target,
274                             gfx::Point* point) const override {}
276  private:
277   friend struct DefaultSingletonTraits<EventGeneratorDelegateMac>;
279   EventGeneratorDelegateMac();
280   ~EventGeneratorDelegateMac() override;
282   ui::test::EventGenerator* owner_;
283   NSWindow* window_;
284   scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_pressed_;
285   scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_current_event_;
286   base::scoped_nsobject<NSMenu> fake_menu_;
288   DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac);
291 EventGeneratorDelegateMac::EventGeneratorDelegateMac()
292     : owner_(NULL),
293       window_(NULL) {
294   DCHECK(!ui::test::EventGenerator::default_delegate);
295   ui::test::EventGenerator::default_delegate = this;
296   // Install a fake "edit" menu. This is normally provided by Chrome's
297   // MainMenu.xib, but src/ui shouldn't depend on that.
298   fake_menu_.reset([[NSMenu alloc] initWithTitle:@"Edit"]);
299   struct {
300     NSString* title;
301     SEL action;
302     NSString* key_equivalent;
303   } fake_menu_item[] = {
304       {@"Undo", @selector(undo:), @"z"},
305       {@"Redo", @selector(redo:), @"Z"},
306       {@"Copy", @selector(copy:), @"c"},
307       {@"Cut", @selector(cut:), @"x"},
308       {@"Paste", @selector(paste:), @"v"},
309       {@"Select All", @selector(selectAll:), @"a"},
310   };
311   for (size_t i = 0; i < arraysize(fake_menu_item); ++i) {
312     [fake_menu_ insertItemWithTitle:fake_menu_item[i].title
313                              action:fake_menu_item[i].action
314                       keyEquivalent:fake_menu_item[i].key_equivalent
315                             atIndex:i];
316   }
319 EventGeneratorDelegateMac::~EventGeneratorDelegateMac() {
320   DCHECK_EQ(this, ui::test::EventGenerator::default_delegate);
321   ui::test::EventGenerator::default_delegate = NULL;
324 scoped_ptr<ui::EventTargetIterator>
325 EventGeneratorDelegateMac::GetChildIterator() const {
326   // Return NULL to dispatch all events to the result of GetRootTarget().
327   return nullptr;
330 void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
331   // For mouse drag events, ensure the swizzled methods return the right flags.
332   base::AutoReset<ui::test::EventGenerator*> reset(&g_active_generator, owner_);
333   NSEvent* ns_event = CreateMouseEventInWindow(window_,
334                                                event->type(),
335                                                event->location(),
336                                                event->changed_button_flags());
337   if (owner_->targeting_application())
338     [NSApp sendEvent:ns_event];
339   else
340     EmulateSendEvent(window_, ns_event);
343 void EventGeneratorDelegateMac::OnKeyEvent(ui::KeyEvent* event) {
344   NSUInteger modifiers = EventFlagsToModifiers(event->flags());
345   NSEvent* ns_event = cocoa_test_event_utils::SynthesizeKeyEvent(
346       window_, event->type() == ui::ET_KEY_PRESSED, event->key_code(),
347       modifiers);
348   if (owner_->targeting_application()) {
349     [NSApp sendEvent:ns_event];
350     return;
351   }
353   if ([fake_menu_ performKeyEquivalent:ns_event])
354     return;
356   EmulateSendEvent(window_, ns_event);
359 void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner,
360                                            gfx::NativeWindow root_window,
361                                            gfx::NativeWindow window) {
362   swizzle_pressed_.reset();
363   swizzle_current_event_.reset();
364   owner_ = owner;
365   window_ = window;
367   // Normally, edit menu items have a `nil` target. This results in -[NSMenu
368   // performKeyEquivalent:] relying on -[NSApplication targetForAction:to:from:]
369   // to find a target starting at the first responder of the key window. Since
370   // non-interactive tests have no key window, that won't work. So set (or
371   // clear) the target explicitly on all menu items.
372   [[fake_menu_ itemArray] makeObjectsPerformSelector:@selector(setTarget:)
373                                           withObject:[window firstResponder]];
375   if (owner_) {
376     swizzle_pressed_.reset(new base::mac::ScopedObjCClassSwizzler(
377         [NSEvent class],
378         [NSEventDonor class],
379         @selector(pressedMouseButtons)));
380     swizzle_current_event_.reset(new base::mac::ScopedObjCClassSwizzler(
381         [NSApplication class],
382         [NSApplicationDonor class],
383         @selector(currentEvent)));
384   }
387 gfx::Point EventGeneratorDelegateMac::CenterOfTarget(
388     const ui::EventTarget* target) const {
389   DCHECK_EQ(target, this);
390   return CenterOfWindow(window_);
393 gfx::Point EventGeneratorDelegateMac::CenterOfWindow(
394     gfx::NativeWindow window) const {
395   DCHECK_EQ(window, window_);
396   return gfx::ScreenRectFromNSRect([window frame]).CenterPoint();
399 }  // namespace
401 namespace views {
402 namespace test {
404 void InitializeMacEventGeneratorDelegate() {
405   EventGeneratorDelegateMac::GetInstance();
408 }  // namespace test
409 }  // namespace views
411 @implementation NSEventDonor
413 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the
414 // active generator.
415 + (NSUInteger)pressedMouseButtons {
416   if (!g_active_generator)
417     return [NSEventDonor pressedMouseButtons];  // Call original implementation.
419   int flags = g_active_generator->flags();
420   NSUInteger bitmask = 0;
421   if (flags & ui::EF_LEFT_MOUSE_BUTTON)
422     bitmask |= 1;
423   if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
424     bitmask |= 1 << 1;
425   if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
426     bitmask |= 1 << 2;
427   return bitmask;
430 @end
432 @implementation NSApplicationDonor
434 - (NSEvent*)currentEvent {
435   if (g_current_event)
436     return g_current_event;
438   // Find the original implementation and invoke it.
439   IMP original = EventGeneratorDelegateMac::GetInstance()->CurrentEventMethod();
440   return original(self, _cmd);
443 @end