Home | History | Annotate | Download | only in renderer_host
      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 
     29 class 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 
    115 NSPoint makePoint(CGFloat x, CGFloat y) {
    116   NSPoint point;
    117   point.x = x;
    118   point.y = y;
    119   return point;
    120 }
    121 
    122 id 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 
    135 id 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 
    151 id scrollWheelEventWithPhase(NSEventPhase phase,
    152                              NSEventPhase momentumPhase) {
    153   return scrollWheelEventWithPhase(phase, momentumPhase, 0, 0);
    154 }
    155 
    156 id scrollWheelEventWithPhase(NSEventPhase phase) {
    157   return scrollWheelEventWithPhase(phase, NSEventPhaseNone);
    158 }
    159 
    160 void 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 
    168 void 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 
    175 void 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 
    183 void 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 
    192 void 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 
    200 void 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.
    207 TEST_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.
    233 TEST_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.
    260 TEST_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.
    278 TEST_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.
    298 TEST_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.
    317 TEST_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.
    344 TEST_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.
    368 TEST_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.
    391 TEST_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.
    416 TEST_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