Revert 268405 "Make sure that ScratchBuffer::Allocate() always r..."
[chromium-blink-merge.git] / content / browser / renderer_host / overscroll_controller.cc
blob77e52d318e5711b9c50cd607979f2af44b145934
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.
5 #include "content/browser/renderer_host/overscroll_controller.h"
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"
13 using blink::WebInputEvent;
15 namespace {
17 bool IsScrollEndEffectEnabled() {
18 return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19 switches::kScrollEndEffect) == "1";
22 } // namespace
24 namespace content {
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) {
34 OverscrollController::~OverscrollController() {
37 OverscrollController::Disposition OverscrollController::DispatchEvent(
38 const blink::WebInputEvent& event,
39 const ui::LatencyInfo& latency_info) {
40 if (scroll_state_ != STATE_UNKNOWN) {
41 switch (event.type) {
42 case blink::WebInputEvent::GestureScrollEnd:
43 case blink::WebInputEvent::GestureFlingStart:
44 scroll_state_ = STATE_UNKNOWN;
45 break;
47 case blink::WebInputEvent::MouseWheel: {
48 const blink::WebMouseWheelEvent& wheel =
49 static_cast<const blink::WebMouseWheelEvent&>(event);
50 if (!wheel.hasPreciseScrollingDeltas ||
51 wheel.phase == blink::WebMouseWheelEvent::PhaseEnded ||
52 wheel.phase == blink::WebMouseWheelEvent::PhaseCancelled) {
53 scroll_state_ = STATE_UNKNOWN;
55 break;
58 default:
59 if (blink::WebInputEvent::isMouseEventType(event.type) ||
60 blink::WebInputEvent::isKeyboardEventType(event.type)) {
61 scroll_state_ = STATE_UNKNOWN;
63 break;
67 if (DispatchEventCompletesAction(event)) {
68 CompleteAction();
70 // If the overscroll was caused by touch-scrolling, then the gesture event
71 // that completes the action needs to be sent to the renderer, because the
72 // touch-scrolls maintain state in the renderer side (in the compositor, for
73 // example), and the event that completes this action needs to be sent to
74 // the renderer so that those states can be updated/reset appropriately.
75 if (blink::WebInputEvent::isGestureEventType(event.type)) {
76 // A gesture-event isn't sent to the GestureEventQueue when overscroll is
77 // in progress. So dispatch the event through the RenderWidgetHost so that
78 // it can reach the GestureEventQueue.
79 return SHOULD_FORWARD_TO_GESTURE_QUEUE;
82 return SHOULD_FORWARD_TO_RENDERER;
85 if (overscroll_mode_ != OVERSCROLL_NONE && DispatchEventResetsState(event)) {
86 SetOverscrollMode(OVERSCROLL_NONE);
87 if (blink::WebInputEvent::isGestureEventType(event.type)) {
88 // A gesture-event isn't sent to the GestureEventQueue when overscroll is
89 // in progress. So dispatch the event through the RenderWidgetHost so that
90 // it can reach the GestureEventQueue.
91 return SHOULD_FORWARD_TO_GESTURE_QUEUE;
94 // Let the event be dispatched to the renderer.
95 return SHOULD_FORWARD_TO_RENDERER;
98 if (overscroll_mode_ != OVERSCROLL_NONE) {
99 // Consume the event only if it updates the overscroll state.
100 if (ProcessEventForOverscroll(event))
101 return CONSUMED;
104 return SHOULD_FORWARD_TO_RENDERER;
107 void OverscrollController::ReceivedEventACK(const blink::WebInputEvent& event,
108 bool processed) {
109 if (processed) {
110 // If a scroll event is consumed by the page, i.e. some content on the page
111 // has been scrolled, then there is not going to be an overscroll gesture,
112 // until the current scroll ends, and a new scroll gesture starts.
113 if (scroll_state_ == STATE_UNKNOWN &&
114 (event.type == blink::WebInputEvent::MouseWheel ||
115 event.type == blink::WebInputEvent::GestureScrollUpdate)) {
116 scroll_state_ = STATE_CONTENT_SCROLLING;
118 return;
120 ProcessEventForOverscroll(event);
123 void OverscrollController::DiscardingGestureEvent(
124 const blink::WebGestureEvent& gesture) {
125 if (scroll_state_ != STATE_UNKNOWN &&
126 (gesture.type == blink::WebInputEvent::GestureScrollEnd ||
127 gesture.type == blink::WebInputEvent::GestureFlingStart)) {
128 scroll_state_ = STATE_UNKNOWN;
132 void OverscrollController::Reset() {
133 overscroll_mode_ = OVERSCROLL_NONE;
134 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
135 scroll_state_ = STATE_UNKNOWN;
138 void OverscrollController::Cancel() {
139 SetOverscrollMode(OVERSCROLL_NONE);
140 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
141 scroll_state_ = STATE_UNKNOWN;
144 bool OverscrollController::DispatchEventCompletesAction (
145 const blink::WebInputEvent& event) const {
146 if (overscroll_mode_ == OVERSCROLL_NONE)
147 return false;
149 // Complete the overscroll gesture if there was a mouse move or a scroll-end
150 // after the threshold.
151 if (event.type != blink::WebInputEvent::MouseMove &&
152 event.type != blink::WebInputEvent::GestureScrollEnd &&
153 event.type != blink::WebInputEvent::GestureFlingStart)
154 return false;
156 if (!delegate_)
157 return false;
159 gfx::Rect bounds = delegate_->GetVisibleBounds();
160 if (bounds.IsEmpty())
161 return false;
163 if (event.type == blink::WebInputEvent::GestureFlingStart) {
164 // Check to see if the fling is in the same direction of the overscroll.
165 const blink::WebGestureEvent gesture =
166 static_cast<const blink::WebGestureEvent&>(event);
167 switch (overscroll_mode_) {
168 case OVERSCROLL_EAST:
169 if (gesture.data.flingStart.velocityX < 0)
170 return false;
171 break;
172 case OVERSCROLL_WEST:
173 if (gesture.data.flingStart.velocityX > 0)
174 return false;
175 break;
176 case OVERSCROLL_NORTH:
177 if (gesture.data.flingStart.velocityY > 0)
178 return false;
179 break;
180 case OVERSCROLL_SOUTH:
181 if (gesture.data.flingStart.velocityY < 0)
182 return false;
183 break;
184 case OVERSCROLL_NONE:
185 case OVERSCROLL_COUNT:
186 NOTREACHED();
190 float ratio, threshold;
191 if (overscroll_mode_ == OVERSCROLL_WEST ||
192 overscroll_mode_ == OVERSCROLL_EAST) {
193 ratio = fabs(overscroll_delta_x_) / bounds.width();
194 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE);
195 } else {
196 ratio = fabs(overscroll_delta_y_) / bounds.height();
197 threshold = GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE);
200 return ratio >= threshold;
203 bool OverscrollController::DispatchEventResetsState(
204 const blink::WebInputEvent& event) const {
205 switch (event.type) {
206 case blink::WebInputEvent::MouseWheel: {
207 // Only wheel events with precise deltas (i.e. from trackpad) contribute
208 // to the overscroll gesture.
209 const blink::WebMouseWheelEvent& wheel =
210 static_cast<const blink::WebMouseWheelEvent&>(event);
211 return !wheel.hasPreciseScrollingDeltas;
214 case blink::WebInputEvent::GestureScrollUpdate:
215 case blink::WebInputEvent::GestureFlingCancel:
216 return false;
218 default:
219 // Touch events can arrive during an overscroll gesture initiated by
220 // touch-scrolling. These events should not reset the overscroll state.
221 return !blink::WebInputEvent::isTouchEventType(event.type);
225 bool OverscrollController::ProcessEventForOverscroll(
226 const blink::WebInputEvent& event) {
227 bool event_processed = false;
228 switch (event.type) {
229 case blink::WebInputEvent::MouseWheel: {
230 const blink::WebMouseWheelEvent& wheel =
231 static_cast<const blink::WebMouseWheelEvent&>(event);
232 if (!wheel.hasPreciseScrollingDeltas)
233 break;
235 ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX,
236 wheel.deltaY * wheel.accelerationRatioY,
237 wheel.type);
238 event_processed = true;
239 break;
241 case blink::WebInputEvent::GestureScrollUpdate: {
242 const blink::WebGestureEvent& gesture =
243 static_cast<const blink::WebGestureEvent&>(event);
244 ProcessOverscroll(gesture.data.scrollUpdate.deltaX,
245 gesture.data.scrollUpdate.deltaY,
246 gesture.type);
247 event_processed = true;
248 break;
250 case blink::WebInputEvent::GestureFlingStart: {
251 const float kFlingVelocityThreshold = 1100.f;
252 const blink::WebGestureEvent& gesture =
253 static_cast<const blink::WebGestureEvent&>(event);
254 float velocity_x = gesture.data.flingStart.velocityX;
255 float velocity_y = gesture.data.flingStart.velocityY;
256 if (fabs(velocity_x) > kFlingVelocityThreshold) {
257 if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) ||
258 (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) {
259 CompleteAction();
260 event_processed = true;
261 break;
263 } else if (fabs(velocity_y) > kFlingVelocityThreshold) {
264 if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) ||
265 (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) {
266 CompleteAction();
267 event_processed = true;
268 break;
272 // Reset overscroll state if fling didn't complete the overscroll gesture.
273 SetOverscrollMode(OVERSCROLL_NONE);
274 break;
277 default:
278 DCHECK(blink::WebInputEvent::isGestureEventType(event.type) ||
279 blink::WebInputEvent::isTouchEventType(event.type))
280 << "Received unexpected event: " << event.type;
282 return event_processed;
285 void OverscrollController::ProcessOverscroll(float delta_x,
286 float delta_y,
287 blink::WebInputEvent::Type type) {
288 if (scroll_state_ != STATE_CONTENT_SCROLLING)
289 overscroll_delta_x_ += delta_x;
290 overscroll_delta_y_ += delta_y;
292 float horiz_threshold = GetOverscrollConfig(
293 WebInputEvent::isGestureEventType(type) ?
294 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN :
295 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD);
296 float vert_threshold = GetOverscrollConfig(
297 OVERSCROLL_CONFIG_VERT_THRESHOLD_START);
298 if (fabs(overscroll_delta_x_) <= horiz_threshold &&
299 fabs(overscroll_delta_y_) <= vert_threshold) {
300 SetOverscrollMode(OVERSCROLL_NONE);
301 return;
304 // Compute the current overscroll direction. If the direction is different
305 // from the current direction, then always switch to no-overscroll mode first
306 // to make sure that subsequent scroll events go through to the page first.
307 OverscrollMode new_mode = OVERSCROLL_NONE;
308 const float kMinRatio = 2.5;
309 if (fabs(overscroll_delta_x_) > horiz_threshold &&
310 fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
311 new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
312 else if (fabs(overscroll_delta_y_) > vert_threshold &&
313 fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
314 new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
316 // The vertical oversrcoll currently does not have any UX effects other then
317 // for the scroll end effect, so testing if it is enabled.
318 if ((new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) &&
319 !IsScrollEndEffectEnabled())
320 new_mode = OVERSCROLL_NONE;
322 if (overscroll_mode_ == OVERSCROLL_NONE)
323 SetOverscrollMode(new_mode);
324 else if (new_mode != overscroll_mode_)
325 SetOverscrollMode(OVERSCROLL_NONE);
327 if (overscroll_mode_ == OVERSCROLL_NONE)
328 return;
330 // Tell the delegate about the overscroll update so that it can update
331 // the display accordingly (e.g. show history preview etc.).
332 if (delegate_) {
333 // Do not include the threshold amount when sending the deltas to the
334 // delegate.
335 float delegate_delta_x = overscroll_delta_x_;
336 if (fabs(delegate_delta_x) > horiz_threshold) {
337 if (delegate_delta_x < 0)
338 delegate_delta_x += horiz_threshold;
339 else
340 delegate_delta_x -= horiz_threshold;
341 } else {
342 delegate_delta_x = 0.f;
345 float delegate_delta_y = overscroll_delta_y_;
346 if (fabs(delegate_delta_y) > vert_threshold) {
347 if (delegate_delta_y < 0)
348 delegate_delta_y += vert_threshold;
349 else
350 delegate_delta_y -= vert_threshold;
351 } else {
352 delegate_delta_y = 0.f;
354 delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
358 void OverscrollController::CompleteAction() {
359 if (delegate_)
360 delegate_->OnOverscrollComplete(overscroll_mode_);
361 overscroll_mode_ = OVERSCROLL_NONE;
362 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
365 void OverscrollController::SetOverscrollMode(OverscrollMode mode) {
366 if (overscroll_mode_ == mode)
367 return;
368 OverscrollMode old_mode = overscroll_mode_;
369 overscroll_mode_ = mode;
370 if (overscroll_mode_ == OVERSCROLL_NONE)
371 overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
372 else
373 scroll_state_ = STATE_OVERSCROLLING;
374 if (delegate_)
375 delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_);
378 } // namespace content