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. Only used to
124  // determine whether swipe events are coming from a Magic Mouse.
125  BOOL inGesture_;
126  // A flag that indicates that Chrome is receiving a series of touch events.
127  BOOL receivingTouches_;
128  // Each time a new gesture begins, we must get a new start point.
129  // This ivar determines whether the start point is valid.
130  int gestureStartPointValid_;
131  // The id of the last gesture that we processed as a history swipe.
132  int lastProcessedGestureId_;
133  // A flag that indicates the user's intended direction with the history swipe.
134  history_swiper::NavigationDirection historySwipeDirection_;
135  // A flag that indicates whether the gesture has its direction inverted.
136  BOOL historySwipeDirectionInverted_;
137
138  // Whether the event with phase NSEventPhaseBegan was not consumed by the
139  // renderer. This variables defaults to NO for new gestures.
140  BOOL beganEventUnconsumed_;
141  history_swiper::RecognitionState recognitionState_;
142
143  id<HistorySwiperDelegate> delegate_;
144
145  // Cumulative scroll delta since scroll gesture start. Only valid during
146  // scroll gesture handling. Used for history swiping.
147  NSSize mouseScrollDelta_;
148}
149
150// Many event types are passed in, but the only one we care about is
151// NSScrollWheel. We look at the phase to determine whether to trigger history
152// swiping
153- (BOOL)handleEvent:(NSEvent*)event;
154- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
155                         consumed:(BOOL)consumed;
156
157// The event passed in is a gesture event, and has touch data associated with
158// the trackpad.
159- (void)touchesBeganWithEvent:(NSEvent*)event;
160- (void)touchesMovedWithEvent:(NSEvent*)event;
161- (void)touchesCancelledWithEvent:(NSEvent*)event;
162- (void)touchesEndedWithEvent:(NSEvent*)event;
163- (void)beginGestureWithEvent:(NSEvent*)event;
164- (void)endGestureWithEvent:(NSEvent*)event;
165
166// These methods control whether a given view is allowed to rubberband in the
167// given direction. This is inversely related to whether the view is allowed to
168// 2-finger history swipe in the given direction.
169- (BOOL)canRubberbandLeft:(NSView*)view;
170- (BOOL)canRubberbandRight:(NSView*)view;
171
172// Designated initializer.
173- (id)initWithDelegate:(id<HistorySwiperDelegate>)delegate;
174
175@property (nonatomic, assign) id<HistorySwiperDelegate> delegate;
176
177@end
178
179// Exposed only for unit testing, do not call directly.
180@interface HistorySwiper (PrivateExposedForTesting)
181+ (void)resetMagicMouseState;
182@end
183
184#endif // CHROME_BROWSER_RENDERER_HOST_CHROME_RENDER_WIDGET_HOST_VIEW_MAC_HISTORY_SWIPER_
185