chrome_render_widget_host_view_mac_history_swiper.h revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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#ifndef CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_ 6#define CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_ 7 8#import <Cocoa/Cocoa.h> 9 10namespace blink { 11class WebMouseWheelEvent; 12} 13 14@class HistorySwiper; 15@protocol HistorySwiperDelegate 16// Return NO from this method is the view/render_widget_host should not 17// allow history swiping. 18- (BOOL)shouldAllowHistorySwiping; 19// The history overlay is added to the view returning from this method. 20- (NSView*)viewThatWantsHistoryOverlay; 21@end 22 23namespace history_swiper { 24enum NavigationDirection { 25 kBackwards = 0, 26 kForwards, 27}; 28enum RecognitionState { 29 // Waiting to see whether the renderer will handle the event with phase 30 // NSEventPhaseBegan. The state machine will also stay in this state if 31 // external conditions prohibit the initialization of history swiping. New 32 // gestures always start in this state. 33 // Events are forwarded to the renderer. 34 kPending, 35 // The gesture looks like the beginning of a history swipe. 36 // Events are forwarded to the renderer. 37 // The history overlay is visible. 38 kPotential, 39 // The gesture is definitely a history swipe. 40 // Events are not forwarded to the renderer. 41 // The history overlay is visible. 42 kTracking, 43 // The history swipe gesture has finished. 44 // Events are not forwarded to the renderer. 45 kCompleted, 46 // The history swipe gesture was cancelled. 47 // Events are forwarded to the renderer. 48 kCancelled, 49}; 50} // history_swiper 51 52// History swiping is the feature wherein a horizontal 2-finger swipe of of a 53// trackpad causes the browser to navigate forwards or backwards. 54// Unfortunately, the act of 2-finger swiping is overloaded, and has 3 possible 55// effects. In descending order of priority, the swipe should: 56// 1. Scroll the content on the web page. 57// 2. Perform a history swipe. 58// 3. Rubberband/overscroll the content past the edge of the window. 59// Effects (1) and (3) are managed by the renderer, whereas effect (2) is 60// managed by this class. 61// 62// Touches on the trackpad enter the run loop as NSEvents, grouped into 63// gestures. The phases of NSEvents within a gesture follow a well defined 64// order. 65// 1. NSEventPhaseMayBegin. (exactly 1 event with this phase) 66// 2. NSEventPhaseBegan. (exactly 1 event with this phase) 67// 3. NSEventPhaseMoved. (many events with this phase) 68// 4. NSEventPhaseEnded. (exactly 1 event with this phase) 69// Events with the phase NSEventPhaseCancelled may come in at any time, and 70// generally mean that an entity within the Cocoa framework has consumed the 71// gesture, and wants to "cancel" previous NSEvents that have been passed to 72// this class. 73// 74// The event handling stack in Chrome passes all events to this class, which is 75// given the opportunity to process and consume the event. If the event is not 76// consumed, it is passed to the renderer via IPC. The renderer returns an IPC 77// indicating whether the event was consumed. To prevent spamming the renderer 78// with IPCs, the browser waits for an ACK from the renderer from the previous 79// event before sending the next one. While waiting for an ACK, the browser 80// coalesces NSEvents with the same phase. It is common for dozens of events 81// with the phase NSEventPhaseMoved to be coalesced. 82// 83// It is difficult to determine from the initial events in a gesture whether 84// the gesture was intended to be a history swipe. The loss of information from 85// the coalescing of events with phase NSEventPhaseMoved before they are passed 86// to the renderer is also problematic. The general approach is as follows: 87// 1. Wait for the renderer to return an ACK for the event with phase 88// NSEventPhaseBegan. If that event was not consumed, change the state to 89// kPotential. If the renderer is not certain about whether the event should 90// be consumed, it tries to not consume the event. 91// 2. In the state kPotential, this class will process events and update its 92// internal state machine, but it will also continue to pass events to the 93// renderer. This class tries to aggressively cancel history swiping to make 94// up for the fact that the renderer errs on the side of allowing history 95// swiping to occur. 96// 3. As more events come in, if the gesture continues to appear horizontal, 97// then this class will transition to the state kTracking. Events are 98// consumed, and not passed to the renderer. 99// 100// There are multiple APIs that provide information about gestures on the 101// trackpad. This class uses two different set of APIs. 102// 1. The -[NSView touches*WithEvent:] APIs provide detailed information 103// about the touches within a gesture. The callbacks happen with more 104// frequency, and have higher accuracy. These APIs are used to transition 105// between all state, exception for kPending -> kPotential. 106// 2. The -[NSView scrollWheel:] API provides less information, but the 107// events are passed to the renderer. This class must process these events so 108// that it can decide whether to consume the events and prevent them from 109// being passed to the renderer. This API is used to transition from kPending 110// -> kPotential. 111@class HistoryOverlayController; 112@interface HistorySwiper : NSObject { 113 @private 114 // This controller will exist if and only if the UI is in history swipe mode. 115 HistoryOverlayController* historyOverlay_; 116 // Each gesture received by the window is given a unique id. 117 // The id is monotonically increasing. 118 int currentGestureId_; 119 // The location of the fingers when the gesture started. 120 NSPoint gestureStartPoint_; 121 // The current location of the fingers in the gesture. 122 NSPoint gestureCurrentPoint_; 123 // A flag that indicates that there is an ongoing gesture. 124 // The method [NSEvent touchesMatchingPhase:inView:] is only valid for events 125 // that are part of a gesture. 126 BOOL inGesture_; 127 // Each time a new gesture begins, we must get a new start point. 128 // This ivar determines whether the start point is valid. 129 int gestureStartPointValid_; 130 // The id of the last gesture that we processed as a history swipe. 131 int lastProcessedGestureId_; 132 // A flag that indicates the user's intended direction with the history swipe. 133 history_swiper::NavigationDirection historySwipeDirection_; 134 // A flag that indicates whether the gesture has its direction inverted. 135 BOOL historySwipeDirectionInverted_; 136 137 // Whether the event with phase NSEventPhaseBegan was not consumed by the 138 // renderer. This variables defaults to NO for new gestures. 139 BOOL beganEventUnconsumed_; 140 history_swiper::RecognitionState recognitionState_; 141 142 id<HistorySwiperDelegate> delegate_; 143 144 // Magic mouse and touchpad swipe events are identical except magic mouse 145 // events do not generate NSTouch callbacks. Since we rely on NSTouch 146 // callbacks to determine vertical scrolling, magic mouse swipe events use an 147 // entirely different set of logic. 148 // 149 // The two event types do not play well together. Just calling the API 150 // `[NSEvent trackSwipeEventWithOptions:]` will block touches{Began, Moved, *} 151 // callbacks for a non-deterministic period of time (even after the swipe has 152 // completed). 153 BOOL receivedTouch_; 154 // Cumulative scroll delta since scroll gesture start. Only valid during 155 // scroll gesture handling. Used for history swiping. 156 NSSize mouseScrollDelta_; 157} 158 159// Many event types are passed in, but the only one we care about is 160// NSScrollWheel. We look at the phase to determine whether to trigger history 161// swiping 162- (BOOL)handleEvent:(NSEvent*)event; 163- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event 164 consumed:(BOOL)consumed; 165 166// The event passed in is a gesture event, and has touch data associated with 167// the trackpad. 168- (void)touchesBeganWithEvent:(NSEvent*)event; 169- (void)touchesMovedWithEvent:(NSEvent*)event; 170- (void)touchesCancelledWithEvent:(NSEvent*)event; 171- (void)touchesEndedWithEvent:(NSEvent*)event; 172- (void)beginGestureWithEvent:(NSEvent*)event; 173- (void)endGestureWithEvent:(NSEvent*)event; 174 175// These methods control whether a given view is allowed to rubberband in the 176// given direction. This is inversely related to whether the view is allowed to 177// 2-finger history swipe in the given direction. 178- (BOOL)canRubberbandLeft:(NSView*)view; 179- (BOOL)canRubberbandRight:(NSView*)view; 180 181// Designated initializer. 182- (id)initWithDelegate:(id<HistorySwiperDelegate>)delegate; 183 184@property (nonatomic, assign) id<HistorySwiperDelegate> delegate; 185 186@end 187 188// Exposed only for unit testing, do not call directly. 189@interface HistorySwiper (PrivateExposedForTesting) 190+ (void)resetMagicMouseState; 191@end 192 193#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_ 194