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.
5 #include "content/renderer/input/input_handler_proxy.h"
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"
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
;
28 // Validate provided event timestamps that interact with animation timestamps.
29 const double kBadTimestampDeltaFromNowInS
= 60. * 60. * 24. * 7.;
31 double InSecondsF(const base::TimeTicks
& time
) {
32 return (time
- base::TimeTicks()).InSecondsF();
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
))
42 ui::LatencyInfo::LatencyMap::const_iterator it
=
43 latency_info
.latency_components
.find(std::make_pair(
44 ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT
, 0));
46 if (it
== latency_info
.latency_components
.end())
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(),
64 InputHandlerProxy::InputHandlerProxy(cc::InputHandler
* input_handler
)
66 input_handler_(input_handler
),
68 expect_scroll_update_end_(false),
70 gesture_scroll_on_impl_thread_(false),
71 gesture_pinch_on_impl_thread_(false),
72 fling_may_be_active_on_main_thread_(false),
73 disallow_horizontal_fling_scroll_(false),
74 disallow_vertical_fling_scroll_(false) {
75 input_handler_
->BindToClient(this);
78 InputHandlerProxy::~InputHandlerProxy() {}
80 void InputHandlerProxy::WillShutdown() {
81 input_handler_
= NULL
;
83 client_
->WillShutdown();
86 void InputHandlerProxy::SetClient(InputHandlerProxyClient
* client
) {
87 DCHECK(!client_
|| !client
);
91 InputHandlerProxy::EventDisposition
92 InputHandlerProxy::HandleInputEventWithLatencyInfo(
93 const WebInputEvent
& event
,
94 ui::LatencyInfo
* latency_info
) {
95 DCHECK(input_handler_
);
97 SendScrollLatencyUma(event
, *latency_info
);
99 scoped_ptr
<cc::SwapPromiseMonitor
> latency_info_swap_promise_monitor
=
100 input_handler_
->CreateLatencyInfoSwapPromiseMonitor(latency_info
);
101 InputHandlerProxy::EventDisposition disposition
= HandleInputEvent(event
);
105 InputHandlerProxy::EventDisposition
InputHandlerProxy::HandleInputEvent(
106 const WebInputEvent
& event
) {
108 DCHECK(input_handler_
);
110 if (event
.type
== WebInputEvent::MouseWheel
) {
111 const WebMouseWheelEvent
& wheel_event
=
112 *static_cast<const WebMouseWheelEvent
*>(&event
);
113 if (wheel_event
.scrollByPage
) {
114 // TODO(jamesr): We don't properly handle scroll by page in the compositor
115 // thread, so punt it to the main thread. http://crbug.com/236639
116 return DID_NOT_HANDLE
;
118 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
119 gfx::Point(wheel_event
.x
, wheel_event
.y
), cc::InputHandler::Wheel
);
120 switch (scroll_status
) {
121 case cc::InputHandler::ScrollStarted
: {
122 TRACE_EVENT_INSTANT2(
124 "InputHandlerProxy::handle_input wheel scroll",
125 TRACE_EVENT_SCOPE_THREAD
,
129 -wheel_event
.deltaY
);
130 bool did_scroll
= input_handler_
->ScrollBy(
131 gfx::Point(wheel_event
.x
, wheel_event
.y
),
132 gfx::Vector2dF(-wheel_event
.deltaX
, -wheel_event
.deltaY
));
133 input_handler_
->ScrollEnd();
134 return did_scroll
? DID_HANDLE
: DROP_EVENT
;
136 case cc::InputHandler::ScrollIgnored
:
137 // TODO(jamesr): This should be DROP_EVENT, but in cases where we fail
138 // to properly sync scrollability it's safer to send the event to the
139 // main thread. Change back to DROP_EVENT once we have synchronization
141 return DID_NOT_HANDLE
;
142 case cc::InputHandler::ScrollOnMainThread
:
143 return DID_NOT_HANDLE
;
145 } else if (event
.type
== WebInputEvent::GestureScrollBegin
) {
146 DCHECK(!gesture_scroll_on_impl_thread_
);
148 DCHECK(!expect_scroll_update_end_
);
149 expect_scroll_update_end_
= true;
151 const WebGestureEvent
& gesture_event
=
152 *static_cast<const WebGestureEvent
*>(&event
);
153 cc::InputHandler::ScrollStatus scroll_status
= input_handler_
->ScrollBegin(
154 gfx::Point(gesture_event
.x
, gesture_event
.y
),
155 cc::InputHandler::Gesture
);
156 switch (scroll_status
) {
157 case cc::InputHandler::ScrollStarted
:
158 gesture_scroll_on_impl_thread_
= true;
160 case cc::InputHandler::ScrollOnMainThread
:
161 return DID_NOT_HANDLE
;
162 case cc::InputHandler::ScrollIgnored
:
165 } else if (event
.type
== WebInputEvent::GestureScrollUpdate
) {
167 DCHECK(expect_scroll_update_end_
);
170 if (!gesture_scroll_on_impl_thread_
&& !gesture_pinch_on_impl_thread_
)
171 return DID_NOT_HANDLE
;
173 const WebGestureEvent
& gesture_event
=
174 *static_cast<const WebGestureEvent
*>(&event
);
175 bool did_scroll
= input_handler_
->ScrollBy(
176 gfx::Point(gesture_event
.x
, gesture_event
.y
),
177 gfx::Vector2dF(-gesture_event
.data
.scrollUpdate
.deltaX
,
178 -gesture_event
.data
.scrollUpdate
.deltaY
));
179 return did_scroll
? DID_HANDLE
: DROP_EVENT
;
180 } else if (event
.type
== WebInputEvent::GestureScrollEnd
) {
182 DCHECK(expect_scroll_update_end_
);
183 expect_scroll_update_end_
= false;
185 input_handler_
->ScrollEnd();
187 if (!gesture_scroll_on_impl_thread_
)
188 return DID_NOT_HANDLE
;
190 gesture_scroll_on_impl_thread_
= false;
192 } else if (event
.type
== WebInputEvent::GesturePinchBegin
) {
193 input_handler_
->PinchGestureBegin();
194 DCHECK(!gesture_pinch_on_impl_thread_
);
195 gesture_pinch_on_impl_thread_
= true;
197 } else if (event
.type
== WebInputEvent::GesturePinchEnd
) {
198 DCHECK(gesture_pinch_on_impl_thread_
);
199 gesture_pinch_on_impl_thread_
= false;
200 input_handler_
->PinchGestureEnd();
202 } else if (event
.type
== WebInputEvent::GesturePinchUpdate
) {
203 DCHECK(gesture_pinch_on_impl_thread_
);
204 const WebGestureEvent
& gesture_event
=
205 *static_cast<const WebGestureEvent
*>(&event
);
206 input_handler_
->PinchGestureUpdate(
207 gesture_event
.data
.pinchUpdate
.scale
,
208 gfx::Point(gesture_event
.x
, gesture_event
.y
));
210 } else if (event
.type
== WebInputEvent::GestureFlingStart
) {
211 const WebGestureEvent
& gesture_event
=
212 *static_cast<const WebGestureEvent
*>(&event
);
213 return HandleGestureFling(gesture_event
);
214 } else if (event
.type
== WebInputEvent::GestureFlingCancel
) {
215 if (CancelCurrentFling())
217 else if (!fling_may_be_active_on_main_thread_
)
219 } else if (event
.type
== WebInputEvent::TouchStart
) {
220 const WebTouchEvent
& touch_event
=
221 *static_cast<const WebTouchEvent
*>(&event
);
222 for (size_t i
= 0; i
< touch_event
.touchesLength
; ++i
) {
223 if (touch_event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
225 if (input_handler_
->HaveTouchEventHandlersAt(touch_event
.touches
[i
]
227 return DID_NOT_HANDLE
;
230 } else if (WebInputEvent::isKeyboardEventType(event
.type
)) {
231 CancelCurrentFling();
232 } else if (event
.type
== WebInputEvent::MouseMove
) {
233 const WebMouseEvent
& mouse_event
=
234 *static_cast<const WebMouseEvent
*>(&event
);
235 // TODO(tony): Ignore when mouse buttons are down?
236 // TODO(davemoore): This should never happen, but bug #326635 showed some
237 // surprising crashes.
238 CHECK(input_handler_
);
239 input_handler_
->MouseMoveAt(gfx::Point(mouse_event
.x
, mouse_event
.y
));
242 return DID_NOT_HANDLE
;
245 InputHandlerProxy::EventDisposition
246 InputHandlerProxy::HandleGestureFling(
247 const WebGestureEvent
& gesture_event
) {
248 cc::InputHandler::ScrollStatus scroll_status
;
250 if (gesture_event
.sourceDevice
== WebGestureEvent::Touchpad
) {
251 scroll_status
= input_handler_
->ScrollBegin(
252 gfx::Point(gesture_event
.x
, gesture_event
.y
),
253 cc::InputHandler::NonBubblingGesture
);
255 if (!gesture_scroll_on_impl_thread_
)
256 scroll_status
= cc::InputHandler::ScrollOnMainThread
;
258 scroll_status
= input_handler_
->FlingScrollBegin();
262 expect_scroll_update_end_
= false;
265 switch (scroll_status
) {
266 case cc::InputHandler::ScrollStarted
: {
267 if (gesture_event
.sourceDevice
== WebGestureEvent::Touchpad
)
268 input_handler_
->ScrollEnd();
270 fling_curve_
.reset(client_
->CreateFlingAnimationCurve(
271 gesture_event
.sourceDevice
,
272 WebFloatPoint(gesture_event
.data
.flingStart
.velocityX
,
273 gesture_event
.data
.flingStart
.velocityY
),
275 disallow_horizontal_fling_scroll_
=
276 !gesture_event
.data
.flingStart
.velocityX
;
277 disallow_vertical_fling_scroll_
=
278 !gesture_event
.data
.flingStart
.velocityY
;
279 TRACE_EVENT_ASYNC_BEGIN0(
281 "InputHandlerProxy::HandleGestureFling::started",
283 if (gesture_event
.timeStampSeconds
) {
284 fling_parameters_
.startTime
= gesture_event
.timeStampSeconds
;
285 DCHECK_LT(fling_parameters_
.startTime
-
286 InSecondsF(gfx::FrameTime::Now()),
287 kBadTimestampDeltaFromNowInS
);
289 fling_parameters_
.delta
=
290 WebFloatPoint(gesture_event
.data
.flingStart
.velocityX
,
291 gesture_event
.data
.flingStart
.velocityY
);
292 fling_parameters_
.point
= WebPoint(gesture_event
.x
, gesture_event
.y
);
293 fling_parameters_
.globalPoint
=
294 WebPoint(gesture_event
.globalX
, gesture_event
.globalY
);
295 fling_parameters_
.modifiers
= gesture_event
.modifiers
;
296 fling_parameters_
.sourceDevice
= gesture_event
.sourceDevice
;
297 input_handler_
->ScheduleAnimation();
300 case cc::InputHandler::ScrollOnMainThread
: {
301 TRACE_EVENT_INSTANT0("renderer",
302 "InputHandlerProxy::HandleGestureFling::"
303 "scroll_on_main_thread",
304 TRACE_EVENT_SCOPE_THREAD
);
305 fling_may_be_active_on_main_thread_
= true;
306 return DID_NOT_HANDLE
;
308 case cc::InputHandler::ScrollIgnored
: {
309 TRACE_EVENT_INSTANT0(
311 "InputHandlerProxy::HandleGestureFling::ignored",
312 TRACE_EVENT_SCOPE_THREAD
);
313 if (gesture_event
.sourceDevice
== WebGestureEvent::Touchpad
) {
314 // We still pass the curve to the main thread if there's nothing
315 // scrollable, in case something
316 // registers a handler before the curve is over.
317 return DID_NOT_HANDLE
;
322 return DID_NOT_HANDLE
;
325 void InputHandlerProxy::Animate(base::TimeTicks time
) {
329 double monotonic_time_sec
= InSecondsF(time
);
330 if (!fling_parameters_
.startTime
) {
331 fling_parameters_
.startTime
= monotonic_time_sec
;
332 input_handler_
->ScheduleAnimation();
336 bool fling_is_active
=
337 fling_curve_
->apply(monotonic_time_sec
- fling_parameters_
.startTime
,
340 if (disallow_vertical_fling_scroll_
&& disallow_horizontal_fling_scroll_
)
341 fling_is_active
= false;
343 if (fling_is_active
) {
344 input_handler_
->ScheduleAnimation();
346 TRACE_EVENT_INSTANT0("renderer",
347 "InputHandlerProxy::animate::flingOver",
348 TRACE_EVENT_SCOPE_THREAD
);
349 CancelCurrentFling();
353 void InputHandlerProxy::MainThreadHasStoppedFlinging() {
354 fling_may_be_active_on_main_thread_
= false;
357 void InputHandlerProxy::DidOverscroll(const cc::DidOverscrollParams
& params
) {
360 static const int kFlingOverscrollThreshold
= 1;
361 disallow_horizontal_fling_scroll_
|=
362 std::abs(params
.accumulated_overscroll
.x()) >=
363 kFlingOverscrollThreshold
;
364 disallow_vertical_fling_scroll_
|=
365 std::abs(params
.accumulated_overscroll
.y()) >=
366 kFlingOverscrollThreshold
;
369 client_
->DidOverscroll(params
);
372 bool InputHandlerProxy::CancelCurrentFling() {
373 bool had_fling_animation
= fling_curve_
;
374 if (had_fling_animation
&&
375 fling_parameters_
.sourceDevice
== WebGestureEvent::Touchscreen
) {
376 input_handler_
->ScrollEnd();
377 TRACE_EVENT_ASYNC_END0(
379 "InputHandlerProxy::HandleGestureFling::started",
383 TRACE_EVENT_INSTANT1("renderer",
384 "InputHandlerProxy::CancelCurrentFling",
385 TRACE_EVENT_SCOPE_THREAD
,
386 "had_fling_animation",
387 had_fling_animation
);
388 fling_curve_
.reset();
389 gesture_scroll_on_impl_thread_
= false;
390 fling_parameters_
= blink::WebActiveWheelFlingParameters();
391 return had_fling_animation
;
394 bool InputHandlerProxy::TouchpadFlingScroll(
395 const WebFloatSize
& increment
) {
396 WebMouseWheelEvent synthetic_wheel
;
397 synthetic_wheel
.type
= WebInputEvent::MouseWheel
;
398 synthetic_wheel
.deltaX
= increment
.width
;
399 synthetic_wheel
.deltaY
= increment
.height
;
400 synthetic_wheel
.hasPreciseScrollingDeltas
= true;
401 synthetic_wheel
.x
= fling_parameters_
.point
.x
;
402 synthetic_wheel
.y
= fling_parameters_
.point
.y
;
403 synthetic_wheel
.globalX
= fling_parameters_
.globalPoint
.x
;
404 synthetic_wheel
.globalY
= fling_parameters_
.globalPoint
.y
;
405 synthetic_wheel
.modifiers
= fling_parameters_
.modifiers
;
407 InputHandlerProxy::EventDisposition disposition
=
408 HandleInputEvent(synthetic_wheel
);
409 switch (disposition
) {
415 TRACE_EVENT_INSTANT0("renderer",
416 "InputHandlerProxy::scrollBy::AbortFling",
417 TRACE_EVENT_SCOPE_THREAD
);
418 // If we got a DID_NOT_HANDLE, that means we need to deliver wheels on the
419 // main thread. In this case we need to schedule a commit and transfer the
420 // fling curve over to the main thread and run the rest of the wheels from
421 // there. This can happen when flinging a page that contains a scrollable
422 // subarea that we can't scroll on the thread if the fling starts outside
423 // the subarea but then is flung "under" the pointer.
424 client_
->TransferActiveWheelFlingAnimation(fling_parameters_
);
425 fling_may_be_active_on_main_thread_
= true;
426 CancelCurrentFling();
433 static gfx::Vector2dF
ToClientScrollIncrement(const WebFloatSize
& increment
) {
434 return gfx::Vector2dF(-increment
.width
, -increment
.height
);
437 void InputHandlerProxy::scrollBy(const WebFloatSize
& increment
) {
438 WebFloatSize clipped_increment
;
439 if (!disallow_horizontal_fling_scroll_
)
440 clipped_increment
.width
= increment
.width
;
441 if (!disallow_vertical_fling_scroll_
)
442 clipped_increment
.height
= increment
.height
;
444 if (clipped_increment
== WebFloatSize())
447 TRACE_EVENT2("renderer",
448 "InputHandlerProxy::scrollBy",
450 clipped_increment
.width
,
452 clipped_increment
.height
);
454 bool did_scroll
= false;
456 switch (fling_parameters_
.sourceDevice
) {
457 case WebGestureEvent::Touchpad
:
458 did_scroll
= TouchpadFlingScroll(clipped_increment
);
460 case WebGestureEvent::Touchscreen
:
461 clipped_increment
= ToClientScrollIncrement(clipped_increment
);
462 did_scroll
= input_handler_
->ScrollBy(fling_parameters_
.point
,
468 fling_parameters_
.cumulativeScroll
.width
+= clipped_increment
.width
;
469 fling_parameters_
.cumulativeScroll
.height
+= clipped_increment
.height
;
473 void InputHandlerProxy::notifyCurrentFlingVelocity(
474 const WebFloatSize
& velocity
) {
475 TRACE_EVENT2("renderer",
476 "InputHandlerProxy::notifyCurrentFlingVelocity",
481 input_handler_
->NotifyCurrentFlingVelocity(ToClientScrollIncrement(velocity
));
484 } // namespace content