overscroll_controller.cc revision 46d4c2bc3267f3f028f39e7e311b0f89aba2e4fd
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
13using blink::WebInputEvent;
14
15namespace {
16
17bool IsScrollEndEffectEnabled() {
18  return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19      switches::kScrollEndEffect) == "1";
20}
21
22}  // namespace
23
24namespace content {
25
26OverscrollController::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
34OverscrollController::~OverscrollController() {
35}
36
37bool 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
88void 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
104void 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
113void OverscrollController::Reset() {
114  overscroll_mode_ = OVERSCROLL_NONE;
115  overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
116  scroll_state_ = STATE_UNKNOWN;
117}
118
119void OverscrollController::Cancel() {
120  SetOverscrollMode(OVERSCROLL_NONE);
121  overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
122  scroll_state_ = STATE_UNKNOWN;
123}
124
125bool 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
184bool 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
206bool 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
266void 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
339void 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
346void 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