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.
4
5 #include "content/renderer/input/input_handler_proxy.h"
6
7 #include "base/auto_reset.h"
8 #include "base/debug/trace_event.h"
9 #include "base/logging.h"
10 #include "base/metrics/histogram.h"
11 #include "content/common/input/did_overscroll_params.h"
12 #include "content/common/input/web_input_event_traits.h"
13 #include "content/renderer/input/input_handler_proxy_client.h"
14 #include "third_party/WebKit/public/platform/Platform.h"
15 #include "third_party/WebKit/public/web/WebInputEvent.h"
16 #include "ui/events/latency_info.h"
17 #include "ui/gfx/frame_time.h"
18 #include "ui/gfx/geometry/point_conversions.h"
19
20 using blink::WebFloatPoint;
21 using blink::WebFloatSize;
22 using blink::WebGestureEvent;
23 using blink::WebInputEvent;
24 using blink::WebMouseEvent;
25 using blink::WebMouseWheelEvent;
26 using blink::WebPoint;
27 using blink::WebTouchEvent;
28 using blink::WebTouchPoint;
29
30 namespace {
31
32 // Maximum time between a fling event's timestamp and the first |Animate| call
33 // for the fling curve to use the fling timestamp as the initial animation time.
34 // Two frames allows a minor delay between event creation and the first animate.
35 const double kMaxSecondsFromFlingTimestampToFirstAnimate = 2. / 60.;
36
37 // Threshold for determining whether a fling scroll delta should have caused the
38 // client to scroll.
39 const float kScrollEpsilon = 0.1f;
40
41 // Minimum fling velocity required for the active fling and new fling for the
42 // two to accumulate.
43 const double kMinBoostFlingSpeedSquare = 350. * 350.;
44
45 // Minimum velocity for the active touch scroll to preserve (boost) an active
46 // fling for which cancellation has been deferred.
47 const double kMinBoostTouchScrollSpeedSquare = 150 * 150.;
48
49 // Timeout window after which the active fling will be cancelled if no scrolls
50 // or flings of sufficient velocity relative to the current fling are received.
51 // The default value on Android native views is 40ms, but we use a slightly
52 // increased value to accomodate small IPC message delays.
53 const double kFlingBoostTimeoutDelaySeconds = 0.045;
54
ToClientScrollIncrement(const WebFloatSize & increment)55 gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) {
56 return gfx::Vector2dF(-increment.width, -increment.height);
57 }
58
InSecondsF(const base::TimeTicks & time)59 double InSecondsF(const base::TimeTicks& time) {
60 return (time - base::TimeTicks()).InSecondsF();
61 }
62
ShouldSuppressScrollForFlingBoosting(const gfx::Vector2dF & current_fling_velocity,const WebGestureEvent & scroll_update_event,double time_since_last_boost_event)63 bool ShouldSuppressScrollForFlingBoosting(
64 const gfx::Vector2dF& current_fling_velocity,
65 const WebGestureEvent& scroll_update_event,
66 double time_since_last_boost_event) {
67 DCHECK_EQ(WebInputEvent::GestureScrollUpdate, scroll_update_event.type);
68
69 gfx::Vector2dF dx(scroll_update_event.data.scrollUpdate.deltaX,
70 scroll_update_event.data.scrollUpdate.deltaY);
71 if (gfx::DotProduct(current_fling_velocity, dx) < 0)
72 return false;
73
74 if (time_since_last_boost_event < 0.001)
75 return true;
76
77 // TODO(jdduke): Use |scroll_update_event.data.scrollUpdate.velocity{X,Y}|.
78 // The scroll must be of sufficient velocity to maintain the active fling.
79 const gfx::Vector2dF scroll_velocity =
80 gfx::ScaleVector2d(dx, 1. / time_since_last_boost_event);
81 if (scroll_velocity.LengthSquared() < kMinBoostTouchScrollSpeedSquare)
82 return false;
83
84 return true;
85 }
86
ShouldBoostFling(const gfx::Vector2dF & current_fling_velocity,const WebGestureEvent & fling_start_event)87 bool ShouldBoostFling(const gfx::Vector2dF& current_fling_velocity,
88 const WebGestureEvent& fling_start_event) {
89 DCHECK_EQ(WebInputEvent::GestureFlingStart, fling_start_event.type);
90
91 gfx::Vector2dF new_fling_velocity(
92 fling_start_event.data.flingStart.velocityX,
93 fling_start_event.data.flingStart.velocityY);
94
95 if (gfx::DotProduct(current_fling_velocity, new_fling_velocity) < 0)
96 return false;
97
98 if (current_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
99 return false;
100
101 if (new_fling_velocity.LengthSquared() < kMinBoostFlingSpeedSquare)
102 return false;
103
104 return true;
105 }
106
ObtainGestureScrollBegin(const WebGestureEvent & event)107 WebGestureEvent ObtainGestureScrollBegin(const WebGestureEvent& event) {
108 WebGestureEvent scroll_begin_event = event;
109 scroll_begin_event.type = WebInputEvent::GestureScrollBegin;
110 scroll_begin_event.data.scrollBegin.deltaXHint = 0;
111 scroll_begin_event.data.scrollBegin.deltaYHint = 0;
112 return scroll_begin_event;
113 }
114
SendScrollLatencyUma(const WebInputEvent & event,const ui::LatencyInfo & latency_info)115 void SendScrollLatencyUma(const WebInputEvent& event,
116 const ui::LatencyInfo& latency_info) {
117 if (!(event.type == WebInputEvent::GestureScrollBegin ||
118 event.type == WebInputEvent::GestureScrollUpdate ||
119 event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation))
120 return;
121
122 ui::LatencyInfo::LatencyMap::const_iterator it =
123 latency_info.latency_components.find(std::make_pair(
124 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0));
125
126 if (it == latency_info.latency_components.end())
127 return;
128
129 base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time;
130 for (size_t i = 0; i < it->second.event_count; ++i) {
131 UMA_HISTOGRAM_CUSTOM_COUNTS(
132 "Event.Latency.RendererImpl.GestureScroll2",
133 delta.InMicroseconds(),
134 1,
135 1000000,
136 100);
137 }
138 } // namespace
139
140 }
141
142 namespace content {
143
InputHandlerProxy(cc::InputHandler * input_handler,InputHandlerProxyClient * client)144 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler,
145 InputHandlerProxyClient* client)
146 : client_(client),
147 input_handler_(input_handler),
148 deferred_fling_cancel_time_seconds_(0),
149 #ifndef NDEBUG
150 expect_scroll_update_end_(false),
151 #endif
152 gesture_scroll_on_impl_thread_(false),
153 gesture_pinch_on_impl_thread_(false),
154 fling_may_be_active_on_main_thread_(false),
155 disallow_horizontal_fling_scroll_(false),
156 disallow_vertical_fling_scroll_(false),
157 has_fling_animation_started_(false) {
158 DCHECK(client);
159 input_handler_->BindToClient(this);
160 }
161
~InputHandlerProxy()162 InputHandlerProxy::~InputHandlerProxy() {}
163
WillShutdown()164 void InputHandlerProxy::WillShutdown() {
165 input_handler_ = NULL;
166 client_->WillShutdown();
167 }
168
169 InputHandlerProxy::EventDisposition
HandleInputEventWithLatencyInfo(const WebInputEvent & event,ui::LatencyInfo * latency_info)170 InputHandlerProxy::HandleInputEventWithLatencyInfo(
171 const WebInputEvent& event,
172 ui::LatencyInfo* latency_info) {
173 DCHECK(input_handler_);
174
175 SendScrollLatencyUma(event, *latency_info);
176
177 TRACE_EVENT_FLOW_STEP0(
178 "input",
179 "LatencyInfo.Flow",
180 TRACE_ID_DONT_MANGLE(latency_info->trace_id),
181 "HanldeInputEventImpl");
182
183 scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
184 input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info);
185 InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event);
186 return disposition;
187 }
188
HandleInputEvent(const WebInputEvent & event)189 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
190 const WebInputEvent& event) {
191 DCHECK(input_handler_);
192 TRACE_EVENT1("input", "InputHandlerProxy::HandleInputEvent",
193 "type", WebInputEventTraits::GetName(event.type));
194
195 if (FilterInputEventForFlingBoosting(event))
196 return DID_HANDLE;
197
198 if (event.type == WebInputEvent::MouseWheel) {
199 const WebMouseWheelEvent& wheel_event =
200 *static_cast<const WebMouseWheelEvent*>(&event);
201 if (wheel_event.scrollByPage) {
202 // TODO(jamesr): We don't properly handle scroll by page in the compositor
203 // thread, so punt it to the main thread. http://crbug.com/236639
204 return DID_NOT_HANDLE;
205 }
206 if (wheel_event.modifiers & WebInputEvent::ControlKey) {
207 // Wheel events involving the control key never trigger scrolling, only
208 // event handlers. Forward to the main thread.
209 return DID_NOT_HANDLE;
210 }
211 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
212 gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel);
213 switch (scroll_status) {
214 case cc::InputHandler::ScrollStarted: {
215 TRACE_EVENT_INSTANT2(
216 "input",
217 "InputHandlerProxy::handle_input wheel scroll",
218 TRACE_EVENT_SCOPE_THREAD,
219 "deltaX",
220 -wheel_event.deltaX,
221 "deltaY",
222 -wheel_event.deltaY);
223 bool did_scroll = input_handler_->ScrollBy(
224 gfx::Point(wheel_event.x, wheel_event.y),
225 gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
226 input_handler_->ScrollEnd();
227 return did_scroll ? DID_HANDLE : DROP_EVENT;
228 }
229 case cc::InputHandler::ScrollIgnored:
230 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
231 // to properly sync scrollability it's safer to send the event to the
232 // main thread. Change back to DROP_EVENT once we have synchronization
233 // bugs sorted out.
234 return DID_NOT_HANDLE;
235 case cc::InputHandler::ScrollUnknown:
236 case cc::InputHandler::ScrollOnMainThread:
237 return DID_NOT_HANDLE;
238 case cc::InputHandler::ScrollStatusCount:
239 NOTREACHED();
240 break;
241 }
242 } else if (event.type == WebInputEvent::GestureScrollBegin) {
243 DCHECK(!gesture_scroll_on_impl_thread_);
244 #ifndef NDEBUG
245 DCHECK(!expect_scroll_update_end_);
246 expect_scroll_update_end_ = true;
247 #endif
248 const WebGestureEvent& gesture_event =
249 *static_cast<const WebGestureEvent*>(&event);
250 cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
251 gfx::Point(gesture_event.x, gesture_event.y),
252 cc::InputHandler::Gesture);
253 UMA_HISTOGRAM_ENUMERATION("Renderer4.CompositorScrollHitTestResult",
254 scroll_status,
255 cc::InputHandler::ScrollStatusCount);
256 switch (scroll_status) {
257 case cc::InputHandler::ScrollStarted:
258 TRACE_EVENT_INSTANT0("input",
259 "InputHandlerProxy::handle_input gesture scroll",
260 TRACE_EVENT_SCOPE_THREAD);
261 gesture_scroll_on_impl_thread_ = true;
262 return DID_HANDLE;
263 case cc::InputHandler::ScrollUnknown:
264 case cc::InputHandler::ScrollOnMainThread:
265 return DID_NOT_HANDLE;
266 case cc::InputHandler::ScrollIgnored:
267 return DROP_EVENT;
268 case cc::InputHandler::ScrollStatusCount:
269 NOTREACHED();
270 break;
271 }
272 } else if (event.type == WebInputEvent::GestureScrollUpdate) {
273 #ifndef NDEBUG
274 DCHECK(expect_scroll_update_end_);
275 #endif
276
277 if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_)
278 return DID_NOT_HANDLE;
279
280 const WebGestureEvent& gesture_event =
281 *static_cast<const WebGestureEvent*>(&event);
282 bool did_scroll = input_handler_->ScrollBy(
283 gfx::Point(gesture_event.x, gesture_event.y),
284 gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX,
285 -gesture_event.data.scrollUpdate.deltaY));
286 return did_scroll ? DID_HANDLE : DROP_EVENT;
287 } else if (event.type == WebInputEvent::GestureScrollEnd) {
288 #ifndef NDEBUG
289 DCHECK(expect_scroll_update_end_);
290 expect_scroll_update_end_ = false;
291 #endif
292 input_handler_->ScrollEnd();
293
294 if (!gesture_scroll_on_impl_thread_)
295 return DID_NOT_HANDLE;
296
297 gesture_scroll_on_impl_thread_ = false;
298 return DID_HANDLE;
299 } else if (event.type == WebInputEvent::GesturePinchBegin) {
300 input_handler_->PinchGestureBegin();
301 DCHECK(!gesture_pinch_on_impl_thread_);
302 gesture_pinch_on_impl_thread_ = true;
303 return DID_HANDLE;
304 } else if (event.type == WebInputEvent::GesturePinchEnd) {
305 DCHECK(gesture_pinch_on_impl_thread_);
306 gesture_pinch_on_impl_thread_ = false;
307 input_handler_->PinchGestureEnd();
308 return DID_HANDLE;
309 } else if (event.type == WebInputEvent::GesturePinchUpdate) {
310 DCHECK(gesture_pinch_on_impl_thread_);
311 const WebGestureEvent& gesture_event =
312 *static_cast<const WebGestureEvent*>(&event);
313 input_handler_->PinchGestureUpdate(
314 gesture_event.data.pinchUpdate.scale,
315 gfx::Point(gesture_event.x, gesture_event.y));
316 return DID_HANDLE;
317 } else if (event.type == WebInputEvent::GestureFlingStart) {
318 const WebGestureEvent& gesture_event =
319 *static_cast<const WebGestureEvent*>(&event);
320 return HandleGestureFling(gesture_event);
321 } else if (event.type == WebInputEvent::GestureFlingCancel) {
322 if (CancelCurrentFling(true))
323 return DID_HANDLE;
324 else if (!fling_may_be_active_on_main_thread_)
325 return DROP_EVENT;
326 } else if (event.type == WebInputEvent::TouchStart) {
327 const WebTouchEvent& touch_event =
328 *static_cast<const WebTouchEvent*>(&event);
329 for (size_t i = 0; i < touch_event.touchesLength; ++i) {
330 if (touch_event.touches[i].state != WebTouchPoint::StatePressed)
331 continue;
332 if (input_handler_->HaveTouchEventHandlersAt(
333 gfx::Point(touch_event.touches[i].position.x,
334 touch_event.touches[i].position.y))) {
335 return DID_NOT_HANDLE;
336 }
337 }
338 return DROP_EVENT;
339 } else if (WebInputEvent::isKeyboardEventType(event.type)) {
340 // Only call |CancelCurrentFling()| if a fling was active, as it will
341 // otherwise disrupt an in-progress touch scroll.
342 if (fling_curve_)
343 CancelCurrentFling(true);
344 } else if (event.type == WebInputEvent::MouseMove) {
345 const WebMouseEvent& mouse_event =
346 *static_cast<const WebMouseEvent*>(&event);
347 // TODO(tony): Ignore when mouse buttons are down?
348 // TODO(davemoore): This should never happen, but bug #326635 showed some
349 // surprising crashes.
350 CHECK(input_handler_);
351 input_handler_->MouseMoveAt(gfx::Point(mouse_event.x, mouse_event.y));
352 }
353
354 return DID_NOT_HANDLE;
355 }
356
357 InputHandlerProxy::EventDisposition
HandleGestureFling(const WebGestureEvent & gesture_event)358 InputHandlerProxy::HandleGestureFling(
359 const WebGestureEvent& gesture_event) {
360 cc::InputHandler::ScrollStatus scroll_status;
361
362 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
363 scroll_status = input_handler_->ScrollBegin(
364 gfx::Point(gesture_event.x, gesture_event.y),
365 cc::InputHandler::NonBubblingGesture);
366 } else {
367 if (!gesture_scroll_on_impl_thread_)
368 scroll_status = cc::InputHandler::ScrollOnMainThread;
369 else
370 scroll_status = input_handler_->FlingScrollBegin();
371 }
372
373 #ifndef NDEBUG
374 expect_scroll_update_end_ = false;
375 #endif
376
377 switch (scroll_status) {
378 case cc::InputHandler::ScrollStarted: {
379 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad)
380 input_handler_->ScrollEnd();
381
382 const float vx = gesture_event.data.flingStart.velocityX;
383 const float vy = gesture_event.data.flingStart.velocityY;
384 current_fling_velocity_ = gfx::Vector2dF(vx, vy);
385 fling_curve_.reset(client_->CreateFlingAnimationCurve(
386 gesture_event.sourceDevice,
387 WebFloatPoint(vx, vy),
388 blink::WebSize()));
389 disallow_horizontal_fling_scroll_ = !vx;
390 disallow_vertical_fling_scroll_ = !vy;
391 TRACE_EVENT_ASYNC_BEGIN2("input",
392 "InputHandlerProxy::HandleGestureFling::started",
393 this,
394 "vx",
395 vx,
396 "vy",
397 vy);
398 // Note that the timestamp will only be used to kickstart the animation if
399 // its sufficiently close to the timestamp of the first call |Animate()|.
400 has_fling_animation_started_ = false;
401 fling_parameters_.startTime = gesture_event.timeStampSeconds;
402 fling_parameters_.delta = WebFloatPoint(vx, vy);
403 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
404 fling_parameters_.globalPoint =
405 WebPoint(gesture_event.globalX, gesture_event.globalY);
406 fling_parameters_.modifiers = gesture_event.modifiers;
407 fling_parameters_.sourceDevice = gesture_event.sourceDevice;
408 input_handler_->SetNeedsAnimate();
409 return DID_HANDLE;
410 }
411 case cc::InputHandler::ScrollUnknown:
412 case cc::InputHandler::ScrollOnMainThread: {
413 TRACE_EVENT_INSTANT0("input",
414 "InputHandlerProxy::HandleGestureFling::"
415 "scroll_on_main_thread",
416 TRACE_EVENT_SCOPE_THREAD);
417 fling_may_be_active_on_main_thread_ = true;
418 return DID_NOT_HANDLE;
419 }
420 case cc::InputHandler::ScrollIgnored: {
421 TRACE_EVENT_INSTANT0(
422 "input",
423 "InputHandlerProxy::HandleGestureFling::ignored",
424 TRACE_EVENT_SCOPE_THREAD);
425 if (gesture_event.sourceDevice == blink::WebGestureDeviceTouchpad) {
426 // We still pass the curve to the main thread if there's nothing
427 // scrollable, in case something
428 // registers a handler before the curve is over.
429 return DID_NOT_HANDLE;
430 }
431 return DROP_EVENT;
432 }
433 case cc::InputHandler::ScrollStatusCount:
434 NOTREACHED();
435 break;
436 }
437 return DID_NOT_HANDLE;
438 }
439
FilterInputEventForFlingBoosting(const WebInputEvent & event)440 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
441 const WebInputEvent& event) {
442 if (!WebInputEvent::isGestureEventType(event.type))
443 return false;
444
445 if (!fling_curve_) {
446 DCHECK(!deferred_fling_cancel_time_seconds_);
447 return false;
448 }
449
450 const WebGestureEvent& gesture_event =
451 static_cast<const WebGestureEvent&>(event);
452 if (gesture_event.type == WebInputEvent::GestureFlingCancel) {
453 if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
454 return false;
455
456 TRACE_EVENT_INSTANT0("input",
457 "InputHandlerProxy::FlingBoostStart",
458 TRACE_EVENT_SCOPE_THREAD);
459 deferred_fling_cancel_time_seconds_ =
460 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
461 return true;
462 }
463
464 // A fling is either inactive or is "free spinning", i.e., has yet to be
465 // interrupted by a touch gesture, in which case there is nothing to filter.
466 if (!deferred_fling_cancel_time_seconds_)
467 return false;
468
469 // Gestures from a different source should immediately interrupt the fling.
470 if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
471 FlingBoostCancelAndResumeScrollingIfNecessary();
472 return false;
473 }
474
475 switch (gesture_event.type) {
476 case WebInputEvent::GestureTapCancel:
477 case WebInputEvent::GestureTapDown:
478 return false;
479
480 case WebInputEvent::GestureScrollBegin:
481 if (!input_handler_->IsCurrentlyScrollingLayerAt(
482 gfx::Point(gesture_event.x, gesture_event.y),
483 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchpad
484 ? cc::InputHandler::NonBubblingGesture
485 : cc::InputHandler::Gesture)) {
486 CancelCurrentFling(true);
487 return false;
488 }
489
490 // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
491 // determine if the ScrollBegin should immediately cancel the fling.
492 FlingBoostExtend(gesture_event);
493 return true;
494
495 case WebInputEvent::GestureScrollUpdate: {
496 const double time_since_last_boost_event =
497 event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds;
498 if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_,
499 gesture_event,
500 time_since_last_boost_event)) {
501 FlingBoostExtend(gesture_event);
502 return true;
503 }
504
505 FlingBoostCancelAndResumeScrollingIfNecessary();
506 return false;
507 }
508
509 case WebInputEvent::GestureScrollEnd:
510 CancelCurrentFling(true);
511 return true;
512
513 case WebInputEvent::GestureFlingStart: {
514 DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
515
516 bool fling_boosted =
517 fling_parameters_.modifiers == gesture_event.modifiers &&
518 ShouldBoostFling(current_fling_velocity_, gesture_event);
519
520 gfx::Vector2dF new_fling_velocity(
521 gesture_event.data.flingStart.velocityX,
522 gesture_event.data.flingStart.velocityY);
523
524 if (fling_boosted)
525 current_fling_velocity_ += new_fling_velocity;
526 else
527 current_fling_velocity_ = new_fling_velocity;
528
529 WebFloatPoint velocity(current_fling_velocity_.x(),
530 current_fling_velocity_.y());
531 deferred_fling_cancel_time_seconds_ = 0;
532 last_fling_boost_event_ = WebGestureEvent();
533 fling_curve_.reset(client_->CreateFlingAnimationCurve(
534 gesture_event.sourceDevice,
535 velocity,
536 blink::WebSize()));
537 fling_parameters_.startTime = gesture_event.timeStampSeconds;
538 fling_parameters_.delta = velocity;
539 fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
540 fling_parameters_.globalPoint =
541 WebPoint(gesture_event.globalX, gesture_event.globalY);
542
543 TRACE_EVENT_INSTANT2("input",
544 fling_boosted ? "InputHandlerProxy::FlingBoosted"
545 : "InputHandlerProxy::FlingReplaced",
546 TRACE_EVENT_SCOPE_THREAD,
547 "vx",
548 current_fling_velocity_.x(),
549 "vy",
550 current_fling_velocity_.y());
551
552 // The client expects balanced calls between a consumed GestureFlingStart
553 // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
554 // |DidStopFlinging()| and only send after the accumulated fling ends.
555 client_->DidStopFlinging();
556 return true;
557 }
558
559 default:
560 // All other types of gestures (taps, presses, etc...) will complete the
561 // deferred fling cancellation.
562 FlingBoostCancelAndResumeScrollingIfNecessary();
563 return false;
564 }
565 }
566
FlingBoostExtend(const blink::WebGestureEvent & event)567 void InputHandlerProxy::FlingBoostExtend(const blink::WebGestureEvent& event) {
568 TRACE_EVENT_INSTANT0(
569 "input", "InputHandlerProxy::FlingBoostExtend", TRACE_EVENT_SCOPE_THREAD);
570 deferred_fling_cancel_time_seconds_ =
571 event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
572 last_fling_boost_event_ = event;
573 }
574
FlingBoostCancelAndResumeScrollingIfNecessary()575 void InputHandlerProxy::FlingBoostCancelAndResumeScrollingIfNecessary() {
576 TRACE_EVENT_INSTANT0(
577 "input", "InputHandlerProxy::FlingBoostCancel", TRACE_EVENT_SCOPE_THREAD);
578 DCHECK(deferred_fling_cancel_time_seconds_);
579
580 // Note: |last_fling_boost_event_| is cleared by |CancelCurrentFling()|.
581 WebGestureEvent last_fling_boost_event = last_fling_boost_event_;
582
583 CancelCurrentFling(true);
584
585 if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin ||
586 last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) {
587 // Synthesize a GestureScrollBegin, as the original was suppressed.
588 HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event));
589 }
590 }
591
Animate(base::TimeTicks time)592 void InputHandlerProxy::Animate(base::TimeTicks time) {
593 if (!fling_curve_)
594 return;
595
596 double monotonic_time_sec = InSecondsF(time);
597
598 if (deferred_fling_cancel_time_seconds_ &&
599 monotonic_time_sec > deferred_fling_cancel_time_seconds_) {
600 FlingBoostCancelAndResumeScrollingIfNecessary();
601 return;
602 }
603
604 if (!has_fling_animation_started_) {
605 has_fling_animation_started_ = true;
606 // Guard against invalid, future or sufficiently stale start times, as there
607 // are no guarantees fling event and animation timestamps are compatible.
608 if (!fling_parameters_.startTime ||
609 monotonic_time_sec <= fling_parameters_.startTime ||
610 monotonic_time_sec >= fling_parameters_.startTime +
611 kMaxSecondsFromFlingTimestampToFirstAnimate) {
612 fling_parameters_.startTime = monotonic_time_sec;
613 input_handler_->SetNeedsAnimate();
614 return;
615 }
616 }
617
618 bool fling_is_active =
619 fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
620 this);
621
622 if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
623 fling_is_active = false;
624
625 if (fling_is_active) {
626 input_handler_->SetNeedsAnimate();
627 } else {
628 TRACE_EVENT_INSTANT0("input",
629 "InputHandlerProxy::animate::flingOver",
630 TRACE_EVENT_SCOPE_THREAD);
631 CancelCurrentFling(true);
632 }
633 }
634
MainThreadHasStoppedFlinging()635 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
636 fling_may_be_active_on_main_thread_ = false;
637 client_->DidStopFlinging();
638 }
639
DidOverscroll(const gfx::Vector2dF & accumulated_overscroll,const gfx::Vector2dF & latest_overscroll_delta)640 void InputHandlerProxy::DidOverscroll(
641 const gfx::Vector2dF& accumulated_overscroll,
642 const gfx::Vector2dF& latest_overscroll_delta) {
643 DCHECK(client_);
644
645 TRACE_EVENT2("input",
646 "InputHandlerProxy::DidOverscroll",
647 "dx",
648 latest_overscroll_delta.x(),
649 "dy",
650 latest_overscroll_delta.y());
651
652 DidOverscrollParams params;
653 params.accumulated_overscroll = accumulated_overscroll;
654 params.latest_overscroll_delta = latest_overscroll_delta;
655 params.current_fling_velocity =
656 ToClientScrollIncrement(current_fling_velocity_);
657
658 if (fling_curve_) {
659 static const int kFlingOverscrollThreshold = 1;
660 disallow_horizontal_fling_scroll_ |=
661 std::abs(params.accumulated_overscroll.x()) >=
662 kFlingOverscrollThreshold;
663 disallow_vertical_fling_scroll_ |=
664 std::abs(params.accumulated_overscroll.y()) >=
665 kFlingOverscrollThreshold;
666 }
667
668 client_->DidOverscroll(params);
669 }
670
CancelCurrentFling(bool send_fling_stopped_notification)671 bool InputHandlerProxy::CancelCurrentFling(
672 bool send_fling_stopped_notification) {
673 bool had_fling_animation = fling_curve_;
674 if (had_fling_animation &&
675 fling_parameters_.sourceDevice == blink::WebGestureDeviceTouchscreen) {
676 input_handler_->ScrollEnd();
677 TRACE_EVENT_ASYNC_END0(
678 "input",
679 "InputHandlerProxy::HandleGestureFling::started",
680 this);
681 }
682
683 TRACE_EVENT_INSTANT1("input",
684 "InputHandlerProxy::CancelCurrentFling",
685 TRACE_EVENT_SCOPE_THREAD,
686 "had_fling_animation",
687 had_fling_animation);
688 fling_curve_.reset();
689 has_fling_animation_started_ = false;
690 gesture_scroll_on_impl_thread_ = false;
691 current_fling_velocity_ = gfx::Vector2dF();
692 fling_parameters_ = blink::WebActiveWheelFlingParameters();
693 deferred_fling_cancel_time_seconds_ = 0;
694 last_fling_boost_event_ = WebGestureEvent();
695 if (send_fling_stopped_notification && had_fling_animation)
696 client_->DidStopFlinging();
697 return had_fling_animation;
698 }
699
TouchpadFlingScroll(const WebFloatSize & increment)700 bool InputHandlerProxy::TouchpadFlingScroll(
701 const WebFloatSize& increment) {
702 WebMouseWheelEvent synthetic_wheel;
703 synthetic_wheel.type = WebInputEvent::MouseWheel;
704 synthetic_wheel.deltaX = increment.width;
705 synthetic_wheel.deltaY = increment.height;
706 synthetic_wheel.hasPreciseScrollingDeltas = true;
707 synthetic_wheel.x = fling_parameters_.point.x;
708 synthetic_wheel.y = fling_parameters_.point.y;
709 synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
710 synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
711 synthetic_wheel.modifiers = fling_parameters_.modifiers;
712
713 InputHandlerProxy::EventDisposition disposition =
714 HandleInputEvent(synthetic_wheel);
715 switch (disposition) {
716 case DID_HANDLE:
717 return true;
718 case DROP_EVENT:
719 break;
720 case DID_NOT_HANDLE:
721 TRACE_EVENT_INSTANT0("input",
722 "InputHandlerProxy::scrollBy::AbortFling",
723 TRACE_EVENT_SCOPE_THREAD);
724 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
725 // main thread. In this case we need to schedule a commit and transfer the
726 // fling curve over to the main thread and run the rest of the wheels from
727 // there. This can happen when flinging a page that contains a scrollable
728 // subarea that we can't scroll on the thread if the fling starts outside
729 // the subarea but then is flung "under" the pointer.
730 client_->TransferActiveWheelFlingAnimation(fling_parameters_);
731 fling_may_be_active_on_main_thread_ = true;
732 CancelCurrentFling(false);
733 break;
734 }
735
736 return false;
737 }
738
scrollBy(const WebFloatSize & increment,const WebFloatSize & velocity)739 bool InputHandlerProxy::scrollBy(const WebFloatSize& increment,
740 const WebFloatSize& velocity) {
741 WebFloatSize clipped_increment;
742 WebFloatSize clipped_velocity;
743 if (!disallow_horizontal_fling_scroll_) {
744 clipped_increment.width = increment.width;
745 clipped_velocity.width = velocity.width;
746 }
747 if (!disallow_vertical_fling_scroll_) {
748 clipped_increment.height = increment.height;
749 clipped_velocity.height = velocity.height;
750 }
751
752 current_fling_velocity_ = clipped_velocity;
753
754 // Early out if the increment is zero, but avoid early terimination if the
755 // velocity is still non-zero.
756 if (clipped_increment == WebFloatSize())
757 return clipped_velocity != WebFloatSize();
758
759 TRACE_EVENT2("input",
760 "InputHandlerProxy::scrollBy",
761 "x",
762 clipped_increment.width,
763 "y",
764 clipped_increment.height);
765
766 bool did_scroll = false;
767
768 switch (fling_parameters_.sourceDevice) {
769 case blink::WebGestureDeviceTouchpad:
770 did_scroll = TouchpadFlingScroll(clipped_increment);
771 break;
772 case blink::WebGestureDeviceTouchscreen:
773 clipped_increment = ToClientScrollIncrement(clipped_increment);
774 did_scroll = input_handler_->ScrollBy(fling_parameters_.point,
775 clipped_increment);
776 break;
777 }
778
779 if (did_scroll) {
780 fling_parameters_.cumulativeScroll.width += clipped_increment.width;
781 fling_parameters_.cumulativeScroll.height += clipped_increment.height;
782 }
783
784 // It's possible the provided |increment| is sufficiently small as to not
785 // trigger a scroll, e.g., with a trivial time delta between fling updates.
786 // Return true in this case to prevent early fling termination.
787 if (std::abs(clipped_increment.width) < kScrollEpsilon &&
788 std::abs(clipped_increment.height) < kScrollEpsilon)
789 return true;
790
791 return did_scroll;
792 }
793
794 } // namespace content
795