Upstream version 11.40.277.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 base::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 bool 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
88 void 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
104 void 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
113 void OverscrollController::Reset() {
114   overscroll_mode_ = OVERSCROLL_NONE;
115   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
116   scroll_state_ = STATE_UNKNOWN;
117 }
118
119 void OverscrollController::Cancel() {
120   SetOverscrollMode(OVERSCROLL_NONE);
121   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
122   scroll_state_ = STATE_UNKNOWN;
123 }
124
125 bool 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
184 bool 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
206 bool 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       event_processed =
216           ProcessOverscroll(wheel.deltaX * wheel.accelerationRatioX,
217                             wheel.deltaY * wheel.accelerationRatioY,
218                             wheel.type);
219       break;
220     }
221     case blink::WebInputEvent::GestureScrollUpdate: {
222       const blink::WebGestureEvent& gesture =
223           static_cast<const blink::WebGestureEvent&>(event);
224       event_processed = ProcessOverscroll(gesture.data.scrollUpdate.deltaX,
225                                           gesture.data.scrollUpdate.deltaY,
226                                           gesture.type);
227       break;
228     }
229     case blink::WebInputEvent::GestureFlingStart: {
230       const float kFlingVelocityThreshold = 1100.f;
231       const blink::WebGestureEvent& gesture =
232           static_cast<const blink::WebGestureEvent&>(event);
233       float velocity_x = gesture.data.flingStart.velocityX;
234       float velocity_y = gesture.data.flingStart.velocityY;
235       if (fabs(velocity_x) > kFlingVelocityThreshold) {
236         if ((overscroll_mode_ == OVERSCROLL_WEST && velocity_x < 0) ||
237             (overscroll_mode_ == OVERSCROLL_EAST && velocity_x > 0)) {
238           CompleteAction();
239           event_processed = true;
240           break;
241         }
242       } else if (fabs(velocity_y) > kFlingVelocityThreshold) {
243         if ((overscroll_mode_ == OVERSCROLL_NORTH && velocity_y < 0) ||
244             (overscroll_mode_ == OVERSCROLL_SOUTH && velocity_y > 0)) {
245           CompleteAction();
246           event_processed = true;
247           break;
248         }
249       }
250
251       // Reset overscroll state if fling didn't complete the overscroll gesture.
252       SetOverscrollMode(OVERSCROLL_NONE);
253       break;
254     }
255
256     default:
257       DCHECK(blink::WebInputEvent::isGestureEventType(event.type) ||
258              blink::WebInputEvent::isTouchEventType(event.type))
259           << "Received unexpected event: " << event.type;
260   }
261   return event_processed;
262 }
263
264 bool OverscrollController::ProcessOverscroll(float delta_x,
265                                              float delta_y,
266                                              blink::WebInputEvent::Type type) {
267   if (scroll_state_ != STATE_CONTENT_SCROLLING)
268     overscroll_delta_x_ += delta_x;
269   overscroll_delta_y_ += delta_y;
270
271   float horiz_threshold = GetOverscrollConfig(
272       WebInputEvent::isGestureEventType(type) ?
273           OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN :
274           OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD);
275   float vert_threshold = GetOverscrollConfig(
276       OVERSCROLL_CONFIG_VERT_THRESHOLD_START);
277   if (fabs(overscroll_delta_x_) <= horiz_threshold &&
278       fabs(overscroll_delta_y_) <= vert_threshold) {
279     SetOverscrollMode(OVERSCROLL_NONE);
280     return true;
281   }
282
283   // Compute the current overscroll direction. If the direction is different
284   // from the current direction, then always switch to no-overscroll mode first
285   // to make sure that subsequent scroll events go through to the page first.
286   OverscrollMode new_mode = OVERSCROLL_NONE;
287   const float kMinRatio = 2.5;
288   if (fabs(overscroll_delta_x_) > horiz_threshold &&
289       fabs(overscroll_delta_x_) > fabs(overscroll_delta_y_) * kMinRatio)
290     new_mode = overscroll_delta_x_ > 0.f ? OVERSCROLL_EAST : OVERSCROLL_WEST;
291   else if (fabs(overscroll_delta_y_) > vert_threshold &&
292            fabs(overscroll_delta_y_) > fabs(overscroll_delta_x_) * kMinRatio)
293     new_mode = overscroll_delta_y_ > 0.f ? OVERSCROLL_SOUTH : OVERSCROLL_NORTH;
294
295   // The vertical oversrcoll currently does not have any UX effects other then
296   // for the scroll end effect, so testing if it is enabled.
297   if ((new_mode == OVERSCROLL_SOUTH || new_mode == OVERSCROLL_NORTH) &&
298       !IsScrollEndEffectEnabled())
299     new_mode = OVERSCROLL_NONE;
300
301   if (overscroll_mode_ == OVERSCROLL_NONE)
302     SetOverscrollMode(new_mode);
303   else if (new_mode != overscroll_mode_)
304     SetOverscrollMode(OVERSCROLL_NONE);
305
306   if (overscroll_mode_ == OVERSCROLL_NONE)
307     return false;
308
309   // Tell the delegate about the overscroll update so that it can update
310   // the display accordingly (e.g. show history preview etc.).
311   if (delegate_) {
312     // Do not include the threshold amount when sending the deltas to the
313     // delegate.
314     float delegate_delta_x = overscroll_delta_x_;
315     if (fabs(delegate_delta_x) > horiz_threshold) {
316       if (delegate_delta_x < 0)
317         delegate_delta_x += horiz_threshold;
318       else
319         delegate_delta_x -= horiz_threshold;
320     } else {
321       delegate_delta_x = 0.f;
322     }
323
324     float delegate_delta_y = overscroll_delta_y_;
325     if (fabs(delegate_delta_y) > vert_threshold) {
326       if (delegate_delta_y < 0)
327         delegate_delta_y += vert_threshold;
328       else
329         delegate_delta_y -= vert_threshold;
330     } else {
331       delegate_delta_y = 0.f;
332     }
333     return delegate_->OnOverscrollUpdate(delegate_delta_x, delegate_delta_y);
334   }
335   return false;
336 }
337
338 void OverscrollController::CompleteAction() {
339   if (delegate_)
340     delegate_->OnOverscrollComplete(overscroll_mode_);
341   overscroll_mode_ = OVERSCROLL_NONE;
342   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
343 }
344
345 void OverscrollController::SetOverscrollMode(OverscrollMode mode) {
346   if (overscroll_mode_ == mode)
347     return;
348   OverscrollMode old_mode = overscroll_mode_;
349   overscroll_mode_ = mode;
350   if (overscroll_mode_ == OVERSCROLL_NONE)
351     overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
352   else
353     scroll_state_ = STATE_OVERSCROLLING;
354   if (delegate_)
355     delegate_->OnOverscrollModeChange(old_mode, overscroll_mode_);
356 }
357
358 }  // namespace content