// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/events/event_utils.h" #import #include "base/mac/scoped_cftyperef.h" #import "base/mac/scoped_objc_class_swizzler.h" #include "base/memory/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/event_constants.h" #import "ui/events/test/cocoa_test_event_utils.h" #include "ui/gfx/point.h" #import "ui/gfx/test/ui_cocoa_test_helper.h" namespace { NSWindow* g_test_window = nil; } // namespace // Mac APIs for creating test events are frustrating. Quartz APIs have, e.g., // CGEventCreateMouseEvent() which can't set a window or modifier flags. // Cocoa APIs have +[NSEvent mouseEventWithType:..] which can't set // buttonNumber or scroll deltas. To work around this, these tests use some // Objective C magic to donate member functions to NSEvent temporarily. @interface MiddleMouseButtonNumberDonor : NSObject @end @interface TestWindowDonor : NSObject @end @implementation MiddleMouseButtonNumberDonor - (NSInteger)buttonNumber { return 2; } @end @implementation TestWindowDonor - (NSWindow*)window { return g_test_window; } @end namespace ui { namespace { class EventsMacTest : public CocoaTest { public: EventsMacTest() {} gfx::Point Flip(gfx::Point window_location) { NSRect window_frame = [test_window() frame]; CGFloat content_height = NSHeight([test_window() contentRectForFrameRect:window_frame]); window_location.set_y(content_height - window_location.y()); return window_location; } void SwizzleMiddleMouseButton() { DCHECK(!swizzler_); swizzler_.reset(new base::mac::ScopedObjCClassSwizzler( [NSEvent class], [MiddleMouseButtonNumberDonor class], @selector(buttonNumber))); } void SwizzleTestWindow() { DCHECK(!g_test_window); DCHECK(!swizzler_); g_test_window = test_window(); swizzler_.reset(new base::mac::ScopedObjCClassSwizzler( [NSEvent class], [TestWindowDonor class], @selector(window))); } void ClearSwizzle() { swizzler_.reset(); g_test_window = nil; } NSEvent* TestMouseEvent(NSEventType type, const gfx::Point &window_location, NSInteger modifier_flags) { NSPoint point = NSPointFromCGPoint(Flip(window_location).ToCGPoint()); return [NSEvent mouseEventWithType:type location:point modifierFlags:modifier_flags timestamp:0 windowNumber:[test_window() windowNumber] context:nil eventNumber:0 clickCount:0 pressure:1.0]; } NSEvent* TestScrollEvent(const gfx::Point& window_location, int32_t delta_x, int32_t delta_y) { SwizzleTestWindow(); base::ScopedCFTypeRef scroll( CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 2, delta_y, delta_x)); // CGEvents are always in global display coordinates. These are like screen // coordinates, but flipped. But first the point needs to be converted out // of window coordinates (which also requires flipping). NSPoint window_point = NSPointFromCGPoint(Flip(window_location).ToCGPoint()); NSPoint screen_point = [test_window() convertBaseToScreen:window_point]; CGFloat primary_screen_height = NSHeight([[[NSScreen screens] objectAtIndex:0] frame]); screen_point.y = primary_screen_height - screen_point.y; CGEventSetLocation(scroll, NSPointToCGPoint(screen_point)); return [NSEvent eventWithCGEvent:scroll]; } private: scoped_ptr swizzler_; DISALLOW_COPY_AND_ASSIGN(EventsMacTest); }; } // namespace TEST_F(EventsMacTest, EventFlagsFromNative) { // Left click. NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left)); // Right click. NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp, 0); EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right)); // Middle click. NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp, 0); EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle)); // Caps + Left NSEvent* caps = cocoa_test_event_utils::MouseEventWithType( NSLeftMouseUp, NSAlphaShiftKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CAPS_LOCK_DOWN, EventFlagsFromNative(caps)); // Shift + Left NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, NSShiftKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift)); // Ctrl + Left NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, NSControlKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CONTROL_DOWN, EventFlagsFromNative(ctrl)); // Alt + Left NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, NSAlternateKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt)); // Cmd + Left NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, NSCommandKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN, EventFlagsFromNative(cmd)); // Shift + Ctrl + Left NSEvent* shiftctrl = cocoa_test_event_utils::MouseEventWithType( NSLeftMouseUp, NSShiftKeyMask | NSControlKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN | EF_CONTROL_DOWN, EventFlagsFromNative(shiftctrl)); // Cmd + Alt + Right NSEvent* cmdalt = cocoa_test_event_utils::MouseEventWithType( NSLeftMouseUp, NSCommandKeyMask | NSAlternateKeyMask); EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN | EF_ALT_DOWN, EventFlagsFromNative(cmdalt)); } // Tests mouse button presses and mouse wheel events. TEST_F(EventsMacTest, ButtonEvents) { gfx::Point location(5, 10); gfx::Vector2d offset; NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0); EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); SwizzleMiddleMouseButton(); event = TestMouseEvent(NSOtherMouseDown, location, NSShiftKeyMask); EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); EXPECT_EQ(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_SHIFT_DOWN, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); ClearSwizzle(); event = TestMouseEvent(NSRightMouseUp, location, 0); EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(event)); EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); // Scroll up. event = TestScrollEvent(location, 0, 1); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); EXPECT_EQ(0, ui::EventFlagsFromNative(event)); EXPECT_EQ(location.ToString(), ui::EventLocationFromNative(event).ToString()); offset = ui::GetMouseWheelOffset(event); EXPECT_GT(offset.y(), 0); EXPECT_EQ(0, offset.x()); ClearSwizzle(); // Scroll down. event = TestScrollEvent(location, 0, -1); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); EXPECT_EQ(0, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); offset = ui::GetMouseWheelOffset(event); EXPECT_LT(offset.y(), 0); EXPECT_EQ(0, offset.x()); ClearSwizzle(); // Scroll left. event = TestScrollEvent(location, 1, 0); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); EXPECT_EQ(0, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); offset = ui::GetMouseWheelOffset(event); EXPECT_EQ(0, offset.y()); EXPECT_GT(offset.x(), 0); ClearSwizzle(); // Scroll right. event = TestScrollEvent(location, -1, 0); EXPECT_EQ(ui::ET_MOUSEWHEEL, ui::EventTypeFromNative(event)); EXPECT_EQ(0, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); offset = ui::GetMouseWheelOffset(event); EXPECT_EQ(0, offset.y()); EXPECT_LT(offset.x(), 0); ClearSwizzle(); } // Test correct location when the window has a native titlebar. TEST_F(EventsMacTest, NativeTitlebarEventLocation) { gfx::Point location(5, 10); NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask; // First check that the window provided by ui::CocoaTest is how we think. DCHECK_EQ(NSBorderlessWindowMask, [test_window() styleMask]); [test_window() setStyleMask:style_mask]; DCHECK_EQ(style_mask, [test_window() styleMask]); // EventLocationFromNative should behave the same as the ButtonEvents test. NSEvent* event = TestMouseEvent(NSLeftMouseDown, location, 0); EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); EXPECT_EQ(location, ui::EventLocationFromNative(event)); // And be explicit, to ensure the test doesn't depend on some property of the // test harness. The change to the frame rect could be OS-specfic, so set it // to a known value. const CGFloat kTestHeight = 400; NSRect content_rect = NSMakeRect(0, 0, 600, kTestHeight); NSRect frame_rect = [test_window() frameRectForContentRect:content_rect]; [test_window() setFrame:frame_rect display:YES]; event = [NSEvent mouseEventWithType:NSLeftMouseDown location:NSMakePoint(0, 0) // Bottom-left corner. modifierFlags:0 timestamp:0 windowNumber:[test_window() windowNumber] context:nil eventNumber:0 clickCount:0 pressure:1.0]; // Bottom-left corner should be flipped. EXPECT_EQ(gfx::Point(0, kTestHeight), ui::EventLocationFromNative(event)); // Removing the border, and sending the same event should move it down in the // toolkit-views coordinate system. int height_change = NSHeight(frame_rect) - kTestHeight; EXPECT_GT(height_change, 0); [test_window() setStyleMask:NSBorderlessWindowMask]; [test_window() setFrame:frame_rect display:YES]; EXPECT_EQ(gfx::Point(0, kTestHeight + height_change), ui::EventLocationFromNative(event)); } // Testing for ui::EventTypeFromNative() not covered by ButtonEvents. TEST_F(EventsMacTest, EventTypeFromNative) { NSEvent* event = cocoa_test_event_utils::KeyEventWithType(NSKeyDown, 0); EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0); EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseDragged, 0); EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::MouseEventWithType(NSRightMouseDragged, 0); EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseDragged, 0); EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0); EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseEntered); EXPECT_EQ(ui::ET_MOUSE_ENTERED, ui::EventTypeFromNative(event)); event = cocoa_test_event_utils::EnterExitEventWithType(NSMouseExited); EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(event)); } } // namespace ui