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