• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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