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    overscroll_delta_x_ += delta_x;
282  overscroll_delta_y_ += delta_y;
283
284  float horiz_threshold = GetOverscrollConfig(
285      OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START);
286  float vert_threshold = GetOverscrollConfig(
287      OVERSCROLL_CONFIG_VERT_THRESHOLD_START);
288  if (fabs(overscroll_delta_x_) <= horiz_threshold &&
289      fabs(overscroll_delta_y_) <= vert_threshold) {
290    SetOverscrollMode(OVERSCROLL_NONE);
291    return;
292  }
293
294  // Compute the current overscroll direction. If the direction is different
295  // from the current direction, then always switch to no-overscroll mode first
296  // to make sure that subsequent scroll events go through to the page first.
297  OverscrollMode new_mode = OVERSCROLL_NONE;
298  const float kMinRatio = 2.5;
299  if (fabs(overscroll_delta_x_) > horiz_threshold &&
300      fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
301    new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
302  else if (fabs(overscroll_delta_y_) > vert_threshold &&
303           fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
304    new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
305
306  if (overscroll_mode_ == OVERSCROLL_NONE)
307    SetOverscrollMode(new_mode);
308  else if (new_mode != overscroll_mode_)
309    SetOverscrollMode(OVERSCROLL_NONE);
310
311  if (overscroll_mode_ == OVERSCROLL_NONE)
312    return;
313
314  // Tell the delegate about the overscroll update so that it can update
315  // the display accordingly (e.g. show history preview etc.).
316  if (delegate_) {
317    // Do not include the threshold amount when sending the deltas to the
318    // delegate.
319    float delegate_delta_x = overscroll_delta_x_;
320    if (fabs(delegate_delta_x) > horiz_threshold) {
321      if (delegate_delta_x < 0)
322        delegate_delta_x += horiz_threshold;
323      else
324        delegate_delta_x -= horiz_threshold;
325    } else {
326      delegate_delta_x = 0.f;
327    }
328
329    float delegate_delta_y = overscroll_delta_y_;
330    if (fabs(delegate_delta_y) > vert_threshold) {
331      if (delegate_delta_y < 0)
332        delegate_delta_y += vert_threshold;
333      else
334        delegate_delta_y -= vert_threshold;
335    } else {
336      delegate_delta_y = 0.f;
337    }
338    delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
339  }
340}
341
342void OverscrollController::CompleteAction() {
343  if (delegate_)
344    delegate_->OnOverscrollComplete(overscroll_mode_);
345  overscroll_mode_ = OVERSCROLL_NONE;
346  overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
347}
348
349void OverscrollController::SetOverscrollMode(OverscrollMode mode) {
350  if (overscroll_mode_ == mode)
351    return;
352  OverscrollMode old_mode = overscroll_mode_;
353  overscroll_mode_ = mode;
354  if (overscroll_mode_ == OVERSCROLL_NONE)
355    overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
356  else
357    scroll_state_ = STATE_OVERSCROLLING;
358  if (delegate_)
359    delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_);
360}
361
362bool OverscrollController::ShouldForwardToHost(
363    const WebKit::WebInputEvent& event) const {
364  if (!WebKit::WebInputEvent::isGestureEventType(event.type))
365    return false;
366
367  // If the RenderWidgetHost already processed this event, then the event must
368  // not be sent again.
369  return !render_widget_host_->HasQueuedGestureEvents();
370}
371
372}  // namespace content
373