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/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "base/trace_event/trace_event.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 "content/renderer/input/input_scroll_elasticity_controller.h"
17 #include "third_party/WebKit/public/platform/Platform.h"
18 #include "third_party/WebKit/public/web/WebInputEvent.h"
19 #include "ui/events/latency_info.h"
20 #include "ui/gfx/frame_time.h"
21 #include "ui/gfx/geometry/point_conversions.h"
23 using blink::WebFloatPoint
;
24 using blink::WebFloatSize
;
25 using blink::WebGestureEvent
;
26 using blink::WebInputEvent
;
27 using blink::WebMouseEvent
;
28 using blink::WebMouseWheelEvent
;
29 using blink::WebPoint
;
30 using blink::WebTouchEvent
;
31 using blink::WebTouchPoint
;
35 // Maximum time between a fling event's timestamp and the first |Animate| call
36 // for the fling curve to use the fling timestamp as the initial animation time.
37 // Two frames allows a minor delay between event creation and the first animate.
38 const double kMaxSecondsFromFlingTimestampToFirstAnimate
= 2. / 60.;
40 // Threshold for determining whether a fling scroll delta should have caused the
42 const float kScrollEpsilon
= 0.1f
;
44 // Minimum fling velocity required for the active fling and new fling for the
46 const double kMinBoostFlingSpeedSquare
= 350. * 350.;
48 // Minimum velocity for the active touch scroll to preserve (boost) an active
49 // fling for which cancellation has been deferred.
50 const double kMinBoostTouchScrollSpeedSquare
= 150 * 150.;
52 // Timeout window after which the active fling will be cancelled if no scrolls
53 // or flings of sufficient velocity relative to the current fling are received.
54 // The default value on Android native views is 40ms, but we use a slightly
55 // increased value to accomodate small IPC message delays.
56 const double kFlingBoostTimeoutDelaySeconds
= 0.045;
58 gfx::Vector2dF
ToClientScrollIncrement(const WebFloatSize
& increment
) {
59 return gfx::Vector2dF(-increment
.width
, -increment
.height
);
62 double InSecondsF(const base::TimeTicks
& time
) {
63 return (time
- base::TimeTicks()).InSecondsF();
66 bool ShouldSuppressScrollForFlingBoosting(
67 const gfx::Vector2dF
& current_fling_velocity
,
68 const WebGestureEvent
& scroll_update_event
,
69 double time_since_last_boost_event
) {
70 DCHECK_EQ(WebInputEvent::GestureScrollUpdate
, scroll_update_event
.type
);
72 gfx::Vector2dF
dx(scroll_update_event
.data
.scrollUpdate
.deltaX
,
73 scroll_update_event
.data
.scrollUpdate
.deltaY
);
74 if (gfx::DotProduct(current_fling_velocity
, dx
) <= 0)
77 if (time_since_last_boost_event
< 0.001)
80 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
81 // The scroll must be of sufficient velocity to maintain the active fling.
82 const gfx::Vector2dF scroll_velocity
=
83 gfx::ScaleVector2d(dx
, 1. / time_since_last_boost_event
);
84 if (scroll_velocity
.LengthSquared() < kMinBoostTouchScrollSpeedSquare
)
90 bool ShouldBoostFling(const gfx::Vector2dF
& current_fling_velocity
,
91 const WebGestureEvent
& fling_start_event
) {
92 DCHECK_EQ(WebInputEvent::GestureFlingStart
, fling_start_event
.type
);
94 gfx::Vector2dF
new_fling_velocity(
95 fling_start_event
.data
.flingStart
.velocityX
,
96 fling_start_event
.data
.flingStart
.velocityY
);
98 if (gfx::DotProduct(current_fling_velocity
, new_fling_velocity
) <= 0)
101 if (current_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
104 if (new_fling_velocity
.LengthSquared() < kMinBoostFlingSpeedSquare
)
110 WebGestureEvent
ObtainGestureScrollBegin(const WebGestureEvent
& event
) {
111 WebGestureEvent scroll_begin_event
= event
;
112 scroll_begin_event
.type
= WebInputEvent::GestureScrollBegin
;
113 scroll_begin_event
.data
.scrollBegin
.deltaXHint
= 0;
114 scroll_begin_event
.data
.scrollBegin
.deltaYHint
= 0;
115 return scroll_begin_event
;
118 void ReportInputEventLatencyUma(const WebInputEvent
& event
,
119 const ui::LatencyInfo
& latency_info
) {
120 if (!(event
.type
== WebInputEvent::GestureScrollBegin
||
121 event
.type
== WebInputEvent::GestureScrollUpdate
||
122 event
.type
== WebInputEvent::GesturePinchBegin
||
123 event
.type
== WebInputEvent::GesturePinchUpdate
||
124 event
.type
== WebInputEvent::GestureFlingStart
)) {
128 ui::LatencyInfo::LatencyMap::const_iterator it
=
129 latency_info
.latency_components
.find(std::make_pair(
130 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT
, 0));
132 if (it
== latency_info
.latency_components
.end())
135 base::TimeDelta delta
= base::TimeTicks::Now() - it
->second
.event_time
;
136 for (size_t i
= 0; i
< it
->second
.event_count
; ++i
) {
137 switch (event
.type
) {
138 case blink::WebInputEvent::GestureScrollBegin
:
139 UMA_HISTOGRAM_CUSTOM_COUNTS(
140 "Event.Latency.RendererImpl.GestureScrollBegin",
141 delta
.InMicroseconds(), 1, 1000000, 100);
143 case blink::WebInputEvent::GestureScrollUpdate
:
144 UMA_HISTOGRAM_CUSTOM_COUNTS(
145 // So named for historical reasons.
146 "Event.Latency.RendererImpl.GestureScroll2",
147 delta
.InMicroseconds(), 1, 1000000, 100);
149 case blink::WebInputEvent::GesturePinchBegin
:
150 UMA_HISTOGRAM_CUSTOM_COUNTS(
151 "Event.Latency.RendererImpl.GesturePinchBegin",
152 delta
.InMicroseconds(), 1, 1000000, 100);
154 case blink::WebInputEvent::GesturePinchUpdate
:
155 UMA_HISTOGRAM_CUSTOM_COUNTS(
156 "Event.Latency.RendererImpl.GesturePinchUpdate",
157 delta
.InMicroseconds(), 1, 1000000, 100);
159 case blink::WebInputEvent::GestureFlingStart
:
160 UMA_HISTOGRAM_CUSTOM_COUNTS(
161 "Event.Latency.RendererImpl.GestureFlingStart",
162 delta
.InMicroseconds(), 1, 1000000, 100);
175 InputHandlerProxy::InputHandlerProxy(cc::InputHandler
* input_handler
,
176 InputHandlerProxyClient
* client
)
178 input_handler_(input_handler
),
179 deferred_fling_cancel_time_seconds_(0),
181 expect_scroll_update_end_(false),
183 gesture_scroll_on_impl_thread_(false),
184 gesture_pinch_on_impl_thread_(false),
185 fling_may_be_active_on_main_thread_(false),
186 disallow_horizontal_fling_scroll_(false),
187 disallow_vertical_fling_scroll_(false),
188 has_fling_animation_started_(false),
189 uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) {
191 input_handler_
->BindToClient(this);
192 smooth_scroll_enabled_
= base::CommandLine::ForCurrentProcess()->HasSwitch(
193 switches::kEnableSmoothScrolling
);
194 cc::ScrollElasticityHelper
* scroll_elasticity_helper
=
195 input_handler_
->CreateScrollElasticityHelper();
196 if (scroll_elasticity_helper
) {
197 scroll_elasticity_controller_
.reset(
198 new InputScrollElasticityController(scroll_elasticity_helper
));
202 InputHandlerProxy::~InputHandlerProxy() {}
204 void InputHandlerProxy::WillShutdown() {
205 scroll_elasticity_controller_
.reset();
206 input_handler_
= NULL
;
207 client_
->WillShutdown();
210 InputHandlerProxy::EventDisposition
211 InputHandlerProxy::HandleInputEventWithLatencyInfo(
212 const WebInputEvent
& event
,
213 ui::LatencyInfo
* latency_info
) {
214 DCHECK(input_handler_
);
216 if (uma_latency_reporting_enabled_
)
217 ReportInputEventLatencyUma(event
, *latency_info
);
219 TRACE_EVENT_FLOW_STEP0("input,benchmark",
221 TRACE_ID_DONT_MANGLE(latency_info
->trace_id
),
222 "HandleInputEventImpl");
224 scoped_ptr
<cc::SwapPromiseMonitor
> latency_info_swap_promise_monitor
=
225 input_handler_
->CreateLatencyInfoSwapPromiseMonitor(latency_info
);
226 InputHandlerProxy::EventDisposition disposition
= HandleInputEvent(event
);
230 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleInputEvent(
231 const WebInputEvent
& event
) {
232 DCHECK(input_handler_
);
233 TRACE_EVENT1("input,benchmark", "InputHandlerProxy::HandleInputEvent",
234 "type", WebInputEventTraits::GetName(event
.type
));
236 client_
->DidReceiveInputEvent(event
);
237 if (FilterInputEventForFlingBoosting(event
))
240 switch (event
.type
) {
241 case WebInputEvent::MouseWheel
:
242 return HandleMouseWheel(static_cast<const WebMouseWheelEvent
&>(event
));
244 case WebInputEvent::GestureScrollBegin
:
245 return HandleGestureScrollBegin(
246 static_cast<const WebGestureEvent
&>(event
));
248 case WebInputEvent::GestureScrollUpdate
:
249 return HandleGestureScrollUpdate(
250 static_cast<const WebGestureEvent
&>(event
));
252 case WebInputEvent::GestureScrollEnd
:
253 return HandleGestureScrollEnd(static_cast<const WebGestureEvent
&>(event
));
255 case WebInputEvent::GesturePinchBegin
: {
256 DCHECK(!gesture_pinch_on_impl_thread_
);
257 const WebGestureEvent
& gesture_event
=
258 static_cast<const WebGestureEvent
&>(event
);
259 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
&&
260 input_handler_
->HaveWheelEventHandlersAt(
261 gfx::Point(gesture_event
.x
, gesture_event
.y
))) {
262 return DID_NOT_HANDLE
;
264 input_handler_
->PinchGestureBegin();
265 gesture_pinch_on_impl_thread_
= true;
270 case WebInputEvent::GesturePinchEnd
:
271 if (gesture_pinch_on_impl_thread_
) {
272 gesture_pinch_on_impl_thread_
= false;
273 input_handler_
->PinchGestureEnd();
276 return DID_NOT_HANDLE
;
279 case WebInputEvent::GesturePinchUpdate
: {
280 if (gesture_pinch_on_impl_thread_
) {
281 const WebGestureEvent
& gesture_event
=
282 static_cast<const WebGestureEvent
&>(event
);
283 input_handler_
->PinchGestureUpdate(
284 gesture_event
.data
.pinchUpdate
.scale
,
285 gfx::Point(gesture_event
.x
, gesture_event
.y
));
288 return DID_NOT_HANDLE
;
292 case WebInputEvent::GestureFlingStart
:
293 return HandleGestureFlingStart(
294 *static_cast<const WebGestureEvent
*>(&event
));
296 case WebInputEvent::GestureFlingCancel
:
297 if (CancelCurrentFling())
299 else if (!fling_may_be_active_on_main_thread_
)
301 return DID_NOT_HANDLE
;
303 case WebInputEvent::TouchStart
:
304 return HandleTouchStart(static_cast<const WebTouchEvent
&>(event
));
306 case WebInputEvent::MouseMove
: {
307 const WebMouseEvent
& mouse_event
=
308 static_cast<const WebMouseEvent
&>(event
);
309 // TODO(tony): Ignore when mouse buttons are down?
310 // TODO(davemoore): This should never happen, but bug #326635 showed some
311 // surprising crashes.
312 CHECK(input_handler_
);
313 input_handler_
->MouseMoveAt(gfx::Point(mouse_event
.x
, mouse_event
.y
));
314 return DID_NOT_HANDLE
;
318 if (WebInputEvent::isKeyboardEventType(event
.type
)) {
319 // Only call |CancelCurrentFling()| if a fling was active, as it will
320 // otherwise disrupt an in-progress touch scroll.
322 CancelCurrentFling();
327 return DID_NOT_HANDLE
;
330 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleMouseWheel(
331 const WebMouseWheelEvent
& wheel_event
) {
332 InputHandlerProxy::EventDisposition result
= DID_NOT_HANDLE
;
333 cc::InputHandlerScrollResult scroll_result
;
335 // TODO(ccameron): The rail information should be pushed down into
337 gfx::Vector2dF
scroll_delta(
338 wheel_event
.railsMode
!= WebInputEvent::RailsModeVertical
339 ? -wheel_event
.deltaX
341 wheel_event
.railsMode
!= WebInputEvent::RailsModeHorizontal
342 ? -wheel_event
.deltaY
345 if (wheel_event
.scrollByPage
) {
346 // TODO(jamesr): We don't properly handle scroll by page in the compositor
347 // thread, so punt it to the main thread. http://crbug.com/236639
348 result
= DID_NOT_HANDLE
;
349 } else if (!wheel_event
.canScroll
) {
350 // Wheel events with |canScroll| == false will not trigger scrolling,
351 // only event handlers. Forward to the main thread.
352 result
= DID_NOT_HANDLE
;
353 } else if (smooth_scroll_enabled_
) {
354 cc::InputHandler::ScrollStatus scroll_status
=
355 input_handler_
->ScrollAnimated(gfx::Point(wheel_event
.x
, wheel_event
.y
),
357 switch (scroll_status
) {
358 case cc::InputHandler::SCROLL_STARTED
:
361 case cc::InputHandler::SCROLL_IGNORED
:
364 result
= DID_NOT_HANDLE
;
368 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
369 gfx::Point(wheel_event
.x
, wheel_event
.y
), cc::InputHandler::WHEEL
);
370 switch (scroll_status
) {
371 case cc::InputHandler::SCROLL_STARTED
: {
372 TRACE_EVENT_INSTANT2("input",
373 "InputHandlerProxy::handle_input wheel scroll",
374 TRACE_EVENT_SCOPE_THREAD
, "deltaX",
375 scroll_delta
.x(), "deltaY", scroll_delta
.y());
376 gfx::Point
scroll_point(wheel_event
.x
, wheel_event
.y
);
377 scroll_result
= input_handler_
->ScrollBy(scroll_point
, scroll_delta
);
378 HandleOverscroll(scroll_point
, scroll_result
);
379 input_handler_
->ScrollEnd();
380 result
= scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
383 case cc::InputHandler::SCROLL_IGNORED
:
384 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
385 // to properly sync scrollability it's safer to send the event to the
386 // main thread. Change back to DROP_EVENT once we have synchronization
388 result
= DID_NOT_HANDLE
;
390 case cc::InputHandler::SCROLL_UNKNOWN
:
391 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
392 result
= DID_NOT_HANDLE
;
394 case cc::InputHandler::ScrollStatusCount
:
400 // Send the event and its disposition to the elasticity controller to update
401 // the over-scroll animation. If the event is to be handled on the main
402 // thread, the event and its disposition will be sent to the elasticity
403 // controller after being handled on the main thread.
404 if (scroll_elasticity_controller_
&& result
!= DID_NOT_HANDLE
) {
405 // Note that the call to the elasticity controller is made asynchronously,
406 // to minimize divergence between main thread and impl thread event
408 base::MessageLoop::current()->PostTask(
410 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult
,
411 scroll_elasticity_controller_
->GetWeakPtr(), wheel_event
,
417 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollBegin(
418 const WebGestureEvent
& gesture_event
) {
419 DCHECK(!gesture_scroll_on_impl_thread_
);
421 DCHECK(!expect_scroll_update_end_
);
422 expect_scroll_update_end_
= true;
424 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
425 gfx::Point(gesture_event
.x
, gesture_event
.y
), cc::InputHandler::GESTURE
);
426 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
428 cc::InputHandler::ScrollStatusCount
);
429 switch (scroll_status
) {
430 case cc::InputHandler::SCROLL_STARTED
:
431 TRACE_EVENT_INSTANT0("input",
432 "InputHandlerProxy::handle_input gesture scroll",
433 TRACE_EVENT_SCOPE_THREAD
);
434 gesture_scroll_on_impl_thread_
= true;
436 case cc::InputHandler::SCROLL_UNKNOWN
:
437 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
:
438 return DID_NOT_HANDLE
;
439 case cc::InputHandler::SCROLL_IGNORED
:
441 case cc::InputHandler::ScrollStatusCount
:
445 return DID_NOT_HANDLE
;
448 InputHandlerProxy::EventDisposition
449 InputHandlerProxy::HandleGestureScrollUpdate(
450 const WebGestureEvent
& gesture_event
) {
452 DCHECK(expect_scroll_update_end_
);
455 if (!gesture_scroll_on_impl_thread_
&& !gesture_pinch_on_impl_thread_
)
456 return DID_NOT_HANDLE
;
458 gfx::Point
scroll_point(gesture_event
.x
, gesture_event
.y
);
459 gfx::Vector2dF
scroll_delta(-gesture_event
.data
.scrollUpdate
.deltaX
,
460 -gesture_event
.data
.scrollUpdate
.deltaY
);
461 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
462 scroll_point
, scroll_delta
);
463 HandleOverscroll(scroll_point
, scroll_result
);
464 return scroll_result
.did_scroll
? DID_HANDLE
: DROP_EVENT
;
467 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureScrollEnd(
468 const WebGestureEvent
& gesture_event
) {
470 DCHECK(expect_scroll_update_end_
);
471 expect_scroll_update_end_
= false;
473 input_handler_
->ScrollEnd();
474 if (!gesture_scroll_on_impl_thread_
)
475 return DID_NOT_HANDLE
;
476 gesture_scroll_on_impl_thread_
= false;
480 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleGestureFlingStart(
481 const WebGestureEvent
& gesture_event
) {
482 cc::InputHandler::ScrollStatus scroll_status
;
484 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
485 scroll_status
= input_handler_
->ScrollBegin(
486 gfx::Point(gesture_event
.x
, gesture_event
.y
),
487 cc::InputHandler::NON_BUBBLING_GESTURE
);
489 if (!gesture_scroll_on_impl_thread_
)
490 scroll_status
= cc::InputHandler::SCROLL_ON_MAIN_THREAD
;
492 scroll_status
= input_handler_
->FlingScrollBegin();
496 expect_scroll_update_end_
= false;
499 switch (scroll_status
) {
500 case cc::InputHandler::SCROLL_STARTED
: {
501 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
)
502 input_handler_
->ScrollEnd();
504 const float vx
= gesture_event
.data
.flingStart
.velocityX
;
505 const float vy
= gesture_event
.data
.flingStart
.velocityY
;
506 current_fling_velocity_
= gfx::Vector2dF(vx
, vy
);
507 DCHECK(!current_fling_velocity_
.IsZero());
508 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
509 gesture_event
.sourceDevice
,
510 WebFloatPoint(vx
, vy
),
512 disallow_horizontal_fling_scroll_
= !vx
;
513 disallow_vertical_fling_scroll_
= !vy
;
514 TRACE_EVENT_ASYNC_BEGIN2("input",
515 "InputHandlerProxy::HandleGestureFling::started",
521 // Note that the timestamp will only be used to kickstart the animation if
522 // its sufficiently close to the timestamp of the first call |Animate()|.
523 has_fling_animation_started_
= false;
524 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
525 fling_parameters_
.delta
= WebFloatPoint(vx
, vy
);
526 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
527 fling_parameters_
.globalPoint
=
528 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
529 fling_parameters_
.modifiers
= gesture_event
.modifiers
;
530 fling_parameters_
.sourceDevice
= gesture_event
.sourceDevice
;
531 input_handler_
->SetNeedsAnimate();
534 case cc::InputHandler::SCROLL_UNKNOWN
:
535 case cc::InputHandler::SCROLL_ON_MAIN_THREAD
: {
536 TRACE_EVENT_INSTANT0("input",
537 "InputHandlerProxy::HandleGestureFling::"
538 "scroll_on_main_thread",
539 TRACE_EVENT_SCOPE_THREAD
);
540 gesture_scroll_on_impl_thread_
= false;
541 fling_may_be_active_on_main_thread_
= true;
542 return DID_NOT_HANDLE
;
544 case cc::InputHandler::SCROLL_IGNORED
: {
545 TRACE_EVENT_INSTANT0(
547 "InputHandlerProxy::HandleGestureFling::ignored",
548 TRACE_EVENT_SCOPE_THREAD
);
549 gesture_scroll_on_impl_thread_
= false;
550 if (gesture_event
.sourceDevice
== blink::WebGestureDeviceTouchpad
) {
551 // We still pass the curve to the main thread if there's nothing
552 // scrollable, in case something
553 // registers a handler before the curve is over.
554 return DID_NOT_HANDLE
;
558 case cc::InputHandler::ScrollStatusCount
:
562 return DID_NOT_HANDLE
;
565 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleTouchStart(
566 const blink::WebTouchEvent
& touch_event
) {
567 for (size_t i
= 0; i
< touch_event
.touchesLength
; ++i
) {
568 if (touch_event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
570 if (input_handler_
->DoTouchEventsBlockScrollAt(
571 gfx::Point(touch_event
.touches
[i
].position
.x
,
572 touch_event
.touches
[i
].position
.y
))) {
573 // TODO(rbyers): We should consider still sending the touch events to
574 // main asynchronously (crbug.com/455539).
575 return DID_NOT_HANDLE
;
581 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
582 const WebInputEvent
& event
) {
583 if (!WebInputEvent::isGestureEventType(event
.type
))
587 DCHECK(!deferred_fling_cancel_time_seconds_
);
591 const WebGestureEvent
& gesture_event
=
592 static_cast<const WebGestureEvent
&>(event
);
593 if (gesture_event
.type
== WebInputEvent::GestureFlingCancel
) {
594 if (gesture_event
.data
.flingCancel
.preventBoosting
)
597 if (current_fling_velocity_
.LengthSquared() < kMinBoostFlingSpeedSquare
)
600 TRACE_EVENT_INSTANT0("input",
601 "InputHandlerProxy::FlingBoostStart",
602 TRACE_EVENT_SCOPE_THREAD
);
603 deferred_fling_cancel_time_seconds_
=
604 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
608 // A fling is either inactive or is "free spinning", i.e., has yet to be
609 // interrupted by a touch gesture, in which case there is nothing to filter.
610 if (!deferred_fling_cancel_time_seconds_
)
613 // Gestures from a different source should immediately interrupt the fling.
614 if (gesture_event
.sourceDevice
!= fling_parameters_
.sourceDevice
) {
615 CancelCurrentFling();
619 switch (gesture_event
.type
) {
620 case WebInputEvent::GestureTapCancel
:
621 case WebInputEvent::GestureTapDown
:
624 case WebInputEvent::GestureScrollBegin
:
625 if (!input_handler_
->IsCurrentlyScrollingLayerAt(
626 gfx::Point(gesture_event
.x
, gesture_event
.y
),
627 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchpad
628 ? cc::InputHandler::NON_BUBBLING_GESTURE
629 : cc::InputHandler::GESTURE
)) {
630 CancelCurrentFling();
634 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
635 // determine if the ScrollBegin should immediately cancel the fling.
636 ExtendBoostedFlingTimeout(gesture_event
);
639 case WebInputEvent::GestureScrollUpdate
: {
640 const double time_since_last_boost_event
=
641 event
.timeStampSeconds
- last_fling_boost_event_
.timeStampSeconds
;
642 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_
,
644 time_since_last_boost_event
)) {
645 ExtendBoostedFlingTimeout(gesture_event
);
649 CancelCurrentFling();
653 case WebInputEvent::GestureScrollEnd
:
654 // Clear the last fling boost event *prior* to fling cancellation,
655 // preventing insertion of a synthetic GestureScrollBegin.
656 last_fling_boost_event_
= WebGestureEvent();
657 CancelCurrentFling();
660 case WebInputEvent::GestureFlingStart
: {
661 DCHECK_EQ(fling_parameters_
.sourceDevice
, gesture_event
.sourceDevice
);
664 fling_parameters_
.modifiers
== gesture_event
.modifiers
&&
665 ShouldBoostFling(current_fling_velocity_
, gesture_event
);
667 gfx::Vector2dF
new_fling_velocity(
668 gesture_event
.data
.flingStart
.velocityX
,
669 gesture_event
.data
.flingStart
.velocityY
);
670 DCHECK(!new_fling_velocity
.IsZero());
673 current_fling_velocity_
+= new_fling_velocity
;
675 current_fling_velocity_
= new_fling_velocity
;
677 WebFloatPoint
velocity(current_fling_velocity_
.x(),
678 current_fling_velocity_
.y());
679 deferred_fling_cancel_time_seconds_
= 0;
680 disallow_horizontal_fling_scroll_
= !velocity
.x
;
681 disallow_vertical_fling_scroll_
= !velocity
.y
;
682 last_fling_boost_event_
= WebGestureEvent();
683 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
684 gesture_event
.sourceDevice
,
687 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
688 fling_parameters_
.delta
= velocity
;
689 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
690 fling_parameters_
.globalPoint
=
691 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
693 TRACE_EVENT_INSTANT2("input",
694 fling_boosted
? "InputHandlerProxy::FlingBoosted"
695 : "InputHandlerProxy::FlingReplaced",
696 TRACE_EVENT_SCOPE_THREAD
,
698 current_fling_velocity_
.x(),
700 current_fling_velocity_
.y());
702 // The client expects balanced calls between a consumed GestureFlingStart
703 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
704 // |DidStopFlinging()| and only send after the accumulated fling ends.
705 client_
->DidStopFlinging();
710 // All other types of gestures (taps, presses, etc...) will complete the
711 // deferred fling cancellation.
712 CancelCurrentFling();
717 void InputHandlerProxy::ExtendBoostedFlingTimeout(
718 const blink::WebGestureEvent
& event
) {
719 TRACE_EVENT_INSTANT0("input",
720 "InputHandlerProxy::ExtendBoostedFlingTimeout",
721 TRACE_EVENT_SCOPE_THREAD
);
722 deferred_fling_cancel_time_seconds_
=
723 event
.timeStampSeconds
+ kFlingBoostTimeoutDelaySeconds
;
724 last_fling_boost_event_
= event
;
727 void InputHandlerProxy::Animate(base::TimeTicks time
) {
728 if (scroll_elasticity_controller_
)
729 scroll_elasticity_controller_
->Animate(time
);
734 double monotonic_time_sec
= InSecondsF(time
);
736 if (deferred_fling_cancel_time_seconds_
&&
737 monotonic_time_sec
> deferred_fling_cancel_time_seconds_
) {
738 CancelCurrentFling();
742 client_
->DidAnimateForInput();
744 if (!has_fling_animation_started_
) {
745 has_fling_animation_started_
= true;
746 // Guard against invalid, future or sufficiently stale start times, as there
747 // are no guarantees fling event and animation timestamps are compatible.
748 if (!fling_parameters_
.startTime
||
749 monotonic_time_sec
<= fling_parameters_
.startTime
||
750 monotonic_time_sec
>= fling_parameters_
.startTime
+
751 kMaxSecondsFromFlingTimestampToFirstAnimate
) {
752 fling_parameters_
.startTime
= monotonic_time_sec
;
753 input_handler_
->SetNeedsAnimate();
758 bool fling_is_active
=
759 fling_curve_
->apply(monotonic_time_sec
- fling_parameters_
.startTime
,
762 if (disallow_vertical_fling_scroll_
&& disallow_horizontal_fling_scroll_
)
763 fling_is_active
= false;
765 if (fling_is_active
) {
766 input_handler_
->SetNeedsAnimate();
768 TRACE_EVENT_INSTANT0("input",
769 "InputHandlerProxy::animate::flingOver",
770 TRACE_EVENT_SCOPE_THREAD
);
771 CancelCurrentFling();
775 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
776 fling_may_be_active_on_main_thread_
= false;
777 client_
->DidStopFlinging();
780 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
781 if (scroll_elasticity_controller_
)
782 scroll_elasticity_controller_
->ReconcileStretchAndScroll();
785 void InputHandlerProxy::HandleOverscroll(
786 const gfx::Point
& causal_event_viewport_point
,
787 const cc::InputHandlerScrollResult
& scroll_result
) {
789 if (!scroll_result
.did_overscroll_root
)
792 TRACE_EVENT2("input",
793 "InputHandlerProxy::DidOverscroll",
795 scroll_result
.unused_scroll_delta
.x(),
797 scroll_result
.unused_scroll_delta
.y());
799 DidOverscrollParams params
;
800 params
.accumulated_overscroll
= scroll_result
.accumulated_root_overscroll
;
801 params
.latest_overscroll_delta
= scroll_result
.unused_scroll_delta
;
802 params
.current_fling_velocity
=
803 ToClientScrollIncrement(current_fling_velocity_
);
804 params
.causal_event_viewport_point
= causal_event_viewport_point
;
807 static const int kFlingOverscrollThreshold
= 1;
808 disallow_horizontal_fling_scroll_
|=
809 std::abs(params
.accumulated_overscroll
.x()) >=
810 kFlingOverscrollThreshold
;
811 disallow_vertical_fling_scroll_
|=
812 std::abs(params
.accumulated_overscroll
.y()) >=
813 kFlingOverscrollThreshold
;
816 client_
->DidOverscroll(params
);
819 bool InputHandlerProxy::CancelCurrentFling() {
820 if (CancelCurrentFlingWithoutNotifyingClient()) {
821 client_
->DidStopFlinging();
827 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
828 bool had_fling_animation
= fling_curve_
;
829 if (had_fling_animation
&&
830 fling_parameters_
.sourceDevice
== blink::WebGestureDeviceTouchscreen
) {
831 input_handler_
->ScrollEnd();
832 TRACE_EVENT_ASYNC_END0(
834 "InputHandlerProxy::HandleGestureFling::started",
838 TRACE_EVENT_INSTANT1("input",
839 "InputHandlerProxy::CancelCurrentFling",
840 TRACE_EVENT_SCOPE_THREAD
,
841 "had_fling_animation",
842 had_fling_animation
);
843 fling_curve_
.reset();
844 has_fling_animation_started_
= false;
845 gesture_scroll_on_impl_thread_
= false;
846 current_fling_velocity_
= gfx::Vector2dF();
847 fling_parameters_
= blink::WebActiveWheelFlingParameters();
849 if (deferred_fling_cancel_time_seconds_
) {
850 deferred_fling_cancel_time_seconds_
= 0;
852 WebGestureEvent last_fling_boost_event
= last_fling_boost_event_
;
853 last_fling_boost_event_
= WebGestureEvent();
854 if (last_fling_boost_event
.type
== WebInputEvent::GestureScrollBegin
||
855 last_fling_boost_event
.type
== WebInputEvent::GestureScrollUpdate
) {
856 // Synthesize a GestureScrollBegin, as the original was suppressed.
857 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event
));
861 return had_fling_animation
;
864 bool InputHandlerProxy::TouchpadFlingScroll(
865 const WebFloatSize
& increment
) {
866 WebMouseWheelEvent synthetic_wheel
;
867 synthetic_wheel
.type
= WebInputEvent::MouseWheel
;
868 synthetic_wheel
.deltaX
= increment
.width
;
869 synthetic_wheel
.deltaY
= increment
.height
;
870 synthetic_wheel
.hasPreciseScrollingDeltas
= true;
871 synthetic_wheel
.x
= fling_parameters_
.point
.x
;
872 synthetic_wheel
.y
= fling_parameters_
.point
.y
;
873 synthetic_wheel
.globalX
= fling_parameters_
.globalPoint
.x
;
874 synthetic_wheel
.globalY
= fling_parameters_
.globalPoint
.y
;
875 synthetic_wheel
.modifiers
= fling_parameters_
.modifiers
;
877 InputHandlerProxy::EventDisposition disposition
=
878 HandleInputEvent(synthetic_wheel
);
879 switch (disposition
) {
885 TRACE_EVENT_INSTANT0("input",
886 "InputHandlerProxy::scrollBy::AbortFling",
887 TRACE_EVENT_SCOPE_THREAD
);
888 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
889 // main thread. In this case we need to schedule a commit and transfer the
890 // fling curve over to the main thread and run the rest of the wheels from
891 // there. This can happen when flinging a page that contains a scrollable
892 // subarea that we can't scroll on the thread if the fling starts outside
893 // the subarea but then is flung "under" the pointer.
894 client_
->TransferActiveWheelFlingAnimation(fling_parameters_
);
895 fling_may_be_active_on_main_thread_
= true;
896 CancelCurrentFlingWithoutNotifyingClient();
903 bool InputHandlerProxy::scrollBy(const WebFloatSize
& increment
,
904 const WebFloatSize
& velocity
) {
905 WebFloatSize clipped_increment
;
906 WebFloatSize clipped_velocity
;
907 if (!disallow_horizontal_fling_scroll_
) {
908 clipped_increment
.width
= increment
.width
;
909 clipped_velocity
.width
= velocity
.width
;
911 if (!disallow_vertical_fling_scroll_
) {
912 clipped_increment
.height
= increment
.height
;
913 clipped_velocity
.height
= velocity
.height
;
916 current_fling_velocity_
= clipped_velocity
;
918 // Early out if the increment is zero, but avoid early terimination if the
919 // velocity is still non-zero.
920 if (clipped_increment
== WebFloatSize())
921 return clipped_velocity
!= WebFloatSize();
923 TRACE_EVENT2("input",
924 "InputHandlerProxy::scrollBy",
926 clipped_increment
.width
,
928 clipped_increment
.height
);
930 bool did_scroll
= false;
932 switch (fling_parameters_
.sourceDevice
) {
933 case blink::WebGestureDeviceTouchpad
:
934 did_scroll
= TouchpadFlingScroll(clipped_increment
);
936 case blink::WebGestureDeviceTouchscreen
: {
937 clipped_increment
= ToClientScrollIncrement(clipped_increment
);
938 cc::InputHandlerScrollResult scroll_result
= input_handler_
->ScrollBy(
939 fling_parameters_
.point
, clipped_increment
);
940 HandleOverscroll(fling_parameters_
.point
, scroll_result
);
941 did_scroll
= scroll_result
.did_scroll
;
946 fling_parameters_
.cumulativeScroll
.width
+= clipped_increment
.width
;
947 fling_parameters_
.cumulativeScroll
.height
+= clipped_increment
.height
;
950 // It's possible the provided |increment| is sufficiently small as to not
951 // trigger a scroll, e.g., with a trivial time delta between fling updates.
952 // Return true in this case to prevent early fling termination.
953 if (std::abs(clipped_increment
.width
) < kScrollEpsilon
&&
954 std::abs(clipped_increment
.height
) < kScrollEpsilon
)
960 } // namespace content