• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
IsScrollEndEffectEnabled()17 bool IsScrollEndEffectEnabled() {
18   return CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19       switches::kScrollEndEffect) == "1";
20 }
21 
22 }  // namespace
23 
24 namespace content {
25 
OverscrollController()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 
~OverscrollController()34 OverscrollController::~OverscrollController() {
35 }
36 
WillHandleEvent(const blink::WebInputEvent & event)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 
ReceivedEventACK(const blink::WebInputEvent & event,bool processed)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 
DiscardingGestureEvent(const blink::WebGestureEvent & gesture)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 
Reset()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 
Cancel()119 void OverscrollController::Cancel() {
120   SetOverscrollMode(OVERSCROLL_NONE);
121   overscroll_delta_x_ = overscroll_delta_y_ = 0.f;
122   scroll_state_ = STATE_UNKNOWN;
123 }
124 
DispatchEventCompletesAction(const blink::WebInputEvent & event) const125 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 
DispatchEventResetsState(const blink::WebInputEvent & event) const184 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 
ProcessEventForOverscroll(const blink::WebInputEvent & event)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 
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 
ProcessOverscroll(float delta_x,float delta_y,blink::WebInputEvent::Type type)266 void 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 
CompleteAction()339 void 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 
SetOverscrollMode(OverscrollMode mode)346 void 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