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 bool reset_scroll_state
= false;
39 if (scroll_state_
!= STATE_UNKNOWN
||
40 overscroll_delta_x_
|| overscroll_delta_y_
) {
42 case blink::WebInputEvent::GestureScrollEnd
:
43 case blink::WebInputEvent::GestureFlingStart
:
44 reset_scroll_state
= true;
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 reset_scroll_state
= true;
59 if (blink::WebInputEvent::isMouseEventType(event
.type
) ||
60 blink::WebInputEvent::isKeyboardEventType(event
.type
)) {
61 reset_scroll_state
= true;
67 if (reset_scroll_state
)
68 scroll_state_
= STATE_UNKNOWN
;
70 if (DispatchEventCompletesAction(event
)) {
73 // Let the event be dispatched to the renderer.
77 if (overscroll_mode_
!= OVERSCROLL_NONE
&& DispatchEventResetsState(event
)) {
78 SetOverscrollMode(OVERSCROLL_NONE
);
80 // Let the event be dispatched to the renderer.
84 if (overscroll_mode_
!= OVERSCROLL_NONE
) {
85 // Consume the event only if it updates the overscroll state.
86 if (ProcessEventForOverscroll(event
))
88 } else if (reset_scroll_state
) {
89 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
96 void OverscrollController::ReceivedEventACK(const blink::WebInputEvent
& event
,
99 // If a scroll event is consumed by the page, i.e. some content on the page
100 // has been scrolled, then there is not going to be an overscroll gesture,
101 // until the current scroll ends, and a new scroll gesture starts.
102 if (scroll_state_
== STATE_UNKNOWN
&&
103 (event
.type
== blink::WebInputEvent::MouseWheel
||
104 event
.type
== blink::WebInputEvent::GestureScrollUpdate
)) {
105 scroll_state_
= STATE_CONTENT_SCROLLING
;
109 ProcessEventForOverscroll(event
);
112 void OverscrollController::DiscardingGestureEvent(
113 const blink::WebGestureEvent
& gesture
) {
114 if (scroll_state_
!= STATE_UNKNOWN
&&
115 (gesture
.type
== blink::WebInputEvent::GestureScrollEnd
||
116 gesture
.type
== blink::WebInputEvent::GestureFlingStart
)) {
117 scroll_state_
= STATE_UNKNOWN
;
121 void OverscrollController::Reset() {
122 overscroll_mode_
= OVERSCROLL_NONE
;
123 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
124 scroll_state_
= STATE_UNKNOWN
;
127 void OverscrollController::Cancel() {
128 SetOverscrollMode(OVERSCROLL_NONE
);
129 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
130 scroll_state_
= STATE_UNKNOWN
;
133 bool OverscrollController::DispatchEventCompletesAction (
134 const blink::WebInputEvent
& event
) const {
135 if (overscroll_mode_
== OVERSCROLL_NONE
)
138 // Complete the overscroll gesture if there was a mouse move or a scroll-end
139 // after the threshold.
140 if (event
.type
!= blink::WebInputEvent::MouseMove
&&
141 event
.type
!= blink::WebInputEvent::GestureScrollEnd
&&
142 event
.type
!= blink::WebInputEvent::GestureFlingStart
)
148 gfx::Rect bounds
= delegate_
->GetVisibleBounds();
149 if (bounds
.IsEmpty())
152 if (event
.type
== blink::WebInputEvent::GestureFlingStart
) {
153 // Check to see if the fling is in the same direction of the overscroll.
154 const blink::WebGestureEvent gesture
=
155 static_cast<const blink::WebGestureEvent
&>(event
);
156 switch (overscroll_mode_
) {
157 case OVERSCROLL_EAST
:
158 if (gesture
.data
.flingStart
.velocityX
< 0)
161 case OVERSCROLL_WEST
:
162 if (gesture
.data
.flingStart
.velocityX
> 0)
165 case OVERSCROLL_NORTH
:
166 if (gesture
.data
.flingStart
.velocityY
> 0)
169 case OVERSCROLL_SOUTH
:
170 if (gesture
.data
.flingStart
.velocityY
< 0)
173 case OVERSCROLL_NONE
:
178 float ratio
, threshold
;
179 if (overscroll_mode_
== OVERSCROLL_WEST
||
180 overscroll_mode_
== OVERSCROLL_EAST
) {
181 ratio
= fabs(overscroll_delta_x_
) / bounds
.width();
182 threshold
= GetOverscrollConfig(OVERSCROLL_CONFIG_HORIZ_THRESHOLD_COMPLETE
);
184 ratio
= fabs(overscroll_delta_y_
) / bounds
.height();
185 threshold
= GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE
);
188 return ratio
>= threshold
;
191 bool OverscrollController::DispatchEventResetsState(
192 const blink::WebInputEvent
& event
) const {
193 switch (event
.type
) {
194 case blink::WebInputEvent::MouseWheel
: {
195 // Only wheel events with precise deltas (i.e. from trackpad) contribute
196 // to the overscroll gesture.
197 const blink::WebMouseWheelEvent
& wheel
=
198 static_cast<const blink::WebMouseWheelEvent
&>(event
);
199 return !wheel
.hasPreciseScrollingDeltas
;
202 case blink::WebInputEvent::GestureScrollUpdate
:
203 case blink::WebInputEvent::GestureFlingCancel
:
207 // Touch events can arrive during an overscroll gesture initiated by
208 // touch-scrolling. These events should not reset the overscroll state.
209 return !blink::WebInputEvent::isTouchEventType(event
.type
);
213 bool OverscrollController::ProcessEventForOverscroll(
214 const blink::WebInputEvent
& event
) {
215 bool event_processed
= false;
216 switch (event
.type
) {
217 case blink::WebInputEvent::MouseWheel
: {
218 const blink::WebMouseWheelEvent
& wheel
=
219 static_cast<const blink::WebMouseWheelEvent
&>(event
);
220 if (!wheel
.hasPreciseScrollingDeltas
)
223 ProcessOverscroll(wheel
.deltaX
* wheel
.accelerationRatioX
,
224 wheel
.deltaY
* wheel
.accelerationRatioY
,
228 case blink::WebInputEvent::GestureScrollUpdate
: {
229 const blink::WebGestureEvent
& gesture
=
230 static_cast<const blink::WebGestureEvent
&>(event
);
231 event_processed
= ProcessOverscroll(gesture
.data
.scrollUpdate
.deltaX
,
232 gesture
.data
.scrollUpdate
.deltaY
,
236 case blink::WebInputEvent::GestureFlingStart
: {
237 const float kFlingVelocityThreshold
= 1100.f
;
238 const blink::WebGestureEvent
& gesture
=
239 static_cast<const blink::WebGestureEvent
&>(event
);
240 float velocity_x
= gesture
.data
.flingStart
.velocityX
;
241 float velocity_y
= gesture
.data
.flingStart
.velocityY
;
242 if (fabs(velocity_x
) > kFlingVelocityThreshold
) {
243 if ((overscroll_mode_
== OVERSCROLL_WEST
&& velocity_x
< 0) ||
244 (overscroll_mode_
== OVERSCROLL_EAST
&& velocity_x
> 0)) {
246 event_processed
= true;
249 } else if (fabs(velocity_y
) > kFlingVelocityThreshold
) {
250 if ((overscroll_mode_
== OVERSCROLL_NORTH
&& velocity_y
< 0) ||
251 (overscroll_mode_
== OVERSCROLL_SOUTH
&& velocity_y
> 0)) {
253 event_processed
= true;
258 // Reset overscroll state if fling didn't complete the overscroll gesture.
259 SetOverscrollMode(OVERSCROLL_NONE
);
264 DCHECK(blink::WebInputEvent::isGestureEventType(event
.type
) ||
265 blink::WebInputEvent::isTouchEventType(event
.type
))
266 << "Received unexpected event: " << event
.type
;
268 return event_processed
;
271 bool OverscrollController::ProcessOverscroll(float delta_x
,
273 blink::WebInputEvent::Type type
) {
274 if (scroll_state_
!= STATE_CONTENT_SCROLLING
)
275 overscroll_delta_x_
+= delta_x
;
276 overscroll_delta_y_
+= delta_y
;
278 float horiz_threshold
= GetOverscrollConfig(
279 WebInputEvent::isGestureEventType(type
) ?
280 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHSCREEN
:
281 OVERSCROLL_CONFIG_HORIZ_THRESHOLD_START_TOUCHPAD
);
282 float vert_threshold
= GetOverscrollConfig(
283 OVERSCROLL_CONFIG_VERT_THRESHOLD_START
);
284 if (fabs(overscroll_delta_x_
) <= horiz_threshold
&&
285 fabs(overscroll_delta_y_
) <= vert_threshold
) {
286 SetOverscrollMode(OVERSCROLL_NONE
);
290 // Compute the current overscroll direction. If the direction is different
291 // from the current direction, then always switch to no-overscroll mode first
292 // to make sure that subsequent scroll events go through to the page first.
293 OverscrollMode new_mode
= OVERSCROLL_NONE
;
294 const float kMinRatio
= 2.5;
295 if (fabs(overscroll_delta_x_
) > horiz_threshold
&&
296 fabs(overscroll_delta_x_
) > fabs(overscroll_delta_y_
) * kMinRatio
)
297 new_mode
= overscroll_delta_x_
> 0.f
? OVERSCROLL_EAST
: OVERSCROLL_WEST
;
298 else if (fabs(overscroll_delta_y_
) > vert_threshold
&&
299 fabs(overscroll_delta_y_
) > fabs(overscroll_delta_x_
) * kMinRatio
)
300 new_mode
= overscroll_delta_y_
> 0.f
? OVERSCROLL_SOUTH
: OVERSCROLL_NORTH
;
302 // The vertical oversrcoll currently does not have any UX effects other then
303 // for the scroll end effect, so testing if it is enabled.
304 if ((new_mode
== OVERSCROLL_SOUTH
|| new_mode
== OVERSCROLL_NORTH
) &&
305 !IsScrollEndEffectEnabled())
306 new_mode
= OVERSCROLL_NONE
;
308 if (overscroll_mode_
== OVERSCROLL_NONE
)
309 SetOverscrollMode(new_mode
);
310 else if (new_mode
!= overscroll_mode_
)
311 SetOverscrollMode(OVERSCROLL_NONE
);
313 if (overscroll_mode_
== OVERSCROLL_NONE
)
316 // Tell the delegate about the overscroll update so that it can update
317 // the display accordingly (e.g. show history preview etc.).
319 // Do not include the threshold amount when sending the deltas to the
321 float delegate_delta_x
= overscroll_delta_x_
;
322 if (fabs(delegate_delta_x
) > horiz_threshold
) {
323 if (delegate_delta_x
< 0)
324 delegate_delta_x
+= horiz_threshold
;
326 delegate_delta_x
-= horiz_threshold
;
328 delegate_delta_x
= 0.f
;
331 float delegate_delta_y
= overscroll_delta_y_
;
332 if (fabs(delegate_delta_y
) > vert_threshold
) {
333 if (delegate_delta_y
< 0)
334 delegate_delta_y
+= vert_threshold
;
336 delegate_delta_y
-= vert_threshold
;
338 delegate_delta_y
= 0.f
;
340 return delegate_
->OnOverscrollUpdate(delegate_delta_x
, delegate_delta_y
);
345 void OverscrollController::CompleteAction() {
347 delegate_
->OnOverscrollComplete(overscroll_mode_
);
348 overscroll_mode_
= OVERSCROLL_NONE
;
349 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
352 void OverscrollController::SetOverscrollMode(OverscrollMode mode
) {
353 if (overscroll_mode_
== mode
)
355 OverscrollMode old_mode
= overscroll_mode_
;
356 overscroll_mode_
= mode
;
357 if (overscroll_mode_
== OVERSCROLL_NONE
)
358 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
360 scroll_state_
= STATE_OVERSCROLLING
;
362 delegate_
->OnOverscrollModeChange(old_mode
, overscroll_mode_
);
365 } // namespace content