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#import <Carbon/Carbon.h> 6#import <Cocoa/Cocoa.h> 7#include <stdint.h> 8 9#include <memory> 10#include <utility> 11 12#include "base/mac/scoped_cftyperef.h" 13#include "base/macros.h" 14#include "testing/gtest/include/gtest/gtest.h" 15#import "ui/base/test/cocoa_helper.h" 16#import "ui/events/cocoa/cocoa_event_utils.h" 17#include "ui/events/event_constants.h" 18#include "ui/events/event_utils.h" 19#import "ui/events/test/cocoa_test_event_utils.h" 20#include "ui/events/types/event_type.h" 21#include "ui/gfx/geometry/point.h" 22 23namespace ui { 24 25namespace { 26 27// Although CGEventFlags is just a typedef to int in 10.10 and earlier headers, 28// the 10.11 header makes this a CF_ENUM, but doesn't give an option for "none". 29const CGEventFlags kNoEventFlags = static_cast<CGEventFlags>(0); 30 31class EventsMacTest : public CocoaTest { 32 public: 33 EventsMacTest() {} 34 35 gfx::Point Flip(gfx::Point window_location) { 36 NSRect window_frame = [test_window() frame]; 37 CGFloat content_height = 38 NSHeight([test_window() contentRectForFrameRect:window_frame]); 39 window_location.set_y(content_height - window_location.y()); 40 return window_location; 41 } 42 43 // TODO(tapted): Move this to cocoa_test_event_utils. It's not a drop-in 44 // replacement because -[NSApp sendEvent:] may route events generated this way 45 // differently. 46 NSEvent* TestMouseEvent(CGEventType type, 47 const gfx::Point& window_location, 48 CGEventFlags event_flags) { 49 // CGEventCreateMouseEvent() ignores the CGMouseButton parameter unless 50 // |type| is one of kCGEventOtherMouse{Up,Down,Dragged}. It can be an 51 // integer up to 31. However, constants are only supplied up to 2. For now, 52 // just assume "other" means the third/center mouse button, and rely on 53 // Quartz ignoring it when the type is not "other". 54 CGMouseButton other_button = kCGMouseButtonCenter; 55 CGPoint screen_point = cocoa_test_event_utils::ScreenPointFromWindow( 56 Flip(window_location).ToCGPoint(), test_window()); 57 base::ScopedCFTypeRef<CGEventRef> mouse( 58 CGEventCreateMouseEvent(nullptr, type, screen_point, other_button)); 59 CGEventSetFlags(mouse, event_flags); 60 return cocoa_test_event_utils::AttachWindowToCGEvent(mouse, test_window()); 61 } 62 63 // Creates a scroll event from a "real" mouse wheel (i.e. not a trackpad). 64 NSEvent* TestScrollEvent(const gfx::Point& window_location, 65 int32_t delta_x, 66 int32_t delta_y) { 67 bool precise = false; 68 return cocoa_test_event_utils::TestScrollEvent( 69 Flip(window_location).ToCGPoint(), test_window(), delta_x, delta_y, 70 precise, NSEventPhaseNone, NSEventPhaseNone); 71 } 72 73 // Creates the sequence of events generated by a trackpad scroll. 74 // |initial_rest| indicates whether there is a pause before scrolling starts. 75 // |delta_y| is the portion to scroll without momentum (fingers on the 76 // trackpad). |momentum_delta_y| is the momentum portion. A zero delta skips 77 // that phase (if both are zero, the |initial_rest| is cancelled). 78 NSArray* TrackpadScrollSequence(bool initial_rest, 79 int32_t delta_y, 80 int32_t momentum_delta_y); 81 82 protected: 83 const gfx::Point default_location_ = gfx::Point(10, 20); 84 85 private: 86 DISALLOW_COPY_AND_ASSIGN(EventsMacTest); 87}; 88 89// Trackpad scroll sequences below determined empirically on OSX 10.11 (linking 90// to 10.10 SDK), and dumping out with NSLog in -[NSView scrollWheel:]. First 91// created using a Magic Trackpad 2 on a MacPro. See the Trackpad* test cases 92// below for example event streams. 93NSArray* EventsMacTest::TrackpadScrollSequence(bool initial_rest, 94 int32_t delta_y, 95 int32_t momentum_delta_y) { 96 int32_t delta_x = 0; // Just test vertical scrolling for now. 97 base::scoped_nsobject<NSMutableArray> events([[NSMutableArray alloc] init]); 98 99 // Resting part. 100 if (initial_rest) { 101 // MayBegin always has a zero delta. 102 [events addObject:cocoa_test_event_utils::TestScrollEvent( 103 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 104 NSEventPhaseMayBegin, NSEventPhaseNone)]; 105 if (delta_y == 0) { 106 // Rest and release: event gets cancelled. 107 DCHECK_EQ(0, momentum_delta_y); // Pretty sure this is impossible. 108 [events addObject:cocoa_test_event_utils::TestScrollEvent( 109 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 110 NSEventPhaseCancelled, NSEventPhaseNone)]; 111 return events.autorelease(); 112 } 113 } 114 115 // With or without a rest, a begin is sent. It can have a non-zero 116 // deviceDeltaY but regular deltaY is always 0. 117 [events addObject:cocoa_test_event_utils::TestScrollEvent( 118 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 119 NSEventPhaseBegan, NSEventPhaseNone)]; 120 121 [events addObject:cocoa_test_event_utils::TestScrollEvent( 122 Flip(default_location_).ToCGPoint(), test_window(), delta_x, delta_y, 123 true, NSEventPhaseChanged, NSEventPhaseNone)]; 124 125 // With or without momentum, an end is sent for the non-momentum part. 126 [events addObject:cocoa_test_event_utils::TestScrollEvent( 127 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 128 NSEventPhaseEnded, NSEventPhaseNone)]; 129 130 if (momentum_delta_y == 0) 131 return events.autorelease(); 132 133 // Flick part. Basically the same, but with phase and momentumPhase swapped. 134 [events addObject:cocoa_test_event_utils::TestScrollEvent( 135 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 136 NSEventPhaseNone, NSEventPhaseBegan)]; 137 138 [events addObject:cocoa_test_event_utils::TestScrollEvent( 139 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 140 momentum_delta_y, true, NSEventPhaseNone, NSEventPhaseChanged)]; 141 142 [events addObject:cocoa_test_event_utils::TestScrollEvent( 143 Flip(default_location_).ToCGPoint(), test_window(), delta_x, 0, true, 144 NSEventPhaseNone, NSEventPhaseEnded)]; 145 return events.autorelease(); 146} 147 148} // namespace 149 150TEST_F(EventsMacTest, EventFlagsFromNative) { 151 // Left click. 152 NSEvent* left = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 0); 153 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON, EventFlagsFromNative(left)); 154 155 // Right click. 156 NSEvent* right = cocoa_test_event_utils::MouseEventWithType(NSRightMouseUp, 157 0); 158 EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(right)); 159 160 // Middle click. 161 NSEvent* middle = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseUp, 162 0); 163 EXPECT_EQ(EF_MIDDLE_MOUSE_BUTTON, EventFlagsFromNative(middle)); 164 165 // Caps + Left 166 NSEvent* caps = cocoa_test_event_utils::MouseEventWithType( 167 NSLeftMouseUp, NSAlphaShiftKeyMask); 168 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_CAPS_LOCK_ON, EventFlagsFromNative(caps)); 169 170 // Shift + Left 171 NSEvent* shift = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 172 NSShiftKeyMask); 173 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_SHIFT_DOWN, EventFlagsFromNative(shift)); 174 175 // Ctrl + Left. Note we map this to a right click on Mac and remove Control. 176 NSEvent* ctrl = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 177 NSControlKeyMask); 178 EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON, EventFlagsFromNative(ctrl)); 179 180 // Ctrl + Right. Remains a right click. 181 NSEvent* ctrl_right = cocoa_test_event_utils::MouseEventWithType( 182 NSRightMouseUp, NSControlKeyMask); 183 EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON | EF_CONTROL_DOWN, 184 EventFlagsFromNative(ctrl_right)); 185 186 // Alt + Left 187 NSEvent* alt = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 188 NSAlternateKeyMask); 189 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_ALT_DOWN, EventFlagsFromNative(alt)); 190 191 // Cmd + Left 192 NSEvent* cmd = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseUp, 193 NSCommandKeyMask); 194 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN, EventFlagsFromNative(cmd)); 195 196 // Shift + Ctrl + Left. Also mapped to a right-click. Control removed. 197 NSEvent* shiftctrl = cocoa_test_event_utils::MouseEventWithType( 198 NSLeftMouseUp, NSShiftKeyMask | NSControlKeyMask); 199 EXPECT_EQ(EF_RIGHT_MOUSE_BUTTON | EF_SHIFT_DOWN, 200 EventFlagsFromNative(shiftctrl)); 201 202 // Cmd + Alt + Right 203 NSEvent* cmdalt = cocoa_test_event_utils::MouseEventWithType( 204 NSLeftMouseUp, NSCommandKeyMask | NSAlternateKeyMask); 205 EXPECT_EQ(EF_LEFT_MOUSE_BUTTON | EF_COMMAND_DOWN | EF_ALT_DOWN, 206 EventFlagsFromNative(cmdalt)); 207} 208 209// Tests mouse button presses and mouse wheel events. 210TEST_F(EventsMacTest, ButtonEvents) { 211 gfx::Point location(5, 10); 212 gfx::Vector2d offset; 213 214 NSEvent* event = 215 TestMouseEvent(kCGEventLeftMouseDown, location, kNoEventFlags); 216 EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); 217 EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); 218 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 219 220 event = 221 TestMouseEvent(kCGEventOtherMouseDown, location, kCGEventFlagMaskShift); 222 EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); 223 EXPECT_EQ(ui::EF_MIDDLE_MOUSE_BUTTON | ui::EF_SHIFT_DOWN, 224 ui::EventFlagsFromNative(event)); 225 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 226 227 event = TestMouseEvent(kCGEventRightMouseUp, location, kNoEventFlags); 228 EXPECT_EQ(ui::ET_MOUSE_RELEASED, ui::EventTypeFromNative(event)); 229 EXPECT_EQ(ui::EF_RIGHT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); 230 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 231 232 // Scroll up. 233 event = TestScrollEvent(location, 0, 1); 234 EXPECT_EQ(ui::ET_SCROLL, ui::EventTypeFromNative(event)); 235 EXPECT_EQ(0, ui::EventFlagsFromNative(event)); 236 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 237 offset = ui::GetMouseWheelOffset(event); 238 EXPECT_GT(offset.y(), 0); 239 EXPECT_EQ(0, offset.x()); 240 241 // Scroll down. 242 event = TestScrollEvent(location, 0, -1); 243 EXPECT_EQ(ui::ET_SCROLL, ui::EventTypeFromNative(event)); 244 EXPECT_EQ(0, ui::EventFlagsFromNative(event)); 245 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 246 offset = ui::GetMouseWheelOffset(event); 247 EXPECT_LT(offset.y(), 0); 248 EXPECT_EQ(0, offset.x()); 249 250 // Scroll left. 251 event = TestScrollEvent(location, 1, 0); 252 EXPECT_EQ(ui::ET_SCROLL, ui::EventTypeFromNative(event)); 253 EXPECT_EQ(0, ui::EventFlagsFromNative(event)); 254 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 255 offset = ui::GetMouseWheelOffset(event); 256 EXPECT_EQ(0, offset.y()); 257 EXPECT_GT(offset.x(), 0); 258 259 // Scroll right. 260 event = TestScrollEvent(location, -1, 0); 261 EXPECT_EQ(ui::ET_SCROLL, ui::EventTypeFromNative(event)); 262 EXPECT_EQ(0, ui::EventFlagsFromNative(event)); 263 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 264 offset = ui::GetMouseWheelOffset(event); 265 EXPECT_EQ(0, offset.y()); 266 EXPECT_LT(offset.x(), 0); 267} 268 269// Test correct location when the window has a native titlebar. 270TEST_F(EventsMacTest, NativeTitlebarEventLocation) { 271 gfx::Point location(5, 10); 272 NSUInteger style_mask = NSTitledWindowMask | NSClosableWindowMask | 273 NSMiniaturizableWindowMask | NSResizableWindowMask; 274 275 // First check that the window provided by ui::CocoaTest is how we think. 276 DCHECK_EQ(NSBorderlessWindowMask, [test_window() styleMask]); 277 [test_window() setStyleMask:style_mask]; 278 DCHECK_EQ(style_mask, [test_window() styleMask]); 279 280 // EventLocationFromNative should behave the same as the ButtonEvents test. 281 NSEvent* event = 282 TestMouseEvent(kCGEventLeftMouseDown, location, kNoEventFlags); 283 EXPECT_EQ(ui::ET_MOUSE_PRESSED, ui::EventTypeFromNative(event)); 284 EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, ui::EventFlagsFromNative(event)); 285 EXPECT_EQ(location, gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 286 287 // And be explicit, to ensure the test doesn't depend on some property of the 288 // test harness. The change to the frame rect could be OS-specfic, so set it 289 // to a known value. 290 const CGFloat kTestHeight = 400; 291 NSRect content_rect = NSMakeRect(0, 0, 600, kTestHeight); 292 NSRect frame_rect = [test_window() frameRectForContentRect:content_rect]; 293 [test_window() setFrame:frame_rect display:YES]; 294 event = [NSEvent mouseEventWithType:NSLeftMouseDown 295 location:NSMakePoint(0, 0) // Bottom-left corner. 296 modifierFlags:0 297 timestamp:0 298 windowNumber:[test_window() windowNumber] 299 context:nil 300 eventNumber:0 301 clickCount:0 302 pressure:1.0]; 303 // Bottom-left corner should be flipped. 304 EXPECT_EQ(gfx::Point(0, kTestHeight), 305 gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 306 307 // Removing the border, and sending the same event should move it down in the 308 // toolkit-views coordinate system. 309 int height_change = NSHeight(frame_rect) - kTestHeight; 310 EXPECT_GT(height_change, 0); 311 [test_window() setStyleMask:NSBorderlessWindowMask]; 312 [test_window() setFrame:frame_rect display:YES]; 313 EXPECT_EQ(gfx::Point(0, kTestHeight + height_change), 314 gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 315} 316 317// Test that window-less events preserve location (are in screen coordinates). 318TEST_F(EventsMacTest, NoWindowLocation) { 319 const CGPoint location = CGPointMake(5, 10); 320 321 base::ScopedCFTypeRef<CGEventRef> mouse(CGEventCreateMouseEvent( 322 nullptr, kCGEventMouseMoved, location, kCGMouseButtonLeft)); 323 324 NSEvent* event = [NSEvent eventWithCGEvent:mouse]; 325 EXPECT_FALSE(event.window); 326 EXPECT_EQ(gfx::Point(location), 327 gfx::ToFlooredPoint(ui::EventLocationFromNative(event))); 328} 329 330// Testing for ui::EventTypeFromNative() not covered by ButtonEvents. 331TEST_F(EventsMacTest, EventTypeFromNative) { 332 NSEvent* event = cocoa_test_event_utils::KeyEventWithType(NSKeyDown, 0); 333 EXPECT_EQ(ui::ET_KEY_PRESSED, ui::EventTypeFromNative(event)); 334 335 event = cocoa_test_event_utils::KeyEventWithType(NSKeyUp, 0); 336 EXPECT_EQ(ui::ET_KEY_RELEASED, ui::EventTypeFromNative(event)); 337 338 event = cocoa_test_event_utils::MouseEventWithType(NSLeftMouseDragged, 0); 339 EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); 340 event = cocoa_test_event_utils::MouseEventWithType(NSRightMouseDragged, 0); 341 EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); 342 event = cocoa_test_event_utils::MouseEventWithType(NSOtherMouseDragged, 0); 343 EXPECT_EQ(ui::ET_MOUSE_DRAGGED, ui::EventTypeFromNative(event)); 344 345 event = cocoa_test_event_utils::MouseEventWithType(NSMouseMoved, 0); 346 EXPECT_EQ(ui::ET_MOUSE_MOVED, ui::EventTypeFromNative(event)); 347 348 event = cocoa_test_event_utils::EnterEvent(); 349 EXPECT_EQ(ui::ET_MOUSE_ENTERED, ui::EventTypeFromNative(event)); 350 event = cocoa_test_event_utils::ExitEvent(); 351 EXPECT_EQ(ui::ET_MOUSE_EXITED, ui::EventTypeFromNative(event)); 352} 353 354// Verify that a mouse wheel scroll event is correctly lacking phase data. 355TEST_F(EventsMacTest, MouseWheelScroll) { 356 int32_t wheel_delta_y = 2; 357 358 NSEvent* ns_wheel = TestScrollEvent(default_location_, 0, wheel_delta_y); 359 EXPECT_FALSE([ns_wheel hasPreciseScrollingDeltas]); 360 ui::ScrollEvent wheel(ns_wheel); 361 EXPECT_EQ(ui::ET_SCROLL, wheel.type()); 362 363 // Currently wheel events still say two for finger count, but this may change. 364 EXPECT_EQ(2, wheel.finger_count()); 365 366 // Note the phase is "end" for wheel events, not "none". There is always an 367 // "end" when no more events are expected. 368 EXPECT_EQ(ui::EventMomentumPhase::END, wheel.momentum_phase()); 369 EXPECT_EQ(default_location_, wheel.location()); 370 371 float pixel_delta_y = wheel_delta_y * ui::kScrollbarPixelsPerCocoaTick; 372 EXPECT_EQ(pixel_delta_y, wheel.y_offset_ordinal()); 373 EXPECT_EQ(0, wheel.x_offset_ordinal()); 374} 375 376// Test the event flow for a trackpad "rest" that doesn't result in scrolling 377// nor momentum. Also check the boring stuff like type, finger count and 378// location, which isn't phase-specific. 379// Sequence: 380// (1) NSEvent: type=ScrollWheel loc=(780,41) time=14909.3 flags=0x100 win=<set> 381// {deviceD,d}elta{X,Y,Z}=0 count:0 phase=MayBegin momentumPhase=None 382// (2) NSEvent: type=ScrollWheel loc=(780,41) time=14912.9 flags=0x100 win=<set> 383// {deviceD,d}elta{X,Y,Z}=0 count:0 phase=Cancelled momentumPhase=None. 384TEST_F(EventsMacTest, TrackpadRestRelease) { 385 NSArray* ns_events = TrackpadScrollSequence(true, 0, 0); 386 ASSERT_EQ(2u, [ns_events count]); 387 EXPECT_TRUE([ns_events[0] hasPreciseScrollingDeltas]); 388 389 ui::ScrollEvent rest(ns_events[0]); 390 EXPECT_EQ(ui::ET_SCROLL, rest.type()); 391 EXPECT_EQ(2, rest.finger_count()); 392 EXPECT_EQ(ui::EventMomentumPhase::MAY_BEGIN, rest.momentum_phase()); 393 EXPECT_EQ(0, rest.y_offset_ordinal()); 394 EXPECT_EQ(default_location_, rest.location()); 395 396 ui::ScrollEvent cancel(ns_events[1]); 397 EXPECT_EQ(ui::ET_SCROLL, cancel.type()); 398 EXPECT_EQ(2, cancel.finger_count()); 399 EXPECT_EQ(ui::EventMomentumPhase::END, cancel.momentum_phase()); 400 EXPECT_EQ(0, cancel.y_offset_ordinal()); 401 EXPECT_EQ(default_location_, cancel.location()); 402} 403 404// Test the event flow for touching the trackpad while "in motion" already, then 405// pausing so that a flick is not generated. deltaX and deltaZ are always zero. 406// Note, deviceDeltaX may take on an integer value even though deltaX is zero. 407// Sequence: 408// (1) NSEvent: type=ScrollWheel loc=(780,41) time=15263.2 flags=0x100 win=<set> 409// deltaY=0.000000 deviceDeltaY=1.000000 phase=Began momentumPhase=None 410// (n) NSEvent: type=ScrollWheel loc=(780,41) time=15263.2 flags=0x100 win=<set> 411// deltaY=0.400024 deviceDeltaY=3.000000 phase=Changed momentumPhase=None 412// (3) NSEvent: type=ScrollWheel loc=(780,41) time=15264.2 flags=0x100 win=<set> 413// deltaY=0.000000 deviceDeltaY=0.000000 phase=Ended momentumPhase=None. 414TEST_F(EventsMacTest, TrackpadScrollThenRest) { 415 int32_t delta_y = 21; 416 417 NSArray* ns_events = TrackpadScrollSequence(false, delta_y, 0); 418 ASSERT_EQ(3u, [ns_events count]); 419 420 ui::ScrollEvent begin(ns_events[0]); 421 EXPECT_EQ(ui::EventMomentumPhase::MAY_BEGIN, begin.momentum_phase()); 422 EXPECT_EQ(0, begin.y_offset_ordinal()); 423 424 ui::ScrollEvent update(ns_events[1]); 425 // There's no momentum yet, so phase is none. 426 EXPECT_EQ(ui::EventMomentumPhase::NONE, update.momentum_phase()); 427 // Note: No pixel conversion for "precise" deltas. 428 EXPECT_EQ(delta_y, update.y_offset_ordinal()); 429 430 ui::ScrollEvent end(ns_events[2]); 431 EXPECT_EQ(ui::EventMomentumPhase::END, end.momentum_phase()); 432 EXPECT_EQ(0, end.y_offset_ordinal()); 433} 434 435// Same as the above, but with an initial rest, which is not cancelled. This 436// results in multiple MAY_BEGIN phases. 437TEST_F(EventsMacTest, TrackpadRestThenScrollThenRest) { 438 int32_t delta_y = 21; 439 440 NSArray* ns_events = TrackpadScrollSequence(true, delta_y, 0); 441 ASSERT_EQ(4u, [ns_events count]); 442 443 ui::ScrollEvent rest(ns_events[0]); 444 EXPECT_EQ(ui::EventMomentumPhase::MAY_BEGIN, rest.momentum_phase()); 445 EXPECT_EQ(0, rest.y_offset_ordinal()); 446 447 ui::ScrollEvent begin(ns_events[1]); 448 EXPECT_EQ(ui::EventMomentumPhase::MAY_BEGIN, begin.momentum_phase()); 449 EXPECT_EQ(0, begin.y_offset_ordinal()); 450 451 ui::ScrollEvent update(ns_events[2]); 452 EXPECT_EQ(ui::EventMomentumPhase::NONE, update.momentum_phase()); 453 EXPECT_EQ(delta_y, update.y_offset_ordinal()); 454 455 ui::ScrollEvent end(ns_events[3]); 456 EXPECT_EQ(ui::EventMomentumPhase::END, end.momentum_phase()); 457 EXPECT_EQ(0, end.y_offset_ordinal()); 458} 459 460// Test the event flows that lead to momentum, with and without an initial rest. 461// Example sequence (no initial rest): 462// (1) NSEvent: type=ScrollWheel loc=(780,41) time=15187.5 flags=0x100 win=<set> 463// deltaY=0.000000 deviceDeltaY=1.000000 phase=Began momentumPhase=None 464// (n) NSEvent: type=ScrollWheel loc=(780,41) time=15187.5 flags=0x100 win=<set> 465// deltaY=0.500031 deviceDeltaY=4.000000 phase=Changed momentumPhase=None 466// (3) NSEvent: type=ScrollWheel loc=(780,41) time=15187.6 flags=0x100 win=<set> 467// deltaY=0.000000 deviceDeltaY=0.000000 phase=Ended momentumPhase=None 468// (4) NSEvent: type=ScrollWheel loc=(780,41) time=15187.6 flags=0x100 win=<set> 469// deltaY=0.900055 deviceDeltaY=3.000000 phase=None momentumPhase=Began 470// (n) NSEvent: type=ScrollWheel loc=(780,41) time=15187.6 flags=0x100 win=<set> 471// deltaY=0.300018 deviceDeltaY=3.000000 phase=None momentumPhase=Changed 472// (6) NSEvent: type=ScrollWheel loc=(780,41) time=15188.0 flags=0x100 win=<set> 473// deltaY=0.000000 deviceDeltaY=0.000000 phase=None momentumPhase=Ended. 474TEST_F(EventsMacTest, TrackpadScrollThenFlick) { 475 int32_t delta_y = 21; 476 int32_t momentum_delta_y = 33; 477 478 NSArray* ns_events = TrackpadScrollSequence(false, delta_y, momentum_delta_y); 479 ASSERT_EQ(6u, [ns_events count]); 480 481 // Non-momentum part. 482 { 483 ui::ScrollEvent begin(ns_events[0]); 484 EXPECT_EQ(ui::EventMomentumPhase::MAY_BEGIN, begin.momentum_phase()); 485 EXPECT_EQ(0, begin.y_offset_ordinal()); 486 487 ui::ScrollEvent update(ns_events[1]); 488 EXPECT_EQ(ui::EventMomentumPhase::NONE, update.momentum_phase()); 489 EXPECT_EQ(delta_y, update.y_offset_ordinal()); 490 491 ui::ScrollEvent end(ns_events[2]); 492 // Even though the event stream continues, AppKit doesn't provide a way to 493 // know this without peeking at future events. So this "end" mid-stream is 494 // unavoidable. 495 EXPECT_EQ(ui::EventMomentumPhase::END, end.momentum_phase()); 496 EXPECT_EQ(0, end.y_offset_ordinal()); 497 } 498 // Momentum part. 499 { 500 ui::ScrollEvent begin(ns_events[3]); 501 // Since a momentum "begin" is really a continuation of the stream, it's 502 // currently treated as an update, but the offsets should always be zero. 503 EXPECT_EQ(ui::EventMomentumPhase::INERTIAL_UPDATE, begin.momentum_phase()); 504 EXPECT_EQ(0, begin.y_offset_ordinal()); 505 506 ui::ScrollEvent update(ns_events[4]); 507 EXPECT_EQ(ui::EventMomentumPhase::INERTIAL_UPDATE, update.momentum_phase()); 508 EXPECT_EQ(momentum_delta_y, update.y_offset_ordinal()); 509 510 ui::ScrollEvent end(ns_events[5]); 511 EXPECT_EQ(ui::EventMomentumPhase::END, end.momentum_phase()); 512 EXPECT_EQ(0, end.y_offset_ordinal()); 513 } 514} 515 516// Check that NSFlagsChanged event is translated to key press or release event. 517TEST_F(EventsMacTest, HandleModifierOnlyKeyEvents) { 518 struct { 519 const char* description; 520 NSEventModifierFlags modifier_flags; 521 uint16_t key_code; 522 EventType expected_type; 523 KeyboardCode expected_key_code; 524 } test_cases[] = { 525 {"CapsLock pressed", NSAlphaShiftKeyMask, kVK_CapsLock, ET_KEY_PRESSED, 526 VKEY_CAPITAL}, 527 {"CapsLock released", 0, kVK_CapsLock, ET_KEY_RELEASED, VKEY_CAPITAL}, 528 {"Shift pressed", NSShiftKeyMask, kVK_Shift, ET_KEY_PRESSED, VKEY_SHIFT}, 529 {"Shift released", 0, kVK_Shift, ET_KEY_RELEASED, VKEY_SHIFT}, 530 {"Control pressed", NSControlKeyMask, kVK_Control, ET_KEY_PRESSED, 531 VKEY_CONTROL}, 532 {"Control released", 0, kVK_Control, ET_KEY_RELEASED, VKEY_CONTROL}, 533 {"Option pressed", NSAlternateKeyMask, kVK_Option, ET_KEY_PRESSED, 534 VKEY_MENU}, 535 {"Option released", 0, kVK_Option, ET_KEY_RELEASED, VKEY_MENU}, 536 {"Command pressed", NSCommandKeyMask, kVK_Command, ET_KEY_PRESSED, 537 VKEY_LWIN}, 538 {"Command released", 0, kVK_Command, ET_KEY_RELEASED, VKEY_LWIN}, 539 {"Shift pressed with CapsLock on", NSShiftKeyMask | NSAlphaShiftKeyMask, 540 kVK_Shift, ET_KEY_PRESSED, VKEY_SHIFT}, 541 {"Shift released with CapsLock off", NSAlphaShiftKeyMask, kVK_Shift, 542 ET_KEY_RELEASED, VKEY_SHIFT}, 543 }; 544 for (const auto& test_case : test_cases) { 545 SCOPED_TRACE(::testing::Message() << "While checking case: " 546 << test_case.description); 547 NSEvent* native_event = cocoa_test_event_utils::KeyEventWithModifierOnly( 548 test_case.key_code, test_case.modifier_flags); 549 std::unique_ptr<ui::Event> event = EventFromNative(native_event); 550 EXPECT_TRUE(event); 551 EXPECT_EQ(test_case.expected_type, event->type()); 552 EXPECT_EQ(test_case.expected_key_code, event->AsKeyEvent()->key_code()); 553 } 554} 555 556} // namespace ui 557