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 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 OverscrollController::Disposition
OverscrollController::DispatchEvent(
38 const blink::WebInputEvent
& event
,
39 const ui::LatencyInfo
& latency_info
) {
40 if (scroll_state_
!= STATE_UNKNOWN
) {
42 case blink::WebInputEvent::GestureScrollEnd
:
43 case blink::WebInputEvent::GestureFlingStart
:
44 scroll_state_
= STATE_UNKNOWN
;
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
;
59 if (blink::WebInputEvent::isMouseEventType(event
.type
) ||
60 blink::WebInputEvent::isKeyboardEventType(event
.type
)) {
61 scroll_state_
= STATE_UNKNOWN
;
67 if (DispatchEventCompletesAction(event
)) {
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
;
82 return SHOULD_FORWARD_TO_RENDERER
;
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
;
94 // Let the event be dispatched to the renderer.
95 return SHOULD_FORWARD_TO_RENDERER
;
98 if (overscroll_mode_
!= OVERSCROLL_NONE
) {
99 // Consume the event only if it updates the overscroll state.
100 if (ProcessEventForOverscroll(event
))
104 return SHOULD_FORWARD_TO_RENDERER
;
107 void OverscrollController::ReceivedEventACK(const blink::WebInputEvent
& event
,
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
;
120 ProcessEventForOverscroll(event
);
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
;
132 void OverscrollController::Reset() {
133 overscroll_mode_
= OVERSCROLL_NONE
;
134 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
135 scroll_state_
= STATE_UNKNOWN
;
138 void OverscrollController::Cancel() {
139 SetOverscrollMode(OVERSCROLL_NONE
);
140 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
141 scroll_state_
= STATE_UNKNOWN
;
144 bool OverscrollController::DispatchEventCompletesAction (
145 const blink::WebInputEvent
& event
) const {
146 if (overscroll_mode_
== OVERSCROLL_NONE
)
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
)
159 gfx::Rect bounds
= delegate_
->GetVisibleBounds();
160 if (bounds
.IsEmpty())
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)
172 case OVERSCROLL_WEST
:
173 if (gesture
.data
.flingStart
.velocityX
> 0)
176 case OVERSCROLL_NORTH
:
177 if (gesture
.data
.flingStart
.velocityY
> 0)
180 case OVERSCROLL_SOUTH
:
181 if (gesture
.data
.flingStart
.velocityY
< 0)
184 case OVERSCROLL_NONE
:
185 case OVERSCROLL_COUNT
:
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
);
196 ratio
= fabs(overscroll_delta_y_
) / bounds
.height();
197 threshold
= GetOverscrollConfig(OVERSCROLL_CONFIG_VERT_THRESHOLD_COMPLETE
);
200 return ratio
>= threshold
;
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
;
214 case blink::WebInputEvent::GestureScrollUpdate
:
215 case blink::WebInputEvent::GestureFlingCancel
:
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
);
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
)
235 ProcessOverscroll(wheel
.deltaX
* wheel
.accelerationRatioX
,
236 wheel
.deltaY
* wheel
.accelerationRatioY
,
238 event_processed
= true;
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
,
247 event_processed
= true;
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)) {
260 event_processed
= true;
263 } else if (fabs(velocity_y
) > kFlingVelocityThreshold
) {
264 if ((overscroll_mode_
== OVERSCROLL_NORTH
&& velocity_y
< 0) ||
265 (overscroll_mode_
== OVERSCROLL_SOUTH
&& velocity_y
> 0)) {
267 event_processed
= true;
272 // Reset overscroll state if fling didn't complete the overscroll gesture.
273 SetOverscrollMode(OVERSCROLL_NONE
);
278 DCHECK(blink::WebInputEvent::isGestureEventType(event
.type
) ||
279 blink::WebInputEvent::isTouchEventType(event
.type
))
280 << "Received unexpected event: " << event
.type
;
282 return event_processed
;
285 void OverscrollController::ProcessOverscroll(float delta_x
,
287 blink::WebInputEvent::Type type
) {
288 if (scroll_state_
!= STATE_CONTENT_SCROLLING
)
289 overscroll_delta_x_
+= delta_x
;
290 overscroll_delta_y_
+= delta_y
;
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
);
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
;
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
;
322 if (overscroll_mode_
== OVERSCROLL_NONE
)
323 SetOverscrollMode(new_mode
);
324 else if (new_mode
!= overscroll_mode_
)
325 SetOverscrollMode(OVERSCROLL_NONE
);
327 if (overscroll_mode_
== OVERSCROLL_NONE
)
330 // Tell the delegate about the overscroll update so that it can update
331 // the display accordingly (e.g. show history preview etc.).
333 // Do not include the threshold amount when sending the deltas to the
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
;
340 delegate_delta_x
-= horiz_threshold
;
342 delegate_delta_x
= 0.f
;
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
;
350 delegate_delta_y
-= vert_threshold
;
352 delegate_delta_y
= 0.f
;
354 delegate_
->OnOverscrollUpdate(delegate_delta_x
, delegate_delta_y
);
358 void OverscrollController::CompleteAction() {
360 delegate_
->OnOverscrollComplete(overscroll_mode_
);
361 overscroll_mode_
= OVERSCROLL_NONE
;
362 overscroll_delta_x_
= overscroll_delta_y_
= 0.f
;
365 void OverscrollController::SetOverscrollMode(OverscrollMode mode
) {
366 if (overscroll_mode_
== mode
)
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
;
373 scroll_state_
= STATE_OVERSCROLLING
;
375 delegate_
->OnOverscrollModeChange(old_mode
, overscroll_mode_
);
378 } // namespace content