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