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