Home | History | Annotate | Download | only in cocoa
      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 #include "ui/events/event_utils.h"
      6 
      7 #import <Cocoa/Cocoa.h>
      8 
      9 #include "base/mac/scoped_cftyperef.h"
     10 #import "base/mac/scoped_objc_class_swizzler.h"
     11 #include "base/memory/scoped_ptr.h"
     12 #include "testing/gtest/include/gtest/gtest.h"
     13 #include "ui/events/event_constants.h"
     14 #import "ui/events/test/cocoa_test_event_utils.h"
     15 #include "ui/gfx/point.h"
     16 #import "ui/gfx/test/ui_cocoa_test_helper.h"
     17 
     18 namespace {
     19 
     20 NSWindow* g_test_window = nil;
     21 
     22 }  // namespace
     23 
     24 // Mac APIs for creating test events are frustrating. Quartz APIs have, e.g.,
     25 // CGEventCreateMouseEvent() which can't set a window or modifier flags.
     26 // Cocoa APIs have +[NSEvent mouseEventWithType:..] which can't set
     27 // buttonNumber or scroll deltas. To work around this, these tests use some
     28 // Objective C magic to donate member functions to NSEvent temporarily.
     29 @interface MiddleMouseButtonNumberDonor : NSObject
     30 @end
     31 
     32 @interface TestWindowDonor : NSObject
     33 @end
     34 
     35 @implementation MiddleMouseButtonNumberDonor
     36 - (NSInteger)buttonNumber { return 2; }
     37 @end
     38 
     39 @implementation TestWindowDonor
     40 - (NSWindow*)window { return g_test_window; }
     41 @end
     42 
     43 namespace ui {
     44 
     45 namespace {
     46 
     47 class EventsMacTest : public CocoaTest {
     48  public:
     49   EventsMacTest() {}
     50 
     51   gfx::Point Flip(gfx::Point window_location) {
     52     NSRect window_frame = [test_window() frame];
     53     CGFloat content_height =
     54         NSHeight([test_window() contentRectForFrameRect:window_frame]);
     55     window_location.set_y(content_height - window_location.y());
     56     return window_location;
     57   }
     58 
     59   void SwizzleMiddleMouseButton() {
     60     DCHECK(!swizzler_);
     61     swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
     62         [NSEvent class],
     63         [MiddleMouseButtonNumberDonor class],
     64         @selector(buttonNumber)));
     65   }
     66 
     67   void SwizzleTestWindow() {
     68     DCHECK(!g_test_window);
     69     DCHECK(!swizzler_);
     70     g_test_window = test_window();
     71     swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
     72         [NSEvent class], [TestWindowDonor class], @selector(window)));
     73   }
     74 
     75   void ClearSwizzle() {
     76     swizzler_.reset();
     77     g_test_window = nil;
     78   }
     79 
     80   NSEvent* TestMouseEvent(NSEventType type,
     81                           const gfx::Point &window_location,
     82                           NSInteger modifier_flags) {
     83     NSPoint point = NSPointFromCGPoint(Flip(window_location).ToCGPoint());
     84     return [NSEvent mouseEventWithType:type
     85                               location:point
     86                          modifierFlags:modifier_flags
     87                              timestamp:0
     88                           windowNumber:[test_window() windowNumber]
     89                                context:nil
     90                            eventNumber:0
     91                             clickCount:0
     92                               pressure:1.0];
     93   }
     94 
     95   NSEvent* TestScrollEvent(const gfx::Point& window_location,
     96                            int32_t delta_x,
     97                            int32_t delta_y) {
     98     SwizzleTestWindow();
     99     base::ScopedCFTypeRef<CGEventRef> scroll(
    100         CGEventCreateScrollWheelEvent(NULL,
    101                                       kCGScrollEventUnitLine,
    102                                       2,
    103                                       delta_y,
    104                                       delta_x));
    105     // CGEvents are always in global display coordinates. These are like screen
    106     // coordinates, but flipped. But first the point needs to be converted out
    107     // of window coordinates (which also requires flipping).
    108     NSPoint window_point =
    109         NSPointFromCGPoint(Flip(window_location).ToCGPoint());
    110     NSPoint screen_point = [test_window() convertBaseToScreen:window_point];
    111     CGFloat primary_screen_height =
    112         NSHeight([[[NSScreen screens] objectAtIndex:0] frame]);
    113     screen_point.y = primary_screen_height - screen_point.y;
    114     CGEventSetLocation(scroll, NSPointToCGPoint(screen_point));
    115     return [NSEvent eventWithCGEvent:scroll];
    116   }
    117 
    118  private:
    119   scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzler_;
    120 
    121   DISALLOW_COPY_AND_ASSIGN(EventsMacTest);
    122 };
    123 
    124 }  // namespace
    125 
    126 TEST_F(EventsMacTest, EventFlagsFromNative) {
    127   // Left click.
    128   NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0);
    129   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left));
    130 
    131   // Right click.
    132   NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp,
    133                                                               0);
    134   EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right));
    135 
    136   // Middle click.
    137   NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp,
    138                                                                0);
    139   EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle));
    140 
    141   // Caps + Left
    142   NSEvent* caps = cocoa_test_event_utils::MouseEventWithType(
    143       NSLeftMouseUp, NSAlphaShiftKeyMask);
    144   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CAPS_LOCK_DOWN,
    145             EventFlagsFromNative(caps));
    146 
    147   // Shift + Left
    148   NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
    149                                                               NSShiftKeyMask);
    150   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift));
    151 
    152   // Ctrl + Left
    153   NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
    154                                                              NSControlKeyMask);
    155   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CONTROL_DOWN, EventFlagsFromNative(ctrl));
    156 
    157   // Alt + Left
    158   NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
    159                                                             NSAlternateKeyMask);
    160   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt));
    161 
    162   // Cmd + Left
    163   NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
    164                                                             NSCommandKeyMask);
    165   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN, EventFlagsFromNative(cmd));
    166 
    167   // Shift + Ctrl + Left
    168   NSEvent* shiftctrl = cocoa_test_event_utils::MouseEventWithType(
    169       NSLeftMouseUp, NSShiftKeyMask | NSControlKeyMask);
    170   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN | EF_CONTROL_DOWN,
    171             EventFlagsFromNative(shiftctrl));
    172 
    173   // Cmd + Alt + Right
    174   NSEvent* cmdalt = cocoa_test_event_utils::MouseEventWithType(
    175       NSLeftMouseUp, NSCommandKeyMask | NSAlternateKeyMask);
    176   EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN | EF_ALT_DOWN,
    177             EventFlagsFromNative(cmdalt));
    178 }
    179 
    180 // Tests mouse button presses and mouse wheel events.
    181 TEST_F(EventsMacTest, ButtonEvents) {
    182   gfx::Point location(5, 10);
    183   gfx::Vector2d offset;
    184 
    185   NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0);
    186   EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
    187   EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
    188   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    189 
    190   SwizzleMiddleMouseButton();
    191   event = TestMouseEvent(NSOtherMouseDown, location, NSShiftKeyMask);
    192   EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
    193   EXPECT_EQ(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_SHIFT_DOWN,
    194             ui::EventFlagsFromNative(event));
    195   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    196   ClearSwizzle();
    197 
    198   event = TestMouseEvent(NSRightMouseUp, location, 0);
    199   EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(event));
    200   EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
    201   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    202 
    203   // Scroll up.
    204   event = TestScrollEvent(location, 0, 1);
    205   EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
    206   EXPECT_EQ(0, ui::EventFlagsFromNative(event));
    207   EXPECT_EQ(location.ToString(), ui::EventLocationFromNative(event).ToString());
    208   offset = ui::GetMouseWheelOffset(event);
    209   EXPECT_GT(offset.y(), 0);
    210   EXPECT_EQ(0, offset.x());
    211   ClearSwizzle();
    212 
    213   // Scroll down.
    214   event = TestScrollEvent(location, 0, -1);
    215   EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
    216   EXPECT_EQ(0, ui::EventFlagsFromNative(event));
    217   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    218   offset = ui::GetMouseWheelOffset(event);
    219   EXPECT_LT(offset.y(), 0);
    220   EXPECT_EQ(0, offset.x());
    221   ClearSwizzle();
    222 
    223   // Scroll left.
    224   event = TestScrollEvent(location, 1, 0);
    225   EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
    226   EXPECT_EQ(0, ui::EventFlagsFromNative(event));
    227   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    228   offset = ui::GetMouseWheelOffset(event);
    229   EXPECT_EQ(0, offset.y());
    230   EXPECT_GT(offset.x(), 0);
    231   ClearSwizzle();
    232 
    233   // Scroll right.
    234   event = TestScrollEvent(location, -1, 0);
    235   EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event));
    236   EXPECT_EQ(0, ui::EventFlagsFromNative(event));
    237   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    238   offset = ui::GetMouseWheelOffset(event);
    239   EXPECT_EQ(0, offset.y());
    240   EXPECT_LT(offset.x(), 0);
    241   ClearSwizzle();
    242 }
    243 
    244 // Test correct location when the window has a native titlebar.
    245 TEST_F(EventsMacTest, NativeTitlebarEventLocation) {
    246   gfx::Point location(5, 10);
    247   NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask |
    248                           NSMiniaturizableWindowMask | NSResizableWindowMask;
    249 
    250   // First check that the window provided by ui::CocoaTest is how we think.
    251   DCHECK_EQ(NSBorderlessWindowMask, [test_window() styleMask]);
    252   [test_window() setStyleMask:style_mask];
    253   DCHECK_EQ(style_mask, [test_window() styleMask]);
    254 
    255   // EventLocationFromNative should behave the same as the ButtonEvents test.
    256   NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0);
    257   EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event));
    258   EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event));
    259   EXPECT_EQ(location, ui::EventLocationFromNative(event));
    260 
    261   // And be explicit, to ensure the test doesn't depend on some property of the
    262   // test harness. The change to the frame rect could be OS-specfic, so set it
    263   // to a known value.
    264   const CGFloat kTestHeight = 400;
    265   NSRect content_rect = NSMakeRect(0, 0, 600, kTestHeight);
    266   NSRect frame_rect = [test_window() frameRectForContentRect:content_rect];
    267   [test_window() setFrame:frame_rect display:YES];
    268   event = [NSEvent mouseEventWithType:NSLeftMouseDown
    269                              location:NSMakePoint(0, 0)  // Bottom-left corner.
    270                         modifierFlags:0
    271                             timestamp:0
    272                          windowNumber:[test_window() windowNumber]
    273                               context:nil
    274                           eventNumber:0
    275                            clickCount:0
    276                              pressure:1.0];
    277   // Bottom-left corner should be flipped.
    278   EXPECT_EQ(gfx::Point(0, kTestHeight), ui::EventLocationFromNative(event));
    279 
    280   // Removing the border, and sending the same event should move it down in the
    281   // toolkit-views coordinate system.
    282   int height_change = NSHeight(frame_rect) - kTestHeight;
    283   EXPECT_GT(height_change, 0);
    284   [test_window() setStyleMask:NSBorderlessWindowMask];
    285   [test_window() setFrame:frame_rect display:YES];
    286   EXPECT_EQ(gfx::Point(0, kTestHeight + height_change),
    287             ui::EventLocationFromNative(event));
    288 }
    289 
    290 // Testing for ui::EventTypeFromNative() not covered by ButtonEvents.
    291 TEST_F(EventsMacTest, EventTypeFromNative) {
    292   NSEvent* event = cocoa_test_event_utils::KeyEventWithType(NSKeyDown, 0);
    293   EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(event));
    294 
    295   event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0);
    296   EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event));
    297 
    298   event = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseDragged, 0);
    299   EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
    300   event = cocoa_test_event_utils::MouseEventWithType(NSRightMouseDragged, 0);
    301   EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
    302   event = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseDragged, 0);
    303   EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event));
    304 
    305   event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0);
    306   EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event));
    307 
    308   event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseEntered);
    309   EXPECT_EQ(ui::ET_MOUSE_ENTERED, ui::EventTypeFromNative(event));
    310   event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseExited);
    311   EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(event));
    312 }
    313 
    314 }  // namespace ui
    315