Home | History | Annotate | Download | only in test
      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