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