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/location.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/thread_task_runner_handle.h"
14 #include "base/trace_event/trace_event.h"
15 #include "content/common/input/did_overscroll_params.h"
16 #include "content/common/input/web_input_event_traits.h"
17 #include "content/public/common/content_switches.h"
18 #include "content/renderer/input/input_handler_proxy_client.h"
19 #include "content/renderer/input/input_scroll_elasticity_controller.h"
20 #include "third_party/WebKit/public/platform/Platform.h"
21 #include "third_party/WebKit/public/web/WebInputEvent.h"
22 #include "ui/events/latency_info.h"
23 #include "ui/gfx/geometry/point_conversions.h"
25 using blink::WebFloatPoint
;
26 using blink::WebFloatSize
;
27 using blink::WebGestureEvent
;
28 using blink::WebInputEvent
;
29 using blink::WebMouseEvent
;
30 using blink::WebMouseWheelEvent
;
31 using blink::WebPoint
;
32 using blink::WebTouchEvent
;
33 using blink::WebTouchPoint
;
37 // Maximum time between a fling event's timestamp and the first |Animate| call
38 // for the fling curve to use the fling timestamp as the initial animation time.
39 // Two frames allows a minor delay between event creation and the first animate.
40 const double kMaxSecondsFromFlingTimestampToFirstAnimate
= 2. / 60.;
42 // Threshold for determining whether a fling scroll delta should have caused the
44 const float kScrollEpsilon
= 0.1f
;
46 // Minimum fling velocity required for the active fling and new fling for the
48 const double kMinBoostFlingSpeedSquare
= 350. * 350.;
50 // Minimum velocity for the active touch scroll to preserve (boost) an active
51 // fling for which cancellation has been deferred.
52 const double kMinBoostTouchScrollSpeedSquare
= 150 * 150.;
54 // Timeout window after which the active fling will be cancelled if no scrolls
55 // or flings of sufficient velocity relative to the current fling are received.
56 // The default value on Android native views is 40ms, but we use a slightly
57 // increased value to accomodate small IPC message delays.
58 const double kFlingBoostTimeoutDelaySeconds
= 0.045;
60 gfx::Vector2dF
ToClientScrollIncrement(const WebFloatSize
& increment
) {
61 return gfx::Vector2dF(-increment
.width
, -increment
.height
);
64 double InSecondsF(const base::TimeTicks
& time
) {
65 return (time
- base::TimeTicks()).InSecondsF();
68 bool ShouldSuppressScrollForFlingBoosting(
69 const gfx::Vector2dF
& current_fling_velocity
,
70 const WebGestureEvent
& scroll_update_event
,
71 double time_since_last_boost_event
) {
72 DCHECK_EQ(WebInputEvent::GestureScrollUpdate
, scroll_update_event
.type
);
74 gfx::Vector2dF
dx(scroll_update_event
.data
.scrollUpdate
.deltaX
,
75 scroll_update_event
.data
.scrollUpdate
.deltaY
);
76 if (gfx::DotProduct(current_fling_velocity
, dx
) <= 0)
79 if (time_since_last_boost_event
< 0.001)
82 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
83 // The scroll must be of sufficient velocity to maintain the active fling.
84 const gfx::Vector2dF scroll_velocity
=
85 gfx::ScaleVector2d(dx
, 1. / time_since_last_boost_event
);
86 if (scroll_velocity
.LengthSquared() < kMinBoostTouchScrollSpeedSquare
)
92 bool ShouldBoostFling(const gfx::Vector2dF
& current_fling_velocity
,
93 const WebGestureEvent
& fling_start_event
) {
94 DCHECK_EQ(WebInputEvent::GestureFlingStart
, fling_start_event
.type
);
96 gfx::Vector2dF
new_fling_velocity(
97 fling_start_event
.data
.flingStart
.velocityX
,
98 fling_start_event
.data
.flingStart
.velocityY
);
100 if (gfx::DotProduct(current_fling_velocity
, new_fling_velocity
) <= 0)
103 if (current_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
106 if (new_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
112 WebGestureEvent
ObtainGestureScrollBegin(const WebGestureEvent
& event
) {
113 WebGestureEvent scroll_begin_event
= event
;
114 scroll_begin_event
.type
= WebInputEvent::GestureScrollBegin
;
115 scroll_begin_event
.data
.scrollBegin
.deltaXHint
= 0;
116 scroll_begin_event
.data
.scrollBegin
.deltaYHint
= 0;
117 return scroll_begin_event
;
120 void ReportInputEventLatencyUma(const WebInputEvent
& event
,
121 const ui::LatencyInfo
& latency_info
) {
122 if (!(event
.type
== WebInputEvent::GestureScrollBegin
||
123 event
.type
== WebInputEvent::GestureScrollUpdate
||
124 event
.type
== WebInputEvent::GesturePinchBegin
||
125 event
.type
== WebInputEvent::GesturePinchUpdate
||
126 event
.type
== WebInputEvent::GestureFlingStart
)) {
130 ui::LatencyInfo::LatencyMap::const_iterator it
=
131 latency_info
.latency_components
.find(std::make_pair(
132 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT
, 0));
134 if (it
== latency_info
.latency_components
.end())
137 base::TimeDelta delta
= base::TimeTicks::Now() - it
->second
.event_time
;
138 for (size_t i
= 0; i
< it
->second
.event_count
; ++i
) {
139 switch (event
.type
) {
140 case blink::WebInputEvent::GestureScrollBegin
:
141 UMA_HISTOGRAM_CUSTOM_COUNTS(
142 "Event.Latency.RendererImpl.GestureScrollBegin",
143 delta
.InMicroseconds(), 1, 1000000, 100);
145 case blink::WebInputEvent::GestureScrollUpdate
:
146 UMA_HISTOGRAM_CUSTOM_COUNTS(
147 // So named for historical reasons.
148 "Event.Latency.RendererImpl.GestureScroll2",
149 delta
.InMicroseconds(), 1, 1000000, 100);
151 case blink::WebInputEvent::GesturePinchBegin
:
152 UMA_HISTOGRAM_CUSTOM_COUNTS(
153 "Event.Latency.RendererImpl.GesturePinchBegin",
154 delta
.InMicroseconds(), 1, 1000000, 100);
156 case blink::WebInputEvent::GesturePinchUpdate
:
157 UMA_HISTOGRAM_CUSTOM_COUNTS(
158 "Event.Latency.RendererImpl.GesturePinchUpdate",
159 delta
.InMicroseconds(), 1, 1000000, 100);
161 case blink::WebInputEvent::GestureFlingStart
:
162 UMA_HISTOGRAM_CUSTOM_COUNTS(
163 "Event.Latency.RendererImpl.GestureFlingStart",
164 delta
.InMicroseconds(), 1, 1000000, 100);
177 InputHandlerProxy::InputHandlerProxy(cc::InputHandler
* input_handler
,
178 InputHandlerProxyClient
* client
)
180 input_handler_(input_handler
),
181 deferred_fling_cancel_time_seconds_(0),
183 expect_scroll_update_end_(false),
185 gesture_scroll_on_impl_thread_(false),
186 gesture_pinch_on_impl_thread_(false),
187 fling_may_be_active_on_main_thread_(false),
188 disallow_horizontal_fling_scroll_(false),
189 disallow_vertical_fling_scroll_(false),
190 has_fling_animation_started_(false),
191 uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) {
193 input_handler_
->BindToClient(this);
194 smooth_scroll_enabled_
= base::CommandLine::ForCurrentProcess()->HasSwitch(
195 switches::kEnableSmoothScrolling
);
196 cc::ScrollElasticityHelper
* scroll_elasticity_helper
=
197 input_handler_
->CreateScrollElasticityHelper();
198 if (scroll_elasticity_helper
) {
199 scroll_elasticity_controller_
.reset(
200 new InputScrollElasticityController(scroll_elasticity_helper
));
204 InputHandlerProxy::~InputHandlerProxy() {}
206 void InputHandlerProxy::WillShutdown() {
207 scroll_elasticity_controller_
.reset();
208 input_handler_
= NULL
;
209 client_
->WillShutdown();
212 InputHandlerProxy::EventDisposition
213 InputHandlerProxy::HandleInputEventWithLatencyInfo(
214 const WebInputEvent
& event
,
215 ui::LatencyInfo
* latency_info
) {
216 DCHECK(input_handler_
);
218 if (uma_latency_reporting_enabled_
)
219 ReportInputEventLatencyUma(event
, *latency_info
);
221 TRACE_EVENT_FLOW_STEP0("input,benchmark",
223 TRACE_ID_DONT_MANGLE(latency_info
->trace_id
),
224 "HandleInputEventImpl");
226 scoped_ptr
<cc::SwapPromiseMonitor
> latency_info_swap_promise_monitor
=
227 input_handler_
->CreateLatencyInfoSwapPromiseMonitor(latency_info
);
228 InputHandlerProxy::EventDisposition disposition
= HandleInputEvent(event
);
232 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleInputEvent(
233 const WebInputEvent
& event
) {
234 DCHECK(input_handler_
);
235 TRACE_EVENT1("input,benchmark", "InputHandlerProxy::HandleInputEvent",
236 "type", WebInputEventTraits::GetName(event
.type
));
238 if (FilterInputEventForFlingBoosting(event
))
241 switch (event
.type
) {
242 case WebInputEvent::MouseWheel
:
243 return HandleMouseWheel(static_cast<const WebMouseWheelEvent
&>(event
));
245 case WebInputEvent::GestureScrollBegin
:
246 return HandleGestureScrollBegin(
247 static_cast<const WebGestureEvent
&>(event
));
249 case WebInputEvent::GestureScrollUpdate
:
250 return HandleGestureScrollUpdate(
251 static_cast<const WebGestureEvent
&>(event
));
253 case WebInputEvent::GestureScrollEnd
:
254 return HandleGestureScrollEnd(static_cast<const WebGestureEvent
&>(event
));
256 case WebInputEvent::GesturePinchBegin
: {
257 DCHECK(!gesture_pinch_on_impl_thread_
);
258 const WebGestureEvent
& gesture_event
=
259 static_cast<const WebGestureEvent
&>(event
);
260 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
&&
261 input_handler_
->HaveWheelEventHandlersAt(
262 gfx::Point(gesture_event
.x
, gesture_event
.y
))) {
263 return DID_NOT_HANDLE
;
265 input_handler_
->PinchGestureBegin();
266 gesture_pinch_on_impl_thread_
= true;
271 case WebInputEvent::GesturePinchEnd
:
272 if (gesture_pinch_on_impl_thread_
) {
273 gesture_pinch_on_impl_thread_
= false;
274 input_handler_
->PinchGestureEnd();
277 return DID_NOT_HANDLE
;
280 case WebInputEvent::GesturePinchUpdate
: {
281 if (gesture_pinch_on_impl_thread_
) {
282 const WebGestureEvent
& gesture_event
=
283 static_cast<const WebGestureEvent
&>(event
);
284 if (gesture_event
.data
.pinchUpdate
.zoomDisabled
)
286 input_handler_
->PinchGestureUpdate(
287 gesture_event
.data
.pinchUpdate
.scale
,
288 gfx::Point(gesture_event
.x
, gesture_event
.y
));
291 return DID_NOT_HANDLE
;
295 case WebInputEvent::GestureFlingStart
:
296 return HandleGestureFlingStart(
297 *static_cast<const WebGestureEvent
*>(&event
));
299 case WebInputEvent::GestureFlingCancel
:
300 if (CancelCurrentFling())
302 else if (!fling_may_be_active_on_main_thread_
)
304 return DID_NOT_HANDLE
;
306 case WebInputEvent::TouchStart
:
307 return HandleTouchStart(static_cast<const WebTouchEvent
&>(event
));
309 case WebInputEvent::MouseMove
: {
310 const WebMouseEvent
& mouse_event
=
311 static_cast<const WebMouseEvent
&>(event
);
312 // TODO(tony): Ignore when mouse buttons are down?
313 // TODO(davemoore): This should never happen, but bug #326635 showed some
314 // surprising crashes.
315 CHECK(input_handler_
);
316 input_handler_
->MouseMoveAt(gfx::Point(mouse_event
.x
, mouse_event
.y
));
317 return DID_NOT_HANDLE
;
321 if (WebInputEvent::isKeyboardEventType(event
.type
)) {
322 // Only call |CancelCurrentFling()| if a fling was active, as it will
323 // otherwise disrupt an in-progress touch scroll.
325 CancelCurrentFling();
330 return DID_NOT_HANDLE
;
333 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleMouseWheel(
334 const WebMouseWheelEvent
& wheel_event
) {
335 InputHandlerProxy::EventDisposition result
= DID_NOT_HANDLE
;
336 cc::InputHandlerScrollResult scroll_result
;
338 // TODO(ccameron): The rail information should be pushed down into
340 gfx::Vector2dF
scroll_delta(
341 wheel_event
.railsMode
!= WebInputEvent::RailsModeVertical
342 ? -wheel_event
.deltaX
344 wheel_event
.railsMode
!= WebInputEvent::RailsModeHorizontal
345 ? -wheel_event
.deltaY
348 if (wheel_event
.scrollByPage
) {
349 // TODO(jamesr): We don't properly handle scroll by page in the compositor
350 // thread, so punt it to the main thread. http://crbug.com/236639
351 result
= DID_NOT_HANDLE
;
352 } else if (!wheel_event
.canScroll
) {
353 // Wheel events with |canScroll| == false will not trigger scrolling,
354 // only event handlers. Forward to the main thread.
355 result
= DID_NOT_HANDLE
;
356 } else if (smooth_scroll_enabled_
) {
357 cc::InputHandler::ScrollStatus scroll_status
=
358 input_handler_
->ScrollAnimated(gfx::Point(wheel_event
.x
, wheel_event
.y
),
360 switch (scroll_status
) {
361 case cc::InputHandler::SCROLL_STARTED
:
364 case cc::InputHandler::SCROLL_IGNORED
:
367 result
= DID_NOT_HANDLE
;
371 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
372 gfx::Point(wheel_event
.x
, wheel_event
.y
), cc::InputHandler::WHEEL
);
373 switch (scroll_status
) {
374 case cc::InputHandler::SCROLL_STARTED
: {
375 TRACE_EVENT_INSTANT2("input",
376 "InputHandlerProxy::handle_input wheel scroll",
377 TRACE_EVENT_SCOPE_THREAD
, "deltaX",
378 scroll_delta
.x(), "deltaY", scroll_delta
.y());
379 gfx::Point
scroll_point(wheel_event
.x
, wheel_event
.y
);
380 scroll_result
= input_handler_
->ScrollBy(scroll_point
, scroll_delta
);
381 HandleOverscroll(scroll_point
, scroll_result
);
382 input_handler_
->ScrollEnd();
383 result
= scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
386 case cc::InputHandler::SCROLL_IGNORED
:
387 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
388 // to properly sync scrollability it's safer to send the event to the
389 // main thread. Change back to DROP_EVENT once we have synchronization
391 result
= DID_NOT_HANDLE
;
393 case cc::InputHandler::SCROLL_UNKNOWN
:
394 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
395 result
= DID_NOT_HANDLE
;
397 case cc::InputHandler::ScrollStatusCount
:
403 // Send the event and its disposition to the elasticity controller to update
404 // the over-scroll animation. If the event is to be handled on the main
405 // thread, the event and its disposition will be sent to the elasticity
406 // controller after being handled on the main thread.
407 if (scroll_elasticity_controller_
&& result
!= DID_NOT_HANDLE
) {
408 // Note that the call to the elasticity controller is made asynchronously,
409 // to minimize divergence between main thread and impl thread event
411 base::ThreadTaskRunnerHandle::Get()->PostTask(
413 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult
,
414 scroll_elasticity_controller_
->GetWeakPtr(), wheel_event
,
420 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollBegin(
421 const WebGestureEvent
& gesture_event
) {
422 if (gesture_scroll_on_impl_thread_
)
423 CancelCurrentFling();
426 DCHECK(!expect_scroll_update_end_
);
427 expect_scroll_update_end_
= true;
429 cc::InputHandler::ScrollStatus scroll_status
;
430 if (gesture_event
.data
.scrollBegin
.targetViewport
) {
431 scroll_status
= input_handler_
->RootScrollBegin(cc::InputHandler::GESTURE
);
433 scroll_status
= input_handler_
->ScrollBegin(
434 gfx::Point(gesture_event
.x
, gesture_event
.y
),
435 cc::InputHandler::GESTURE
);
437 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
439 cc::InputHandler::ScrollStatusCount
);
440 switch (scroll_status
) {
441 case cc::InputHandler::SCROLL_STARTED
:
442 TRACE_EVENT_INSTANT0("input",
443 "InputHandlerProxy::handle_input gesture scroll",
444 TRACE_EVENT_SCOPE_THREAD
);
445 gesture_scroll_on_impl_thread_
= true;
447 case cc::InputHandler::SCROLL_UNKNOWN
:
448 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
449 return DID_NOT_HANDLE
;
450 case cc::InputHandler::SCROLL_IGNORED
:
452 case cc::InputHandler::ScrollStatusCount
:
456 return DID_NOT_HANDLE
;
459 InputHandlerProxy::EventDisposition
460 InputHandlerProxy::HandleGestureScrollUpdate(
461 const WebGestureEvent
& gesture_event
) {
463 DCHECK(expect_scroll_update_end_
);
466 if (!gesture_scroll_on_impl_thread_
&& !gesture_pinch_on_impl_thread_
)
467 return DID_NOT_HANDLE
;
469 gfx::Point
scroll_point(gesture_event
.x
, gesture_event
.y
);
470 gfx::Vector2dF
scroll_delta(-gesture_event
.data
.scrollUpdate
.deltaX
,
471 -gesture_event
.data
.scrollUpdate
.deltaY
);
472 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
473 scroll_point
, scroll_delta
);
474 HandleOverscroll(scroll_point
, scroll_result
);
475 return scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
478 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollEnd(
479 const WebGestureEvent
& gesture_event
) {
481 DCHECK(expect_scroll_update_end_
);
482 expect_scroll_update_end_
= false;
484 input_handler_
->ScrollEnd();
485 if (!gesture_scroll_on_impl_thread_
)
486 return DID_NOT_HANDLE
;
487 gesture_scroll_on_impl_thread_
= false;
491 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureFlingStart(
492 const WebGestureEvent
& gesture_event
) {
493 cc::InputHandler::ScrollStatus scroll_status
;
495 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
496 if (gesture_event
.data
.flingStart
.targetViewport
) {
497 scroll_status
= input_handler_
->RootScrollBegin(
498 cc::InputHandler::NON_BUBBLING_GESTURE
);
500 scroll_status
= input_handler_
->ScrollBegin(
501 gfx::Point(gesture_event
.x
, gesture_event
.y
),
502 cc::InputHandler::NON_BUBBLING_GESTURE
);
505 if (!gesture_scroll_on_impl_thread_
)
506 scroll_status
= cc::InputHandler::SCROLL_ON_MAIN_THREAD
;
508 scroll_status
= input_handler_
->FlingScrollBegin();
512 expect_scroll_update_end_
= false;
515 switch (scroll_status
) {
516 case cc::InputHandler::SCROLL_STARTED
: {
517 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
)
518 input_handler_
->ScrollEnd();
520 const float vx
= gesture_event
.data
.flingStart
.velocityX
;
521 const float vy
= gesture_event
.data
.flingStart
.velocityY
;
522 current_fling_velocity_
= gfx::Vector2dF(vx
, vy
);
523 DCHECK(!current_fling_velocity_
.IsZero());
524 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
525 gesture_event
.sourceDevice
,
526 WebFloatPoint(vx
, vy
),
528 disallow_horizontal_fling_scroll_
= !vx
;
529 disallow_vertical_fling_scroll_
= !vy
;
530 TRACE_EVENT_ASYNC_BEGIN2("input",
531 "InputHandlerProxy::HandleGestureFling::started",
537 // Note that the timestamp will only be used to kickstart the animation if
538 // its sufficiently close to the timestamp of the first call |Animate()|.
539 has_fling_animation_started_
= false;
540 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
541 fling_parameters_
.delta
= WebFloatPoint(vx
, vy
);
542 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
543 fling_parameters_
.globalPoint
=
544 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
545 fling_parameters_
.modifiers
= gesture_event
.modifiers
;
546 fling_parameters_
.sourceDevice
= gesture_event
.sourceDevice
;
547 input_handler_
->SetNeedsAnimateInput();
550 case cc::InputHandler::SCROLL_UNKNOWN
:
551 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
: {
552 TRACE_EVENT_INSTANT0("input",
553 "InputHandlerProxy::HandleGestureFling::"
554 "scroll_on_main_thread",
555 TRACE_EVENT_SCOPE_THREAD
);
556 gesture_scroll_on_impl_thread_
= false;
557 fling_may_be_active_on_main_thread_
= true;
558 return DID_NOT_HANDLE
;
560 case cc::InputHandler::SCROLL_IGNORED
: {
561 TRACE_EVENT_INSTANT0(
563 "InputHandlerProxy::HandleGestureFling::ignored",
564 TRACE_EVENT_SCOPE_THREAD
);
565 gesture_scroll_on_impl_thread_
= false;
566 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
567 // We still pass the curve to the main thread if there's nothing
568 // scrollable, in case something
569 // registers a handler before the curve is over.
570 return DID_NOT_HANDLE
;
574 case cc::InputHandler::ScrollStatusCount
:
578 return DID_NOT_HANDLE
;
581 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleTouchStart(
582 const blink::WebTouchEvent
& touch_event
) {
583 for (size_t i
= 0; i
< touch_event
.touchesLength
; ++i
) {
584 if (touch_event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
586 if (input_handler_
->DoTouchEventsBlockScrollAt(
587 gfx::Point(touch_event
.touches
[i
].position
.x
,
588 touch_event
.touches
[i
].position
.y
))) {
589 // TODO(rbyers): We should consider still sending the touch events to
590 // main asynchronously (crbug.com/455539).
591 return DID_NOT_HANDLE
;
597 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
598 const WebInputEvent
& event
) {
599 if (!WebInputEvent::isGestureEventType(event
.type
))
603 DCHECK(!deferred_fling_cancel_time_seconds_
);
607 const WebGestureEvent
& gesture_event
=
608 static_cast<const WebGestureEvent
&>(event
);
609 if (gesture_event
.type
== WebInputEvent::GestureFlingCancel
) {
610 if (gesture_event
.data
.flingCancel
.preventBoosting
)
613 if (current_fling_velocity_
.LengthSquared() < kMinBoostFlingSpeedSquare
)
616 TRACE_EVENT_INSTANT0("input",
617 "InputHandlerProxy::FlingBoostStart",
618 TRACE_EVENT_SCOPE_THREAD
);
619 deferred_fling_cancel_time_seconds_
=
620 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
624 // A fling is either inactive or is "free spinning", i.e., has yet to be
625 // interrupted by a touch gesture, in which case there is nothing to filter.
626 if (!deferred_fling_cancel_time_seconds_
)
629 // Gestures from a different source should immediately interrupt the fling.
630 if (gesture_event
.sourceDevice
!= fling_parameters_
.sourceDevice
) {
631 CancelCurrentFling();
635 switch (gesture_event
.type
) {
636 case WebInputEvent::GestureTapCancel
:
637 case WebInputEvent::GestureTapDown
:
640 case WebInputEvent::GestureScrollBegin
:
641 if (!input_handler_
->IsCurrentlyScrollingLayerAt(
642 gfx::Point(gesture_event
.x
, gesture_event
.y
),
643 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchpad
644 ? cc::InputHandler::NON_BUBBLING_GESTURE
645 : cc::InputHandler::GESTURE
)) {
646 CancelCurrentFling();
650 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
651 // determine if the ScrollBegin should immediately cancel the fling.
652 ExtendBoostedFlingTimeout(gesture_event
);
655 case WebInputEvent::GestureScrollUpdate
: {
656 const double time_since_last_boost_event
=
657 event
.timeStampSeconds
- last_fling_boost_event_
.timeStampSeconds
;
658 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_
,
660 time_since_last_boost_event
)) {
661 ExtendBoostedFlingTimeout(gesture_event
);
665 CancelCurrentFling();
669 case WebInputEvent::GestureScrollEnd
:
670 // Clear the last fling boost event *prior* to fling cancellation,
671 // preventing insertion of a synthetic GestureScrollBegin.
672 last_fling_boost_event_
= WebGestureEvent();
673 CancelCurrentFling();
676 case WebInputEvent::GestureFlingStart
: {
677 DCHECK_EQ(fling_parameters_
.sourceDevice
, gesture_event
.sourceDevice
);
680 fling_parameters_
.modifiers
== gesture_event
.modifiers
&&
681 ShouldBoostFling(current_fling_velocity_
, gesture_event
);
683 gfx::Vector2dF
new_fling_velocity(
684 gesture_event
.data
.flingStart
.velocityX
,
685 gesture_event
.data
.flingStart
.velocityY
);
686 DCHECK(!new_fling_velocity
.IsZero());
689 current_fling_velocity_
+= new_fling_velocity
;
691 current_fling_velocity_
= new_fling_velocity
;
693 WebFloatPoint
velocity(current_fling_velocity_
.x(),
694 current_fling_velocity_
.y());
695 deferred_fling_cancel_time_seconds_
= 0;
696 disallow_horizontal_fling_scroll_
= !velocity
.x
;
697 disallow_vertical_fling_scroll_
= !velocity
.y
;
698 last_fling_boost_event_
= WebGestureEvent();
699 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
700 gesture_event
.sourceDevice
,
703 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
704 fling_parameters_
.delta
= velocity
;
705 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
706 fling_parameters_
.globalPoint
=
707 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
709 TRACE_EVENT_INSTANT2("input",
710 fling_boosted
? "InputHandlerProxy::FlingBoosted"
711 : "InputHandlerProxy::FlingReplaced",
712 TRACE_EVENT_SCOPE_THREAD
,
714 current_fling_velocity_
.x(),
716 current_fling_velocity_
.y());
718 // The client expects balanced calls between a consumed GestureFlingStart
719 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
720 // |DidStopFlinging()| and only send after the accumulated fling ends.
721 client_
->DidStopFlinging();
726 // All other types of gestures (taps, presses, etc...) will complete the
727 // deferred fling cancellation.
728 CancelCurrentFling();
733 void InputHandlerProxy::ExtendBoostedFlingTimeout(
734 const blink::WebGestureEvent
& event
) {
735 TRACE_EVENT_INSTANT0("input",
736 "InputHandlerProxy::ExtendBoostedFlingTimeout",
737 TRACE_EVENT_SCOPE_THREAD
);
738 deferred_fling_cancel_time_seconds_
=
739 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
740 last_fling_boost_event_
= event
;
743 void InputHandlerProxy::Animate(base::TimeTicks time
) {
744 if (scroll_elasticity_controller_
)
745 scroll_elasticity_controller_
->Animate(time
);
750 double monotonic_time_sec
= InSecondsF(time
);
752 if (deferred_fling_cancel_time_seconds_
&&
753 monotonic_time_sec
> deferred_fling_cancel_time_seconds_
) {
754 CancelCurrentFling();
758 client_
->DidAnimateForInput();
760 if (!has_fling_animation_started_
) {
761 has_fling_animation_started_
= true;
762 // Guard against invalid, future or sufficiently stale start times, as there
763 // are no guarantees fling event and animation timestamps are compatible.
764 if (!fling_parameters_
.startTime
||
765 monotonic_time_sec
<= fling_parameters_
.startTime
||
766 monotonic_time_sec
>= fling_parameters_
.startTime
+
767 kMaxSecondsFromFlingTimestampToFirstAnimate
) {
768 fling_parameters_
.startTime
= monotonic_time_sec
;
769 input_handler_
->SetNeedsAnimateInput();
774 bool fling_is_active
=
775 fling_curve_
->apply(monotonic_time_sec
- fling_parameters_
.startTime
,
778 if (disallow_vertical_fling_scroll_
&& disallow_horizontal_fling_scroll_
)
779 fling_is_active
= false;
781 if (fling_is_active
) {
782 input_handler_
->SetNeedsAnimateInput();
784 TRACE_EVENT_INSTANT0("input",
785 "InputHandlerProxy::animate::flingOver",
786 TRACE_EVENT_SCOPE_THREAD
);
787 CancelCurrentFling();
791 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
792 fling_may_be_active_on_main_thread_
= false;
793 client_
->DidStopFlinging();
796 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
797 if (scroll_elasticity_controller_
)
798 scroll_elasticity_controller_
->ReconcileStretchAndScroll();
801 void InputHandlerProxy::HandleOverscroll(
802 const gfx::Point
& causal_event_viewport_point
,
803 const cc::InputHandlerScrollResult
& scroll_result
) {
805 if (!scroll_result
.did_overscroll_root
)
808 TRACE_EVENT2("input",
809 "InputHandlerProxy::DidOverscroll",
811 scroll_result
.unused_scroll_delta
.x(),
813 scroll_result
.unused_scroll_delta
.y());
815 DidOverscrollParams params
;
816 params
.accumulated_overscroll
= scroll_result
.accumulated_root_overscroll
;
817 params
.latest_overscroll_delta
= scroll_result
.unused_scroll_delta
;
818 params
.current_fling_velocity
=
819 ToClientScrollIncrement(current_fling_velocity_
);
820 params
.causal_event_viewport_point
= causal_event_viewport_point
;
823 static const int kFlingOverscrollThreshold
= 1;
824 disallow_horizontal_fling_scroll_
|=
825 std::abs(params
.accumulated_overscroll
.x()) >=
826 kFlingOverscrollThreshold
;
827 disallow_vertical_fling_scroll_
|=
828 std::abs(params
.accumulated_overscroll
.y()) >=
829 kFlingOverscrollThreshold
;
832 client_
->DidOverscroll(params
);
835 bool InputHandlerProxy::CancelCurrentFling() {
836 if (CancelCurrentFlingWithoutNotifyingClient()) {
837 client_
->DidStopFlinging();
843 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
844 bool had_fling_animation
= fling_curve_
;
845 if (had_fling_animation
&&
846 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchscreen
) {
847 input_handler_
->ScrollEnd();
848 TRACE_EVENT_ASYNC_END0(
850 "InputHandlerProxy::HandleGestureFling::started",
854 TRACE_EVENT_INSTANT1("input",
855 "InputHandlerProxy::CancelCurrentFling",
856 TRACE_EVENT_SCOPE_THREAD
,
857 "had_fling_animation",
858 had_fling_animation
);
859 fling_curve_
.reset();
860 has_fling_animation_started_
= false;
861 gesture_scroll_on_impl_thread_
= false;
862 current_fling_velocity_
= gfx::Vector2dF();
863 fling_parameters_
= blink::WebActiveWheelFlingParameters();
865 if (deferred_fling_cancel_time_seconds_
) {
866 deferred_fling_cancel_time_seconds_
= 0;
868 WebGestureEvent last_fling_boost_event
= last_fling_boost_event_
;
869 last_fling_boost_event_
= WebGestureEvent();
870 if (last_fling_boost_event
.type
== WebInputEvent::GestureScrollBegin
||
871 last_fling_boost_event
.type
== WebInputEvent::GestureScrollUpdate
) {
872 // Synthesize a GestureScrollBegin, as the original was suppressed.
873 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event
));
877 return had_fling_animation
;
880 bool InputHandlerProxy::TouchpadFlingScroll(
881 const WebFloatSize
& increment
) {
882 WebMouseWheelEvent synthetic_wheel
;
883 synthetic_wheel
.type
= WebInputEvent::MouseWheel
;
884 synthetic_wheel
.deltaX
= increment
.width
;
885 synthetic_wheel
.deltaY
= increment
.height
;
886 synthetic_wheel
.hasPreciseScrollingDeltas
= true;
887 synthetic_wheel
.x
= fling_parameters_
.point
.x
;
888 synthetic_wheel
.y
= fling_parameters_
.point
.y
;
889 synthetic_wheel
.globalX
= fling_parameters_
.globalPoint
.x
;
890 synthetic_wheel
.globalY
= fling_parameters_
.globalPoint
.y
;
891 synthetic_wheel
.modifiers
= fling_parameters_
.modifiers
;
893 InputHandlerProxy::EventDisposition disposition
=
894 HandleInputEvent(synthetic_wheel
);
895 switch (disposition
) {
901 TRACE_EVENT_INSTANT0("input",
902 "InputHandlerProxy::scrollBy::AbortFling",
903 TRACE_EVENT_SCOPE_THREAD
);
904 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
905 // main thread. In this case we need to schedule a commit and transfer the
906 // fling curve over to the main thread and run the rest of the wheels from
907 // there. This can happen when flinging a page that contains a scrollable
908 // subarea that we can't scroll on the thread if the fling starts outside
909 // the subarea but then is flung "under" the pointer.
910 client_
->TransferActiveWheelFlingAnimation(fling_parameters_
);
911 fling_may_be_active_on_main_thread_
= true;
912 CancelCurrentFlingWithoutNotifyingClient();
919 bool InputHandlerProxy::scrollBy(const WebFloatSize
& increment
,
920 const WebFloatSize
& velocity
) {
921 WebFloatSize clipped_increment
;
922 WebFloatSize clipped_velocity
;
923 if (!disallow_horizontal_fling_scroll_
) {
924 clipped_increment
.width
= increment
.width
;
925 clipped_velocity
.width
= velocity
.width
;
927 if (!disallow_vertical_fling_scroll_
) {
928 clipped_increment
.height
= increment
.height
;
929 clipped_velocity
.height
= velocity
.height
;
932 current_fling_velocity_
= clipped_velocity
;
934 // Early out if the increment is zero, but avoid early terimination if the
935 // velocity is still non-zero.
936 if (clipped_increment
== WebFloatSize())
937 return clipped_velocity
!= WebFloatSize();
939 TRACE_EVENT2("input",
940 "InputHandlerProxy::scrollBy",
942 clipped_increment
.width
,
944 clipped_increment
.height
);
946 bool did_scroll
= false;
948 switch (fling_parameters_
.sourceDevice
) {
949 case blink::WebGestureDeviceTouchpad
:
950 did_scroll
= TouchpadFlingScroll(clipped_increment
);
952 case blink::WebGestureDeviceTouchscreen
: {
953 clipped_increment
= ToClientScrollIncrement(clipped_increment
);
954 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
955 fling_parameters_
.point
, clipped_increment
);
956 HandleOverscroll(fling_parameters_
.point
, scroll_result
);
957 did_scroll
= scroll_result
.did_scroll
;
962 fling_parameters_
.cumulativeScroll
.width
+= clipped_increment
.width
;
963 fling_parameters_
.cumulativeScroll
.height
+= clipped_increment
.height
;
966 // It's possible the provided |increment| is sufficiently small as to not
967 // trigger a scroll, e.g., with a trivial time delta between fling updates.
968 // Return true in this case to prevent early fling termination.
969 if (std::abs(clipped_increment
.width
) < kScrollEpsilon
&&
970 std::abs(clipped_increment
.height
) < kScrollEpsilon
)
976 } // namespace content