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