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/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 == 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 
    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 
    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 
    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 
    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 
    635 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
    636   fling_may_be_active_on_main_thread_ = false;
    637   client_->DidStopFlinging();
    638 }
    639 
    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 
    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 
    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 
    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