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_objc_class_swizzler.h"
8 #include "base/memory/singleton.h"
9 #include "ui/events/event_processor.h"
10 #include "ui/events/event_target.h"
11 #include "ui/events/event_target_iterator.h"
12 #include "ui/events/event_targeter.h"
13 #include "ui/events/test/event_generator.h"
14 #include "ui/gfx/mac/coordinate_conversion.h"
18 // Singleton to provide state for swizzled Objective C methods.
19 ui::test::EventGenerator* g_active_generator = NULL;
23 @interface NSEventDonor : NSObject
26 @implementation NSEventDonor
28 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the
30 + (NSUInteger)pressedMouseButtons {
31 int flags = g_active_generator->flags();
32 NSUInteger bitmask = 0;
33 if (flags & ui::EF_LEFT_MOUSE_BUTTON)
35 if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
37 if (flags & ui::EF_MIDDLE_MOUSE_BUTTON)
46 NSPoint ConvertRootPointToTarget(NSWindow* target,
47 const gfx::Point& point_in_root) {
48 // Normally this would do [NSWindow convertScreenToBase:]. However, Cocoa can
49 // reposition the window on screen and make things flaky. Initially, just
50 // assume that the contentRect of |target| is at the top-left corner of the
52 NSRect content_rect = [target contentRectForFrameRect:[target frame]];
53 return NSMakePoint(point_in_root.x(),
54 NSHeight(content_rect) - point_in_root.y());
57 // Inverse of ui::EventFlagsFromModifiers().
58 NSUInteger EventFlagsToModifiers(int flags) {
59 NSUInteger modifiers = 0;
60 modifiers |= (flags & ui::EF_CAPS_LOCK_DOWN) ? NSAlphaShiftKeyMask : 0;
61 modifiers |= (flags & ui::EF_SHIFT_DOWN) ? NSShiftKeyMask : 0;
62 modifiers |= (flags & ui::EF_CONTROL_DOWN) ? NSControlKeyMask : 0;
63 modifiers |= (flags & ui::EF_ALT_DOWN) ? NSAlternateKeyMask : 0;
64 modifiers |= (flags & ui::EF_COMMAND_DOWN) ? NSCommandKeyMask : 0;
65 // ui::EF_*_MOUSE_BUTTON not handled here.
66 // NSFunctionKeyMask, NSNumericPadKeyMask and NSHelpKeyMask not mapped.
70 // Picks the corresponding mouse event type for the buttons set in |flags|.
71 NSEventType PickMouseEventType(int flags,
75 if (flags & ui::EF_LEFT_MOUSE_BUTTON)
77 if (flags & ui::EF_RIGHT_MOUSE_BUTTON)
82 // Inverse of ui::EventTypeFromNative(). If non-null |modifiers| will be set
83 // using the inverse of ui::EventFlagsFromNSEventWithModifiers().
84 NSEventType EventTypeToNative(ui::EventType ui_event_type,
86 NSUInteger* modifiers) {
88 *modifiers = EventFlagsToModifiers(flags);
89 switch (ui_event_type) {
92 case ui::ET_KEY_PRESSED:
94 case ui::ET_KEY_RELEASED:
96 case ui::ET_MOUSE_PRESSED:
97 return PickMouseEventType(flags,
101 case ui::ET_MOUSE_RELEASED:
102 return PickMouseEventType(flags,
106 case ui::ET_MOUSE_DRAGGED:
107 return PickMouseEventType(flags,
110 NSOtherMouseDragged);
111 case ui::ET_MOUSE_MOVED:
113 case ui::ET_MOUSEWHEEL:
114 return NSScrollWheel;
115 case ui::ET_MOUSE_ENTERED:
116 return NSMouseEntered;
117 case ui::ET_MOUSE_EXITED:
118 return NSMouseExited;
119 case ui::ET_SCROLL_FLING_START:
120 return NSEventTypeSwipe;
127 // Emulate the dispatching that would be performed by -[NSWindow sendEvent:].
128 // sendEvent is a black box which (among other things) will try to peek at the
129 // event queue and can block indefinitely.
130 void EmulateSendEvent(NSWindow* window, NSEvent* event) {
131 NSResponder* responder = [window firstResponder];
132 switch ([event type]) {
134 [responder keyDown:event];
137 [responder keyUp:event];
141 // For mouse events, NSWindow will use -[NSView hitTest:] for the initial
142 // mouseDown, and then keep track of the NSView returned. The toolkit-views
143 // RootView does this too. So, for tests, assume tracking will be done there,
144 // and the NSWindow's contentView is wrapping a views::internal::RootView.
145 responder = [window contentView];
146 switch ([event type]) {
147 case NSLeftMouseDown:
148 [responder mouseDown:event];
150 case NSRightMouseDown:
151 [responder rightMouseDown:event];
153 case NSOtherMouseDown:
154 [responder otherMouseDown:event];
157 [responder mouseUp:event];
160 [responder rightMouseUp:event];
163 [responder otherMouseUp:event];
165 case NSLeftMouseDragged:
166 [responder mouseDragged:event];
168 case NSRightMouseDragged:
169 [responder rightMouseDragged:event];
171 case NSOtherMouseDragged:
172 [responder otherMouseDragged:event];
175 // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that
176 // NSTrackingAreas have been appropriately installed on |responder|.
177 [responder mouseMoved:event];
180 [responder scrollWheel:event];
184 // With the assumptions in NSMouseMoved, it doesn't make sense for the
185 // generator to handle entered/exited separately. It's the responsibility
186 // of views::internal::RootView to convert the moved events into entered
187 // and exited events for the individual views.
190 case NSEventTypeSwipe:
191 // NSEventTypeSwipe events can't be generated using public interfaces on
192 // NSEvent, so this will need to be handled at a higher level.
200 void DispatchMouseEventInWindow(NSWindow* window,
201 ui::EventType event_type,
202 const gfx::Point& point_in_root,
204 NSUInteger click_count = 0;
205 if (event_type == ui::ET_MOUSE_PRESSED ||
206 event_type == ui::ET_MOUSE_RELEASED) {
207 if (flags & ui::EF_IS_TRIPLE_CLICK)
209 else if (flags & ui::EF_IS_DOUBLE_CLICK)
214 NSPoint point = ConvertRootPointToTarget(window, point_in_root);
215 NSUInteger modifiers = 0;
216 NSEventType type = EventTypeToNative(event_type, flags, &modifiers);
217 NSEvent* event = [NSEvent mouseEventWithType:type
219 modifierFlags:modifiers
221 windowNumber:[window windowNumber]
224 clickCount:click_count
227 // Typically events go through NSApplication. For tests, dispatch the event
228 // directly to make things more predicatble.
229 EmulateSendEvent(window, event);
232 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything
233 // defined inline is just a stub. Interesting overrides are defined below the
235 class EventGeneratorDelegateMac : public ui::EventTarget,
236 public ui::EventSource,
237 public ui::EventProcessor,
238 public ui::EventTargeter,
239 public ui::test::EventGeneratorDelegate {
241 static EventGeneratorDelegateMac* GetInstance() {
242 return Singleton<EventGeneratorDelegateMac>::get();
245 // Overridden from ui::EventTarget:
246 virtual bool CanAcceptEvent(const ui::Event& event) override { return true; }
247 virtual ui::EventTarget* GetParentTarget() override { return NULL; }
248 virtual scoped_ptr<ui::EventTargetIterator> GetChildIterator() const override;
249 virtual ui::EventTargeter* GetEventTargeter() override {
253 // Overridden from ui::EventHandler (via ui::EventTarget):
254 virtual void OnMouseEvent(ui::MouseEvent* event) override;
256 // Overridden from ui::EventSource:
257 virtual ui::EventProcessor* GetEventProcessor() override { return this; }
259 // Overridden from ui::EventProcessor:
260 virtual ui::EventTarget* GetRootTarget() override { return this; }
262 // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor):
263 virtual bool CanDispatchToTarget(EventTarget* target) override {
267 // Overridden from ui::test::EventGeneratorDelegate:
268 virtual void SetContext(ui::test::EventGenerator* owner,
269 gfx::NativeWindow root_window,
270 gfx::NativeWindow window) override;
271 virtual ui::EventTarget* GetTargetAt(const gfx::Point& location) override {
274 virtual ui::EventSource* GetEventSource(ui::EventTarget* target) override {
277 virtual gfx::Point CenterOfTarget(
278 const ui::EventTarget* target) const override;
279 virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const override;
281 virtual void ConvertPointFromTarget(const ui::EventTarget* target,
282 gfx::Point* point) const override {}
283 virtual void ConvertPointToTarget(const ui::EventTarget* target,
284 gfx::Point* point) const override {}
285 virtual void ConvertPointFromHost(const ui::EventTarget* hosted_target,
286 gfx::Point* point) const override {}
289 friend struct DefaultSingletonTraits<EventGeneratorDelegateMac>;
291 EventGeneratorDelegateMac();
292 virtual ~EventGeneratorDelegateMac();
294 ui::test::EventGenerator* owner_;
296 scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_pressed_;
298 DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac);
301 EventGeneratorDelegateMac::EventGeneratorDelegateMac()
304 DCHECK(!ui::test::EventGenerator::default_delegate);
305 ui::test::EventGenerator::default_delegate = this;
308 EventGeneratorDelegateMac::~EventGeneratorDelegateMac() {
309 DCHECK_EQ(this, ui::test::EventGenerator::default_delegate);
310 ui::test::EventGenerator::default_delegate = NULL;
313 scoped_ptr<ui::EventTargetIterator>
314 EventGeneratorDelegateMac::GetChildIterator() const {
315 // Return NULL to dispatch all events to the result of GetRootTarget().
316 return scoped_ptr<ui::EventTargetIterator>();
319 void EventGeneratorDelegateMac::OnMouseEvent(ui::MouseEvent* event) {
320 // For mouse drag events, ensure the swizzled methods return the right flags.
321 base::AutoReset<ui::test::EventGenerator*> reset(&g_active_generator, owner_);
322 DispatchMouseEventInWindow(window_,
325 event->changed_button_flags());
328 void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner,
329 gfx::NativeWindow root_window,
330 gfx::NativeWindow window) {
331 swizzle_pressed_.reset();
335 swizzle_pressed_.reset(new base::mac::ScopedObjCClassSwizzler(
337 [NSEventDonor class],
338 @selector(pressedMouseButtons)));
342 gfx::Point EventGeneratorDelegateMac::CenterOfTarget(
343 const ui::EventTarget* target) const {
344 DCHECK_EQ(target, this);
345 return CenterOfWindow(window_);
348 gfx::Point EventGeneratorDelegateMac::CenterOfWindow(
349 gfx::NativeWindow window) const {
350 DCHECK_EQ(window, window_);
351 return gfx::ScreenRectFromNSRect([window frame]).CenterPoint();
359 void InitializeMacEventGeneratorDelegate() {
360 EventGeneratorDelegateMac::GetInstance();