Re-subimission of https://codereview.chromium.org/1041213003/
[chromium-blink-merge.git] / content / renderer / input / input_handler_proxy.cc
blobf714ef1f3d956678b8ba0b3cd744f85715a142a7
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;
33 namespace {
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
41 // client to scroll.
42 const float kScrollEpsilon = 0.1f;
44 // Minimum fling velocity required for the active fling and new fling for the
45 // two to accumulate.
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)
75 return false;
77 if (time_since_last_boost_event < 0.001)
78 return true;
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)
85 return false;
87 return true;
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)
99 return false;
101 if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
102 return false;
104 if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
105 return false;
107 return true;
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)) {
125 return;
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())
133 return;
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);
142 break;
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);
148 break;
149 case blink::WebInputEvent::GesturePinchBegin:
150 UMA_HISTOGRAM_CUSTOM_COUNTS(
151 "Event.Latency.RendererImpl.GesturePinchBegin",
152 delta.InMicroseconds(), 1, 1000000, 100);
153 break;
154 case blink::WebInputEvent::GesturePinchUpdate:
155 UMA_HISTOGRAM_CUSTOM_COUNTS(
156 "Event.Latency.RendererImpl.GesturePinchUpdate",
157 delta.InMicroseconds(), 1, 1000000, 100);
158 break;
159 case blink::WebInputEvent::GestureFlingStart:
160 UMA_HISTOGRAM_CUSTOM_COUNTS(
161 "Event.Latency.RendererImpl.GestureFlingStart",
162 delta.InMicroseconds(), 1, 1000000, 100);
163 break;
164 default:
165 NOTREACHED();
166 break;
171 } // namespace
173 namespace content {
175 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler,
176 InputHandlerProxyClient* client)
177 : client_(client),
178 input_handler_(input_handler),
179 deferred_fling_cancel_time_seconds_(0),
180 #ifndef NDEBUG
181 expect_scroll_update_end_(false),
182 #endif
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()) {
190 DCHECK(client);
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",
220 "LatencyInfo.Flow",
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);
227 return disposition;
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))
238 return DID_HANDLE;
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;
263 } else {
264 input_handler_->PinchGestureBegin();
265 gesture_pinch_on_impl_thread_ = true;
266 return DID_HANDLE;
270 case WebInputEvent::GesturePinchEnd:
271 if (gesture_pinch_on_impl_thread_) {
272 gesture_pinch_on_impl_thread_ = false;
273 input_handler_->PinchGestureEnd();
274 return DID_HANDLE;
275 } else {
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));
286 return DID_HANDLE;
287 } else {
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())
298 return DID_HANDLE;
299 else if (!fling_may_be_active_on_main_thread_)
300 return DROP_EVENT;
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;
317 default:
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.
321 if (fling_curve_)
322 CancelCurrentFling();
324 break;
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
336 // InputHandler.
337 gfx::Vector2dF scroll_delta(
338 wheel_event.railsMode != WebInputEvent::RailsModeVertical
339 ? -wheel_event.deltaX
340 : 0,
341 wheel_event.railsMode != WebInputEvent::RailsModeHorizontal
342 ? -wheel_event.deltaY
343 : 0);
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),
356 scroll_delta);
357 switch (scroll_status) {
358 case cc::InputHandler::SCROLL_STARTED:
359 result = DID_HANDLE;
360 break;
361 case cc::InputHandler::SCROLL_IGNORED:
362 result = DROP_EVENT;
363 default:
364 result = DID_NOT_HANDLE;
365 break;
367 } else {
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;
381 break;
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
387 // bugs sorted out.
388 result = DID_NOT_HANDLE;
389 break;
390 case cc::InputHandler::SCROLL_UNKNOWN:
391 case cc::InputHandler::SCROLL_ON_MAIN_THREAD:
392 result = DID_NOT_HANDLE;
393 break;
394 case cc::InputHandler::ScrollStatusCount:
395 NOTREACHED();
396 break;
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
407 // handling paths.
408 base::MessageLoop::current()->PostTask(
409 FROM_HERE,
410 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult,
411 scroll_elasticity_controller_->GetWeakPtr(), wheel_event,
412 scroll_result));
414 return result;
417 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin(
418 const WebGestureEvent& gesture_event) {
419 DCHECK(!gesture_scroll_on_impl_thread_);
420 #ifndef NDEBUG
421 DCHECK(!expect_scroll_update_end_);
422 expect_scroll_update_end_ = true;
423 #endif
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",
427 scroll_status,
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;
435 return DID_HANDLE;
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:
440 return DROP_EVENT;
441 case cc::InputHandler::ScrollStatusCount:
442 NOTREACHED();
443 break;
445 return DID_NOT_HANDLE;
448 InputHandlerProxy::EventDisposition
449 InputHandlerProxy::HandleGestureScrollUpdate(
450 const WebGestureEvent& gesture_event) {
451 #ifndef NDEBUG
452 DCHECK(expect_scroll_update_end_);
453 #endif
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) {
469 #ifndef NDEBUG
470 DCHECK(expect_scroll_update_end_);
471 expect_scroll_update_end_ = false;
472 #endif
473 input_handler_->ScrollEnd();
474 if (!gesture_scroll_on_impl_thread_)
475 return DID_NOT_HANDLE;
476 gesture_scroll_on_impl_thread_ = false;
477 return DID_HANDLE;
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);
488 } else {
489 if (!gesture_scroll_on_impl_thread_)
490 scroll_status = cc::InputHandler::SCROLL_ON_MAIN_THREAD;
491 else
492 scroll_status = input_handler_->FlingScrollBegin();
495 #ifndef NDEBUG
496 expect_scroll_update_end_ = false;
497 #endif
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),
511 blink::WebSize()));
512 disallow_horizontal_fling_scroll_ = !vx;
513 disallow_vertical_fling_scroll_ = !vy;
514 TRACE_EVENT_ASYNC_BEGIN2("input",
515 "InputHandlerProxy::HandleGestureFling::started",
516 this,
517 "vx",
519 "vy",
520 vy);
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();
532 return DID_HANDLE;
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(
546 "input",
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;
556 return DROP_EVENT;
558 case cc::InputHandler::ScrollStatusCount:
559 NOTREACHED();
560 break;
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)
569 continue;
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;
578 return DROP_EVENT;
581 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
582 const WebInputEvent& event) {
583 if (!WebInputEvent::isGestureEventType(event.type))
584 return false;
586 if (!fling_curve_) {
587 DCHECK(!deferred_fling_cancel_time_seconds_);
588 return false;
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)
595 return false;
597 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
598 return false;
600 TRACE_EVENT_INSTANT0("input",
601 "InputHandlerProxy::FlingBoostStart",
602 TRACE_EVENT_SCOPE_THREAD);
603 deferred_fling_cancel_time_seconds_ =
604 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
605 return true;
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_)
611 return false;
613 // Gestures from a different source should immediately interrupt the fling.
614 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
615 CancelCurrentFling();
616 return false;
619 switch (gesture_event.type) {
620 case WebInputEvent::GestureTapCancel:
621 case WebInputEvent::GestureTapDown:
622 return false;
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();
631 return false;
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);
637 return true;
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_,
643 gesture_event,
644 time_since_last_boost_event)) {
645 ExtendBoostedFlingTimeout(gesture_event);
646 return true;
649 CancelCurrentFling();
650 return false;
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();
658 return true;
660 case WebInputEvent::GestureFlingStart: {
661 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
663 bool fling_boosted =
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());
672 if (fling_boosted)
673 current_fling_velocity_ += new_fling_velocity;
674 else
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,
685 velocity,
686 blink::WebSize()));
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,
697 "vx",
698 current_fling_velocity_.x(),
699 "vy",
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();
706 return true;
709 default:
710 // All other types of gestures (taps, presses, etc...) will complete the
711 // deferred fling cancellation.
712 CancelCurrentFling();
713 return false;
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);
731 if (!fling_curve_)
732 return;
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();
739 return;
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();
754 return;
758 bool fling_is_active =
759 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
760 this);
762 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
763 fling_is_active = false;
765 if (fling_is_active) {
766 input_handler_->SetNeedsAnimate();
767 } else {
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) {
788 DCHECK(client_);
789 if (!scroll_result.did_overscroll_root)
790 return;
792 TRACE_EVENT2("input",
793 "InputHandlerProxy::DidOverscroll",
794 "dx",
795 scroll_result.unused_scroll_delta.x(),
796 "dy",
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;
806 if (fling_curve_) {
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();
822 return true;
824 return false;
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(
833 "input",
834 "InputHandlerProxy::HandleGestureFling::started",
835 this);
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) {
880 case DID_HANDLE:
881 return true;
882 case DROP_EVENT:
883 break;
884 case DID_NOT_HANDLE:
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();
897 break;
900 return false;
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",
925 "x",
926 clipped_increment.width,
927 "y",
928 clipped_increment.height);
930 bool did_scroll = false;
932 switch (fling_parameters_.sourceDevice) {
933 case blink::WebGestureDeviceTouchpad:
934 did_scroll = TouchpadFlingScroll(clipped_increment);
935 break;
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;
942 } break;
945 if (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)
955 return true;
957 return did_scroll;
960 } // namespace content