1 // Copyright (c) 2012 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 "content/browser/renderer_host/overscroll_controller.h"
6
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "content/browser/renderer_host/overscroll_controller_delegate.h"
10 #include "content/public/browser/overscroll_configuration.h"
11 #include "content/public/common/content_switches.h"
12
13 using blink::WebInputEvent;
14
15 namespace {
16
IsScrollEndEffectEnabled()17 bool IsScrollEndEffectEnabled() {
18 return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19 switches::kScrollEndEffect) == "1";
20 }
21
22 } // namespace
23
24 namespace content {
25
OverscrollController()26 OverscrollController::OverscrollController()
27 : overscroll_mode_(OVERSCROLL_NONE),
28 scroll_state_(STATE_UNKNOWN),
29 overscroll_delta_x_(0.f),
30 overscroll_delta_y_(0.f),
31 delegate_(NULL) {
32 }
33
~OverscrollController()34 OverscrollController::~OverscrollController() {
35 }
36
WillHandleEvent(const blink::WebInputEvent & event)37 bool OverscrollController::WillHandleEvent(const blink::WebInputEvent& event) {
38 if (scroll_state_ != STATE_UNKNOWN) {
39 switch (event.type) {
40 case blink::WebInputEvent::GestureScrollEnd:
41 case blink::WebInputEvent::GestureFlingStart:
42 scroll_state_ = STATE_UNKNOWN;
43 break;
44
45 case blink::WebInputEvent::MouseWheel: {
46 const blink::WebMouseWheelEvent& wheel =
47 static_cast<const blink::WebMouseWheelEvent&>(event);
48 if (!wheel.hasPreciseScrollingDeltas ||
49 wheel.phase == blink::WebMouseWheelEvent::PhaseEnded ||
50 wheel.phase == blink::WebMouseWheelEvent::PhaseCancelled) {
51 scroll_state_ = STATE_UNKNOWN;
52 }
53 break;
54 }
55
56 default:
57 if (blink::WebInputEvent::isMouseEventType(event.type) ||
58 blink::WebInputEvent::isKeyboardEventType(event.type)) {
59 scroll_state_ = STATE_UNKNOWN;
60 }
61 break;
62 }
63 }
64
65 if (DispatchEventCompletesAction(event)) {
66 CompleteAction();
67
68 // Let the event be dispatched to the renderer.
69 return false;
70 }
71
72 if (overscroll_mode_ != OVERSCROLL_NONE && DispatchEventResetsState(event)) {
73 SetOverscrollMode(OVERSCROLL_NONE);
74
75 // Let the event be dispatched to the renderer.
76 return false;
77 }
78
79 if (overscroll_mode_ != OVERSCROLL_NONE) {
80 // Consume the event only if it updates the overscroll state.
81 if (ProcessEventForOverscroll(event))
82 return true;
83 }
84
85 return false;
86 }
87
ReceivedEventACK(const blink::WebInputEvent & event,bool processed)88 void OverscrollController::ReceivedEventACK(const blink::WebInputEvent& event,
89 bool processed) {
90 if (processed) {
91 // If a scroll event is consumed by the page, i.e. some content on the page
92 // has been scrolled, then there is not going to be an overscroll gesture,
93 // until the current scroll ends, and a new scroll gesture starts.
94 if (scroll_state_ == STATE_UNKNOWN &&
95 (event.type == blink::WebInputEvent::MouseWheel ||
96 event.type == blink::WebInputEvent::GestureScrollUpdate)) {
97 scroll_state_ = STATE_CONTENT_SCROLLING;
98 }
99 return;
100 }
101 ProcessEventForOverscroll(event);
102 }
103
DiscardingGestureEvent(const blink::WebGestureEvent & gesture)104 void OverscrollController::DiscardingGestureEvent(
105 const blink::WebGestureEvent& gesture) {
106 if (scroll_state_ != STATE_UNKNOWN &&
107 (gesture.type == blink::WebInputEvent::GestureScrollEnd ||
108 gesture.type == blink::WebInputEvent::GestureFlingStart)) {
109 scroll_state_ = STATE_UNKNOWN;
110 }
111 }
112
Reset()113 void OverscrollController::Reset() {
114 overscroll_mode_ = OVERSCROLL_NONE;
115 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
116 scroll_state_ = STATE_UNKNOWN;
117 }
118
Cancel()119 void OverscrollController::Cancel() {
120 SetOverscrollMode(OVERSCROLL_NONE);
121 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
122 scroll_state_ = STATE_UNKNOWN;
123 }
124
DispatchEventCompletesAction(const blink::WebInputEvent & event) const125 bool OverscrollController::DispatchEventCompletesAction (
126 const blink::WebInputEvent& event) const {
127 if (overscroll_mode_ == OVERSCROLL_NONE)
128 return false;
129
130 // Complete the overscroll gesture if there was a mouse move or a scroll-end
131 // after the threshold.
132 if (event.type != blink::WebInputEvent::MouseMove &&
133 event.type != blink::WebInputEvent::GestureScrollEnd &&
134 event.type != blink::WebInputEvent::GestureFlingStart)
135 return false;
136
137 if (!delegate_)
138 return false;
139
140 gfx::Rect bounds = delegate_->GetVisibleBounds();
141 if (bounds.IsEmpty())
142 return false;
143
144 if (event.type == blink::WebInputEvent::GestureFlingStart) {
145 // Check to see if the fling is in the same direction of the overscroll.
146 const blink::WebGestureEvent gesture =
147 static_cast<const blink::WebGestureEvent&>(event);
148 switch (overscroll_mode_) {
149 case OVERSCROLL_EAST:
150 if (gesture.data.flingStart.velocityX < 0)
151 return false;
152 break;
153 case OVERSCROLL_WEST:
154 if (gesture.data.flingStart.velocityX > 0)
155 return false;
156 break;
157 case OVERSCROLL_NORTH:
158 if (gesture.data.flingStart.velocityY > 0)
159 return false;
160 break;
161 case OVERSCROLL_SOUTH:
162 if (gesture.data.flingStart.velocityY < 0)
163 return false;
164 break;
165 case OVERSCROLL_NONE:
166 case OVERSCROLL_COUNT:
167 NOTREACHED();
168 }
169 }
170
171 float ratio, threshold;
172 if (overscroll_mode_ == OVERSCROLL_WEST ||
173 overscroll_mode_ == OVERSCROLL_EAST) {
174 ratio = fabs(overscroll_delta_x_) / bounds.width();
175 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
176 } else {
177 ratio = fabs(overscroll_delta_y_) / bounds.height();
178 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE);
179 }
180
181 return ratio >= threshold;
182 }
183
DispatchEventResetsState(const blink::WebInputEvent & event) const184 bool OverscrollController::DispatchEventResetsState(
185 const blink::WebInputEvent& event) const {
186 switch (event.type) {
187 case blink::WebInputEvent::MouseWheel: {
188 // Only wheel events with precise deltas (i.e. from trackpad) contribute
189 // to the overscroll gesture.
190 const blink::WebMouseWheelEvent& wheel =
191 static_cast<const blink::WebMouseWheelEvent&>(event);
192 return !wheel.hasPreciseScrollingDeltas;
193 }
194
195 case blink::WebInputEvent::GestureScrollUpdate:
196 case blink::WebInputEvent::GestureFlingCancel:
197 return false;
198
199 default:
200 // Touch events can arrive during an overscroll gesture initiated by
201 // touch-scrolling. These events should not reset the overscroll state.
202 return !blink::WebInputEvent::isTouchEventType(event.type);
203 }
204 }
205
ProcessEventForOverscroll(const blink::WebInputEvent & event)206 bool OverscrollController::ProcessEventForOverscroll(
207 const blink::WebInputEvent& event) {
208 bool event_processed = false;
209 switch (event.type) {
210 case blink::WebInputEvent::MouseWheel: {
211 const blink::WebMouseWheelEvent& wheel =
212 static_cast<const blink::WebMouseWheelEvent&>(event);
213 if (!wheel.hasPreciseScrollingDeltas)
214 break;
215
216 ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX,
217 wheel.deltaY * wheel.accelerationRatioY,
218 wheel.type);
219 event_processed = true;
220 break;
221 }
222 case blink::WebInputEvent::GestureScrollUpdate: {
223 const blink::WebGestureEvent& gesture =
224 static_cast<const blink::WebGestureEvent&>(event);
225 ProcessOverscroll(gesture.data.scrollUpdate.deltaX,
226 gesture.data.scrollUpdate.deltaY,
227 gesture.type);
228 event_processed = true;
229 break;
230 }
231 case blink::WebInputEvent::GestureFlingStart: {
232 const float kFlingVelocityThreshold = 1100.f;
233 const blink::WebGestureEvent& gesture =
234 static_cast<const blink::WebGestureEvent&>(event);
235 float velocity_x = gesture.data.flingStart.velocityX;
236 float velocity_y = gesture.data.flingStart.velocityY;
237 if (fabs(velocity_x) > kFlingVelocityThreshold) {
238 if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) ||
239 (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) {
240 CompleteAction();
241 event_processed = true;
242 break;
243 }
244 } else if (fabs(velocity_y) > kFlingVelocityThreshold) {
245 if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) ||
246 (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) {
247 CompleteAction();
248 event_processed = true;
249 break;
250 }
251 }
252
253 // Reset overscroll state if fling didn't complete the overscroll gesture.
254 SetOverscrollMode(OVERSCROLL_NONE);
255 break;
256 }
257
258 default:
259 DCHECK(blink::WebInputEvent::isGestureEventType(event.type) ||
260 blink::WebInputEvent::isTouchEventType(event.type))
261 << "Received unexpected event: " << event.type;
262 }
263 return event_processed;
264 }
265
ProcessOverscroll(float delta_x,float delta_y,blink::WebInputEvent::Type type)266 void OverscrollController::ProcessOverscroll(float delta_x,
267 float delta_y,
268 blink::WebInputEvent::Type type) {
269 if (scroll_state_ != STATE_CONTENT_SCROLLING)
270 overscroll_delta_x_ += delta_x;
271 overscroll_delta_y_ += delta_y;
272
273 float horiz_threshold = GetOverscrollConfig(
274 WebInputEvent::isGestureEventType(type) ?
275 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN :
276 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD);
277 float vert_threshold = GetOverscrollConfig(
278 OVERSCROLL_CONFIG_VERT_THRESHOLD_START);
279 if (fabs(overscroll_delta_x_) <= horiz_threshold &&
280 fabs(overscroll_delta_y_) <= vert_threshold) {
281 SetOverscrollMode(OVERSCROLL_NONE);
282 return;
283 }
284
285 // Compute the current overscroll direction. If the direction is different
286 // from the current direction, then always switch to no-overscroll mode first
287 // to make sure that subsequent scroll events go through to the page first.
288 OverscrollMode new_mode = OVERSCROLL_NONE;
289 const float kMinRatio = 2.5;
290 if (fabs(overscroll_delta_x_) > horiz_threshold &&
291 fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
292 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
293 else if (fabs(overscroll_delta_y_) > vert_threshold &&
294 fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
295 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
296
297 // The vertical oversrcoll currently does not have any UX effects other then
298 // for the scroll end effect, so testing if it is enabled.
299 if ((new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) &&
300 !IsScrollEndEffectEnabled())
301 new_mode = OVERSCROLL_NONE;
302
303 if (overscroll_mode_ == OVERSCROLL_NONE)
304 SetOverscrollMode(new_mode);
305 else if (new_mode != overscroll_mode_)
306 SetOverscrollMode(OVERSCROLL_NONE);
307
308 if (overscroll_mode_ == OVERSCROLL_NONE)
309 return;
310
311 // Tell the delegate about the overscroll update so that it can update
312 // the display accordingly (e.g. show history preview etc.).
313 if (delegate_) {
314 // Do not include the threshold amount when sending the deltas to the
315 // delegate.
316 float delegate_delta_x = overscroll_delta_x_;
317 if (fabs(delegate_delta_x) > horiz_threshold) {
318 if (delegate_delta_x < 0)
319 delegate_delta_x += horiz_threshold;
320 else
321 delegate_delta_x -= horiz_threshold;
322 } else {
323 delegate_delta_x = 0.f;
324 }
325
326 float delegate_delta_y = overscroll_delta_y_;
327 if (fabs(delegate_delta_y) > vert_threshold) {
328 if (delegate_delta_y < 0)
329 delegate_delta_y += vert_threshold;
330 else
331 delegate_delta_y -= vert_threshold;
332 } else {
333 delegate_delta_y = 0.f;
334 }
335 delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
336 }
337 }
338
CompleteAction()339 void OverscrollController::CompleteAction() {
340 if (delegate_)
341 delegate_->OnOverscrollComplete(overscroll_mode_);
342 overscroll_mode_ = OVERSCROLL_NONE;
343 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
344 }
345
SetOverscrollMode(OverscrollMode mode)346 void OverscrollController::SetOverscrollMode(OverscrollMode mode) {
347 if (overscroll_mode_ == mode)
348 return;
349 OverscrollMode old_mode = overscroll_mode_;
350 overscroll_mode_ = mode;
351 if (overscroll_mode_ == OVERSCROLL_NONE)
352 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
353 else
354 scroll_state_ = STATE_OVERSCROLLING;
355 if (delegate_)
356 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_);
357 }
358
359 } // namespace content
360