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. 4 5 #import <Cocoa/Cocoa.h> 6 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" 15 16 namespace { 17 18 // Singleton to provide state for swizzled Objective C methods. 19 ui::test::EventGenerator* g_active_generator = NULL; 20 21 } // namespace 22 23 @interface NSEventDonor : NSObject 24 @end 25 26 @implementation NSEventDonor 27 28 // Donate +[NSEvent pressedMouseButtons] by retrieving the flags from the 29 // active generator. 30 + (NSUInteger)pressedMouseButtons { 31 int flags = g_active_generator->flags(); 32 NSUInteger bitmask = 0; 33 if (flags & ui::EF_LEFT_MOUSE_BUTTON) 34 bitmask |= 1; 35 if (flags & ui::EF_RIGHT_MOUSE_BUTTON) 36 bitmask |= 1 << 1; 37 if (flags & ui::EF_MIDDLE_MOUSE_BUTTON) 38 bitmask |= 1 << 2; 39 return bitmask; 40 } 41 42 @end 43 44 namespace { 45 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 51 // screen. 52 NSRect content_rect = [target contentRectForFrameRect:[target frame]]; 53 return NSMakePoint(point_in_root.x(), 54 NSHeight(content_rect) - point_in_root.y()); 55 } 56 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. 67 return modifiers; 68 } 69 70 // Picks the corresponding mouse event type for the buttons set in |flags|. 71 NSEventType PickMouseEventType(int flags, 72 NSEventType left, 73 NSEventType right, 74 NSEventType other) { 75 if (flags & ui::EF_LEFT_MOUSE_BUTTON) 76 return left; 77 if (flags & ui::EF_RIGHT_MOUSE_BUTTON) 78 return right; 79 return other; 80 } 81 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, 85 int flags, 86 NSUInteger* modifiers) { 87 if (modifiers) 88 *modifiers = EventFlagsToModifiers(flags); 89 switch (ui_event_type) { 90 case ui::ET_UNKNOWN: 91 return 0; 92 case ui::ET_KEY_PRESSED: 93 return NSKeyDown; 94 case ui::ET_KEY_RELEASED: 95 return NSKeyUp; 96 case ui::ET_MOUSE_PRESSED: 97 return PickMouseEventType(flags, 98 NSLeftMouseDown, 99 NSRightMouseDown, 100 NSOtherMouseDown); 101 case ui::ET_MOUSE_RELEASED: 102 return PickMouseEventType(flags, 103 NSLeftMouseUp, 104 NSRightMouseUp, 105 NSOtherMouseUp); 106 case ui::ET_MOUSE_DRAGGED: 107 return PickMouseEventType(flags, 108 NSLeftMouseDragged, 109 NSRightMouseDragged, 110 NSOtherMouseDragged); 111 case ui::ET_MOUSE_MOVED: 112 return NSMouseMoved; 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; 121 default: 122 NOTREACHED(); 123 return 0; 124 } 125 } 126 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]) { 133 case NSKeyDown: 134 [responder keyDown:event]; 135 return; 136 case NSKeyUp: 137 [responder keyUp:event]; 138 return; 139 } 140 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]; 149 break; 150 case NSRightMouseDown: 151 [responder rightMouseDown:event]; 152 break; 153 case NSOtherMouseDown: 154 [responder otherMouseDown:event]; 155 break; 156 case NSLeftMouseUp: 157 [responder mouseUp:event]; 158 break; 159 case NSRightMouseUp: 160 [responder rightMouseUp:event]; 161 break; 162 case NSOtherMouseUp: 163 [responder otherMouseUp:event]; 164 break; 165 case NSLeftMouseDragged: 166 [responder mouseDragged:event]; 167 break; 168 case NSRightMouseDragged: 169 [responder rightMouseDragged:event]; 170 break; 171 case NSOtherMouseDragged: 172 [responder otherMouseDragged:event]; 173 break; 174 case NSMouseMoved: 175 // Assumes [NSWindow acceptsMouseMovedEvents] would return YES, and that 176 // NSTrackingAreas have been appropriately installed on |responder|. 177 [responder mouseMoved:event]; 178 break; 179 case NSScrollWheel: 180 [responder scrollWheel:event]; 181 break; 182 case NSMouseEntered: 183 case NSMouseExited: 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. 188 NOTREACHED(); 189 break; 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. 193 NOTREACHED(); 194 break; 195 default: 196 NOTREACHED(); 197 } 198 } 199 200 void DispatchMouseEventInWindow(NSWindow* window, 201 ui::EventType event_type, 202 const gfx::Point& point_in_root, 203 int flags) { 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) 208 click_count = 3; 209 else if (flags & ui::EF_IS_DOUBLE_CLICK) 210 click_count = 2; 211 else 212 click_count = 1; 213 } 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 218 location:point 219 modifierFlags:modifiers 220 timestamp:0 221 windowNumber:[window windowNumber] 222 context:nil 223 eventNumber:0 224 clickCount:click_count 225 pressure:1.0]; 226 227 // Typically events go through NSApplication. For tests, dispatch the event 228 // directly to make things more predicatble. 229 EmulateSendEvent(window, event); 230 } 231 232 // Implementation of ui::test::EventGeneratorDelegate for Mac. Everything 233 // defined inline is just a stub. Interesting overrides are defined below the 234 // class. 235 class EventGeneratorDelegateMac : public ui::EventTarget, 236 public ui::EventSource, 237 public ui::EventProcessor, 238 public ui::EventTargeter, 239 public ui::test::EventGeneratorDelegate { 240 public: 241 static EventGeneratorDelegateMac* GetInstance() { 242 return Singleton<EventGeneratorDelegateMac>::get(); 243 } 244 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 { 250 return this; 251 } 252 253 // Overridden from ui::EventHandler (via ui::EventTarget): 254 virtual void OnMouseEvent(ui::MouseEvent* event) OVERRIDE; 255 256 // Overridden from ui::EventSource: 257 virtual ui::EventProcessor* GetEventProcessor() OVERRIDE { return this; } 258 259 // Overridden from ui::EventProcessor: 260 virtual ui::EventTarget* GetRootTarget() OVERRIDE { return this; } 261 262 // Overridden from ui::EventDispatcherDelegate (via ui::EventProcessor): 263 virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE { 264 return true; 265 } 266 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 { 272 return this; 273 } 274 virtual ui::EventSource* GetEventSource(ui::EventTarget* target) OVERRIDE { 275 return this; 276 } 277 virtual gfx::Point CenterOfTarget( 278 const ui::EventTarget* target) const OVERRIDE; 279 virtual gfx::Point CenterOfWindow(gfx::NativeWindow window) const OVERRIDE; 280 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 {} 287 288 private: 289 friend struct DefaultSingletonTraits<EventGeneratorDelegateMac>; 290 291 EventGeneratorDelegateMac(); 292 virtual ~EventGeneratorDelegateMac(); 293 294 ui::test::EventGenerator* owner_; 295 NSWindow* window_; 296 scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzle_pressed_; 297 298 DISALLOW_COPY_AND_ASSIGN(EventGeneratorDelegateMac); 299 }; 300 301 EventGeneratorDelegateMac::EventGeneratorDelegateMac() 302 : owner_(NULL), 303 window_(NULL) { 304 DCHECK(!ui::test::EventGenerator::default_delegate); 305 ui::test::EventGenerator::default_delegate = this; 306 } 307 308 EventGeneratorDelegateMac::~EventGeneratorDelegateMac() { 309 DCHECK_EQ(this, ui::test::EventGenerator::default_delegate); 310 ui::test::EventGenerator::default_delegate = NULL; 311 } 312 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>(); 317 } 318 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_, 323 event->type(), 324 event->location(), 325 event->changed_button_flags()); 326 } 327 328 void EventGeneratorDelegateMac::SetContext(ui::test::EventGenerator* owner, 329 gfx::NativeWindow root_window, 330 gfx::NativeWindow window) { 331 swizzle_pressed_.reset(); 332 owner_ = owner; 333 window_ = window; 334 if (owner_) { 335 swizzle_pressed_.reset(new base::mac::ScopedObjCClassSwizzler( 336 [NSEvent class], 337 [NSEventDonor class], 338 @selector(pressedMouseButtons))); 339 } 340 } 341 342 gfx::Point EventGeneratorDelegateMac::CenterOfTarget( 343 const ui::EventTarget* target) const { 344 DCHECK_EQ(target, this); 345 return CenterOfWindow(window_); 346 } 347 348 gfx::Point EventGeneratorDelegateMac::CenterOfWindow( 349 gfx::NativeWindow window) const { 350 DCHECK_EQ(window, window_); 351 return gfx::ScreenRectFromNSRect([window frame]).CenterPoint(); 352 } 353 354 } // namespace 355 356 namespace views { 357 namespace test { 358 359 void InitializeMacEventGeneratorDelegate() { 360 EventGeneratorDelegateMac::GetInstance(); 361 } 362 363 } // namespace test 364 } // namespace views 365