1 // Copyright 2013 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/renderer/input/input_handler_proxy.h"
7 #include "base/auto_reset.h"
8 #include "base/command_line.h"
9 #include "base/debug/trace_event.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "content/common/input/did_overscroll_params.h"
13 #include "content/common/input/web_input_event_traits.h"
14 #include "content/public/common/content_switches.h"
15 #include "content/renderer/input/input_handler_proxy_client.h"
16 #include "third_party/WebKit/public/platform/Platform.h"
17 #include "third_party/WebKit/public/web/WebInputEvent.h"
18 #include "ui/events/latency_info.h"
19 #include "ui/gfx/frame_time.h"
20 #include "ui/gfx/geometry/point_conversions.h"
22 using blink::WebFloatPoint
;
23 using blink::WebFloatSize
;
24 using blink::WebGestureEvent
;
25 using blink::WebInputEvent
;
26 using blink::WebMouseEvent
;
27 using blink::WebMouseWheelEvent
;
28 using blink::WebPoint
;
29 using blink::WebTouchEvent
;
30 using blink::WebTouchPoint
;
34 // Maximum time between a fling event's timestamp and the first |Animate| call
35 // for the fling curve to use the fling timestamp as the initial animation time.
36 // Two frames allows a minor delay between event creation and the first animate.
37 const double kMaxSecondsFromFlingTimestampToFirstAnimate
= 2. / 60.;
39 // Threshold for determining whether a fling scroll delta should have caused the
41 const float kScrollEpsilon
= 0.1f
;
43 // Minimum fling velocity required for the active fling and new fling for the
45 const double kMinBoostFlingSpeedSquare
= 350. * 350.;
47 // Minimum velocity for the active touch scroll to preserve (boost) an active
48 // fling for which cancellation has been deferred.
49 const double kMinBoostTouchScrollSpeedSquare
= 150 * 150.;
51 // Timeout window after which the active fling will be cancelled if no scrolls
52 // or flings of sufficient velocity relative to the current fling are received.
53 // The default value on Android native views is 40ms, but we use a slightly
54 // increased value to accomodate small IPC message delays.
55 const double kFlingBoostTimeoutDelaySeconds
= 0.045;
57 gfx::Vector2dF
ToClientScrollIncrement(const WebFloatSize
& increment
) {
58 return gfx::Vector2dF(-increment
.width
, -increment
.height
);
61 double InSecondsF(const base::TimeTicks
& time
) {
62 return (time
- base::TimeTicks()).InSecondsF();
65 bool ShouldSuppressScrollForFlingBoosting(
66 const gfx::Vector2dF
& current_fling_velocity
,
67 const WebGestureEvent
& scroll_update_event
,
68 double time_since_last_boost_event
) {
69 DCHECK_EQ(WebInputEvent::GestureScrollUpdate
, scroll_update_event
.type
);
71 gfx::Vector2dF
dx(scroll_update_event
.data
.scrollUpdate
.deltaX
,
72 scroll_update_event
.data
.scrollUpdate
.deltaY
);
73 if (gfx::DotProduct(current_fling_velocity
, dx
) < 0)
76 if (time_since_last_boost_event
< 0.001)
79 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
80 // The scroll must be of sufficient velocity to maintain the active fling.
81 const gfx::Vector2dF scroll_velocity
=
82 gfx::ScaleVector2d(dx
, 1. / time_since_last_boost_event
);
83 if (scroll_velocity
.LengthSquared() < kMinBoostTouchScrollSpeedSquare
)
89 bool ShouldBoostFling(const gfx::Vector2dF
& current_fling_velocity
,
90 const WebGestureEvent
& fling_start_event
) {
91 DCHECK_EQ(WebInputEvent::GestureFlingStart
, fling_start_event
.type
);
93 gfx::Vector2dF
new_fling_velocity(
94 fling_start_event
.data
.flingStart
.velocityX
,
95 fling_start_event
.data
.flingStart
.velocityY
);
97 if (gfx::DotProduct(current_fling_velocity
, new_fling_velocity
) < 0)
100 if (current_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
103 if (new_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
109 WebGestureEvent
ObtainGestureScrollBegin(const WebGestureEvent
& event
) {
110 WebGestureEvent scroll_begin_event
= event
;
111 scroll_begin_event
.type
= WebInputEvent::GestureScrollBegin
;
112 scroll_begin_event
.data
.scrollBegin
.deltaXHint
= 0;
113 scroll_begin_event
.data
.scrollBegin
.deltaYHint
= 0;
114 return scroll_begin_event
;
117 void SendScrollLatencyUma(const WebInputEvent
& event
,
118 const ui::LatencyInfo
& latency_info
) {
119 if (!(event
.type
== WebInputEvent::GestureScrollBegin
||
120 event
.type
== WebInputEvent::GestureScrollUpdate
||
121 event
.type
== WebInputEvent::GestureScrollUpdateWithoutPropagation
))
124 ui::LatencyInfo::LatencyMap::const_iterator it
=
125 latency_info
.latency_components
.find(std::make_pair(
126 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT
, 0));
128 if (it
== latency_info
.latency_components
.end())
131 base::TimeDelta delta
= base::TimeTicks::HighResNow() - it
->second
.event_time
;
132 for (size_t i
= 0; i
< it
->second
.event_count
; ++i
) {
133 UMA_HISTOGRAM_CUSTOM_COUNTS(
134 "Event.Latency.RendererImpl.GestureScroll2",
135 delta
.InMicroseconds(),
146 InputHandlerProxy::InputHandlerProxy(cc::InputHandler
* input_handler
,
147 InputHandlerProxyClient
* client
)
149 input_handler_(input_handler
),
150 deferred_fling_cancel_time_seconds_(0),
152 expect_scroll_update_end_(false),
154 gesture_scroll_on_impl_thread_(false),
155 gesture_pinch_on_impl_thread_(false),
156 fling_may_be_active_on_main_thread_(false),
157 disallow_horizontal_fling_scroll_(false),
158 disallow_vertical_fling_scroll_(false),
159 has_fling_animation_started_(false) {
161 input_handler_
->BindToClient(this);
162 smooth_scroll_enabled_
= CommandLine::ForCurrentProcess()->HasSwitch(
163 switches::kEnableSmoothScrolling
);
166 InputHandlerProxy::~InputHandlerProxy() {}
168 void InputHandlerProxy::WillShutdown() {
169 input_handler_
= NULL
;
170 client_
->WillShutdown();
173 InputHandlerProxy::EventDisposition
174 InputHandlerProxy::HandleInputEventWithLatencyInfo(
175 const WebInputEvent
& event
,
176 ui::LatencyInfo
* latency_info
) {
177 DCHECK(input_handler_
);
179 SendScrollLatencyUma(event
, *latency_info
);
181 TRACE_EVENT_FLOW_STEP0("input",
183 TRACE_ID_DONT_MANGLE(latency_info
->trace_id
),
184 "HandleInputEventImpl");
186 scoped_ptr
<cc::SwapPromiseMonitor
> latency_info_swap_promise_monitor
=
187 input_handler_
->CreateLatencyInfoSwapPromiseMonitor(latency_info
);
188 InputHandlerProxy::EventDisposition disposition
= HandleInputEvent(event
);
192 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleInputEvent(
193 const WebInputEvent
& event
) {
194 DCHECK(input_handler_
);
195 TRACE_EVENT1("input", "InputHandlerProxy::HandleInputEvent",
196 "type", WebInputEventTraits::GetName(event
.type
));
198 if (FilterInputEventForFlingBoosting(event
))
201 if (event
.type
== WebInputEvent::MouseWheel
) {
202 const WebMouseWheelEvent
& wheel_event
=
203 *static_cast<const WebMouseWheelEvent
*>(&event
);
204 if (wheel_event
.scrollByPage
) {
205 // TODO(jamesr): We don't properly handle scroll by page in the compositor
206 // thread, so punt it to the main thread. http://crbug.com/236639
207 return DID_NOT_HANDLE
;
209 if (wheel_event
.modifiers
& WebInputEvent::ControlKey
) {
210 // Wheel events involving the control key never trigger scrolling, only
211 // event handlers. Forward to the main thread.
212 return DID_NOT_HANDLE
;
214 if (smooth_scroll_enabled_
) {
215 cc::InputHandler::ScrollStatus scroll_status
=
216 input_handler_
->ScrollAnimated(
217 gfx::Point(wheel_event
.x
, wheel_event
.y
),
218 gfx::Vector2dF(-wheel_event
.deltaX
, -wheel_event
.deltaY
));
219 switch (scroll_status
) {
220 case cc::InputHandler::ScrollStarted
:
222 case cc::InputHandler::ScrollIgnored
:
225 return DID_NOT_HANDLE
;
228 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
229 gfx::Point(wheel_event
.x
, wheel_event
.y
), cc::InputHandler::Wheel
);
230 switch (scroll_status
) {
231 case cc::InputHandler::ScrollStarted
: {
232 TRACE_EVENT_INSTANT2(
234 "InputHandlerProxy::handle_input wheel scroll",
235 TRACE_EVENT_SCOPE_THREAD
,
239 -wheel_event
.deltaY
);
240 bool did_scroll
= input_handler_
->ScrollBy(
241 gfx::Point(wheel_event
.x
, wheel_event
.y
),
242 gfx::Vector2dF(-wheel_event
.deltaX
, -wheel_event
.deltaY
));
243 input_handler_
->ScrollEnd();
244 return did_scroll
? DID_HANDLE
: DROP_EVENT
;
246 case cc::InputHandler::ScrollIgnored
:
247 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
248 // to properly sync scrollability it's safer to send the event to the
249 // main thread. Change back to DROP_EVENT once we have synchronization
251 return DID_NOT_HANDLE
;
252 case cc::InputHandler::ScrollUnknown
:
253 case cc::InputHandler::ScrollOnMainThread
:
254 return DID_NOT_HANDLE
;
255 case cc::InputHandler::ScrollStatusCount
:
259 } else if (event
.type
== WebInputEvent::GestureScrollBegin
) {
260 DCHECK(!gesture_scroll_on_impl_thread_
);
262 DCHECK(!expect_scroll_update_end_
);
263 expect_scroll_update_end_
= true;
265 const WebGestureEvent
& gesture_event
=
266 *static_cast<const WebGestureEvent
*>(&event
);
267 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
268 gfx::Point(gesture_event
.x
, gesture_event
.y
),
269 cc::InputHandler::Gesture
);
270 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
272 cc::InputHandler::ScrollStatusCount
);
273 switch (scroll_status
) {
274 case cc::InputHandler::ScrollStarted
:
275 TRACE_EVENT_INSTANT0("input",
276 "InputHandlerProxy::handle_input gesture scroll",
277 TRACE_EVENT_SCOPE_THREAD
);
278 gesture_scroll_on_impl_thread_
= true;
280 case cc::InputHandler::ScrollUnknown
:
281 case cc::InputHandler::ScrollOnMainThread
:
282 return DID_NOT_HANDLE
;
283 case cc::InputHandler::ScrollIgnored
:
285 case cc::InputHandler::ScrollStatusCount
:
289 } else if (event
.type
== WebInputEvent::GestureScrollUpdate
) {
291 DCHECK(expect_scroll_update_end_
);
294 if (!gesture_scroll_on_impl_thread_
&& !gesture_pinch_on_impl_thread_
)
295 return DID_NOT_HANDLE
;
297 const WebGestureEvent
& gesture_event
=
298 *static_cast<const WebGestureEvent
*>(&event
);
299 bool did_scroll
= input_handler_
->ScrollBy(
300 gfx::Point(gesture_event
.x
, gesture_event
.y
),
301 gfx::Vector2dF(-gesture_event
.data
.scrollUpdate
.deltaX
,
302 -gesture_event
.data
.scrollUpdate
.deltaY
));
303 return did_scroll
? DID_HANDLE
: DROP_EVENT
;
304 } else if (event
.type
== WebInputEvent::GestureScrollEnd
) {
306 DCHECK(expect_scroll_update_end_
);
307 expect_scroll_update_end_
= false;
309 input_handler_
->ScrollEnd();
311 if (!gesture_scroll_on_impl_thread_
)
312 return DID_NOT_HANDLE
;
314 gesture_scroll_on_impl_thread_
= false;
316 } else if (event
.type
== WebInputEvent::GesturePinchBegin
) {
317 input_handler_
->PinchGestureBegin();
318 DCHECK(!gesture_pinch_on_impl_thread_
);
319 gesture_pinch_on_impl_thread_
= true;
321 } else if (event
.type
== WebInputEvent::GesturePinchEnd
) {
322 DCHECK(gesture_pinch_on_impl_thread_
);
323 gesture_pinch_on_impl_thread_
= false;
324 input_handler_
->PinchGestureEnd();
326 } else if (event
.type
== WebInputEvent::GesturePinchUpdate
) {
327 DCHECK(gesture_pinch_on_impl_thread_
);
328 const WebGestureEvent
& gesture_event
=
329 *static_cast<const WebGestureEvent
*>(&event
);
330 input_handler_
->PinchGestureUpdate(
331 gesture_event
.data
.pinchUpdate
.scale
,
332 gfx::Point(gesture_event
.x
, gesture_event
.y
));
334 } else if (event
.type
== WebInputEvent::GestureFlingStart
) {
335 const WebGestureEvent
& gesture_event
=
336 *static_cast<const WebGestureEvent
*>(&event
);
337 return HandleGestureFling(gesture_event
);
338 } else if (event
.type
== WebInputEvent::GestureFlingCancel
) {
339 if (CancelCurrentFling())
341 else if (!fling_may_be_active_on_main_thread_
)
343 } else if (event
.type
== WebInputEvent::TouchStart
) {
344 const WebTouchEvent
& touch_event
=
345 *static_cast<const WebTouchEvent
*>(&event
);
346 for (size_t i
= 0; i
< touch_event
.touchesLength
; ++i
) {
347 if (touch_event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
349 if (input_handler_
->HaveTouchEventHandlersAt(
350 gfx::Point(touch_event
.touches
[i
].position
.x
,
351 touch_event
.touches
[i
].position
.y
))) {
352 return DID_NOT_HANDLE
;
356 } else if (WebInputEvent::isKeyboardEventType(event
.type
)) {
357 // Only call |CancelCurrentFling()| if a fling was active, as it will
358 // otherwise disrupt an in-progress touch scroll.
360 CancelCurrentFling();
361 } else if (event
.type
== WebInputEvent::MouseMove
) {
362 const WebMouseEvent
& mouse_event
=
363 *static_cast<const WebMouseEvent
*>(&event
);
364 // TODO(tony): Ignore when mouse buttons are down?
365 // TODO(davemoore): This should never happen, but bug #326635 showed some
366 // surprising crashes.
367 CHECK(input_handler_
);
368 input_handler_
->MouseMoveAt(gfx::Point(mouse_event
.x
, mouse_event
.y
));
371 return DID_NOT_HANDLE
;
374 InputHandlerProxy::EventDisposition
375 InputHandlerProxy::HandleGestureFling(
376 const WebGestureEvent
& gesture_event
) {
377 cc::InputHandler::ScrollStatus scroll_status
;
379 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
380 scroll_status
= input_handler_
->ScrollBegin(
381 gfx::Point(gesture_event
.x
, gesture_event
.y
),
382 cc::InputHandler::NonBubblingGesture
);
384 if (!gesture_scroll_on_impl_thread_
)
385 scroll_status
= cc::InputHandler::ScrollOnMainThread
;
387 scroll_status
= input_handler_
->FlingScrollBegin();
391 expect_scroll_update_end_
= false;
394 switch (scroll_status
) {
395 case cc::InputHandler::ScrollStarted
: {
396 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
)
397 input_handler_
->ScrollEnd();
399 const float vx
= gesture_event
.data
.flingStart
.velocityX
;
400 const float vy
= gesture_event
.data
.flingStart
.velocityY
;
401 current_fling_velocity_
= gfx::Vector2dF(vx
, vy
);
402 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
403 gesture_event
.sourceDevice
,
404 WebFloatPoint(vx
, vy
),
406 disallow_horizontal_fling_scroll_
= !vx
;
407 disallow_vertical_fling_scroll_
= !vy
;
408 TRACE_EVENT_ASYNC_BEGIN2("input",
409 "InputHandlerProxy::HandleGestureFling::started",
415 // Note that the timestamp will only be used to kickstart the animation if
416 // its sufficiently close to the timestamp of the first call |Animate()|.
417 has_fling_animation_started_
= false;
418 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
419 fling_parameters_
.delta
= WebFloatPoint(vx
, vy
);
420 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
421 fling_parameters_
.globalPoint
=
422 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
423 fling_parameters_
.modifiers
= gesture_event
.modifiers
;
424 fling_parameters_
.sourceDevice
= gesture_event
.sourceDevice
;
425 input_handler_
->SetNeedsAnimate();
428 case cc::InputHandler::ScrollUnknown
:
429 case cc::InputHandler::ScrollOnMainThread
: {
430 TRACE_EVENT_INSTANT0("input",
431 "InputHandlerProxy::HandleGestureFling::"
432 "scroll_on_main_thread",
433 TRACE_EVENT_SCOPE_THREAD
);
434 fling_may_be_active_on_main_thread_
= true;
435 return DID_NOT_HANDLE
;
437 case cc::InputHandler::ScrollIgnored
: {
438 TRACE_EVENT_INSTANT0(
440 "InputHandlerProxy::HandleGestureFling::ignored",
441 TRACE_EVENT_SCOPE_THREAD
);
442 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
443 // We still pass the curve to the main thread if there's nothing
444 // scrollable, in case something
445 // registers a handler before the curve is over.
446 return DID_NOT_HANDLE
;
450 case cc::InputHandler::ScrollStatusCount
:
454 return DID_NOT_HANDLE
;
457 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
458 const WebInputEvent
& event
) {
459 if (!WebInputEvent::isGestureEventType(event
.type
))
463 DCHECK(!deferred_fling_cancel_time_seconds_
);
467 const WebGestureEvent
& gesture_event
=
468 static_cast<const WebGestureEvent
&>(event
);
469 if (gesture_event
.type
== WebInputEvent::GestureFlingCancel
) {
470 if (current_fling_velocity_
.LengthSquared() < kMinBoostFlingSpeedSquare
)
473 TRACE_EVENT_INSTANT0("input",
474 "InputHandlerProxy::FlingBoostStart",
475 TRACE_EVENT_SCOPE_THREAD
);
476 deferred_fling_cancel_time_seconds_
=
477 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
481 // A fling is either inactive or is "free spinning", i.e., has yet to be
482 // interrupted by a touch gesture, in which case there is nothing to filter.
483 if (!deferred_fling_cancel_time_seconds_
)
486 // Gestures from a different source should immediately interrupt the fling.
487 if (gesture_event
.sourceDevice
!= fling_parameters_
.sourceDevice
) {
488 CancelCurrentFling();
492 switch (gesture_event
.type
) {
493 case WebInputEvent::GestureTapCancel
:
494 case WebInputEvent::GestureTapDown
:
497 case WebInputEvent::GestureScrollBegin
:
498 if (!input_handler_
->IsCurrentlyScrollingLayerAt(
499 gfx::Point(gesture_event
.x
, gesture_event
.y
),
500 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchpad
501 ? cc::InputHandler::NonBubblingGesture
502 : cc::InputHandler::Gesture
)) {
503 CancelCurrentFling();
507 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
508 // determine if the ScrollBegin should immediately cancel the fling.
509 ExtendBoostedFlingTimeout(gesture_event
);
512 case WebInputEvent::GestureScrollUpdate
: {
513 const double time_since_last_boost_event
=
514 event
.timeStampSeconds
- last_fling_boost_event_
.timeStampSeconds
;
515 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_
,
517 time_since_last_boost_event
)) {
518 ExtendBoostedFlingTimeout(gesture_event
);
522 CancelCurrentFling();
526 case WebInputEvent::GestureScrollEnd
:
527 // Clear the last fling boost event *prior* to fling cancellation,
528 // preventing insertion of a synthetic GestureScrollBegin.
529 last_fling_boost_event_
= WebGestureEvent();
530 CancelCurrentFling();
533 case WebInputEvent::GestureFlingStart
: {
534 DCHECK_EQ(fling_parameters_
.sourceDevice
, gesture_event
.sourceDevice
);
537 fling_parameters_
.modifiers
== gesture_event
.modifiers
&&
538 ShouldBoostFling(current_fling_velocity_
, gesture_event
);
540 gfx::Vector2dF
new_fling_velocity(
541 gesture_event
.data
.flingStart
.velocityX
,
542 gesture_event
.data
.flingStart
.velocityY
);
545 current_fling_velocity_
+= new_fling_velocity
;
547 current_fling_velocity_
= new_fling_velocity
;
549 WebFloatPoint
velocity(current_fling_velocity_
.x(),
550 current_fling_velocity_
.y());
551 deferred_fling_cancel_time_seconds_
= 0;
552 last_fling_boost_event_
= WebGestureEvent();
553 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
554 gesture_event
.sourceDevice
,
557 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
558 fling_parameters_
.delta
= velocity
;
559 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
560 fling_parameters_
.globalPoint
=
561 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
563 TRACE_EVENT_INSTANT2("input",
564 fling_boosted
? "InputHandlerProxy::FlingBoosted"
565 : "InputHandlerProxy::FlingReplaced",
566 TRACE_EVENT_SCOPE_THREAD
,
568 current_fling_velocity_
.x(),
570 current_fling_velocity_
.y());
572 // The client expects balanced calls between a consumed GestureFlingStart
573 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
574 // |DidStopFlinging()| and only send after the accumulated fling ends.
575 client_
->DidStopFlinging();
580 // All other types of gestures (taps, presses, etc...) will complete the
581 // deferred fling cancellation.
582 CancelCurrentFling();
587 void InputHandlerProxy::ExtendBoostedFlingTimeout(
588 const blink::WebGestureEvent
& event
) {
589 TRACE_EVENT_INSTANT0("input",
590 "InputHandlerProxy::ExtendBoostedFlingTimeout",
591 TRACE_EVENT_SCOPE_THREAD
);
592 deferred_fling_cancel_time_seconds_
=
593 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
594 last_fling_boost_event_
= event
;
597 void InputHandlerProxy::Animate(base::TimeTicks time
) {
601 double monotonic_time_sec
= InSecondsF(time
);
603 if (deferred_fling_cancel_time_seconds_
&&
604 monotonic_time_sec
> deferred_fling_cancel_time_seconds_
) {
605 CancelCurrentFling();
609 if (!has_fling_animation_started_
) {
610 has_fling_animation_started_
= true;
611 // Guard against invalid, future or sufficiently stale start times, as there
612 // are no guarantees fling event and animation timestamps are compatible.
613 if (!fling_parameters_
.startTime
||
614 monotonic_time_sec
<= fling_parameters_
.startTime
||
615 monotonic_time_sec
>= fling_parameters_
.startTime
+
616 kMaxSecondsFromFlingTimestampToFirstAnimate
) {
617 fling_parameters_
.startTime
= monotonic_time_sec
;
618 input_handler_
->SetNeedsAnimate();
623 bool fling_is_active
=
624 fling_curve_
->apply(monotonic_time_sec
- fling_parameters_
.startTime
,
627 if (disallow_vertical_fling_scroll_
&& disallow_horizontal_fling_scroll_
)
628 fling_is_active
= false;
630 if (fling_is_active
) {
631 input_handler_
->SetNeedsAnimate();
633 TRACE_EVENT_INSTANT0("input",
634 "InputHandlerProxy::animate::flingOver",
635 TRACE_EVENT_SCOPE_THREAD
);
636 CancelCurrentFling();
640 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
641 fling_may_be_active_on_main_thread_
= false;
642 client_
->DidStopFlinging();
645 void InputHandlerProxy::DidOverscroll(
646 const gfx::PointF
& causal_event_viewport_point
,
647 const gfx::Vector2dF
& accumulated_overscroll
,
648 const gfx::Vector2dF
& latest_overscroll_delta
) {
651 TRACE_EVENT2("input",
652 "InputHandlerProxy::DidOverscroll",
654 latest_overscroll_delta
.x(),
656 latest_overscroll_delta
.y());
658 DidOverscrollParams params
;
659 params
.accumulated_overscroll
= accumulated_overscroll
;
660 params
.latest_overscroll_delta
= latest_overscroll_delta
;
661 params
.current_fling_velocity
=
662 ToClientScrollIncrement(current_fling_velocity_
);
663 params
.causal_event_viewport_point
= causal_event_viewport_point
;
666 static const int kFlingOverscrollThreshold
= 1;
667 disallow_horizontal_fling_scroll_
|=
668 std::abs(params
.accumulated_overscroll
.x()) >=
669 kFlingOverscrollThreshold
;
670 disallow_vertical_fling_scroll_
|=
671 std::abs(params
.accumulated_overscroll
.y()) >=
672 kFlingOverscrollThreshold
;
675 client_
->DidOverscroll(params
);
678 bool InputHandlerProxy::CancelCurrentFling() {
679 if (CancelCurrentFlingWithoutNotifyingClient()) {
680 client_
->DidStopFlinging();
686 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
687 bool had_fling_animation
= fling_curve_
;
688 if (had_fling_animation
&&
689 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchscreen
) {
690 input_handler_
->ScrollEnd();
691 TRACE_EVENT_ASYNC_END0(
693 "InputHandlerProxy::HandleGestureFling::started",
697 TRACE_EVENT_INSTANT1("input",
698 "InputHandlerProxy::CancelCurrentFling",
699 TRACE_EVENT_SCOPE_THREAD
,
700 "had_fling_animation",
701 had_fling_animation
);
702 fling_curve_
.reset();
703 has_fling_animation_started_
= false;
704 gesture_scroll_on_impl_thread_
= false;
705 current_fling_velocity_
= gfx::Vector2dF();
706 fling_parameters_
= blink::WebActiveWheelFlingParameters();
708 if (deferred_fling_cancel_time_seconds_
) {
709 deferred_fling_cancel_time_seconds_
= 0;
711 WebGestureEvent last_fling_boost_event
= last_fling_boost_event_
;
712 last_fling_boost_event_
= WebGestureEvent();
713 if (last_fling_boost_event
.type
== WebInputEvent::GestureScrollBegin
||
714 last_fling_boost_event
.type
== WebInputEvent::GestureScrollUpdate
) {
715 // Synthesize a GestureScrollBegin, as the original was suppressed.
716 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event
));
720 return had_fling_animation
;
723 bool InputHandlerProxy::TouchpadFlingScroll(
724 const WebFloatSize
& increment
) {
725 WebMouseWheelEvent synthetic_wheel
;
726 synthetic_wheel
.type
= WebInputEvent::MouseWheel
;
727 synthetic_wheel
.deltaX
= increment
.width
;
728 synthetic_wheel
.deltaY
= increment
.height
;
729 synthetic_wheel
.hasPreciseScrollingDeltas
= true;
730 synthetic_wheel
.x
= fling_parameters_
.point
.x
;
731 synthetic_wheel
.y
= fling_parameters_
.point
.y
;
732 synthetic_wheel
.globalX
= fling_parameters_
.globalPoint
.x
;
733 synthetic_wheel
.globalY
= fling_parameters_
.globalPoint
.y
;
734 synthetic_wheel
.modifiers
= fling_parameters_
.modifiers
;
736 InputHandlerProxy::EventDisposition disposition
=
737 HandleInputEvent(synthetic_wheel
);
738 switch (disposition
) {
744 TRACE_EVENT_INSTANT0("input",
745 "InputHandlerProxy::scrollBy::AbortFling",
746 TRACE_EVENT_SCOPE_THREAD
);
747 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
748 // main thread. In this case we need to schedule a commit and transfer the
749 // fling curve over to the main thread and run the rest of the wheels from
750 // there. This can happen when flinging a page that contains a scrollable
751 // subarea that we can't scroll on the thread if the fling starts outside
752 // the subarea but then is flung "under" the pointer.
753 client_
->TransferActiveWheelFlingAnimation(fling_parameters_
);
754 fling_may_be_active_on_main_thread_
= true;
755 CancelCurrentFlingWithoutNotifyingClient();
762 bool InputHandlerProxy::scrollBy(const WebFloatSize
& increment
,
763 const WebFloatSize
& velocity
) {
764 WebFloatSize clipped_increment
;
765 WebFloatSize clipped_velocity
;
766 if (!disallow_horizontal_fling_scroll_
) {
767 clipped_increment
.width
= increment
.width
;
768 clipped_velocity
.width
= velocity
.width
;
770 if (!disallow_vertical_fling_scroll_
) {
771 clipped_increment
.height
= increment
.height
;
772 clipped_velocity
.height
= velocity
.height
;
775 current_fling_velocity_
= clipped_velocity
;
777 // Early out if the increment is zero, but avoid early terimination if the
778 // velocity is still non-zero.
779 if (clipped_increment
== WebFloatSize())
780 return clipped_velocity
!= WebFloatSize();
782 TRACE_EVENT2("input",
783 "InputHandlerProxy::scrollBy",
785 clipped_increment
.width
,
787 clipped_increment
.height
);
789 bool did_scroll
= false;
791 switch (fling_parameters_
.sourceDevice
) {
792 case blink::WebGestureDeviceTouchpad
:
793 did_scroll
= TouchpadFlingScroll(clipped_increment
);
795 case blink::WebGestureDeviceTouchscreen
:
796 clipped_increment
= ToClientScrollIncrement(clipped_increment
);
797 did_scroll
= input_handler_
->ScrollBy(fling_parameters_
.point
,
803 fling_parameters_
.cumulativeScroll
.width
+= clipped_increment
.width
;
804 fling_parameters_
.cumulativeScroll
.height
+= clipped_increment
.height
;
807 // It's possible the provided |increment| is sufficiently small as to not
808 // trigger a scroll, e.g., with a trivial time delta between fling updates.
809 // Return true in this case to prevent early fling termination.
810 if (std::abs(clipped_increment
.width
) < kScrollEpsilon
&&
811 std::abs(clipped_increment
.height
) < kScrollEpsilon
)
817 } // namespace content