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/debug/trace_event.h"
      8 #include "base/logging.h"
      9 #include "base/metrics/histogram.h"
     10 #include "content/renderer/input/input_handler_proxy_client.h"
     11 #include "third_party/WebKit/public/platform/Platform.h"
     12 #include "third_party/WebKit/public/web/WebInputEvent.h"
     13 #include "ui/events/latency_info.h"
     14 #include "ui/gfx/frame_time.h"
     15 
     16 using blink::WebFloatPoint;
     17 using blink::WebFloatSize;
     18 using blink::WebGestureEvent;
     19 using blink::WebInputEvent;
     20 using blink::WebMouseEvent;
     21 using blink::WebMouseWheelEvent;
     22 using blink::WebPoint;
     23 using blink::WebTouchEvent;
     24 using blink::WebTouchPoint;
     25 
     26 namespace {
     27 
     28 // Validate provided event timestamps that interact with animation timestamps.
     29 const double kBadTimestampDeltaFromNowInS = 60. * 60. * 24. * 7.;
     30 
     31 double InSecondsF(const base::TimeTicks& time) {
     32   return (time - base::TimeTicks()).InSecondsF();
     33 }
     34 
     35 void SendScrollLatencyUma(const WebInputEvent& event,
     36                           const ui::LatencyInfo& latency_info) {
     37   if (!(event.type == WebInputEvent::GestureScrollBegin ||
     38         event.type == WebInputEvent::GestureScrollUpdate ||
     39         event.type == WebInputEvent::GestureScrollUpdateWithoutPropagation))
     40     return;
     41 
     42   ui::LatencyInfo::LatencyMap::const_iterator it =
     43       latency_info.latency_components.find(std::make_pair(
     44           ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0));
     45 
     46   if (it == latency_info.latency_components.end())
     47     return;
     48 
     49   base::TimeDelta delta = base::TimeTicks::HighResNow() - it->second.event_time;
     50   for (size_t i = 0; i < it->second.event_count; ++i) {
     51     UMA_HISTOGRAM_CUSTOM_COUNTS(
     52         "Event.Latency.RendererImpl.GestureScroll2",
     53         delta.InMicroseconds(),
     54         0,
     55         1000000,
     56         100);
     57   }
     58 }  // namespace
     59 
     60 }
     61 
     62 namespace content {
     63 
     64 InputHandlerProxy::InputHandlerProxy(cc::InputHandler* input_handler)
     65     : client_(NULL),
     66       input_handler_(input_handler),
     67 #ifndef NDEBUG
     68       expect_scroll_update_end_(false),
     69       expect_pinch_update_end_(false),
     70 #endif
     71       gesture_scroll_on_impl_thread_(false),
     72       gesture_pinch_on_impl_thread_(false),
     73       fling_may_be_active_on_main_thread_(false),
     74       fling_overscrolled_horizontally_(false),
     75       fling_overscrolled_vertically_(false) {
     76   input_handler_->BindToClient(this);
     77 }
     78 
     79 InputHandlerProxy::~InputHandlerProxy() {}
     80 
     81 void InputHandlerProxy::WillShutdown() {
     82   input_handler_ = NULL;
     83   DCHECK(client_);
     84   client_->WillShutdown();
     85 }
     86 
     87 void InputHandlerProxy::SetClient(InputHandlerProxyClient* client) {
     88   DCHECK(!client_ || !client);
     89   client_ = client;
     90 }
     91 
     92 InputHandlerProxy::EventDisposition
     93 InputHandlerProxy::HandleInputEventWithLatencyInfo(
     94     const WebInputEvent& event,
     95     ui::LatencyInfo* latency_info) {
     96   DCHECK(input_handler_);
     97 
     98   SendScrollLatencyUma(event, *latency_info);
     99 
    100   scoped_ptr<cc::SwapPromiseMonitor> latency_info_swap_promise_monitor =
    101       input_handler_->CreateLatencyInfoSwapPromiseMonitor(latency_info);
    102   InputHandlerProxy::EventDisposition disposition = HandleInputEvent(event);
    103   return disposition;
    104 }
    105 
    106 InputHandlerProxy::EventDisposition InputHandlerProxy::HandleInputEvent(
    107     const WebInputEvent& event) {
    108   DCHECK(client_);
    109   DCHECK(input_handler_);
    110 
    111   if (event.type == WebInputEvent::MouseWheel) {
    112     const WebMouseWheelEvent& wheel_event =
    113         *static_cast<const WebMouseWheelEvent*>(&event);
    114     if (wheel_event.scrollByPage) {
    115       // TODO(jamesr): We don't properly handle scroll by page in the compositor
    116       // thread, so punt it to the main thread. http://crbug.com/236639
    117       return DID_NOT_HANDLE;
    118     }
    119     cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
    120         gfx::Point(wheel_event.x, wheel_event.y), cc::InputHandler::Wheel);
    121     switch (scroll_status) {
    122       case cc::InputHandler::ScrollStarted: {
    123         TRACE_EVENT_INSTANT2(
    124             "renderer",
    125             "InputHandlerProxy::handle_input wheel scroll",
    126             TRACE_EVENT_SCOPE_THREAD,
    127             "deltaX",
    128             -wheel_event.deltaX,
    129             "deltaY",
    130             -wheel_event.deltaY);
    131         bool did_scroll = input_handler_->ScrollBy(
    132             gfx::Point(wheel_event.x, wheel_event.y),
    133             gfx::Vector2dF(-wheel_event.deltaX, -wheel_event.deltaY));
    134         input_handler_->ScrollEnd();
    135         return did_scroll ? DID_HANDLE : DROP_EVENT;
    136       }
    137       case cc::InputHandler::ScrollIgnored:
    138         // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
    139         // to properly sync scrollability it's safer to send the event to the
    140         // main thread. Change back to DROP_EVENT once we have synchronization
    141         // bugs sorted out.
    142         return DID_NOT_HANDLE;
    143       case cc::InputHandler::ScrollOnMainThread:
    144         return DID_NOT_HANDLE;
    145     }
    146   } else if (event.type == WebInputEvent::GestureScrollBegin) {
    147     DCHECK(!gesture_scroll_on_impl_thread_);
    148 #ifndef NDEBUG
    149     DCHECK(!expect_scroll_update_end_);
    150     expect_scroll_update_end_ = true;
    151 #endif
    152     const WebGestureEvent& gesture_event =
    153         *static_cast<const WebGestureEvent*>(&event);
    154     cc::InputHandler::ScrollStatus scroll_status = input_handler_->ScrollBegin(
    155         gfx::Point(gesture_event.x, gesture_event.y),
    156         cc::InputHandler::Gesture);
    157     switch (scroll_status) {
    158       case cc::InputHandler::ScrollStarted:
    159         gesture_scroll_on_impl_thread_ = true;
    160         return DID_HANDLE;
    161       case cc::InputHandler::ScrollOnMainThread:
    162         return DID_NOT_HANDLE;
    163       case cc::InputHandler::ScrollIgnored:
    164         return DROP_EVENT;
    165     }
    166   } else if (event.type == WebInputEvent::GestureScrollUpdate) {
    167 #ifndef NDEBUG
    168     DCHECK(expect_scroll_update_end_);
    169 #endif
    170 
    171     if (!gesture_scroll_on_impl_thread_ && !gesture_pinch_on_impl_thread_)
    172       return DID_NOT_HANDLE;
    173 
    174     const WebGestureEvent& gesture_event =
    175         *static_cast<const WebGestureEvent*>(&event);
    176     bool did_scroll = input_handler_->ScrollBy(
    177         gfx::Point(gesture_event.x, gesture_event.y),
    178         gfx::Vector2dF(-gesture_event.data.scrollUpdate.deltaX,
    179                        -gesture_event.data.scrollUpdate.deltaY));
    180     return did_scroll ? DID_HANDLE : DROP_EVENT;
    181   } else if (event.type == WebInputEvent::GestureScrollEnd) {
    182 #ifndef NDEBUG
    183     DCHECK(expect_scroll_update_end_);
    184     expect_scroll_update_end_ = false;
    185 #endif
    186     input_handler_->ScrollEnd();
    187 
    188     if (!gesture_scroll_on_impl_thread_)
    189       return DID_NOT_HANDLE;
    190 
    191     gesture_scroll_on_impl_thread_ = false;
    192     return DID_HANDLE;
    193   } else if (event.type == WebInputEvent::GesturePinchBegin) {
    194 #ifndef NDEBUG
    195     DCHECK(!expect_pinch_update_end_);
    196     expect_pinch_update_end_ = true;
    197 #endif
    198     input_handler_->PinchGestureBegin();
    199     gesture_pinch_on_impl_thread_ = true;
    200     return DID_HANDLE;
    201   } else if (event.type == WebInputEvent::GesturePinchEnd) {
    202 #ifndef NDEBUG
    203     DCHECK(expect_pinch_update_end_);
    204     expect_pinch_update_end_ = false;
    205 #endif
    206     gesture_pinch_on_impl_thread_ = false;
    207     input_handler_->PinchGestureEnd();
    208     return DID_HANDLE;
    209   } else if (event.type == WebInputEvent::GesturePinchUpdate) {
    210 #ifndef NDEBUG
    211     DCHECK(expect_pinch_update_end_);
    212 #endif
    213     const WebGestureEvent& gesture_event =
    214         *static_cast<const WebGestureEvent*>(&event);
    215     input_handler_->PinchGestureUpdate(
    216         gesture_event.data.pinchUpdate.scale,
    217         gfx::Point(gesture_event.x, gesture_event.y));
    218     return DID_HANDLE;
    219   } else if (event.type == WebInputEvent::GestureFlingStart) {
    220     const WebGestureEvent& gesture_event =
    221         *static_cast<const WebGestureEvent*>(&event);
    222     return HandleGestureFling(gesture_event);
    223   } else if (event.type == WebInputEvent::GestureFlingCancel) {
    224     if (CancelCurrentFling())
    225       return DID_HANDLE;
    226     else if (!fling_may_be_active_on_main_thread_)
    227       return DROP_EVENT;
    228   } else if (event.type == WebInputEvent::TouchStart) {
    229     const WebTouchEvent& touch_event =
    230         *static_cast<const WebTouchEvent*>(&event);
    231     for (size_t i = 0; i < touch_event.touchesLength; ++i) {
    232       if (touch_event.touches[i].state != WebTouchPoint::StatePressed)
    233         continue;
    234       if (input_handler_->HaveTouchEventHandlersAt(touch_event.touches[i]
    235                                                        .position))
    236         return DID_NOT_HANDLE;
    237     }
    238     return DROP_EVENT;
    239   } else if (WebInputEvent::isKeyboardEventType(event.type)) {
    240     CancelCurrentFling();
    241   }
    242 
    243   return DID_NOT_HANDLE;
    244 }
    245 
    246 InputHandlerProxy::EventDisposition
    247 InputHandlerProxy::HandleGestureFling(
    248     const WebGestureEvent& gesture_event) {
    249   cc::InputHandler::ScrollStatus scroll_status;
    250 
    251   if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
    252     scroll_status = input_handler_->ScrollBegin(
    253         gfx::Point(gesture_event.x, gesture_event.y),
    254         cc::InputHandler::NonBubblingGesture);
    255   } else {
    256     if (!gesture_scroll_on_impl_thread_)
    257       scroll_status = cc::InputHandler::ScrollOnMainThread;
    258     else
    259       scroll_status = input_handler_->FlingScrollBegin();
    260   }
    261 
    262 #ifndef NDEBUG
    263   expect_scroll_update_end_ = false;
    264 #endif
    265 
    266   switch (scroll_status) {
    267     case cc::InputHandler::ScrollStarted: {
    268       if (gesture_event.sourceDevice == WebGestureEvent::Touchpad)
    269         input_handler_->ScrollEnd();
    270 
    271       fling_curve_.reset(client_->CreateFlingAnimationCurve(
    272           gesture_event.sourceDevice,
    273           WebFloatPoint(gesture_event.data.flingStart.velocityX,
    274                         gesture_event.data.flingStart.velocityY),
    275           blink::WebSize()));
    276       fling_overscrolled_horizontally_ = false;
    277       fling_overscrolled_vertically_ = false;
    278       TRACE_EVENT_ASYNC_BEGIN0(
    279           "renderer",
    280           "InputHandlerProxy::HandleGestureFling::started",
    281           this);
    282       if (gesture_event.timeStampSeconds) {
    283         fling_parameters_.startTime = gesture_event.timeStampSeconds;
    284         DCHECK_LT(fling_parameters_.startTime -
    285                       InSecondsF(gfx::FrameTime::Now()),
    286                   kBadTimestampDeltaFromNowInS);
    287       }
    288       fling_parameters_.delta =
    289           WebFloatPoint(gesture_event.data.flingStart.velocityX,
    290                         gesture_event.data.flingStart.velocityY);
    291       fling_parameters_.point = WebPoint(gesture_event.x, gesture_event.y);
    292       fling_parameters_.globalPoint =
    293           WebPoint(gesture_event.globalX, gesture_event.globalY);
    294       fling_parameters_.modifiers = gesture_event.modifiers;
    295       fling_parameters_.sourceDevice = gesture_event.sourceDevice;
    296       input_handler_->ScheduleAnimation();
    297       return DID_HANDLE;
    298     }
    299     case cc::InputHandler::ScrollOnMainThread: {
    300       TRACE_EVENT_INSTANT0("renderer",
    301                            "InputHandlerProxy::HandleGestureFling::"
    302                            "scroll_on_main_thread",
    303                            TRACE_EVENT_SCOPE_THREAD);
    304       fling_may_be_active_on_main_thread_ = true;
    305       return DID_NOT_HANDLE;
    306     }
    307     case cc::InputHandler::ScrollIgnored: {
    308       TRACE_EVENT_INSTANT0(
    309           "renderer",
    310           "InputHandlerProxy::HandleGestureFling::ignored",
    311           TRACE_EVENT_SCOPE_THREAD);
    312       if (gesture_event.sourceDevice == WebGestureEvent::Touchpad) {
    313         // We still pass the curve to the main thread if there's nothing
    314         // scrollable, in case something
    315         // registers a handler before the curve is over.
    316         return DID_NOT_HANDLE;
    317       }
    318       return DROP_EVENT;
    319     }
    320   }
    321   return DID_NOT_HANDLE;
    322 }
    323 
    324 void InputHandlerProxy::Animate(base::TimeTicks time) {
    325   if (!fling_curve_)
    326     return;
    327 
    328   double monotonic_time_sec = InSecondsF(time);
    329   if (!fling_parameters_.startTime) {
    330     fling_parameters_.startTime = monotonic_time_sec;
    331     input_handler_->ScheduleAnimation();
    332     return;
    333   }
    334 
    335   if (fling_curve_->apply(monotonic_time_sec - fling_parameters_.startTime,
    336                           this)) {
    337     input_handler_->ScheduleAnimation();
    338   } else {
    339     TRACE_EVENT_INSTANT0("renderer",
    340                          "InputHandlerProxy::animate::flingOver",
    341                          TRACE_EVENT_SCOPE_THREAD);
    342     CancelCurrentFling();
    343   }
    344 }
    345 
    346 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
    347   fling_may_be_active_on_main_thread_ = false;
    348 }
    349 
    350 void InputHandlerProxy::DidOverscroll(const cc::DidOverscrollParams& params) {
    351   DCHECK(client_);
    352   if (fling_curve_) {
    353     static const int kFlingOverscrollThreshold = 1;
    354     fling_overscrolled_horizontally_ |=
    355         std::abs(params.accumulated_overscroll.x()) >=
    356         kFlingOverscrollThreshold;
    357     fling_overscrolled_vertically_ |=
    358         std::abs(params.accumulated_overscroll.y()) >=
    359         kFlingOverscrollThreshold;
    360   }
    361 
    362   client_->DidOverscroll(params);
    363 }
    364 
    365 bool InputHandlerProxy::CancelCurrentFling() {
    366   bool had_fling_animation = fling_curve_;
    367   if (had_fling_animation &&
    368       fling_parameters_.sourceDevice == WebGestureEvent::Touchscreen) {
    369     input_handler_->ScrollEnd();
    370     TRACE_EVENT_ASYNC_END0(
    371         "renderer",
    372         "InputHandlerProxy::HandleGestureFling::started",
    373         this);
    374   }
    375 
    376   TRACE_EVENT_INSTANT1("renderer",
    377                        "InputHandlerProxy::CancelCurrentFling",
    378                        TRACE_EVENT_SCOPE_THREAD,
    379                        "had_fling_animation",
    380                        had_fling_animation);
    381   fling_curve_.reset();
    382   gesture_scroll_on_impl_thread_ = false;
    383   fling_parameters_ = blink::WebActiveWheelFlingParameters();
    384   return had_fling_animation;
    385 }
    386 
    387 bool InputHandlerProxy::TouchpadFlingScroll(
    388     const WebFloatSize& increment) {
    389   WebMouseWheelEvent synthetic_wheel;
    390   synthetic_wheel.type = WebInputEvent::MouseWheel;
    391   synthetic_wheel.deltaX = increment.width;
    392   synthetic_wheel.deltaY = increment.height;
    393   synthetic_wheel.hasPreciseScrollingDeltas = true;
    394   synthetic_wheel.x = fling_parameters_.point.x;
    395   synthetic_wheel.y = fling_parameters_.point.y;
    396   synthetic_wheel.globalX = fling_parameters_.globalPoint.x;
    397   synthetic_wheel.globalY = fling_parameters_.globalPoint.y;
    398   synthetic_wheel.modifiers = fling_parameters_.modifiers;
    399 
    400   InputHandlerProxy::EventDisposition disposition =
    401       HandleInputEvent(synthetic_wheel);
    402   switch (disposition) {
    403     case DID_HANDLE:
    404       return true;
    405     case DROP_EVENT:
    406       break;
    407     case DID_NOT_HANDLE:
    408       TRACE_EVENT_INSTANT0("renderer",
    409                            "InputHandlerProxy::scrollBy::AbortFling",
    410                            TRACE_EVENT_SCOPE_THREAD);
    411       // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
    412       // main thread. In this case we need to schedule a commit and transfer the
    413       // fling curve over to the main thread and run the rest of the wheels from
    414       // there. This can happen when flinging a page that contains a scrollable
    415       // subarea that we can't scroll on the thread if the fling starts outside
    416       // the subarea but then is flung "under" the pointer.
    417       client_->TransferActiveWheelFlingAnimation(fling_parameters_);
    418       fling_may_be_active_on_main_thread_ = true;
    419       CancelCurrentFling();
    420       break;
    421   }
    422 
    423   return false;
    424 }
    425 
    426 static gfx::Vector2dF ToClientScrollIncrement(const WebFloatSize& increment) {
    427   return gfx::Vector2dF(-increment.width, -increment.height);
    428 }
    429 
    430 void InputHandlerProxy::scrollBy(const WebFloatSize& increment) {
    431   WebFloatSize clipped_increment;
    432   if (!fling_overscrolled_horizontally_)
    433     clipped_increment.width = increment.width;
    434   if (!fling_overscrolled_vertically_)
    435     clipped_increment.height = increment.height;
    436 
    437   if (clipped_increment == WebFloatSize())
    438     return;
    439 
    440   TRACE_EVENT2("renderer",
    441                "InputHandlerProxy::scrollBy",
    442                "x",
    443                clipped_increment.width,
    444                "y",
    445                clipped_increment.height);
    446 
    447   bool did_scroll = false;
    448 
    449   switch (fling_parameters_.sourceDevice) {
    450     case WebGestureEvent::Touchpad:
    451       did_scroll = TouchpadFlingScroll(clipped_increment);
    452       break;
    453     case WebGestureEvent::Touchscreen:
    454       clipped_increment = ToClientScrollIncrement(clipped_increment);
    455       did_scroll = input_handler_->ScrollBy(fling_parameters_.point,
    456                                             clipped_increment);
    457       break;
    458   }
    459 
    460   if (did_scroll) {
    461     fling_parameters_.cumulativeScroll.width += clipped_increment.width;
    462     fling_parameters_.cumulativeScroll.height += clipped_increment.height;
    463   }
    464 }
    465 
    466 void InputHandlerProxy::notifyCurrentFlingVelocity(
    467     const WebFloatSize& velocity) {
    468   TRACE_EVENT2("renderer",
    469                "InputHandlerProxy::notifyCurrentFlingVelocity",
    470                "vx",
    471                velocity.width,
    472                "vy",
    473                velocity.height);
    474   input_handler_->NotifyCurrentFlingVelocity(ToClientScrollIncrement(velocity));
    475 }
    476 
    477 }  // namespace content
    478