Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / ui / events / cocoa / events_mac_unittest.mm
blobc4169bf80de7729064c57b1db26a9544d4e46957
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 #include "ui/events/event_utils.h"
7 #import <Cocoa/Cocoa.h>
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/geometry/point.h"
16 #import "ui/gfx/test/ui_cocoa_test_helper.h"
18 namespace {
20 NSWindow* g_test_window = nil;
22 }  // namespace
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
32 @interface TestWindowDonor : NSObject
33 @end
35 @implementation MiddleMouseButtonNumberDonor
36 - (NSInteger)buttonNumber { return 2; }
37 @end
39 @implementation TestWindowDonor
40 - (NSWindow*)window { return g_test_window; }
41 @end
43 namespace ui {
45 namespace {
47 class EventsMacTest : public CocoaTest {
48  public:
49   EventsMacTest() {}
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   }
59   void SwizzleMiddleMouseButton() {
60     DCHECK(!swizzler_);
61     swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
62         [NSEvent class],
63         [MiddleMouseButtonNumberDonor class],
64         @selector(buttonNumber)));
65   }
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   }
75   void ClearSwizzle() {
76     swizzler_.reset();
77     g_test_window = nil;
78   }
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   }
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   }
118  private:
119   scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzler_;
121   DISALLOW_COPY_AND_ASSIGN(EventsMacTest);
124 }  // namespace
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));
131   // Right click.
132   NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp,
133                                                               0);
134   EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right));
136   // Middle click.
137   NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp,
138                                                                0);
139   EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle));
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));
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));
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));
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));
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));
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));
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));
180 // Tests mouse button presses and mouse wheel events.
181 TEST_F(EventsMacTest, ButtonEvents) {
182   gfx::Point location(5, 10);
183   gfx::Vector2d offset;
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));
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();
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));
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();
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();
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();
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();
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;
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]);
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));
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));
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));
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));
295   event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0);
296   EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event));
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));
305   event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0);
306   EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event));
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));
314 }  // namespace ui