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