Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / content / renderer / input / input_handler_proxy.cc
blobb5dcb181261f6f8b19f7d1a13c7dfc73caa41b4b
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 <algorithm>
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;
37 namespace {
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
45 // client to scroll.
46 const float kScrollEpsilon = 0.1f;
48 // Minimum fling velocity required for the active fling and new fling for the
49 // two to accumulate.
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)
80 return false;
82 if (time_since_last_fling_animate > kFlingBoostTimeoutDelaySeconds)
83 return false;
85 if (time_since_last_boost_event < 0.001)
86 return true;
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)
93 return false;
95 return true;
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)
107 return false;
109 if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
110 return false;
112 if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
113 return false;
115 return true;
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)) {
133 return;
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())
141 return;
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);
150 break;
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);
156 break;
157 case blink::WebInputEvent::GesturePinchBegin:
158 UMA_HISTOGRAM_CUSTOM_COUNTS(
159 "Event.Latency.RendererImpl.GesturePinchBegin",
160 delta.InMicroseconds(), 1, 1000000, 100);
161 break;
162 case blink::WebInputEvent::GesturePinchUpdate:
163 UMA_HISTOGRAM_CUSTOM_COUNTS(
164 "Event.Latency.RendererImpl.GesturePinchUpdate",
165 delta.InMicroseconds(), 1, 1000000, 100);
166 break;
167 case blink::WebInputEvent::GestureFlingStart:
168 UMA_HISTOGRAM_CUSTOM_COUNTS(
169 "Event.Latency.RendererImpl.GestureFlingStart",
170 delta.InMicroseconds(), 1, 1000000, 100);
171 break;
172 default:
173 NOTREACHED();
174 break;
179 } // namespace
181 namespace content {
183 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler,
184 InputHandlerProxyClient* client)
185 : client_(client),
186 input_handler_(input_handler),
187 deferred_fling_cancel_time_seconds_(0),
188 synchronous_input_handler_(nullptr),
189 allow_root_animate_(true),
190 #ifndef NDEBUG
191 expect_scroll_update_end_(false),
192 #endif
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()) {
200 DCHECK(client);
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",
230 "LatencyInfo.Flow",
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);
238 return disposition;
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))
248 return DID_HANDLE;
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;
273 } else {
274 input_handler_->PinchGestureBegin();
275 gesture_pinch_on_impl_thread_ = true;
276 return DID_HANDLE;
280 case WebInputEvent::GesturePinchEnd:
281 if (gesture_pinch_on_impl_thread_) {
282 gesture_pinch_on_impl_thread_ = false;
283 input_handler_->PinchGestureEnd();
284 return DID_HANDLE;
285 } else {
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)
294 return DROP_EVENT;
295 input_handler_->PinchGestureUpdate(
296 gesture_event.data.pinchUpdate.scale,
297 gfx::Point(gesture_event.x, gesture_event.y));
298 return DID_HANDLE;
299 } else {
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())
310 return DID_HANDLE;
311 else if (!fling_may_be_active_on_main_thread_)
312 return DROP_EVENT;
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;
329 default:
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.
333 if (fling_curve_)
334 CancelCurrentFling();
336 break;
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
348 // InputHandler.
349 gfx::Vector2dF scroll_delta(
350 wheel_event.railsMode != WebInputEvent::RailsModeVertical
351 ? -wheel_event.deltaX
352 : 0,
353 wheel_event.railsMode != WebInputEvent::RailsModeHorizontal
354 ? -wheel_event.deltaY
355 : 0);
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),
368 scroll_delta);
369 switch (scroll_status) {
370 case cc::InputHandler::SCROLL_STARTED:
371 result = DID_HANDLE;
372 break;
373 case cc::InputHandler::SCROLL_IGNORED:
374 result = DROP_EVENT;
375 default:
376 result = DID_NOT_HANDLE;
377 break;
379 } else {
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;
393 break;
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
399 // bugs sorted out.
400 result = DID_NOT_HANDLE;
401 break;
402 case cc::InputHandler::SCROLL_UNKNOWN:
403 case cc::InputHandler::SCROLL_ON_MAIN_THREAD:
404 result = DID_NOT_HANDLE;
405 break;
406 case cc::InputHandler::ScrollStatusCount:
407 NOTREACHED();
408 break;
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
419 // handling paths.
420 base::ThreadTaskRunnerHandle::Get()->PostTask(
421 FROM_HERE,
422 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult,
423 scroll_elasticity_controller_->GetWeakPtr(), wheel_event,
424 scroll_result));
426 return result;
429 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin(
430 const WebGestureEvent& gesture_event) {
431 if (gesture_scroll_on_impl_thread_)
432 CancelCurrentFling();
434 #ifndef NDEBUG
435 DCHECK(!expect_scroll_update_end_);
436 expect_scroll_update_end_ = true;
437 #endif
438 cc::InputHandler::ScrollStatus scroll_status;
439 if (gesture_event.data.scrollBegin.targetViewport) {
440 scroll_status = input_handler_->RootScrollBegin(cc::InputHandler::GESTURE);
441 } else {
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",
447 scroll_status,
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;
455 return DID_HANDLE;
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:
460 return DROP_EVENT;
461 case cc::InputHandler::ScrollStatusCount:
462 NOTREACHED();
463 break;
465 return DID_NOT_HANDLE;
468 InputHandlerProxy::EventDisposition
469 InputHandlerProxy::HandleGestureScrollUpdate(
470 const WebGestureEvent& gesture_event) {
471 #ifndef NDEBUG
472 DCHECK(expect_scroll_update_end_);
473 #endif
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) {
489 #ifndef NDEBUG
490 DCHECK(expect_scroll_update_end_);
491 expect_scroll_update_end_ = false;
492 #endif
493 input_handler_->ScrollEnd();
494 if (!gesture_scroll_on_impl_thread_)
495 return DID_NOT_HANDLE;
496 gesture_scroll_on_impl_thread_ = false;
497 return DID_HANDLE;
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);
508 } else {
509 scroll_status = input_handler_->ScrollBegin(
510 gfx::Point(gesture_event.x, gesture_event.y),
511 cc::InputHandler::NON_BUBBLING_GESTURE);
513 } else {
514 if (!gesture_scroll_on_impl_thread_)
515 scroll_status = cc::InputHandler::SCROLL_ON_MAIN_THREAD;
516 else
517 scroll_status = input_handler_->FlingScrollBegin();
520 #ifndef NDEBUG
521 expect_scroll_update_end_ = false;
522 #endif
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),
536 blink::WebSize()));
537 disallow_horizontal_fling_scroll_ = !vx;
538 disallow_vertical_fling_scroll_ = !vy;
539 TRACE_EVENT_ASYNC_BEGIN2("input",
540 "InputHandlerProxy::HandleGestureFling::started",
541 this,
542 "vx",
544 "vy",
545 vy);
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;
556 RequestAnimation();
557 return DID_HANDLE;
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(
571 "input",
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;
581 return DROP_EVENT;
583 case cc::InputHandler::ScrollStatusCount:
584 NOTREACHED();
585 break;
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)
594 continue;
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;
603 return DROP_EVENT;
606 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
607 const WebInputEvent& event) {
608 if (!WebInputEvent::isGestureEventType(event.type))
609 return false;
611 if (!fling_curve_) {
612 DCHECK(!deferred_fling_cancel_time_seconds_);
613 return false;
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)
620 return false;
622 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
623 return false;
625 TRACE_EVENT_INSTANT0("input",
626 "InputHandlerProxy::FlingBoostStart",
627 TRACE_EVENT_SCOPE_THREAD);
628 deferred_fling_cancel_time_seconds_ =
629 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
630 return true;
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_)
636 return false;
638 // Gestures from a different source should immediately interrupt the fling.
639 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
640 CancelCurrentFling();
641 return false;
644 switch (gesture_event.type) {
645 case WebInputEvent::GestureTapCancel:
646 case WebInputEvent::GestureTapDown:
647 return false;
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();
656 return false;
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);
662 return true;
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_,
670 gesture_event,
671 time_since_last_boost_event,
672 time_since_last_fling_animate)) {
673 ExtendBoostedFlingTimeout(gesture_event);
674 return true;
677 CancelCurrentFling();
678 return false;
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();
686 return true;
688 case WebInputEvent::GestureFlingStart: {
689 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
691 bool fling_boosted =
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());
700 if (fling_boosted)
701 current_fling_velocity_ += new_fling_velocity;
702 else
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,
713 velocity,
714 blink::WebSize()));
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,
725 "vx",
726 current_fling_velocity_.x(),
727 "vy",
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();
734 return true;
737 default:
738 // All other types of gestures (taps, presses, etc...) will complete the
739 // deferred fling cancellation.
740 CancelCurrentFling();
741 return false;
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);
764 if (!fling_curve_)
765 return;
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();
773 return;
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;
787 RequestAnimation();
788 return;
792 bool fling_is_active =
793 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
794 this);
796 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
797 fling_is_active = false;
799 if (fling_is_active) {
800 RequestAnimation();
801 } else {
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
844 // not.
845 DCHECK(synchronous_input_handler_);
846 DCHECK(!allow_root_animate_);
847 base::AutoReset<bool> reset(&allow_root_animate_, true);
848 Animate(time);
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) {
860 DCHECK(client_);
861 if (!scroll_result.did_overscroll_root)
862 return;
864 TRACE_EVENT2("input",
865 "InputHandlerProxy::DidOverscroll",
866 "dx",
867 scroll_result.unused_scroll_delta.x(),
868 "dy",
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;
878 if (fling_curve_) {
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();
894 return true;
896 return false;
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(
905 "input",
906 "InputHandlerProxy::HandleGestureFling::started",
907 this);
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();
942 else
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) {
962 case DID_HANDLE:
963 return true;
964 case DROP_EVENT:
965 break;
966 case DID_NOT_HANDLE:
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();
979 break;
982 return false;
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",
1007 "x",
1008 clipped_increment.width,
1009 "y",
1010 clipped_increment.height);
1012 bool did_scroll = false;
1014 switch (fling_parameters_.sourceDevice) {
1015 case blink::WebGestureDeviceTouchpad:
1016 did_scroll = TouchpadFlingScroll(clipped_increment);
1017 break;
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;
1024 } break;
1027 if (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)
1037 return true;
1039 return did_scroll;
1042 } // namespace content