Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / content / browser / renderer_host / overscroll_controller.cc
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
17 bool IsScrollEndEffectEnabled() {
18   return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19       switches::kScrollEndEffect) == "1";
20 }
21
22 }  // namespace
23
24 namespace content {
25
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
34 OverscrollController::~OverscrollController() {
35 }
36
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;
46
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;
54         }
55         break;
56       }
57
58       default:
59         if (blink::WebInputEvent::isMouseEventType(event.type) ||
60             blink::WebInputEvent::isKeyboardEventType(event.type)) {
61           scroll_state_ = STATE_UNKNOWN;
62         }
63         break;
64     }
65   }
66
67   if (DispatchEventCompletesAction(event)) {
68     CompleteAction();
69
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;
80     }
81
82     return SHOULD_FORWARD_TO_RENDERER;
83   }
84
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;
92     }
93
94     // Let the event be dispatched to the renderer.
95     return SHOULD_FORWARD_TO_RENDERER;
96   }
97
98   if (overscroll_mode_ != OVERSCROLL_NONE) {
99     // Consume the event only if it updates the overscroll state.
100     if (ProcessEventForOverscroll(event))
101       return CONSUMED;
102   }
103
104   return SHOULD_FORWARD_TO_RENDERER;
105 }
106
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;
117     }
118     return;
119   }
120   ProcessEventForOverscroll(event);
121 }
122
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;
129   }
130 }
131
132 void OverscrollController::Reset() {
133   overscroll_mode_ = OVERSCROLL_NONE;
134   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
135   scroll_state_ = STATE_UNKNOWN;
136 }
137
138 void OverscrollController::Cancel() {
139   SetOverscrollMode(OVERSCROLL_NONE);
140   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
141   scroll_state_ = STATE_UNKNOWN;
142 }
143
144 bool OverscrollController::DispatchEventCompletesAction (
145     const blink::WebInputEvent& event) const {
146   if (overscroll_mode_ == OVERSCROLL_NONE)
147     return false;
148
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;
155
156   if (!delegate_)
157     return false;
158
159   gfx::Rect bounds = delegate_->GetVisibleBounds();
160   if (bounds.IsEmpty())
161     return false;
162
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();
187     }
188   }
189
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);
198   }
199
200   return ratio >= threshold;
201 }
202
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;
212     }
213
214     case blink::WebInputEvent::GestureScrollUpdate:
215     case blink::WebInputEvent::GestureFlingCancel:
216       return false;
217
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);
222   }
223 }
224
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;
234
235       ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX,
236                         wheel.deltaY * wheel.accelerationRatioY,
237                         wheel.type);
238       event_processed = true;
239       break;
240     }
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;
249     }
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;
262         }
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;
269         }
270       }
271
272       // Reset overscroll state if fling didn't complete the overscroll gesture.
273       SetOverscrollMode(OVERSCROLL_NONE);
274       break;
275     }
276
277     default:
278       DCHECK(blink::WebInputEvent::isGestureEventType(event.type) ||
279              blink::WebInputEvent::isTouchEventType(event.type))
280           << "Received unexpected event: " << event.type;
281   }
282   return event_processed;
283 }
284
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;
291
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;
302   }
303
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;
315
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;
321
322   if (overscroll_mode_ == OVERSCROLL_NONE)
323     SetOverscrollMode(new_mode);
324   else if (new_mode != overscroll_mode_)
325     SetOverscrollMode(OVERSCROLL_NONE);
326
327   if (overscroll_mode_ == OVERSCROLL_NONE)
328     return;
329
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;
343     }
344
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;
353     }
354     delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
355   }
356 }
357
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;
363 }
364
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_);
376 }
377
378 }  // namespace content