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"
9 #include "base/auto_reset.h"
10 #include "base/command_line.h"
11 #include "base/location.h"
12 #include "base/logging.h"
13 #include "base/metrics/histogram.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/thread_task_runner_handle.h"
16 #include "base/trace_event/trace_event.h"
17 #include "content/common/input/did_overscroll_params.h"
18 #include "content/common/input/web_input_event_traits.h"
19 #include "content/public/common/content_switches.h"
20 #include "content/renderer/input/input_handler_proxy_client.h"
21 #include "content/renderer/input/input_scroll_elasticity_controller.h"
22 #include "third_party/WebKit/public/platform/Platform.h"
23 #include "third_party/WebKit/public/web/WebInputEvent.h"
24 #include "ui/events/latency_info.h"
25 #include "ui/gfx/geometry/point_conversions.h"
27 using blink::WebFloatPoint
;
28 using blink::WebFloatSize
;
29 using blink::WebGestureEvent
;
30 using blink::WebInputEvent
;
31 using blink::WebMouseEvent
;
32 using blink::WebMouseWheelEvent
;
33 using blink::WebPoint
;
34 using blink::WebTouchEvent
;
35 using blink::WebTouchPoint
;
39 // Maximum time between a fling event's timestamp and the first |Animate| call
40 // for the fling curve to use the fling timestamp as the initial animation time.
41 // Two frames allows a minor delay between event creation and the first animate.
42 const double kMaxSecondsFromFlingTimestampToFirstAnimate
= 2. / 60.;
44 // Threshold for determining whether a fling scroll delta should have caused the
46 const float kScrollEpsilon
= 0.1f
;
48 // Minimum fling velocity required for the active fling and new fling for the
50 const double kMinBoostFlingSpeedSquare
= 350. * 350.;
52 // Minimum velocity for the active touch scroll to preserve (boost) an active
53 // fling for which cancellation has been deferred.
54 const double kMinBoostTouchScrollSpeedSquare
= 150 * 150.;
56 // Timeout window after which the active fling will be cancelled if no animation
57 // ticks, scrolls or flings of sufficient velocity relative to the current fling
58 // are received. The default value on Android native views is 40ms, but we use a
59 // slightly increased value to accomodate small IPC message delays.
60 const double kFlingBoostTimeoutDelaySeconds
= 0.05;
62 gfx::Vector2dF
ToClientScrollIncrement(const WebFloatSize
& increment
) {
63 return gfx::Vector2dF(-increment
.width
, -increment
.height
);
66 double InSecondsF(const base::TimeTicks
& time
) {
67 return (time
- base::TimeTicks()).InSecondsF();
70 bool ShouldSuppressScrollForFlingBoosting(
71 const gfx::Vector2dF
& current_fling_velocity
,
72 const WebGestureEvent
& scroll_update_event
,
73 double time_since_last_boost_event
,
74 double time_since_last_fling_animate
) {
75 DCHECK_EQ(WebInputEvent::GestureScrollUpdate
, scroll_update_event
.type
);
77 gfx::Vector2dF
dx(scroll_update_event
.data
.scrollUpdate
.deltaX
,
78 scroll_update_event
.data
.scrollUpdate
.deltaY
);
79 if (gfx::DotProduct(current_fling_velocity
, dx
) <= 0)
82 if (time_since_last_fling_animate
> kFlingBoostTimeoutDelaySeconds
)
85 if (time_since_last_boost_event
< 0.001)
88 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
89 // The scroll must be of sufficient velocity to maintain the active fling.
90 const gfx::Vector2dF scroll_velocity
=
91 gfx::ScaleVector2d(dx
, 1. / time_since_last_boost_event
);
92 if (scroll_velocity
.LengthSquared() < kMinBoostTouchScrollSpeedSquare
)
98 bool ShouldBoostFling(const gfx::Vector2dF
& current_fling_velocity
,
99 const WebGestureEvent
& fling_start_event
) {
100 DCHECK_EQ(WebInputEvent::GestureFlingStart
, fling_start_event
.type
);
102 gfx::Vector2dF
new_fling_velocity(
103 fling_start_event
.data
.flingStart
.velocityX
,
104 fling_start_event
.data
.flingStart
.velocityY
);
106 if (gfx::DotProduct(current_fling_velocity
, new_fling_velocity
) <= 0)
109 if (current_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
112 if (new_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
118 WebGestureEvent
ObtainGestureScrollBegin(const WebGestureEvent
& event
) {
119 WebGestureEvent scroll_begin_event
= event
;
120 scroll_begin_event
.type
= WebInputEvent::GestureScrollBegin
;
121 scroll_begin_event
.data
.scrollBegin
.deltaXHint
= 0;
122 scroll_begin_event
.data
.scrollBegin
.deltaYHint
= 0;
123 return scroll_begin_event
;
126 void ReportInputEventLatencyUma(const WebInputEvent
& event
,
127 const ui::LatencyInfo
& latency_info
) {
128 if (!(event
.type
== WebInputEvent::GestureScrollBegin
||
129 event
.type
== WebInputEvent::GestureScrollUpdate
||
130 event
.type
== WebInputEvent::GesturePinchBegin
||
131 event
.type
== WebInputEvent::GesturePinchUpdate
||
132 event
.type
== WebInputEvent::GestureFlingStart
)) {
136 ui::LatencyInfo::LatencyMap::const_iterator it
=
137 latency_info
.latency_components().find(std::make_pair(
138 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT
, 0));
140 if (it
== latency_info
.latency_components().end())
143 base::TimeDelta delta
= base::TimeTicks::Now() - it
->second
.event_time
;
144 for (size_t i
= 0; i
< it
->second
.event_count
; ++i
) {
145 switch (event
.type
) {
146 case blink::WebInputEvent::GestureScrollBegin
:
147 UMA_HISTOGRAM_CUSTOM_COUNTS(
148 "Event.Latency.RendererImpl.GestureScrollBegin",
149 delta
.InMicroseconds(), 1, 1000000, 100);
151 case blink::WebInputEvent::GestureScrollUpdate
:
152 UMA_HISTOGRAM_CUSTOM_COUNTS(
153 // So named for historical reasons.
154 "Event.Latency.RendererImpl.GestureScroll2",
155 delta
.InMicroseconds(), 1, 1000000, 100);
157 case blink::WebInputEvent::GesturePinchBegin
:
158 UMA_HISTOGRAM_CUSTOM_COUNTS(
159 "Event.Latency.RendererImpl.GesturePinchBegin",
160 delta
.InMicroseconds(), 1, 1000000, 100);
162 case blink::WebInputEvent::GesturePinchUpdate
:
163 UMA_HISTOGRAM_CUSTOM_COUNTS(
164 "Event.Latency.RendererImpl.GesturePinchUpdate",
165 delta
.InMicroseconds(), 1, 1000000, 100);
167 case blink::WebInputEvent::GestureFlingStart
:
168 UMA_HISTOGRAM_CUSTOM_COUNTS(
169 "Event.Latency.RendererImpl.GestureFlingStart",
170 delta
.InMicroseconds(), 1, 1000000, 100);
183 InputHandlerProxy::InputHandlerProxy(cc::InputHandler
* input_handler
,
184 InputHandlerProxyClient
* client
)
186 input_handler_(input_handler
),
187 deferred_fling_cancel_time_seconds_(0),
188 synchronous_input_handler_(nullptr),
189 allow_root_animate_(true),
191 expect_scroll_update_end_(false),
193 gesture_scroll_on_impl_thread_(false),
194 gesture_pinch_on_impl_thread_(false),
195 fling_may_be_active_on_main_thread_(false),
196 disallow_horizontal_fling_scroll_(false),
197 disallow_vertical_fling_scroll_(false),
198 has_fling_animation_started_(false),
199 uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) {
201 input_handler_
->BindToClient(this);
202 smooth_scroll_enabled_
= base::CommandLine::ForCurrentProcess()->HasSwitch(
203 switches::kEnableSmoothScrolling
);
204 cc::ScrollElasticityHelper
* scroll_elasticity_helper
=
205 input_handler_
->CreateScrollElasticityHelper();
206 if (scroll_elasticity_helper
) {
207 scroll_elasticity_controller_
.reset(
208 new InputScrollElasticityController(scroll_elasticity_helper
));
212 InputHandlerProxy::~InputHandlerProxy() {}
214 void InputHandlerProxy::WillShutdown() {
215 scroll_elasticity_controller_
.reset();
216 input_handler_
= NULL
;
217 client_
->WillShutdown();
220 InputHandlerProxy::EventDisposition
221 InputHandlerProxy::HandleInputEventWithLatencyInfo(
222 const WebInputEvent
& event
,
223 ui::LatencyInfo
* latency_info
) {
224 DCHECK(input_handler_
);
226 if (uma_latency_reporting_enabled_
)
227 ReportInputEventLatencyUma(event
, *latency_info
);
229 TRACE_EVENT_WITH_FLOW1("input,benchmark",
231 TRACE_ID_DONT_MANGLE(latency_info
->trace_id()),
232 TRACE_EVENT_FLAG_FLOW_IN
| TRACE_EVENT_FLAG_FLOW_OUT
,
233 "step", "HandleInputEventImpl");
235 scoped_ptr
<cc::SwapPromiseMonitor
> latency_info_swap_promise_monitor
=
236 input_handler_
->CreateLatencyInfoSwapPromiseMonitor(latency_info
);
237 InputHandlerProxy::EventDisposition disposition
= HandleInputEvent(event
);
241 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleInputEvent(
242 const WebInputEvent
& event
) {
243 DCHECK(input_handler_
);
244 TRACE_EVENT1("input,benchmark", "InputHandlerProxy::HandleInputEvent",
245 "type", WebInputEventTraits::GetName(event
.type
));
247 if (FilterInputEventForFlingBoosting(event
))
250 switch (event
.type
) {
251 case WebInputEvent::MouseWheel
:
252 return HandleMouseWheel(static_cast<const WebMouseWheelEvent
&>(event
));
254 case WebInputEvent::GestureScrollBegin
:
255 return HandleGestureScrollBegin(
256 static_cast<const WebGestureEvent
&>(event
));
258 case WebInputEvent::GestureScrollUpdate
:
259 return HandleGestureScrollUpdate(
260 static_cast<const WebGestureEvent
&>(event
));
262 case WebInputEvent::GestureScrollEnd
:
263 return HandleGestureScrollEnd(static_cast<const WebGestureEvent
&>(event
));
265 case WebInputEvent::GesturePinchBegin
: {
266 DCHECK(!gesture_pinch_on_impl_thread_
);
267 const WebGestureEvent
& gesture_event
=
268 static_cast<const WebGestureEvent
&>(event
);
269 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
&&
270 input_handler_
->HaveWheelEventHandlersAt(
271 gfx::Point(gesture_event
.x
, gesture_event
.y
))) {
272 return DID_NOT_HANDLE
;
274 input_handler_
->PinchGestureBegin();
275 gesture_pinch_on_impl_thread_
= true;
280 case WebInputEvent::GesturePinchEnd
:
281 if (gesture_pinch_on_impl_thread_
) {
282 gesture_pinch_on_impl_thread_
= false;
283 input_handler_
->PinchGestureEnd();
286 return DID_NOT_HANDLE
;
289 case WebInputEvent::GesturePinchUpdate
: {
290 if (gesture_pinch_on_impl_thread_
) {
291 const WebGestureEvent
& gesture_event
=
292 static_cast<const WebGestureEvent
&>(event
);
293 if (gesture_event
.data
.pinchUpdate
.zoomDisabled
)
295 input_handler_
->PinchGestureUpdate(
296 gesture_event
.data
.pinchUpdate
.scale
,
297 gfx::Point(gesture_event
.x
, gesture_event
.y
));
300 return DID_NOT_HANDLE
;
304 case WebInputEvent::GestureFlingStart
:
305 return HandleGestureFlingStart(
306 *static_cast<const WebGestureEvent
*>(&event
));
308 case WebInputEvent::GestureFlingCancel
:
309 if (CancelCurrentFling())
311 else if (!fling_may_be_active_on_main_thread_
)
313 return DID_NOT_HANDLE
;
315 case WebInputEvent::TouchStart
:
316 return HandleTouchStart(static_cast<const WebTouchEvent
&>(event
));
318 case WebInputEvent::MouseMove
: {
319 const WebMouseEvent
& mouse_event
=
320 static_cast<const WebMouseEvent
&>(event
);
321 // TODO(tony): Ignore when mouse buttons are down?
322 // TODO(davemoore): This should never happen, but bug #326635 showed some
323 // surprising crashes.
324 CHECK(input_handler_
);
325 input_handler_
->MouseMoveAt(gfx::Point(mouse_event
.x
, mouse_event
.y
));
326 return DID_NOT_HANDLE
;
330 if (WebInputEvent::isKeyboardEventType(event
.type
)) {
331 // Only call |CancelCurrentFling()| if a fling was active, as it will
332 // otherwise disrupt an in-progress touch scroll.
334 CancelCurrentFling();
339 return DID_NOT_HANDLE
;
342 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleMouseWheel(
343 const WebMouseWheelEvent
& wheel_event
) {
344 InputHandlerProxy::EventDisposition result
= DID_NOT_HANDLE
;
345 cc::InputHandlerScrollResult scroll_result
;
347 // TODO(ccameron): The rail information should be pushed down into
349 gfx::Vector2dF
scroll_delta(
350 wheel_event
.railsMode
!= WebInputEvent::RailsModeVertical
351 ? -wheel_event
.deltaX
353 wheel_event
.railsMode
!= WebInputEvent::RailsModeHorizontal
354 ? -wheel_event
.deltaY
357 if (wheel_event
.scrollByPage
) {
358 // TODO(jamesr): We don't properly handle scroll by page in the compositor
359 // thread, so punt it to the main thread. http://crbug.com/236639
360 result
= DID_NOT_HANDLE
;
361 } else if (!wheel_event
.canScroll
) {
362 // Wheel events with |canScroll| == false will not trigger scrolling,
363 // only event handlers. Forward to the main thread.
364 result
= DID_NOT_HANDLE
;
365 } else if (smooth_scroll_enabled_
) {
366 cc::InputHandler::ScrollStatus scroll_status
=
367 input_handler_
->ScrollAnimated(gfx::Point(wheel_event
.x
, wheel_event
.y
),
369 switch (scroll_status
) {
370 case cc::InputHandler::SCROLL_STARTED
:
373 case cc::InputHandler::SCROLL_IGNORED
:
376 result
= DID_NOT_HANDLE
;
380 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
381 gfx::Point(wheel_event
.x
, wheel_event
.y
), cc::InputHandler::WHEEL
);
382 switch (scroll_status
) {
383 case cc::InputHandler::SCROLL_STARTED
: {
384 TRACE_EVENT_INSTANT2("input",
385 "InputHandlerProxy::handle_input wheel scroll",
386 TRACE_EVENT_SCOPE_THREAD
, "deltaX",
387 scroll_delta
.x(), "deltaY", scroll_delta
.y());
388 gfx::Point
scroll_point(wheel_event
.x
, wheel_event
.y
);
389 scroll_result
= input_handler_
->ScrollBy(scroll_point
, scroll_delta
);
390 HandleOverscroll(scroll_point
, scroll_result
);
391 input_handler_
->ScrollEnd();
392 result
= scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
395 case cc::InputHandler::SCROLL_IGNORED
:
396 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
397 // to properly sync scrollability it's safer to send the event to the
398 // main thread. Change back to DROP_EVENT once we have synchronization
400 result
= DID_NOT_HANDLE
;
402 case cc::InputHandler::SCROLL_UNKNOWN
:
403 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
404 result
= DID_NOT_HANDLE
;
406 case cc::InputHandler::ScrollStatusCount
:
412 // Send the event and its disposition to the elasticity controller to update
413 // the over-scroll animation. If the event is to be handled on the main
414 // thread, the event and its disposition will be sent to the elasticity
415 // controller after being handled on the main thread.
416 if (scroll_elasticity_controller_
&& result
!= DID_NOT_HANDLE
) {
417 // Note that the call to the elasticity controller is made asynchronously,
418 // to minimize divergence between main thread and impl thread event
420 base::ThreadTaskRunnerHandle::Get()->PostTask(
422 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult
,
423 scroll_elasticity_controller_
->GetWeakPtr(), wheel_event
,
429 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollBegin(
430 const WebGestureEvent
& gesture_event
) {
431 if (gesture_scroll_on_impl_thread_
)
432 CancelCurrentFling();
435 DCHECK(!expect_scroll_update_end_
);
436 expect_scroll_update_end_
= true;
438 cc::InputHandler::ScrollStatus scroll_status
;
439 if (gesture_event
.data
.scrollBegin
.targetViewport
) {
440 scroll_status
= input_handler_
->RootScrollBegin(cc::InputHandler::GESTURE
);
442 scroll_status
= input_handler_
->ScrollBegin(
443 gfx::Point(gesture_event
.x
, gesture_event
.y
),
444 cc::InputHandler::GESTURE
);
446 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
448 cc::InputHandler::ScrollStatusCount
);
449 switch (scroll_status
) {
450 case cc::InputHandler::SCROLL_STARTED
:
451 TRACE_EVENT_INSTANT0("input",
452 "InputHandlerProxy::handle_input gesture scroll",
453 TRACE_EVENT_SCOPE_THREAD
);
454 gesture_scroll_on_impl_thread_
= true;
456 case cc::InputHandler::SCROLL_UNKNOWN
:
457 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
458 return DID_NOT_HANDLE
;
459 case cc::InputHandler::SCROLL_IGNORED
:
461 case cc::InputHandler::ScrollStatusCount
:
465 return DID_NOT_HANDLE
;
468 InputHandlerProxy::EventDisposition
469 InputHandlerProxy::HandleGestureScrollUpdate(
470 const WebGestureEvent
& gesture_event
) {
472 DCHECK(expect_scroll_update_end_
);
475 if (!gesture_scroll_on_impl_thread_
&& !gesture_pinch_on_impl_thread_
)
476 return DID_NOT_HANDLE
;
478 gfx::Point
scroll_point(gesture_event
.x
, gesture_event
.y
);
479 gfx::Vector2dF
scroll_delta(-gesture_event
.data
.scrollUpdate
.deltaX
,
480 -gesture_event
.data
.scrollUpdate
.deltaY
);
481 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
482 scroll_point
, scroll_delta
);
483 HandleOverscroll(scroll_point
, scroll_result
);
484 return scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
487 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollEnd(
488 const WebGestureEvent
& gesture_event
) {
490 DCHECK(expect_scroll_update_end_
);
491 expect_scroll_update_end_
= false;
493 input_handler_
->ScrollEnd();
494 if (!gesture_scroll_on_impl_thread_
)
495 return DID_NOT_HANDLE
;
496 gesture_scroll_on_impl_thread_
= false;
500 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureFlingStart(
501 const WebGestureEvent
& gesture_event
) {
502 cc::InputHandler::ScrollStatus scroll_status
;
504 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
505 if (gesture_event
.data
.flingStart
.targetViewport
) {
506 scroll_status
= input_handler_
->RootScrollBegin(
507 cc::InputHandler::NON_BUBBLING_GESTURE
);
509 scroll_status
= input_handler_
->ScrollBegin(
510 gfx::Point(gesture_event
.x
, gesture_event
.y
),
511 cc::InputHandler::NON_BUBBLING_GESTURE
);
514 if (!gesture_scroll_on_impl_thread_
)
515 scroll_status
= cc::InputHandler::SCROLL_ON_MAIN_THREAD
;
517 scroll_status
= input_handler_
->FlingScrollBegin();
521 expect_scroll_update_end_
= false;
524 switch (scroll_status
) {
525 case cc::InputHandler::SCROLL_STARTED
: {
526 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
)
527 input_handler_
->ScrollEnd();
529 const float vx
= gesture_event
.data
.flingStart
.velocityX
;
530 const float vy
= gesture_event
.data
.flingStart
.velocityY
;
531 current_fling_velocity_
= gfx::Vector2dF(vx
, vy
);
532 DCHECK(!current_fling_velocity_
.IsZero());
533 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
534 gesture_event
.sourceDevice
,
535 WebFloatPoint(vx
, vy
),
537 disallow_horizontal_fling_scroll_
= !vx
;
538 disallow_vertical_fling_scroll_
= !vy
;
539 TRACE_EVENT_ASYNC_BEGIN2("input",
540 "InputHandlerProxy::HandleGestureFling::started",
546 // Note that the timestamp will only be used to kickstart the animation if
547 // its sufficiently close to the timestamp of the first call |Animate()|.
548 has_fling_animation_started_
= false;
549 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
550 fling_parameters_
.delta
= WebFloatPoint(vx
, vy
);
551 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
552 fling_parameters_
.globalPoint
=
553 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
554 fling_parameters_
.modifiers
= gesture_event
.modifiers
;
555 fling_parameters_
.sourceDevice
= gesture_event
.sourceDevice
;
559 case cc::InputHandler::SCROLL_UNKNOWN
:
560 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
: {
561 TRACE_EVENT_INSTANT0("input",
562 "InputHandlerProxy::HandleGestureFling::"
563 "scroll_on_main_thread",
564 TRACE_EVENT_SCOPE_THREAD
);
565 gesture_scroll_on_impl_thread_
= false;
566 fling_may_be_active_on_main_thread_
= true;
567 return DID_NOT_HANDLE
;
569 case cc::InputHandler::SCROLL_IGNORED
: {
570 TRACE_EVENT_INSTANT0(
572 "InputHandlerProxy::HandleGestureFling::ignored",
573 TRACE_EVENT_SCOPE_THREAD
);
574 gesture_scroll_on_impl_thread_
= false;
575 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
576 // We still pass the curve to the main thread if there's nothing
577 // scrollable, in case something
578 // registers a handler before the curve is over.
579 return DID_NOT_HANDLE
;
583 case cc::InputHandler::ScrollStatusCount
:
587 return DID_NOT_HANDLE
;
590 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleTouchStart(
591 const blink::WebTouchEvent
& touch_event
) {
592 for (size_t i
= 0; i
< touch_event
.touchesLength
; ++i
) {
593 if (touch_event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
595 if (input_handler_
->DoTouchEventsBlockScrollAt(
596 gfx::Point(touch_event
.touches
[i
].position
.x
,
597 touch_event
.touches
[i
].position
.y
))) {
598 // TODO(rbyers): We should consider still sending the touch events to
599 // main asynchronously (crbug.com/455539).
600 return DID_NOT_HANDLE
;
606 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
607 const WebInputEvent
& event
) {
608 if (!WebInputEvent::isGestureEventType(event
.type
))
612 DCHECK(!deferred_fling_cancel_time_seconds_
);
616 const WebGestureEvent
& gesture_event
=
617 static_cast<const WebGestureEvent
&>(event
);
618 if (gesture_event
.type
== WebInputEvent::GestureFlingCancel
) {
619 if (gesture_event
.data
.flingCancel
.preventBoosting
)
622 if (current_fling_velocity_
.LengthSquared() < kMinBoostFlingSpeedSquare
)
625 TRACE_EVENT_INSTANT0("input",
626 "InputHandlerProxy::FlingBoostStart",
627 TRACE_EVENT_SCOPE_THREAD
);
628 deferred_fling_cancel_time_seconds_
=
629 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
633 // A fling is either inactive or is "free spinning", i.e., has yet to be
634 // interrupted by a touch gesture, in which case there is nothing to filter.
635 if (!deferred_fling_cancel_time_seconds_
)
638 // Gestures from a different source should immediately interrupt the fling.
639 if (gesture_event
.sourceDevice
!= fling_parameters_
.sourceDevice
) {
640 CancelCurrentFling();
644 switch (gesture_event
.type
) {
645 case WebInputEvent::GestureTapCancel
:
646 case WebInputEvent::GestureTapDown
:
649 case WebInputEvent::GestureScrollBegin
:
650 if (!input_handler_
->IsCurrentlyScrollingLayerAt(
651 gfx::Point(gesture_event
.x
, gesture_event
.y
),
652 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchpad
653 ? cc::InputHandler::NON_BUBBLING_GESTURE
654 : cc::InputHandler::GESTURE
)) {
655 CancelCurrentFling();
659 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
660 // determine if the ScrollBegin should immediately cancel the fling.
661 ExtendBoostedFlingTimeout(gesture_event
);
664 case WebInputEvent::GestureScrollUpdate
: {
665 const double time_since_last_boost_event
=
666 event
.timeStampSeconds
- last_fling_boost_event_
.timeStampSeconds
;
667 const double time_since_last_fling_animate
= std::max(
668 0.0, event
.timeStampSeconds
- InSecondsF(last_fling_animate_time_
));
669 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_
,
671 time_since_last_boost_event
,
672 time_since_last_fling_animate
)) {
673 ExtendBoostedFlingTimeout(gesture_event
);
677 CancelCurrentFling();
681 case WebInputEvent::GestureScrollEnd
:
682 // Clear the last fling boost event *prior* to fling cancellation,
683 // preventing insertion of a synthetic GestureScrollBegin.
684 last_fling_boost_event_
= WebGestureEvent();
685 CancelCurrentFling();
688 case WebInputEvent::GestureFlingStart
: {
689 DCHECK_EQ(fling_parameters_
.sourceDevice
, gesture_event
.sourceDevice
);
692 fling_parameters_
.modifiers
== gesture_event
.modifiers
&&
693 ShouldBoostFling(current_fling_velocity_
, gesture_event
);
695 gfx::Vector2dF
new_fling_velocity(
696 gesture_event
.data
.flingStart
.velocityX
,
697 gesture_event
.data
.flingStart
.velocityY
);
698 DCHECK(!new_fling_velocity
.IsZero());
701 current_fling_velocity_
+= new_fling_velocity
;
703 current_fling_velocity_
= new_fling_velocity
;
705 WebFloatPoint
velocity(current_fling_velocity_
.x(),
706 current_fling_velocity_
.y());
707 deferred_fling_cancel_time_seconds_
= 0;
708 disallow_horizontal_fling_scroll_
= !velocity
.x
;
709 disallow_vertical_fling_scroll_
= !velocity
.y
;
710 last_fling_boost_event_
= WebGestureEvent();
711 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
712 gesture_event
.sourceDevice
,
715 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
716 fling_parameters_
.delta
= velocity
;
717 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
718 fling_parameters_
.globalPoint
=
719 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
721 TRACE_EVENT_INSTANT2("input",
722 fling_boosted
? "InputHandlerProxy::FlingBoosted"
723 : "InputHandlerProxy::FlingReplaced",
724 TRACE_EVENT_SCOPE_THREAD
,
726 current_fling_velocity_
.x(),
728 current_fling_velocity_
.y());
730 // The client expects balanced calls between a consumed GestureFlingStart
731 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
732 // |DidStopFlinging()| and only send after the accumulated fling ends.
733 client_
->DidStopFlinging();
738 // All other types of gestures (taps, presses, etc...) will complete the
739 // deferred fling cancellation.
740 CancelCurrentFling();
745 void InputHandlerProxy::ExtendBoostedFlingTimeout(
746 const blink::WebGestureEvent
& event
) {
747 TRACE_EVENT_INSTANT0("input",
748 "InputHandlerProxy::ExtendBoostedFlingTimeout",
749 TRACE_EVENT_SCOPE_THREAD
);
750 deferred_fling_cancel_time_seconds_
=
751 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
752 last_fling_boost_event_
= event
;
755 void InputHandlerProxy::Animate(base::TimeTicks time
) {
756 // If using synchronous animate, then only expect Animate attempts started by
757 // the synchronous system. Don't let the InputHandler try to Animate also.
758 DCHECK_IMPLIES(input_handler_
->IsCurrentlyScrollingRoot(),
759 allow_root_animate_
);
761 if (scroll_elasticity_controller_
)
762 scroll_elasticity_controller_
->Animate(time
);
767 last_fling_animate_time_
= time
;
768 double monotonic_time_sec
= InSecondsF(time
);
770 if (deferred_fling_cancel_time_seconds_
&&
771 monotonic_time_sec
> deferred_fling_cancel_time_seconds_
) {
772 CancelCurrentFling();
776 client_
->DidAnimateForInput();
778 if (!has_fling_animation_started_
) {
779 has_fling_animation_started_
= true;
780 // Guard against invalid, future or sufficiently stale start times, as there
781 // are no guarantees fling event and animation timestamps are compatible.
782 if (!fling_parameters_
.startTime
||
783 monotonic_time_sec
<= fling_parameters_
.startTime
||
784 monotonic_time_sec
>= fling_parameters_
.startTime
+
785 kMaxSecondsFromFlingTimestampToFirstAnimate
) {
786 fling_parameters_
.startTime
= monotonic_time_sec
;
792 bool fling_is_active
=
793 fling_curve_
->apply(monotonic_time_sec
- fling_parameters_
.startTime
,
796 if (disallow_vertical_fling_scroll_
&& disallow_horizontal_fling_scroll_
)
797 fling_is_active
= false;
799 if (fling_is_active
) {
802 TRACE_EVENT_INSTANT0("input",
803 "InputHandlerProxy::animate::flingOver",
804 TRACE_EVENT_SCOPE_THREAD
);
805 CancelCurrentFling();
809 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
810 fling_may_be_active_on_main_thread_
= false;
811 client_
->DidStopFlinging();
814 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
815 if (scroll_elasticity_controller_
)
816 scroll_elasticity_controller_
->ReconcileStretchAndScroll();
819 void InputHandlerProxy::UpdateRootLayerStateForSynchronousInputHandler(
820 const gfx::ScrollOffset
& total_scroll_offset
,
821 const gfx::ScrollOffset
& max_scroll_offset
,
822 const gfx::SizeF
& scrollable_size
,
823 float page_scale_factor
,
824 float min_page_scale_factor
,
825 float max_page_scale_factor
) {
826 if (synchronous_input_handler_
) {
827 synchronous_input_handler_
->UpdateRootLayerState(
828 total_scroll_offset
, max_scroll_offset
, scrollable_size
,
829 page_scale_factor
, min_page_scale_factor
, max_page_scale_factor
);
833 void InputHandlerProxy::SetOnlySynchronouslyAnimateRootFlings(
834 SynchronousInputHandler
* synchronous_input_handler
) {
835 allow_root_animate_
= !synchronous_input_handler
;
836 synchronous_input_handler_
= synchronous_input_handler
;
837 if (synchronous_input_handler_
)
838 input_handler_
->RequestUpdateForSynchronousInputHandler();
841 void InputHandlerProxy::SynchronouslyAnimate(base::TimeTicks time
) {
842 // When this function is used, SetOnlySynchronouslyAnimate() should have been
843 // previously called. IOW you should either be entirely in synchronous mode or
845 DCHECK(synchronous_input_handler_
);
846 DCHECK(!allow_root_animate_
);
847 base::AutoReset
<bool> reset(&allow_root_animate_
, true);
851 void InputHandlerProxy::SynchronouslySetRootScrollOffset(
852 const gfx::ScrollOffset
& root_offset
) {
853 DCHECK(synchronous_input_handler_
);
854 input_handler_
->SetSynchronousInputHandlerRootScrollOffset(root_offset
);
857 void InputHandlerProxy::HandleOverscroll(
858 const gfx::Point
& causal_event_viewport_point
,
859 const cc::InputHandlerScrollResult
& scroll_result
) {
861 if (!scroll_result
.did_overscroll_root
)
864 TRACE_EVENT2("input",
865 "InputHandlerProxy::DidOverscroll",
867 scroll_result
.unused_scroll_delta
.x(),
869 scroll_result
.unused_scroll_delta
.y());
871 DidOverscrollParams params
;
872 params
.accumulated_overscroll
= scroll_result
.accumulated_root_overscroll
;
873 params
.latest_overscroll_delta
= scroll_result
.unused_scroll_delta
;
874 params
.current_fling_velocity
=
875 ToClientScrollIncrement(current_fling_velocity_
);
876 params
.causal_event_viewport_point
= causal_event_viewport_point
;
879 static const int kFlingOverscrollThreshold
= 1;
880 disallow_horizontal_fling_scroll_
|=
881 std::abs(params
.accumulated_overscroll
.x()) >=
882 kFlingOverscrollThreshold
;
883 disallow_vertical_fling_scroll_
|=
884 std::abs(params
.accumulated_overscroll
.y()) >=
885 kFlingOverscrollThreshold
;
888 client_
->DidOverscroll(params
);
891 bool InputHandlerProxy::CancelCurrentFling() {
892 if (CancelCurrentFlingWithoutNotifyingClient()) {
893 client_
->DidStopFlinging();
899 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
900 bool had_fling_animation
= fling_curve_
;
901 if (had_fling_animation
&&
902 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchscreen
) {
903 input_handler_
->ScrollEnd();
904 TRACE_EVENT_ASYNC_END0(
906 "InputHandlerProxy::HandleGestureFling::started",
910 TRACE_EVENT_INSTANT1("input",
911 "InputHandlerProxy::CancelCurrentFling",
912 TRACE_EVENT_SCOPE_THREAD
,
913 "had_fling_animation",
914 had_fling_animation
);
915 fling_curve_
.reset();
916 has_fling_animation_started_
= false;
917 gesture_scroll_on_impl_thread_
= false;
918 current_fling_velocity_
= gfx::Vector2dF();
919 fling_parameters_
= blink::WebActiveWheelFlingParameters();
921 if (deferred_fling_cancel_time_seconds_
) {
922 deferred_fling_cancel_time_seconds_
= 0;
924 WebGestureEvent last_fling_boost_event
= last_fling_boost_event_
;
925 last_fling_boost_event_
= WebGestureEvent();
926 if (last_fling_boost_event
.type
== WebInputEvent::GestureScrollBegin
||
927 last_fling_boost_event
.type
== WebInputEvent::GestureScrollUpdate
) {
928 // Synthesize a GestureScrollBegin, as the original was suppressed.
929 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event
));
933 return had_fling_animation
;
936 void InputHandlerProxy::RequestAnimation() {
937 // When a SynchronousInputHandler is present, root flings should go through
938 // it to allow it to control when or if the root fling is animated. Non-root
939 // flings always go through the normal InputHandler.
940 if (synchronous_input_handler_
&& input_handler_
->IsCurrentlyScrollingRoot())
941 synchronous_input_handler_
->SetNeedsSynchronousAnimateInput();
943 input_handler_
->SetNeedsAnimateInput();
946 bool InputHandlerProxy::TouchpadFlingScroll(
947 const WebFloatSize
& increment
) {
948 WebMouseWheelEvent synthetic_wheel
;
949 synthetic_wheel
.type
= WebInputEvent::MouseWheel
;
950 synthetic_wheel
.deltaX
= increment
.width
;
951 synthetic_wheel
.deltaY
= increment
.height
;
952 synthetic_wheel
.hasPreciseScrollingDeltas
= true;
953 synthetic_wheel
.x
= fling_parameters_
.point
.x
;
954 synthetic_wheel
.y
= fling_parameters_
.point
.y
;
955 synthetic_wheel
.globalX
= fling_parameters_
.globalPoint
.x
;
956 synthetic_wheel
.globalY
= fling_parameters_
.globalPoint
.y
;
957 synthetic_wheel
.modifiers
= fling_parameters_
.modifiers
;
959 InputHandlerProxy::EventDisposition disposition
=
960 HandleInputEvent(synthetic_wheel
);
961 switch (disposition
) {
967 TRACE_EVENT_INSTANT0("input",
968 "InputHandlerProxy::scrollBy::AbortFling",
969 TRACE_EVENT_SCOPE_THREAD
);
970 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
971 // main thread. In this case we need to schedule a commit and transfer the
972 // fling curve over to the main thread and run the rest of the wheels from
973 // there. This can happen when flinging a page that contains a scrollable
974 // subarea that we can't scroll on the thread if the fling starts outside
975 // the subarea but then is flung "under" the pointer.
976 client_
->TransferActiveWheelFlingAnimation(fling_parameters_
);
977 fling_may_be_active_on_main_thread_
= true;
978 CancelCurrentFlingWithoutNotifyingClient();
985 bool InputHandlerProxy::scrollBy(const WebFloatSize
& increment
,
986 const WebFloatSize
& velocity
) {
987 WebFloatSize clipped_increment
;
988 WebFloatSize clipped_velocity
;
989 if (!disallow_horizontal_fling_scroll_
) {
990 clipped_increment
.width
= increment
.width
;
991 clipped_velocity
.width
= velocity
.width
;
993 if (!disallow_vertical_fling_scroll_
) {
994 clipped_increment
.height
= increment
.height
;
995 clipped_velocity
.height
= velocity
.height
;
998 current_fling_velocity_
= clipped_velocity
;
1000 // Early out if the increment is zero, but avoid early terimination if the
1001 // velocity is still non-zero.
1002 if (clipped_increment
== WebFloatSize())
1003 return clipped_velocity
!= WebFloatSize();
1005 TRACE_EVENT2("input",
1006 "InputHandlerProxy::scrollBy",
1008 clipped_increment
.width
,
1010 clipped_increment
.height
);
1012 bool did_scroll
= false;
1014 switch (fling_parameters_
.sourceDevice
) {
1015 case blink::WebGestureDeviceTouchpad
:
1016 did_scroll
= TouchpadFlingScroll(clipped_increment
);
1018 case blink::WebGestureDeviceTouchscreen
: {
1019 clipped_increment
= ToClientScrollIncrement(clipped_increment
);
1020 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
1021 fling_parameters_
.point
, clipped_increment
);
1022 HandleOverscroll(fling_parameters_
.point
, scroll_result
);
1023 did_scroll
= scroll_result
.did_scroll
;
1028 fling_parameters_
.cumulativeScroll
.width
+= clipped_increment
.width
;
1029 fling_parameters_
.cumulativeScroll
.height
+= clipped_increment
.height
;
1032 // It's possible the provided |increment| is sufficiently small as to not
1033 // trigger a scroll, e.g., with a trivial time delta between fling updates.
1034 // Return true in this case to prevent early fling termination.
1035 if (std::abs(clipped_increment
.width
) < kScrollEpsilon
&&
1036 std::abs(clipped_increment
.height
) < kScrollEpsilon
)
1042 } // namespace content