Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / content / renderer / input / input_handler_proxy.cc
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
55 gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) {
56   return gfx::Vector2dF(-increment.width, -increment.height);
57 }
58
59 double InSecondsF(const base::TimeTicks& time) {
60   return (time - base::TimeTicks()).InSecondsF();
61 }
62
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
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
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
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
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
162 InputHandlerProxy::~InputHandlerProxy() {}
163
164 void InputHandlerProxy::WillShutdown() {
165   input_handler_ = NULL;
166   client_->WillShutdown();
167 }
168
169 InputHandlerProxy::EventDisposition
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
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
358 InputHandlerProxy::HandleGestureFling(
359     const WebGestureEvent& gesture_event) {
360   cc::InputHandler::ScrollStatus scroll_status;
361
362   if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
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 == WebGestureEvent::Touchpad)
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, WebFloatPoint(vx, vy), blink::WebSize()));
387       disallow_horizontal_fling_scroll_ = !vx;
388       disallow_vertical_fling_scroll_ = !vy;
389       TRACE_EVENT_ASYNC_BEGIN2("input",
390                                "InputHandlerProxy::HandleGestureFling::started",
391                                this,
392                                "vx",
393                                vx,
394                                "vy",
395                                vy);
396       // Note that the timestamp will only be used to kickstart the animation if
397       // its sufficiently close to the timestamp of the first call |Animate()|.
398       has_fling_animation_started_ = false;
399       fling_parameters_.startTime = gesture_event.timeStampSeconds;
400       fling_parameters_.delta = WebFloatPoint(vx, vy);
401       fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
402       fling_parameters_.globalPoint =
403           WebPoint(gesture_event.globalX, gesture_event.globalY);
404       fling_parameters_.modifiers = gesture_event.modifiers;
405       fling_parameters_.sourceDevice = gesture_event.sourceDevice;
406       input_handler_->SetNeedsAnimate();
407       return DID_HANDLE;
408     }
409     case cc::InputHandler::ScrollUnknown:
410     case cc::InputHandler::ScrollOnMainThread: {
411       TRACE_EVENT_INSTANT0("input",
412                            "InputHandlerProxy::HandleGestureFling::"
413                            "scroll_on_main_thread",
414                            TRACE_EVENT_SCOPE_THREAD);
415       fling_may_be_active_on_main_thread_ = true;
416       return DID_NOT_HANDLE;
417     }
418     case cc::InputHandler::ScrollIgnored: {
419       TRACE_EVENT_INSTANT0(
420           "input",
421           "InputHandlerProxy::HandleGestureFling::ignored",
422           TRACE_EVENT_SCOPE_THREAD);
423       if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
424         // We still pass the curve to the main thread if there's nothing
425         // scrollable, in case something
426         // registers a handler before the curve is over.
427         return DID_NOT_HANDLE;
428       }
429       return DROP_EVENT;
430     }
431     case cc::InputHandler::ScrollStatusCount:
432       NOTREACHED();
433       break;
434   }
435   return DID_NOT_HANDLE;
436 }
437
438 bool InputHandlerProxy::FilterInputEventForFlingBoosting(
439     const WebInputEvent& event) {
440   if (!WebInputEvent::isGestureEventType(event.type))
441     return false;
442
443   if (!fling_curve_) {
444     DCHECK(!deferred_fling_cancel_time_seconds_);
445     return false;
446   }
447
448   const WebGestureEvent& gesture_event =
449       static_cast<const WebGestureEvent&>(event);
450   if (gesture_event.type == WebInputEvent::GestureFlingCancel) {
451     if (current_fling_velocity_.LengthSquared() < kMinBoostFlingSpeedSquare)
452       return false;
453
454     TRACE_EVENT_INSTANT0("input",
455                          "InputHandlerProxy::FlingBoostStart",
456                          TRACE_EVENT_SCOPE_THREAD);
457     deferred_fling_cancel_time_seconds_ =
458         event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
459     return true;
460   }
461
462   // A fling is either inactive or is "free spinning", i.e., has yet to be
463   // interrupted by a touch gesture, in which case there is nothing to filter.
464   if (!deferred_fling_cancel_time_seconds_)
465     return false;
466
467   // Gestures from a different source should immediately interrupt the fling.
468   if (gesture_event.sourceDevice != fling_parameters_.sourceDevice) {
469     FlingBoostCancelAndResumeScrollingIfNecessary();
470     return false;
471   }
472
473   switch (gesture_event.type) {
474     case WebInputEvent::GestureTapCancel:
475     case WebInputEvent::GestureTapDown:
476       return false;
477
478     case WebInputEvent::GestureScrollBegin:
479       if (!input_handler_->IsCurrentlyScrollingLayerAt(
480               gfx::Point(gesture_event.x, gesture_event.y),
481               fling_parameters_.sourceDevice == WebGestureEvent::Touchpad
482                   ? cc::InputHandler::NonBubblingGesture
483                   : cc::InputHandler::Gesture)) {
484         CancelCurrentFling(true);
485         return false;
486       }
487
488       // TODO(jdduke): Use |gesture_event.data.scrollBegin.delta{X,Y}Hint| to
489       // determine if the ScrollBegin should immediately cancel the fling.
490       FlingBoostExtend(gesture_event);
491       return true;
492
493     case WebInputEvent::GestureScrollUpdate: {
494       const double time_since_last_boost_event =
495           event.timeStampSeconds - last_fling_boost_event_.timeStampSeconds;
496       if (ShouldSuppressScrollForFlingBoosting(current_fling_velocity_,
497                                                gesture_event,
498                                                time_since_last_boost_event)) {
499         FlingBoostExtend(gesture_event);
500         return true;
501       }
502
503       FlingBoostCancelAndResumeScrollingIfNecessary();
504       return false;
505     }
506
507     case WebInputEvent::GestureScrollEnd:
508       CancelCurrentFling(true);
509       return true;
510
511     case WebInputEvent::GestureFlingStart: {
512       DCHECK_EQ(fling_parameters_.sourceDevice, gesture_event.sourceDevice);
513
514       bool fling_boosted =
515           fling_parameters_.modifiers == gesture_event.modifiers &&
516           ShouldBoostFling(current_fling_velocity_, gesture_event);
517
518       gfx::Vector2dF new_fling_velocity(
519           gesture_event.data.flingStart.velocityX,
520           gesture_event.data.flingStart.velocityY);
521
522       if (fling_boosted)
523         current_fling_velocity_ += new_fling_velocity;
524       else
525         current_fling_velocity_ = new_fling_velocity;
526
527       WebFloatPoint velocity(current_fling_velocity_.x(),
528                              current_fling_velocity_.y());
529       deferred_fling_cancel_time_seconds_ = 0;
530       last_fling_boost_event_ = WebGestureEvent();
531       fling_curve_.reset(client_->CreateFlingAnimationCurve(
532           gesture_event.sourceDevice, velocity, blink::WebSize()));
533       fling_parameters_.startTime = gesture_event.timeStampSeconds;
534       fling_parameters_.delta = velocity;
535       fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
536       fling_parameters_.globalPoint =
537           WebPoint(gesture_event.globalX, gesture_event.globalY);
538
539       TRACE_EVENT_INSTANT2("input",
540                            fling_boosted ? "InputHandlerProxy::FlingBoosted"
541                                          : "InputHandlerProxy::FlingReplaced",
542                            TRACE_EVENT_SCOPE_THREAD,
543                            "vx",
544                            current_fling_velocity_.x(),
545                            "vy",
546                            current_fling_velocity_.y());
547
548       // The client expects balanced calls between a consumed GestureFlingStart
549       // and |DidStopFlinging()|. TODO(jdduke): Provide a count parameter to
550       // |DidStopFlinging()| and only send after the accumulated fling ends.
551       client_->DidStopFlinging();
552       return true;
553     }
554
555     default:
556       // All other types of gestures (taps, presses, etc...) will complete the
557       // deferred fling cancellation.
558       FlingBoostCancelAndResumeScrollingIfNecessary();
559       return false;
560   }
561 }
562
563 void InputHandlerProxy::FlingBoostExtend(const blink::WebGestureEvent& event) {
564   TRACE_EVENT_INSTANT0(
565       "input", "InputHandlerProxy::FlingBoostExtend", TRACE_EVENT_SCOPE_THREAD);
566   deferred_fling_cancel_time_seconds_ =
567       event.timeStampSeconds + kFlingBoostTimeoutDelaySeconds;
568   last_fling_boost_event_ = event;
569 }
570
571 void InputHandlerProxy::FlingBoostCancelAndResumeScrollingIfNecessary() {
572   TRACE_EVENT_INSTANT0(
573       "input", "InputHandlerProxy::FlingBoostCancel", TRACE_EVENT_SCOPE_THREAD);
574   DCHECK(deferred_fling_cancel_time_seconds_);
575
576   // Note: |last_fling_boost_event_| is cleared by |CancelCurrentFling()|.
577   WebGestureEvent last_fling_boost_event = last_fling_boost_event_;
578
579   CancelCurrentFling(true);
580
581   if (last_fling_boost_event.type == WebInputEvent::GestureScrollBegin ||
582       last_fling_boost_event.type == WebInputEvent::GestureScrollUpdate) {
583     // Synthesize a GestureScrollBegin, as the original was suppressed.
584     HandleInputEvent(ObtainGestureScrollBegin(last_fling_boost_event));
585   }
586 }
587
588 void InputHandlerProxy::Animate(base::TimeTicks time) {
589   if (!fling_curve_)
590     return;
591
592   double monotonic_time_sec = InSecondsF(time);
593
594   if (deferred_fling_cancel_time_seconds_ &&
595       monotonic_time_sec > deferred_fling_cancel_time_seconds_) {
596     FlingBoostCancelAndResumeScrollingIfNecessary();
597     return;
598   }
599
600   if (!has_fling_animation_started_) {
601     has_fling_animation_started_ = true;
602     // Guard against invalid, future or sufficiently stale start times, as there
603     // are no guarantees fling event and animation timestamps are compatible.
604     if (!fling_parameters_.startTime ||
605         monotonic_time_sec <= fling_parameters_.startTime ||
606         monotonic_time_sec >= fling_parameters_.startTime +
607                                   kMaxSecondsFromFlingTimestampToFirstAnimate) {
608       fling_parameters_.startTime = monotonic_time_sec;
609       input_handler_->SetNeedsAnimate();
610       return;
611     }
612   }
613
614   bool fling_is_active =
615       fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
616                           this);
617
618   if (disallow_vertical_fling_scroll_ && disallow_horizontal_fling_scroll_)
619     fling_is_active = false;
620
621   if (fling_is_active) {
622     input_handler_->SetNeedsAnimate();
623   } else {
624     TRACE_EVENT_INSTANT0("input",
625                          "InputHandlerProxy::animate::flingOver",
626                          TRACE_EVENT_SCOPE_THREAD);
627     CancelCurrentFling(true);
628   }
629 }
630
631 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
632   fling_may_be_active_on_main_thread_ = false;
633   client_->DidStopFlinging();
634 }
635
636 void InputHandlerProxy::DidOverscroll(
637     const gfx::Vector2dF& accumulated_overscroll,
638     const gfx::Vector2dF& latest_overscroll_delta) {
639   DCHECK(client_);
640
641   TRACE_EVENT2("input",
642                "InputHandlerProxy::DidOverscroll",
643                "dx",
644                latest_overscroll_delta.x(),
645                "dy",
646                latest_overscroll_delta.y());
647
648   DidOverscrollParams params;
649   params.accumulated_overscroll = accumulated_overscroll;
650   params.latest_overscroll_delta = latest_overscroll_delta;
651   params.current_fling_velocity =
652       ToClientScrollIncrement(current_fling_velocity_);
653
654   if (fling_curve_) {
655     static const int kFlingOverscrollThreshold = 1;
656     disallow_horizontal_fling_scroll_ |=
657         std::abs(params.accumulated_overscroll.x()) >=
658         kFlingOverscrollThreshold;
659     disallow_vertical_fling_scroll_ |=
660         std::abs(params.accumulated_overscroll.y()) >=
661         kFlingOverscrollThreshold;
662   }
663
664   client_->DidOverscroll(params);
665 }
666
667 bool InputHandlerProxy::CancelCurrentFling(
668     bool send_fling_stopped_notification) {
669   bool had_fling_animation = fling_curve_;
670   if (had_fling_animation &&
671       fling_parameters_.sourceDevice == WebGestureEvent::Touchscreen) {
672     input_handler_->ScrollEnd();
673     TRACE_EVENT_ASYNC_END0(
674         "input",
675         "InputHandlerProxy::HandleGestureFling::started",
676         this);
677   }
678
679   TRACE_EVENT_INSTANT1("input",
680                        "InputHandlerProxy::CancelCurrentFling",
681                        TRACE_EVENT_SCOPE_THREAD,
682                        "had_fling_animation",
683                        had_fling_animation);
684   fling_curve_.reset();
685   has_fling_animation_started_ = false;
686   gesture_scroll_on_impl_thread_ = false;
687   current_fling_velocity_ = gfx::Vector2dF();
688   fling_parameters_ = blink::WebActiveWheelFlingParameters();
689   deferred_fling_cancel_time_seconds_ = 0;
690   last_fling_boost_event_ = WebGestureEvent();
691   if (send_fling_stopped_notification && had_fling_animation)
692     client_->DidStopFlinging();
693   return had_fling_animation;
694 }
695
696 bool InputHandlerProxy::TouchpadFlingScroll(
697     const WebFloatSize& increment) {
698   WebMouseWheelEvent synthetic_wheel;
699   synthetic_wheel.type = WebInputEvent::MouseWheel;
700   synthetic_wheel.deltaX = increment.width;
701   synthetic_wheel.deltaY = increment.height;
702   synthetic_wheel.hasPreciseScrollingDeltas = true;
703   synthetic_wheel.x = fling_parameters_.point.x;
704   synthetic_wheel.y = fling_parameters_.point.y;
705   synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
706   synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
707   synthetic_wheel.modifiers = fling_parameters_.modifiers;
708
709   InputHandlerProxy::EventDisposition disposition =
710       HandleInputEvent(synthetic_wheel);
711   switch (disposition) {
712     case DID_HANDLE:
713       return true;
714     case DROP_EVENT:
715       break;
716     case DID_NOT_HANDLE:
717       TRACE_EVENT_INSTANT0("input",
718                            "InputHandlerProxy::scrollBy::AbortFling",
719                            TRACE_EVENT_SCOPE_THREAD);
720       // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
721       // main thread. In this case we need to schedule a commit and transfer the
722       // fling curve over to the main thread and run the rest of the wheels from
723       // there. This can happen when flinging a page that contains a scrollable
724       // subarea that we can't scroll on the thread if the fling starts outside
725       // the subarea but then is flung "under" the pointer.
726       client_->TransferActiveWheelFlingAnimation(fling_parameters_);
727       fling_may_be_active_on_main_thread_ = true;
728       CancelCurrentFling(false);
729       break;
730   }
731
732   return false;
733 }
734
735 bool InputHandlerProxy::scrollBy(const WebFloatSize& increment,
736                                  const WebFloatSize& velocity) {
737   WebFloatSize clipped_increment;
738   WebFloatSize clipped_velocity;
739   if (!disallow_horizontal_fling_scroll_) {
740     clipped_increment.width = increment.width;
741     clipped_velocity.width = velocity.width;
742   }
743   if (!disallow_vertical_fling_scroll_) {
744     clipped_increment.height = increment.height;
745     clipped_velocity.height = velocity.height;
746   }
747
748   current_fling_velocity_ = clipped_velocity;
749
750   // Early out if the increment is zero, but avoid early terimination if the
751   // velocity is still non-zero.
752   if (clipped_increment == WebFloatSize())
753     return clipped_velocity != WebFloatSize();
754
755   TRACE_EVENT2("input",
756                "InputHandlerProxy::scrollBy",
757                "x",
758                clipped_increment.width,
759                "y",
760                clipped_increment.height);
761
762   bool did_scroll = false;
763
764   switch (fling_parameters_.sourceDevice) {
765     case WebGestureEvent::Touchpad:
766       did_scroll = TouchpadFlingScroll(clipped_increment);
767       break;
768     case WebGestureEvent::Touchscreen:
769       clipped_increment = ToClientScrollIncrement(clipped_increment);
770       did_scroll = input_handler_->ScrollBy(fling_parameters_.point,
771                                             clipped_increment);
772       break;
773   }
774
775   if (did_scroll) {
776     fling_parameters_.cumulativeScroll.width += clipped_increment.width;
777     fling_parameters_.cumulativeScroll.height += clipped_increment.height;
778   }
779
780   // It's possible the provided |increment| is sufficiently small as to not
781   // trigger a scroll, e.g., with a trivial time delta between fling updates.
782   // Return true in this case to prevent early fling termination.
783   if (std::abs(clipped_increment.width) < kScrollEpsilon &&
784       std::abs(clipped_increment.height) < kScrollEpsilon)
785     return true;
786
787   return did_scroll;
788 }
789
790 }  // namespace content