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.
5 #include "content/browser/renderer_host/overscroll_controller.h"
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"
13 using blink::WebInputEvent
;
17 bool IsScrollEndEffectEnabled() {
18 return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
19 switches::kScrollEndEffect
) == "1";
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
),
34 OverscrollController::~OverscrollController() {
37 bool OverscrollController::WillHandleEvent(const blink::WebInputEvent
& event
) {
38 if (scroll_state_
!= STATE_UNKNOWN
) {
40 case blink::WebInputEvent::GestureScrollEnd
:
41 case blink::WebInputEvent::GestureFlingStart
:
42 scroll_state_
= STATE_UNKNOWN
;
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
;
57 if (blink::WebInputEvent::isMouseEventType(event
.type
) ||
58 blink::WebInputEvent::isKeyboardEventType(event
.type
)) {
59 scroll_state_
= STATE_UNKNOWN
;
65 if (DispatchEventCompletesAction(event
)) {
68 // Let the event be dispatched to the renderer.
72 if (overscroll_mode_
!= OVERSCROLL_NONE
&& DispatchEventResetsState(event
)) {
73 SetOverscrollMode(OVERSCROLL_NONE
);
75 // Let the event be dispatched to the renderer.
79 if (overscroll_mode_
!= OVERSCROLL_NONE
) {
80 // Consume the event only if it updates the overscroll state.
81 if (ProcessEventForOverscroll(event
))
88 void OverscrollController::ReceivedEventACK(const blink::WebInputEvent
& event
,
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
;
101 ProcessEventForOverscroll(event
);
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
;
113 void OverscrollController::Reset() {
114 overscroll_mode_
= OVERSCROLL_NONE
;
115 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
116 scroll_state_
= STATE_UNKNOWN
;
119 void OverscrollController::Cancel() {
120 SetOverscrollMode(OVERSCROLL_NONE
);
121 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
122 scroll_state_
= STATE_UNKNOWN
;
125 bool OverscrollController::DispatchEventCompletesAction (
126 const blink::WebInputEvent
& event
) const {
127 if (overscroll_mode_
== OVERSCROLL_NONE
)
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
)
140 gfx::Rect bounds
= delegate_
->GetVisibleBounds();
141 if (bounds
.IsEmpty())
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)
153 case OVERSCROLL_WEST
:
154 if (gesture
.data
.flingStart
.velocityX
> 0)
157 case OVERSCROLL_NORTH
:
158 if (gesture
.data
.flingStart
.velocityY
> 0)
161 case OVERSCROLL_SOUTH
:
162 if (gesture
.data
.flingStart
.velocityY
< 0)
165 case OVERSCROLL_NONE
:
166 case OVERSCROLL_COUNT
:
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
);
177 ratio
= fabs(overscroll_delta_y_
) / bounds
.height();
178 threshold
= GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE
);
181 return ratio
>= threshold
;
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
;
195 case blink::WebInputEvent::GestureScrollUpdate
:
196 case blink::WebInputEvent::GestureFlingCancel
:
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
);
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
)
216 ProcessOverscroll(wheel
.deltaX
* wheel
.accelerationRatioX
,
217 wheel
.deltaY
* wheel
.accelerationRatioY
,
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
,
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)) {
239 event_processed
= true;
242 } else if (fabs(velocity_y
) > kFlingVelocityThreshold
) {
243 if ((overscroll_mode_
== OVERSCROLL_NORTH
&& velocity_y
< 0) ||
244 (overscroll_mode_
== OVERSCROLL_SOUTH
&& velocity_y
> 0)) {
246 event_processed
= true;
251 // Reset overscroll state if fling didn't complete the overscroll gesture.
252 SetOverscrollMode(OVERSCROLL_NONE
);
257 DCHECK(blink::WebInputEvent::isGestureEventType(event
.type
) ||
258 blink::WebInputEvent::isTouchEventType(event
.type
))
259 << "Received unexpected event: " << event
.type
;
261 return event_processed
;
264 bool OverscrollController::ProcessOverscroll(float delta_x
,
266 blink::WebInputEvent::Type type
) {
267 if (scroll_state_
!= STATE_CONTENT_SCROLLING
)
268 overscroll_delta_x_
+= delta_x
;
269 overscroll_delta_y_
+= delta_y
;
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
);
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
;
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
;
301 if (overscroll_mode_
== OVERSCROLL_NONE
)
302 SetOverscrollMode(new_mode
);
303 else if (new_mode
!= overscroll_mode_
)
304 SetOverscrollMode(OVERSCROLL_NONE
);
306 if (overscroll_mode_
== OVERSCROLL_NONE
)
309 // Tell the delegate about the overscroll update so that it can update
310 // the display accordingly (e.g. show history preview etc.).
312 // Do not include the threshold amount when sending the deltas to the
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
;
319 delegate_delta_x
-= horiz_threshold
;
321 delegate_delta_x
= 0.f
;
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
;
329 delegate_delta_y
-= vert_threshold
;
331 delegate_delta_y
= 0.f
;
333 return delegate_
->OnOverscrollUpdate(delegate_delta_x
, delegate_delta_y
);
338 void OverscrollController::CompleteAction() {
340 delegate_
->OnOverscrollComplete(overscroll_mode_
);
341 overscroll_mode_
= OVERSCROLL_NONE
;
342 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
345 void OverscrollController::SetOverscrollMode(OverscrollMode mode
) {
346 if (overscroll_mode_
== mode
)
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
;
353 scroll_state_
= STATE_OVERSCROLLING
;
355 delegate_
->OnOverscrollModeChange(old_mode
, overscroll_mode_
);
358 } // namespace content