1// Copyright 2013 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 "testing/gtest/include/gtest/gtest.h" 6#import "chrome/browser/ui/cocoa/cocoa_test_helper.h" 7 8#include "base/mac/scoped_nsobject.h" 9#import "base/mac/sdk_forward_declarations.h" 10#import "chrome/browser/renderer_host/chrome_render_widget_host_view_mac_history_swiper.h" 11#import "third_party/ocmock/OCMock/OCMock.h" 12#import "third_party/ocmock/ocmock_extensions.h" 13#include "third_party/WebKit/public/web/WebInputEvent.h" 14 15@interface HistorySwiper (MacHistorySwiperTest) 16- (BOOL)systemSettingsAllowHistorySwiping:(NSEvent*)event; 17- (BOOL)browserCanNavigateInDirection: 18 (history_swiper::NavigationDirection)forward 19 event:(NSEvent*)event; 20- (void)endHistorySwipe; 21- (void)beginHistorySwipeInDirection: 22 (history_swiper::NavigationDirection)goForward 23 event:(NSEvent*)event; 24- (void)navigateBrowserInDirection:(history_swiper::NavigationDirection)forward; 25- (void)initiateMagicMouseHistorySwipe:(BOOL)isRightScroll 26 event:(NSEvent*)event; 27@end 28 29class MacHistorySwiperTest : public CocoaTest { 30 public: 31 virtual void SetUp() OVERRIDE { 32 CocoaTest::SetUp(); 33 34 [HistorySwiper resetMagicMouseState]; 35 36 view_ = [[NSView alloc] init]; 37 id mockDelegate = 38 [OCMockObject mockForProtocol:@protocol(HistorySwiperDelegate)]; 39 [[[mockDelegate stub] andReturn:view_] viewThatWantsHistoryOverlay]; 40 [[[mockDelegate stub] andReturnBool:YES] shouldAllowHistorySwiping]; 41 42 base::scoped_nsobject<HistorySwiper> historySwiper( 43 [[HistorySwiper alloc] initWithDelegate:mockDelegate]); 44 id mockHistorySwiper = [OCMockObject partialMockForObject:historySwiper]; 45 [[[mockHistorySwiper stub] andReturnBool:YES] 46 systemSettingsAllowHistorySwiping:[OCMArg any]]; 47 [[[mockHistorySwiper stub] andReturnBool:YES] 48 browserCanNavigateInDirection:history_swiper::kForwards 49 event:[OCMArg any]]; 50 [[[mockHistorySwiper stub] andReturnBool:YES] 51 browserCanNavigateInDirection:history_swiper::kBackwards 52 event:[OCMArg any]]; 53 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 54 ++begin_count_; 55 // beginHistorySwipeInDirection: calls endHistorySwipe internally. 56 --end_count_; 57 }] andForwardToRealObject] 58 beginHistorySwipeInDirection:history_swiper::kForwards 59 event:[OCMArg any]]; 60 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 61 ++begin_count_; 62 // beginHistorySwipeInDirection: calls endHistorySwipe internally. 63 --end_count_; 64 }] andForwardToRealObject] 65 beginHistorySwipeInDirection:history_swiper::kBackwards 66 event:[OCMArg any]]; 67 [[[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 68 ++end_count_; 69 }] andForwardToRealObject] endHistorySwipe]; 70 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 71 navigated_right_ = true; 72 }] navigateBrowserInDirection:history_swiper::kForwards]; 73 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 74 navigated_left_ = true; 75 }] navigateBrowserInDirection:history_swiper::kBackwards]; 76 77 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 78 magic_mouse_history_swipe_ = true; 79 }] initiateMagicMouseHistorySwipe:YES event:[OCMArg any]]; 80 [[[mockHistorySwiper stub] andDo:^(NSInvocation* invocation) { 81 magic_mouse_history_swipe_ = true; 82 }] initiateMagicMouseHistorySwipe:NO event:[OCMArg any]]; 83 84 historySwiper_ = [mockHistorySwiper retain]; 85 86 begin_count_ = 0; 87 end_count_ = 0; 88 navigated_right_ = false; 89 navigated_left_ = false; 90 magic_mouse_history_swipe_ = false; 91 } 92 93 virtual void TearDown() OVERRIDE { 94 [view_ release]; 95 [historySwiper_ release]; 96 CocoaTest::TearDown(); 97 } 98 99 void startGestureInMiddle(); 100 void moveGestureInMiddle(); 101 void moveGestureAtPoint(NSPoint point); 102 void momentumMoveGestureAtPoint(NSPoint point); 103 void endGestureAtPoint(NSPoint point); 104 void rendererACKForBeganEvent(); 105 106 HistorySwiper* historySwiper_; 107 NSView* view_; 108 int begin_count_; 109 int end_count_; 110 bool navigated_right_; 111 bool navigated_left_; 112 bool magic_mouse_history_swipe_; 113}; 114 115NSPoint makePoint(CGFloat x, CGFloat y) { 116 NSPoint point; 117 point.x = x; 118 point.y = y; 119 return point; 120} 121 122id mockEventWithPoint(NSPoint point, NSEventType type) { 123 id mockEvent = [OCMockObject mockForClass:[NSEvent class]]; 124 id mockTouch = [OCMockObject mockForClass:[NSTouch class]]; 125 [[[mockTouch stub] andReturnNSPoint:point] normalizedPosition]; 126 NSArray* touches = @[mockTouch]; 127 [[[mockEvent stub] andReturn:touches] touchesMatchingPhase:NSTouchPhaseAny 128 inView:[OCMArg any]]; 129 [[[mockEvent stub] andReturnBool:NO] isDirectionInvertedFromDevice]; 130 [(NSEvent*)[[mockEvent stub] andReturnValue:OCMOCK_VALUE(type)] type]; 131 132 return mockEvent; 133} 134 135id scrollWheelEventWithPhase(NSEventPhase phase, 136 NSEventPhase momentumPhase, 137 CGFloat scrollingDeltaX, 138 CGFloat scrollingDeltaY) { 139 // The point isn't used, so we pass in bogus data. 140 id event = mockEventWithPoint(makePoint(0,0), NSScrollWheel); 141 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase]; 142 [(NSEvent*) 143 [[event stub] andReturnValue:OCMOCK_VALUE(momentumPhase)] momentumPhase]; 144 [(NSEvent*)[[event stub] 145 andReturnValue:OCMOCK_VALUE(scrollingDeltaX)] scrollingDeltaX]; 146 [(NSEvent*)[[event stub] 147 andReturnValue:OCMOCK_VALUE(scrollingDeltaY)] scrollingDeltaY]; 148 return event; 149} 150 151id scrollWheelEventWithPhase(NSEventPhase phase, 152 NSEventPhase momentumPhase) { 153 return scrollWheelEventWithPhase(phase, momentumPhase, 0, 0); 154} 155 156id scrollWheelEventWithPhase(NSEventPhase phase) { 157 return scrollWheelEventWithPhase(phase, NSEventPhaseNone); 158} 159 160void MacHistorySwiperTest::startGestureInMiddle() { 161 NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture); 162 [historySwiper_ touchesBeganWithEvent:event]; 163 [historySwiper_ beginGestureWithEvent:event]; 164 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 165 [historySwiper_ handleEvent:scrollEvent]; 166} 167 168void MacHistorySwiperTest::moveGestureInMiddle() { 169 moveGestureAtPoint(makePoint(0.5, 0.5)); 170 171 // Callbacks from blink to set the relevant state for history swiping. 172 rendererACKForBeganEvent(); 173} 174 175void MacHistorySwiperTest::moveGestureAtPoint(NSPoint point) { 176 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 177 [historySwiper_ touchesMovedWithEvent:event]; 178 179 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged); 180 [historySwiper_ handleEvent:scrollEvent]; 181} 182 183void MacHistorySwiperTest::momentumMoveGestureAtPoint(NSPoint point) { 184 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 185 [historySwiper_ touchesMovedWithEvent:event]; 186 187 NSEvent* scrollEvent = 188 scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged); 189 [historySwiper_ handleEvent:scrollEvent]; 190} 191 192void MacHistorySwiperTest::endGestureAtPoint(NSPoint point) { 193 NSEvent* event = mockEventWithPoint(point, NSEventTypeGesture); 194 [historySwiper_ touchesEndedWithEvent:event]; 195 196 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseEnded); 197 [historySwiper_ handleEvent:scrollEvent]; 198} 199 200void MacHistorySwiperTest::rendererACKForBeganEvent() { 201 blink::WebMouseWheelEvent event; 202 event.phase = blink::WebMouseWheelEvent::PhaseBegan; 203 [historySwiper_ rendererHandledWheelEvent:event consumed:NO]; 204} 205 206// Test that a simple left-swipe causes navigation. 207TEST_F(MacHistorySwiperTest, SwipeLeft) { 208 // These tests require 10.7+ APIs. 209 if (![NSEvent 210 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 211 return; 212 213 startGestureInMiddle(); 214 moveGestureInMiddle(); 215 216 EXPECT_EQ(begin_count_, 0); 217 EXPECT_EQ(end_count_, 0); 218 219 moveGestureAtPoint(makePoint(0.2, 0.5)); 220 EXPECT_EQ(begin_count_, 1); 221 EXPECT_EQ(end_count_, 0); 222 EXPECT_FALSE(navigated_right_); 223 EXPECT_FALSE(navigated_left_); 224 225 endGestureAtPoint(makePoint(0.2, 0.5)); 226 EXPECT_EQ(begin_count_, 1); 227 EXPECT_EQ(end_count_, 1); 228 EXPECT_FALSE(navigated_right_); 229 EXPECT_TRUE(navigated_left_); 230} 231 232// Test that a simple right-swipe causes navigation. 233TEST_F(MacHistorySwiperTest, SwipeRight) { 234 // These tests require 10.7+ APIs. 235 if (![NSEvent 236 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 237 return; 238 239 startGestureInMiddle(); 240 moveGestureInMiddle(); 241 242 EXPECT_EQ(begin_count_, 0); 243 EXPECT_EQ(end_count_, 0); 244 245 moveGestureAtPoint(makePoint(0.8, 0.5)); 246 EXPECT_EQ(begin_count_, 1); 247 EXPECT_EQ(end_count_, 0); 248 EXPECT_FALSE(navigated_right_); 249 EXPECT_FALSE(navigated_left_); 250 251 endGestureAtPoint(makePoint(0.8, 0.5)); 252 EXPECT_EQ(begin_count_, 1); 253 EXPECT_EQ(end_count_, 1); 254 EXPECT_TRUE(navigated_right_); 255 EXPECT_FALSE(navigated_left_); 256} 257 258// If the user doesn't swipe enough, the history swiper should begin, but the 259// browser should not navigate. 260TEST_F(MacHistorySwiperTest, SwipeLeftSmallAmount) { 261 // These tests require 10.7+ APIs. 262 if (![NSEvent 263 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 264 return; 265 266 startGestureInMiddle(); 267 moveGestureInMiddle(); 268 moveGestureAtPoint(makePoint(0.45, 0.5)); 269 endGestureAtPoint(makePoint(0.45, 0.5)); 270 EXPECT_EQ(begin_count_, 1); 271 EXPECT_EQ(end_count_, 1); 272 EXPECT_FALSE(navigated_right_); 273 EXPECT_FALSE(navigated_left_); 274} 275 276// Diagonal swipes with a slight horizontal bias should not start the history 277// swiper. 278TEST_F(MacHistorySwiperTest, SwipeDiagonal) { 279 // These tests require 10.7+ APIs. 280 if (![NSEvent 281 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 282 return; 283 284 startGestureInMiddle(); 285 moveGestureInMiddle(); 286 moveGestureInMiddle(); 287 moveGestureAtPoint(makePoint(0.6, 0.59)); 288 endGestureAtPoint(makePoint(0.6, 0.59)); 289 290 EXPECT_EQ(begin_count_, 1); 291 EXPECT_EQ(end_count_, 1); 292 EXPECT_FALSE(navigated_right_); 293 EXPECT_FALSE(navigated_left_); 294} 295 296// Swiping left and then down should cancel the history swiper without 297// navigating. 298TEST_F(MacHistorySwiperTest, SwipeLeftThenDown) { 299 // These tests require 10.7+ APIs. 300 if (![NSEvent 301 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 302 return; 303 304 startGestureInMiddle(); 305 moveGestureInMiddle(); 306 moveGestureAtPoint(makePoint(0.4, 0.5)); 307 moveGestureAtPoint(makePoint(0.4, 0.3)); 308 endGestureAtPoint(makePoint(0.2, 0.2)); 309 EXPECT_EQ(begin_count_, 1); 310 EXPECT_EQ(end_count_, 1); 311 EXPECT_FALSE(navigated_right_); 312 EXPECT_FALSE(navigated_left_); 313} 314 315// Sometimes Cocoa gets confused and sends us a momentum swipe event instead of 316// a swipe gesture event. Momentum events should not cause history swiping. 317TEST_F(MacHistorySwiperTest, MomentumSwipeLeft) { 318 // These tests require 10.7+ APIs. 319 if (![NSEvent 320 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 321 return; 322 323 startGestureInMiddle(); 324 325 // Send a momentum move gesture. 326 momentumMoveGestureAtPoint(makePoint(0.5, 0.5)); 327 EXPECT_EQ(begin_count_, 0); 328 EXPECT_EQ(end_count_, 0); 329 330 // Callbacks from blink to set the relevant state for history swiping. 331 rendererACKForBeganEvent(); 332 333 momentumMoveGestureAtPoint(makePoint(0.2, 0.5)); 334 EXPECT_EQ(begin_count_, 0); 335 EXPECT_EQ(end_count_, 0); 336 337 endGestureAtPoint(makePoint(0.2, 0.5)); 338 EXPECT_EQ(begin_count_, 0); 339 EXPECT_EQ(end_count_, 0); 340} 341 342// Momentum scroll events for magic mouse should not attempt to trigger the 343// `trackSwipeEventWithOptions:` api, as that throws an exception. 344TEST_F(MacHistorySwiperTest, MagicMouseMomentumSwipe) { 345 // These tests require 10.7+ APIs. 346 if (![NSEvent 347 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 348 return; 349 350 // Magic mouse events don't generate 'touches*' callbacks. 351 NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture); 352 [historySwiper_ beginGestureWithEvent:event]; 353 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 354 [historySwiper_ handleEvent:scrollEvent]; 355 356 // Callbacks from blink to set the relevant state for history swiping. 357 rendererACKForBeganEvent(); 358 359 // Send a momentum move gesture. 360 scrollEvent = 361 scrollWheelEventWithPhase(NSEventPhaseNone, NSEventPhaseChanged, 5.0, 0); 362 [historySwiper_ handleEvent:scrollEvent]; 363 364 EXPECT_FALSE(magic_mouse_history_swipe_); 365} 366 367// User starts a swipe but doesn't move. 368TEST_F(MacHistorySwiperTest, NoSwipe) { 369 // These tests require 10.7+ APIs. 370 if (![NSEvent 371 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 372 return; 373 374 startGestureInMiddle(); 375 moveGestureInMiddle(); 376 377 // Starts the gesture. 378 moveGestureAtPoint(makePoint(0.44, 0.44)); 379 380 // No movement. 381 endGestureAtPoint(makePoint(0.44, 0.44)); 382 383 EXPECT_EQ(begin_count_, 1); 384 EXPECT_EQ(end_count_, 1); 385 EXPECT_FALSE(navigated_right_); 386 EXPECT_FALSE(navigated_left_); 387} 388 389// After a gesture is successfully recognized, momentum events should be 390// swallowed, but new events should pass through. 391TEST_F(MacHistorySwiperTest, TouchEventAfterGestureFinishes) { 392 // These tests require 10.7+ APIs. 393 if (![NSEvent 394 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 395 return; 396 397 // Successfully pass through a gesture. 398 startGestureInMiddle(); 399 moveGestureInMiddle(); 400 moveGestureAtPoint(makePoint(0.8, 0.5)); 401 endGestureAtPoint(makePoint(0.8, 0.5)); 402 EXPECT_TRUE(navigated_right_); 403 404 // Momentum events should be swallowed. 405 NSEvent* momentumEvent = scrollWheelEventWithPhase(NSEventPhaseNone, 406 NSEventPhaseChanged); 407 EXPECT_TRUE([historySwiper_ handleEvent:momentumEvent]); 408 409 // New events should not be swallowed. 410 NSEvent* beganEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 411 EXPECT_FALSE([historySwiper_ handleEvent:beganEvent]); 412} 413 414// The history swipe logic should be resilient against the timing of the 415// different callbacks that result from scrolling. 416TEST_F(MacHistorySwiperTest, SwipeRightEventOrdering) { 417 // These tests require 10.7+ APIs. 418 if (![NSEvent 419 respondsToSelector:@selector(isSwipeTrackingFromScrollEventsEnabled)]) 420 return; 421 422 // Touches began. 423 NSEvent* scrollEvent = scrollWheelEventWithPhase(NSEventPhaseBegan); 424 NSEvent* event = mockEventWithPoint(makePoint(0.5, 0.5), NSEventTypeGesture); 425 [historySwiper_ touchesBeganWithEvent:event]; 426 [historySwiper_ handleEvent:scrollEvent]; 427 rendererACKForBeganEvent(); 428 429 // Touches moved. 430 moveGestureAtPoint(makePoint(0.5, 0.5)); 431 EXPECT_EQ(begin_count_, 0); 432 EXPECT_EQ(end_count_, 0); 433 434 // Touches moved. 435 moveGestureAtPoint(makePoint(0.52, 0.5)); 436 437 // Begin gesture callback is delayed. 438 [historySwiper_ beginGestureWithEvent:event]; 439 440 // Touches moved. 441 moveGestureAtPoint(makePoint(0.52, 0.5)); 442 443 // Complete the rest of the gesture. 444 moveGestureAtPoint(makePoint(0.60, 0.5)); 445 scrollEvent = scrollWheelEventWithPhase(NSEventPhaseChanged); 446 [historySwiper_ handleEvent:scrollEvent]; 447 endGestureAtPoint(makePoint(0.70, 0.5)); 448 449 EXPECT_EQ(begin_count_, 1); 450 EXPECT_EQ(end_count_, 1); 451 EXPECT_TRUE(navigated_right_); 452 EXPECT_FALSE(navigated_left_); 453} 454