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"
20 NSWindow* g_test_window = nil;
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
32 @interface TestWindowDonor : NSObject
35 @implementation MiddleMouseButtonNumberDonor
36 - (NSInteger)buttonNumber { return 2; }
39 @implementation TestWindowDonor
40 - (NSWindow*)window { return g_test_window; }
47 class EventsMacTest : public CocoaTest {
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;
59 void SwizzleMiddleMouseButton() {
61 swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
63 [MiddleMouseButtonNumberDonor class],
64 @selector(buttonNumber)));
67 void SwizzleTestWindow() {
68 DCHECK(!g_test_window);
70 g_test_window = test_window();
71 swizzler_.reset(new base::mac::ScopedObjCClassSwizzler(
72 [NSEvent class], [TestWindowDonor class], @selector(window)));
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
86 modifierFlags:modifier_flags
88 windowNumber:[test_window() windowNumber]
95 NSEvent* TestScrollEvent(const gfx::Point& window_location,
99 base::ScopedCFTypeRef<CGEventRef> scroll(
100 CGEventCreateScrollWheelEvent(NULL,
101 kCGScrollEventUnitLine,
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];
119 scoped_ptr<base::mac::ScopedObjCClassSwizzler> swizzler_;
121 DISALLOW_COPY_AND_ASSIGN(EventsMacTest);
126 TEST_F(EventsMacTest, EventFlagsFromNative) {
128 NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0);
129 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left));
132 NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp,
134 EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right));
137 NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp,
139 EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle));
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));
148 NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
150 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift));
153 NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
155 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CONTROL_DOWN, EventFlagsFromNative(ctrl));
158 NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
160 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt));
163 NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp,
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));
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));
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));
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());
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());
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);
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);
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
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.
272 windowNumber:[test_window() windowNumber]
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));