Roll src/third_party/WebKit 9d2dfea:3aea697 (svn 201972:201973)
[chromium-blink-merge.git] / content / renderer / input / input_handler_proxy.cc
blob633ed8b929813706302cfd70b9f5e027b34b07a2
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 #ifndef NDEBUG
189 expect_scroll_update_end_(false),
190 #endif
191 gesture_scroll_on_impl_thread_(false),
192 gesture_pinch_on_impl_thread_(false),
193 fling_may_be_active_on_main_thread_(false),
194 disallow_horizontal_fling_scroll_(false),
195 disallow_vertical_fling_scroll_(false),
196 has_fling_animation_started_(false),
197 uma_latency_reporting_enabled_(base::TimeTicks::IsHighResolution()) {
198 DCHECK(client);
199 input_handler_->BindToClient(this);
200 smooth_scroll_enabled_ = base::CommandLine::ForCurrentProcess()->HasSwitch(
201 switches::kEnableSmoothScrolling);
202 cc::ScrollElasticityHelper* scroll_elasticity_helper =
203 input_handler_->CreateScrollElasticityHelper();
204 if (scroll_elasticity_helper) {
205 scroll_elasticity_controller_.reset(
206 new InputScrollElasticityController(scroll_elasticity_helper));
210 InputHandlerProxy::~InputHandlerProxy() {}
212 void InputHandlerProxy::WillShutdown() {
213 scroll_elasticity_controller_.reset();
214 input_handler_ = NULL;
215 client_->WillShutdown();
218 InputHandlerProxy::EventDisposition
219 InputHandlerProxy::HandleInputEventWithLatencyInfo(
220 const WebInputEvent& event,
221 ui::LatencyInfo* latency_info) {
222 DCHECK(input_handler_);
224 if (uma_latency_reporting_enabled_)
225 ReportInputEventLatencyUma(event, *latency_info);
227 TRACE_EVENT_WITH_FLOW1("input,benchmark",
228 "LatencyInfo.Flow",
229 TRACE_ID_DONT_MANGLE(latency_info->trace_id()),
230 TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT,
231 "step", "HandleInputEventImpl");
233 scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
234 input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info);
235 InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event);
236 return disposition;
239 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
240 const WebInputEvent& event) {
241 DCHECK(input_handler_);
242 TRACE_EVENT1("input,benchmark", "InputHandlerProxy::HandleInputEvent",
243 "type", WebInputEventTraits::GetName(event.type));
245 if (FilterInputEventForFlingBoosting(event))
246 return DID_HANDLE;
248 switch (event.type) {
249 case WebInputEvent::MouseWheel:
250 return HandleMouseWheel(static_cast<const WebMouseWheelEvent&>(event));
252 case WebInputEvent::GestureScrollBegin:
253 return HandleGestureScrollBegin(
254 static_cast<const WebGestureEvent&>(event));
256 case WebInputEvent::GestureScrollUpdate:
257 return HandleGestureScrollUpdate(
258 static_cast<const WebGestureEvent&>(event));
260 case WebInputEvent::GestureScrollEnd:
261 return HandleGestureScrollEnd(static_cast<const WebGestureEvent&>(event));
263 case WebInputEvent::GesturePinchBegin: {
264 DCHECK(!gesture_pinch_on_impl_thread_);
265 const WebGestureEvent& gesture_event =
266 static_cast<const WebGestureEvent&>(event);
267 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad &&
268 input_handler_->HaveWheelEventHandlersAt(
269 gfx::Point(gesture_event.x, gesture_event.y))) {
270 return DID_NOT_HANDLE;
271 } else {
272 input_handler_->PinchGestureBegin();
273 gesture_pinch_on_impl_thread_ = true;
274 return DID_HANDLE;
278 case WebInputEvent::GesturePinchEnd:
279 if (gesture_pinch_on_impl_thread_) {
280 gesture_pinch_on_impl_thread_ = false;
281 input_handler_->PinchGestureEnd();
282 return DID_HANDLE;
283 } else {
284 return DID_NOT_HANDLE;
287 case WebInputEvent::GesturePinchUpdate: {
288 if (gesture_pinch_on_impl_thread_) {
289 const WebGestureEvent& gesture_event =
290 static_cast<const WebGestureEvent&>(event);
291 if (gesture_event.data.pinchUpdate.zoomDisabled)
292 return DROP_EVENT;
293 input_handler_->PinchGestureUpdate(
294 gesture_event.data.pinchUpdate.scale,
295 gfx::Point(gesture_event.x, gesture_event.y));
296 return DID_HANDLE;
297 } else {
298 return DID_NOT_HANDLE;
302 case WebInputEvent::GestureFlingStart:
303 return HandleGestureFlingStart(
304 *static_cast<const WebGestureEvent*>(&event));
306 case WebInputEvent::GestureFlingCancel:
307 if (CancelCurrentFling())
308 return DID_HANDLE;
309 else if (!fling_may_be_active_on_main_thread_)
310 return DROP_EVENT;
311 return DID_NOT_HANDLE;
313 case WebInputEvent::TouchStart:
314 return HandleTouchStart(static_cast<const WebTouchEvent&>(event));
316 case WebInputEvent::MouseMove: {
317 const WebMouseEvent& mouse_event =
318 static_cast<const WebMouseEvent&>(event);
319 // TODO(tony): Ignore when mouse buttons are down?
320 // TODO(davemoore): This should never happen, but bug #326635 showed some
321 // surprising crashes.
322 CHECK(input_handler_);
323 input_handler_->MouseMoveAt(gfx::Point(mouse_event.x, mouse_event.y));
324 return DID_NOT_HANDLE;
327 default:
328 if (WebInputEvent::isKeyboardEventType(event.type)) {
329 // Only call |CancelCurrentFling()| if a fling was active, as it will
330 // otherwise disrupt an in-progress touch scroll.
331 if (fling_curve_)
332 CancelCurrentFling();
334 break;
337 return DID_NOT_HANDLE;
340 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleMouseWheel(
341 const WebMouseWheelEvent& wheel_event) {
342 InputHandlerProxy::EventDisposition result = DID_NOT_HANDLE;
343 cc::InputHandlerScrollResult scroll_result;
345 // TODO(ccameron): The rail information should be pushed down into
346 // InputHandler.
347 gfx::Vector2dF scroll_delta(
348 wheel_event.railsMode != WebInputEvent::RailsModeVertical
349 ? -wheel_event.deltaX
350 : 0,
351 wheel_event.railsMode != WebInputEvent::RailsModeHorizontal
352 ? -wheel_event.deltaY
353 : 0);
355 if (wheel_event.scrollByPage) {
356 // TODO(jamesr): We don't properly handle scroll by page in the compositor
357 // thread, so punt it to the main thread. http://crbug.com/236639
358 result = DID_NOT_HANDLE;
359 } else if (!wheel_event.canScroll) {
360 // Wheel events with |canScroll| == false will not trigger scrolling,
361 // only event handlers. Forward to the main thread.
362 result = DID_NOT_HANDLE;
363 } else if (smooth_scroll_enabled_) {
364 cc::InputHandler::ScrollStatus scroll_status =
365 input_handler_->ScrollAnimated(gfx::Point(wheel_event.x, wheel_event.y),
366 scroll_delta);
367 switch (scroll_status) {
368 case cc::InputHandler::SCROLL_STARTED:
369 result = DID_HANDLE;
370 break;
371 case cc::InputHandler::SCROLL_IGNORED:
372 result = DROP_EVENT;
373 default:
374 result = DID_NOT_HANDLE;
375 break;
377 } else {
378 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
379 gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::WHEEL);
380 switch (scroll_status) {
381 case cc::InputHandler::SCROLL_STARTED: {
382 TRACE_EVENT_INSTANT2("input",
383 "InputHandlerProxy::handle_input wheel scroll",
384 TRACE_EVENT_SCOPE_THREAD, "deltaX",
385 scroll_delta.x(), "deltaY", scroll_delta.y());
386 gfx::Point scroll_point(wheel_event.x, wheel_event.y);
387 scroll_result = input_handler_->ScrollBy(scroll_point, scroll_delta);
388 HandleOverscroll(scroll_point, scroll_result);
389 input_handler_->ScrollEnd();
390 result = scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT;
391 break;
393 case cc::InputHandler::SCROLL_IGNORED:
394 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
395 // to properly sync scrollability it's safer to send the event to the
396 // main thread. Change back to DROP_EVENT once we have synchronization
397 // bugs sorted out.
398 result = DID_NOT_HANDLE;
399 break;
400 case cc::InputHandler::SCROLL_UNKNOWN:
401 case cc::InputHandler::SCROLL_ON_MAIN_THREAD:
402 result = DID_NOT_HANDLE;
403 break;
404 case cc::InputHandler::ScrollStatusCount:
405 NOTREACHED();
406 break;
410 // Send the event and its disposition to the elasticity controller to update
411 // the over-scroll animation. If the event is to be handled on the main
412 // thread, the event and its disposition will be sent to the elasticity
413 // controller after being handled on the main thread.
414 if (scroll_elasticity_controller_ && result != DID_NOT_HANDLE) {
415 // Note that the call to the elasticity controller is made asynchronously,
416 // to minimize divergence between main thread and impl thread event
417 // handling paths.
418 base::ThreadTaskRunnerHandle::Get()->PostTask(
419 FROM_HERE,
420 base::Bind(&InputScrollElasticityController::ObserveWheelEventAndResult,
421 scroll_elasticity_controller_->GetWeakPtr(), wheel_event,
422 scroll_result));
424 return result;
427 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollBegin(
428 const WebGestureEvent& gesture_event) {
429 if (gesture_scroll_on_impl_thread_)
430 CancelCurrentFling();
432 #ifndef NDEBUG
433 DCHECK(!expect_scroll_update_end_);
434 expect_scroll_update_end_ = true;
435 #endif
436 cc::InputHandler::ScrollStatus scroll_status;
437 if (gesture_event.data.scrollBegin.targetViewport) {
438 scroll_status = input_handler_->RootScrollBegin(cc::InputHandler::GESTURE);
439 } else {
440 scroll_status = input_handler_->ScrollBegin(
441 gfx::Point(gesture_event.x, gesture_event.y),
442 cc::InputHandler::GESTURE);
444 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
445 scroll_status,
446 cc::InputHandler::ScrollStatusCount);
447 switch (scroll_status) {
448 case cc::InputHandler::SCROLL_STARTED:
449 TRACE_EVENT_INSTANT0("input",
450 "InputHandlerProxy::handle_input gesture scroll",
451 TRACE_EVENT_SCOPE_THREAD);
452 gesture_scroll_on_impl_thread_ = true;
453 return DID_HANDLE;
454 case cc::InputHandler::SCROLL_UNKNOWN:
455 case cc::InputHandler::SCROLL_ON_MAIN_THREAD:
456 return DID_NOT_HANDLE;
457 case cc::InputHandler::SCROLL_IGNORED:
458 return DROP_EVENT;
459 case cc::InputHandler::ScrollStatusCount:
460 NOTREACHED();
461 break;
463 return DID_NOT_HANDLE;
466 InputHandlerProxy::EventDisposition
467 InputHandlerProxy::HandleGestureScrollUpdate(
468 const WebGestureEvent& gesture_event) {
469 #ifndef NDEBUG
470 DCHECK(expect_scroll_update_end_);
471 #endif
473 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_)
474 return DID_NOT_HANDLE;
476 gfx::Point scroll_point(gesture_event.x, gesture_event.y);
477 gfx::Vector2dF scroll_delta(-gesture_event.data.scrollUpdate.deltaX,
478 -gesture_event.data.scrollUpdate.deltaY);
479 cc::InputHandlerScrollResult scroll_result = input_handler_->ScrollBy(
480 scroll_point, scroll_delta);
481 HandleOverscroll(scroll_point, scroll_result);
482 return scroll_result.did_scroll ? DID_HANDLE : DROP_EVENT;
485 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureScrollEnd(
486 const WebGestureEvent& gesture_event) {
487 #ifndef NDEBUG
488 DCHECK(expect_scroll_update_end_);
489 expect_scroll_update_end_ = false;
490 #endif
491 input_handler_->ScrollEnd();
492 if (!gesture_scroll_on_impl_thread_)
493 return DID_NOT_HANDLE;
494 gesture_scroll_on_impl_thread_ = false;
495 return DID_HANDLE;
498 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleGestureFlingStart(
499 const WebGestureEvent& gesture_event) {
500 cc::InputHandler::ScrollStatus scroll_status;
502 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
503 if (gesture_event.data.flingStart.targetViewport) {
504 scroll_status = input_handler_->RootScrollBegin(
505 cc::InputHandler::NON_BUBBLING_GESTURE);
506 } else {
507 scroll_status = input_handler_->ScrollBegin(
508 gfx::Point(gesture_event.x, gesture_event.y),
509 cc::InputHandler::NON_BUBBLING_GESTURE);
511 } else {
512 if (!gesture_scroll_on_impl_thread_)
513 scroll_status = cc::InputHandler::SCROLL_ON_MAIN_THREAD;
514 else
515 scroll_status = input_handler_->FlingScrollBegin();
518 #ifndef NDEBUG
519 expect_scroll_update_end_ = false;
520 #endif
522 switch (scroll_status) {
523 case cc::InputHandler::SCROLL_STARTED: {
524 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad)
525 input_handler_->ScrollEnd();
527 const float vx = gesture_event.data.flingStart.velocityX;
528 const float vy = gesture_event.data.flingStart.velocityY;
529 current_fling_velocity_ = gfx::Vector2dF(vx, vy);
530 DCHECK(!current_fling_velocity_.IsZero());
531 fling_curve_.reset(client_->CreateFlingAnimationCurve(
532 gesture_event.sourceDevice,
533 WebFloatPoint(vx, vy),
534 blink::WebSize()));
535 disallow_horizontal_fling_scroll_ = !vx;
536 disallow_vertical_fling_scroll_ = !vy;
537 TRACE_EVENT_ASYNC_BEGIN2("input",
538 "InputHandlerProxy::HandleGestureFling::started",
539 this,
540 "vx",
542 "vy",
543 vy);
544 // Note that the timestamp will only be used to kickstart the animation if
545 // its sufficiently close to the timestamp of the first call |Animate()|.
546 has_fling_animation_started_ = false;
547 fling_parameters_.startTime = gesture_event.timeStampSeconds;
548 fling_parameters_.delta = WebFloatPoint(vx, vy);
549 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
550 fling_parameters_.globalPoint =
551 WebPoint(gesture_event.globalX, gesture_event.globalY);
552 fling_parameters_.modifiers = gesture_event.modifiers;
553 fling_parameters_.sourceDevice = gesture_event.sourceDevice;
554 input_handler_->SetNeedsAnimateInput();
555 return DID_HANDLE;
557 case cc::InputHandler::SCROLL_UNKNOWN:
558 case cc::InputHandler::SCROLL_ON_MAIN_THREAD: {
559 TRACE_EVENT_INSTANT0("input",
560 "InputHandlerProxy::HandleGestureFling::"
561 "scroll_on_main_thread",
562 TRACE_EVENT_SCOPE_THREAD);
563 gesture_scroll_on_impl_thread_ = false;
564 fling_may_be_active_on_main_thread_ = true;
565 return DID_NOT_HANDLE;
567 case cc::InputHandler::SCROLL_IGNORED: {
568 TRACE_EVENT_INSTANT0(
569 "input",
570 "InputHandlerProxy::HandleGestureFling::ignored",
571 TRACE_EVENT_SCOPE_THREAD);
572 gesture_scroll_on_impl_thread_ = false;
573 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
574 // We still pass the curve to the main thread if there's nothing
575 // scrollable, in case something
576 // registers a handler before the curve is over.
577 return DID_NOT_HANDLE;
579 return DROP_EVENT;
581 case cc::InputHandler::ScrollStatusCount:
582 NOTREACHED();
583 break;
585 return DID_NOT_HANDLE;
588 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleTouchStart(
589 const blink::WebTouchEvent& touch_event) {
590 for (size_t i = 0; i < touch_event.touchesLength; ++i) {
591 if (touch_event.touches[i].state != WebTouchPoint::StatePressed)
592 continue;
593 if (input_handler_->DoTouchEventsBlockScrollAt(
594 gfx::Point(touch_event.touches[i].position.x,
595 touch_event.touches[i].position.y))) {
596 // TODO(rbyers): We should consider still sending the touch events to
597 // main asynchronously (crbug.com/455539).
598 return DID_NOT_HANDLE;
601 return DROP_EVENT;
604 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
605 const WebInputEvent& event) {
606 if (!WebInputEvent::isGestureEventType(event.type))
607 return false;
609 if (!fling_curve_) {
610 DCHECK(!deferred_fling_cancel_time_seconds_);
611 return false;
614 const WebGestureEvent& gesture_event =
615 static_cast<const WebGestureEvent&>(event);
616 if (gesture_event.type == WebInputEvent::GestureFlingCancel) {
617 if (gesture_event.data.flingCancel.preventBoosting)
618 return false;
620 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
621 return false;
623 TRACE_EVENT_INSTANT0("input",
624 "InputHandlerProxy::FlingBoostStart",
625 TRACE_EVENT_SCOPE_THREAD);
626 deferred_fling_cancel_time_seconds_ =
627 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
628 return true;
631 // A fling is either inactive or is "free spinning", i.e., has yet to be
632 // interrupted by a touch gesture, in which case there is nothing to filter.
633 if (!deferred_fling_cancel_time_seconds_)
634 return false;
636 // Gestures from a different source should immediately interrupt the fling.
637 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
638 CancelCurrentFling();
639 return false;
642 switch (gesture_event.type) {
643 case WebInputEvent::GestureTapCancel:
644 case WebInputEvent::GestureTapDown:
645 return false;
647 case WebInputEvent::GestureScrollBegin:
648 if (!input_handler_->IsCurrentlyScrollingLayerAt(
649 gfx::Point(gesture_event.x, gesture_event.y),
650 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad
651 ? cc::InputHandler::NON_BUBBLING_GESTURE
652 : cc::InputHandler::GESTURE)) {
653 CancelCurrentFling();
654 return false;
657 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
658 // determine if the ScrollBegin should immediately cancel the fling.
659 ExtendBoostedFlingTimeout(gesture_event);
660 return true;
662 case WebInputEvent::GestureScrollUpdate: {
663 const double time_since_last_boost_event =
664 event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds;
665 const double time_since_last_fling_animate = std::max(
666 0.0, event.timeStampSeconds - InSecondsF(last_fling_animate_time_));
667 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_,
668 gesture_event,
669 time_since_last_boost_event,
670 time_since_last_fling_animate)) {
671 ExtendBoostedFlingTimeout(gesture_event);
672 return true;
675 CancelCurrentFling();
676 return false;
679 case WebInputEvent::GestureScrollEnd:
680 // Clear the last fling boost event *prior* to fling cancellation,
681 // preventing insertion of a synthetic GestureScrollBegin.
682 last_fling_boost_event_ = WebGestureEvent();
683 CancelCurrentFling();
684 return true;
686 case WebInputEvent::GestureFlingStart: {
687 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
689 bool fling_boosted =
690 fling_parameters_.modifiers == gesture_event.modifiers &&
691 ShouldBoostFling(current_fling_velocity_, gesture_event);
693 gfx::Vector2dF new_fling_velocity(
694 gesture_event.data.flingStart.velocityX,
695 gesture_event.data.flingStart.velocityY);
696 DCHECK(!new_fling_velocity.IsZero());
698 if (fling_boosted)
699 current_fling_velocity_ += new_fling_velocity;
700 else
701 current_fling_velocity_ = new_fling_velocity;
703 WebFloatPoint velocity(current_fling_velocity_.x(),
704 current_fling_velocity_.y());
705 deferred_fling_cancel_time_seconds_ = 0;
706 disallow_horizontal_fling_scroll_ = !velocity.x;
707 disallow_vertical_fling_scroll_ = !velocity.y;
708 last_fling_boost_event_ = WebGestureEvent();
709 fling_curve_.reset(client_->CreateFlingAnimationCurve(
710 gesture_event.sourceDevice,
711 velocity,
712 blink::WebSize()));
713 fling_parameters_.startTime = gesture_event.timeStampSeconds;
714 fling_parameters_.delta = velocity;
715 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
716 fling_parameters_.globalPoint =
717 WebPoint(gesture_event.globalX, gesture_event.globalY);
719 TRACE_EVENT_INSTANT2("input",
720 fling_boosted ? "InputHandlerProxy::FlingBoosted"
721 : "InputHandlerProxy::FlingReplaced",
722 TRACE_EVENT_SCOPE_THREAD,
723 "vx",
724 current_fling_velocity_.x(),
725 "vy",
726 current_fling_velocity_.y());
728 // The client expects balanced calls between a consumed GestureFlingStart
729 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
730 // |DidStopFlinging()| and only send after the accumulated fling ends.
731 client_->DidStopFlinging();
732 return true;
735 default:
736 // All other types of gestures (taps, presses, etc...) will complete the
737 // deferred fling cancellation.
738 CancelCurrentFling();
739 return false;
743 void InputHandlerProxy::ExtendBoostedFlingTimeout(
744 const blink::WebGestureEvent& event) {
745 TRACE_EVENT_INSTANT0("input",
746 "InputHandlerProxy::ExtendBoostedFlingTimeout",
747 TRACE_EVENT_SCOPE_THREAD);
748 deferred_fling_cancel_time_seconds_ =
749 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
750 last_fling_boost_event_ = event;
753 void InputHandlerProxy::Animate(base::TimeTicks time) {
754 if (scroll_elasticity_controller_)
755 scroll_elasticity_controller_->Animate(time);
757 if (!fling_curve_)
758 return;
760 last_fling_animate_time_ = time;
761 double monotonic_time_sec = InSecondsF(time);
763 if (deferred_fling_cancel_time_seconds_ &&
764 monotonic_time_sec > deferred_fling_cancel_time_seconds_) {
765 CancelCurrentFling();
766 return;
769 client_->DidAnimateForInput();
771 if (!has_fling_animation_started_) {
772 has_fling_animation_started_ = true;
773 // Guard against invalid, future or sufficiently stale start times, as there
774 // are no guarantees fling event and animation timestamps are compatible.
775 if (!fling_parameters_.startTime ||
776 monotonic_time_sec <= fling_parameters_.startTime ||
777 monotonic_time_sec >= fling_parameters_.startTime +
778 kMaxSecondsFromFlingTimestampToFirstAnimate) {
779 fling_parameters_.startTime = monotonic_time_sec;
780 input_handler_->SetNeedsAnimateInput();
781 return;
785 bool fling_is_active =
786 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
787 this);
789 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
790 fling_is_active = false;
792 if (fling_is_active) {
793 input_handler_->SetNeedsAnimateInput();
794 } else {
795 TRACE_EVENT_INSTANT0("input",
796 "InputHandlerProxy::animate::flingOver",
797 TRACE_EVENT_SCOPE_THREAD);
798 CancelCurrentFling();
802 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
803 fling_may_be_active_on_main_thread_ = false;
804 client_->DidStopFlinging();
807 void InputHandlerProxy::ReconcileElasticOverscrollAndRootScroll() {
808 if (scroll_elasticity_controller_)
809 scroll_elasticity_controller_->ReconcileStretchAndScroll();
812 void InputHandlerProxy::HandleOverscroll(
813 const gfx::Point& causal_event_viewport_point,
814 const cc::InputHandlerScrollResult& scroll_result) {
815 DCHECK(client_);
816 if (!scroll_result.did_overscroll_root)
817 return;
819 TRACE_EVENT2("input",
820 "InputHandlerProxy::DidOverscroll",
821 "dx",
822 scroll_result.unused_scroll_delta.x(),
823 "dy",
824 scroll_result.unused_scroll_delta.y());
826 DidOverscrollParams params;
827 params.accumulated_overscroll = scroll_result.accumulated_root_overscroll;
828 params.latest_overscroll_delta = scroll_result.unused_scroll_delta;
829 params.current_fling_velocity =
830 ToClientScrollIncrement(current_fling_velocity_);
831 params.causal_event_viewport_point = causal_event_viewport_point;
833 if (fling_curve_) {
834 static const int kFlingOverscrollThreshold = 1;
835 disallow_horizontal_fling_scroll_ |=
836 std::abs(params.accumulated_overscroll.x()) >=
837 kFlingOverscrollThreshold;
838 disallow_vertical_fling_scroll_ |=
839 std::abs(params.accumulated_overscroll.y()) >=
840 kFlingOverscrollThreshold;
843 client_->DidOverscroll(params);
846 bool InputHandlerProxy::CancelCurrentFling() {
847 if (CancelCurrentFlingWithoutNotifyingClient()) {
848 client_->DidStopFlinging();
849 return true;
851 return false;
854 bool InputHandlerProxy::CancelCurrentFlingWithoutNotifyingClient() {
855 bool had_fling_animation = fling_curve_;
856 if (had_fling_animation &&
857 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchscreen) {
858 input_handler_->ScrollEnd();
859 TRACE_EVENT_ASYNC_END0(
860 "input",
861 "InputHandlerProxy::HandleGestureFling::started",
862 this);
865 TRACE_EVENT_INSTANT1("input",
866 "InputHandlerProxy::CancelCurrentFling",
867 TRACE_EVENT_SCOPE_THREAD,
868 "had_fling_animation",
869 had_fling_animation);
870 fling_curve_.reset();
871 has_fling_animation_started_ = false;
872 gesture_scroll_on_impl_thread_ = false;
873 current_fling_velocity_ = gfx::Vector2dF();
874 fling_parameters_ = blink::WebActiveWheelFlingParameters();
876 if (deferred_fling_cancel_time_seconds_) {
877 deferred_fling_cancel_time_seconds_ = 0;
879 WebGestureEvent last_fling_boost_event = last_fling_boost_event_;
880 last_fling_boost_event_ = WebGestureEvent();
881 if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin ||
882 last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) {
883 // Synthesize a GestureScrollBegin, as the original was suppressed.
884 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event));
888 return had_fling_animation;
891 bool InputHandlerProxy::TouchpadFlingScroll(
892 const WebFloatSize& increment) {
893 WebMouseWheelEvent synthetic_wheel;
894 synthetic_wheel.type = WebInputEvent::MouseWheel;
895 synthetic_wheel.deltaX = increment.width;
896 synthetic_wheel.deltaY = increment.height;
897 synthetic_wheel.hasPreciseScrollingDeltas = true;
898 synthetic_wheel.x = fling_parameters_.point.x;
899 synthetic_wheel.y = fling_parameters_.point.y;
900 synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
901 synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
902 synthetic_wheel.modifiers = fling_parameters_.modifiers;
904 InputHandlerProxy::EventDisposition disposition =
905 HandleInputEvent(synthetic_wheel);
906 switch (disposition) {
907 case DID_HANDLE:
908 return true;
909 case DROP_EVENT:
910 break;
911 case DID_NOT_HANDLE:
912 TRACE_EVENT_INSTANT0("input",
913 "InputHandlerProxy::scrollBy::AbortFling",
914 TRACE_EVENT_SCOPE_THREAD);
915 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
916 // main thread. In this case we need to schedule a commit and transfer the
917 // fling curve over to the main thread and run the rest of the wheels from
918 // there. This can happen when flinging a page that contains a scrollable
919 // subarea that we can't scroll on the thread if the fling starts outside
920 // the subarea but then is flung "under" the pointer.
921 client_->TransferActiveWheelFlingAnimation(fling_parameters_);
922 fling_may_be_active_on_main_thread_ = true;
923 CancelCurrentFlingWithoutNotifyingClient();
924 break;
927 return false;
930 bool InputHandlerProxy::scrollBy(const WebFloatSize& increment,
931 const WebFloatSize& velocity) {
932 WebFloatSize clipped_increment;
933 WebFloatSize clipped_velocity;
934 if (!disallow_horizontal_fling_scroll_) {
935 clipped_increment.width = increment.width;
936 clipped_velocity.width = velocity.width;
938 if (!disallow_vertical_fling_scroll_) {
939 clipped_increment.height = increment.height;
940 clipped_velocity.height = velocity.height;
943 current_fling_velocity_ = clipped_velocity;
945 // Early out if the increment is zero, but avoid early terimination if the
946 // velocity is still non-zero.
947 if (clipped_increment == WebFloatSize())
948 return clipped_velocity != WebFloatSize();
950 TRACE_EVENT2("input",
951 "InputHandlerProxy::scrollBy",
952 "x",
953 clipped_increment.width,
954 "y",
955 clipped_increment.height);
957 bool did_scroll = false;
959 switch (fling_parameters_.sourceDevice) {
960 case blink::WebGestureDeviceTouchpad:
961 did_scroll = TouchpadFlingScroll(clipped_increment);
962 break;
963 case blink::WebGestureDeviceTouchscreen: {
964 clipped_increment = ToClientScrollIncrement(clipped_increment);
965 cc::InputHandlerScrollResult scroll_result = input_handler_->ScrollBy(
966 fling_parameters_.point, clipped_increment);
967 HandleOverscroll(fling_parameters_.point, scroll_result);
968 did_scroll = scroll_result.did_scroll;
969 } break;
972 if (did_scroll) {
973 fling_parameters_.cumulativeScroll.width += clipped_increment.width;
974 fling_parameters_.cumulativeScroll.height += clipped_increment.height;
977 // It's possible the provided |increment| is sufficiently small as to not
978 // trigger a scroll, e.g., with a trivial time delta between fling updates.
979 // Return true in this case to prevent early fling termination.
980 if (std::abs(clipped_increment.width) < kScrollEpsilon &&
981 std::abs(clipped_increment.height) < kScrollEpsilon)
982 return true;
984 return did_scroll;
987 } // namespace content