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/browser/renderer_host/input/touch_event_queue.h"
7 #include "base/auto_reset.h"
8 #include "base/command_line.h"
9 #include "base/debug/trace_event.h"
10 #include "base/stl_util.h"
11 #include "content/browser/renderer_host/input/timeout_monitor.h"
12 #include "content/common/input/web_input_event_traits.h"
13 #include "content/public/common/content_switches.h"
15 using blink::WebInputEvent
;
16 using blink::WebTouchEvent
;
17 using blink::WebTouchPoint
;
22 const InputEventAckState kDefaultNotForwardedAck
=
23 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
;
25 typedef std::vector
<TouchEventWithLatencyInfo
> WebTouchEventWithLatencyList
;
27 TouchEventWithLatencyInfo
ObtainCancelEventForTouchEvent(
28 const TouchEventWithLatencyInfo
& event_to_cancel
) {
29 TouchEventWithLatencyInfo event
= event_to_cancel
;
30 event
.event
.type
= WebInputEvent::TouchCancel
;
31 for (size_t i
= 0; i
< event
.event
.touchesLength
; i
++)
32 event
.event
.touches
[i
].state
= WebTouchPoint::StateCancelled
;
36 bool IsNewTouchGesture(const WebTouchEvent
& event
) {
37 if (event
.type
!= WebInputEvent::TouchStart
)
39 if (!event
.touchesLength
)
41 for (size_t i
= 0; i
< event
.touchesLength
; i
++) {
42 if (event
.touches
[i
].state
!= WebTouchPoint::StatePressed
)
48 bool ShouldTouchTypeTriggerTimeout(WebInputEvent::Type type
) {
49 return type
== WebInputEvent::TouchStart
||
50 type
== WebInputEvent::TouchMove
;
55 class TouchEventQueue::TouchTimeoutHandler
{
57 TouchTimeoutHandler(TouchEventQueue
* touch_queue
, size_t timeout_delay_ms
)
58 : touch_queue_(touch_queue
),
59 timeout_delay_(base::TimeDelta::FromMilliseconds(timeout_delay_ms
)),
60 pending_ack_state_(PENDING_ACK_NONE
),
61 timeout_monitor_(base::Bind(&TouchTimeoutHandler::OnTimeOut
,
62 base::Unretained(this))) {}
64 ~TouchTimeoutHandler() {}
66 void Start(const TouchEventWithLatencyInfo
& event
) {
67 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_NONE
);
68 DCHECK(ShouldTouchTypeTriggerTimeout(event
.event
.type
));
69 timeout_event_
= event
;
70 timeout_monitor_
.Restart(timeout_delay_
);
73 bool ConfirmTouchEvent(InputEventAckState ack_result
) {
74 switch (pending_ack_state_
) {
75 case PENDING_ACK_NONE
:
76 timeout_monitor_
.Stop();
78 case PENDING_ACK_ORIGINAL_EVENT
:
79 if (AckedTimeoutEventRequiresCancel(ack_result
)) {
80 SetPendingAckState(PENDING_ACK_CANCEL_EVENT
);
81 TouchEventWithLatencyInfo cancel_event
=
82 ObtainCancelEventForTouchEvent(timeout_event_
);
83 touch_queue_
->UpdateTouchAckStates(
84 cancel_event
.event
, kDefaultNotForwardedAck
);
85 touch_queue_
->client_
->SendTouchEventImmediately(cancel_event
);
87 SetPendingAckState(PENDING_ACK_NONE
);
88 touch_queue_
->UpdateTouchAckStates(timeout_event_
.event
, ack_result
);
91 case PENDING_ACK_CANCEL_EVENT
:
92 SetPendingAckState(PENDING_ACK_NONE
);
98 bool HasTimeoutEvent() const {
99 return pending_ack_state_
!= PENDING_ACK_NONE
;
102 bool IsTimeoutTimerRunning() const {
103 return timeout_monitor_
.IsRunning();
107 enum PendingAckState
{
109 PENDING_ACK_ORIGINAL_EVENT
,
110 PENDING_ACK_CANCEL_EVENT
,
114 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT
);
115 touch_queue_
->FlushQueue();
118 // Skip a cancel event if the timed-out event had no consumer and was the
119 // initial event in the gesture.
120 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result
) const {
121 DCHECK(HasTimeoutEvent());
122 if (ack_result
!= INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
)
124 return !IsNewTouchGesture(timeout_event_
.event
);
127 void SetPendingAckState(PendingAckState new_pending_ack_state
) {
128 DCHECK_NE(pending_ack_state_
, new_pending_ack_state
);
129 switch (new_pending_ack_state
) {
130 case PENDING_ACK_ORIGINAL_EVENT
:
131 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_NONE
);
132 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
134 case PENDING_ACK_CANCEL_EVENT
:
135 DCHECK_EQ(pending_ack_state_
, PENDING_ACK_ORIGINAL_EVENT
);
136 DCHECK(!timeout_monitor_
.IsRunning());
137 DCHECK(touch_queue_
->empty());
138 TRACE_EVENT_ASYNC_STEP_INTO0(
139 "input", "TouchEventTimeout", this, "CancelEvent");
141 case PENDING_ACK_NONE
:
142 DCHECK(!timeout_monitor_
.IsRunning());
143 DCHECK(touch_queue_
->empty());
144 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
147 pending_ack_state_
= new_pending_ack_state
;
151 TouchEventQueue
* touch_queue_
;
153 // How long to wait on a touch ack before cancelling the touch sequence.
154 base::TimeDelta timeout_delay_
;
156 // The touch event source for which we expect the next ack.
157 PendingAckState pending_ack_state_
;
159 // The event for which the ack timeout is triggered.
160 TouchEventWithLatencyInfo timeout_event_
;
162 // Provides timeout-based callback behavior.
163 TimeoutMonitor timeout_monitor_
;
167 // This class represents a single coalesced touch event. However, it also keeps
168 // track of all the original touch-events that were coalesced into a single
169 // event. The coalesced event is forwarded to the renderer, while the original
170 // touch-events are sent to the Client (on ACK for the coalesced event) so that
171 // the Client receives the event with their original timestamp.
172 class CoalescedWebTouchEvent
{
174 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo
& event
,
176 : coalesced_event_(event
),
177 ignore_ack_(ignore_ack
) {
178 events_
.push_back(event
);
179 TRACE_EVENT_ASYNC_BEGIN0(
180 "input", "TouchEventQueue::QueueEvent", this);
183 ~CoalescedWebTouchEvent() {
184 TRACE_EVENT_ASYNC_END0(
185 "input", "TouchEventQueue::QueueEvent", this);
188 // Coalesces the event with the existing event if possible. Returns whether
189 // the event was coalesced.
190 bool CoalesceEventIfPossible(
191 const TouchEventWithLatencyInfo
& event_with_latency
) {
195 if (!coalesced_event_
.CanCoalesceWith(event_with_latency
))
198 TRACE_EVENT_INSTANT0(
199 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD
);
200 coalesced_event_
.CoalesceWith(event_with_latency
);
201 events_
.push_back(event_with_latency
);
205 const TouchEventWithLatencyInfo
& coalesced_event() const {
206 return coalesced_event_
;
209 WebTouchEventWithLatencyList::iterator
begin() {
210 return events_
.begin();
213 WebTouchEventWithLatencyList::iterator
end() {
214 return events_
.end();
217 size_t size() const { return events_
.size(); }
219 bool ignore_ack() const { return ignore_ack_
; }
222 // This is the event that is forwarded to the renderer.
223 TouchEventWithLatencyInfo coalesced_event_
;
225 // This is the list of the original events that were coalesced.
226 WebTouchEventWithLatencyList events_
;
228 // If |ignore_ack_| is true, don't send this touch event to client
229 // when the event is acked.
232 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent
);
235 TouchEventQueue::TouchEventQueue(TouchEventQueueClient
* client
)
237 dispatching_touch_ack_(NULL
),
238 dispatching_touch_(false),
239 no_touch_to_renderer_(false),
240 renderer_is_consuming_touch_gesture_(false),
241 ack_timeout_enabled_(false) {
245 TouchEventQueue::~TouchEventQueue() {
246 if (!touch_queue_
.empty())
247 STLDeleteElements(&touch_queue_
);
250 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo
& event
) {
251 // If the queueing of |event| was triggered by an ack dispatch, defer
252 // processing the event until the dispatch has finished.
253 if (touch_queue_
.empty() && !dispatching_touch_ack_
) {
254 // There is no touch event in the queue. Forward it to the renderer
256 touch_queue_
.push_back(new CoalescedWebTouchEvent(event
, false));
257 TryForwardNextEventToRenderer();
261 // If the last queued touch-event was a touch-move, and the current event is
262 // also a touch-move, then the events can be coalesced into a single event.
263 if (touch_queue_
.size() > 1) {
264 CoalescedWebTouchEvent
* last_event
= touch_queue_
.back();
265 if (last_event
->CoalesceEventIfPossible(event
))
268 touch_queue_
.push_back(new CoalescedWebTouchEvent(event
, false));
271 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result
,
272 const ui::LatencyInfo
& latency_info
) {
273 DCHECK(!dispatching_touch_ack_
);
274 dispatching_touch_
= false;
276 if (timeout_handler_
&& timeout_handler_
->ConfirmTouchEvent(ack_result
))
279 if (touch_queue_
.empty())
282 if (ack_result
== INPUT_EVENT_ACK_STATE_CONSUMED
)
283 renderer_is_consuming_touch_gesture_
= true;
285 const WebTouchEvent
& acked_event
=
286 touch_queue_
.front()->coalesced_event().event
;
287 UpdateTouchAckStates(acked_event
, ack_result
);
288 PopTouchEventToClient(ack_result
, latency_info
);
289 TryForwardNextEventToRenderer();
292 void TouchEventQueue::TryForwardNextEventToRenderer() {
293 DCHECK(!dispatching_touch_ack_
);
294 // If there are queued touch events, then try to forward them to the renderer
295 // immediately, or ACK the events back to the client if appropriate.
296 while (!touch_queue_
.empty()) {
297 const TouchEventWithLatencyInfo
& touch
=
298 touch_queue_
.front()->coalesced_event();
299 if (IsNewTouchGesture(touch
.event
))
300 renderer_is_consuming_touch_gesture_
= false;
301 if (ShouldForwardToRenderer(touch
.event
)) {
302 ForwardToRenderer(touch
);
305 PopTouchEventToClient(kDefaultNotForwardedAck
, ui::LatencyInfo());
309 void TouchEventQueue::ForwardToRenderer(
310 const TouchEventWithLatencyInfo
& touch
) {
311 DCHECK(!dispatching_touch_
);
312 // A synchronous ack will reset |dispatching_touch_|, in which case
313 // the touch timeout should not be started.
314 base::AutoReset
<bool> dispatching_touch(&dispatching_touch_
, true);
315 client_
->SendTouchEventImmediately(touch
);
316 if (ack_timeout_enabled_
&&
317 dispatching_touch_
&&
318 !renderer_is_consuming_touch_gesture_
&&
319 ShouldTouchTypeTriggerTimeout(touch
.event
.type
)) {
320 DCHECK(timeout_handler_
);
321 timeout_handler_
->Start(touch
);
325 void TouchEventQueue::OnGestureScrollEvent(
326 const GestureEventWithLatencyInfo
& gesture_event
) {
327 blink::WebInputEvent::Type type
= gesture_event
.event
.type
;
328 if (type
== blink::WebInputEvent::GestureScrollBegin
) {
329 // We assume the scroll event are generated synchronously from
330 // dispatching a touch event ack, so that we can fake a cancel
331 // event that has the correct touch ids as the touch event that
332 // is being acked. If not, we don't do the touch-cancel optimization.
333 if (no_touch_to_renderer_
|| !dispatching_touch_ack_
)
335 no_touch_to_renderer_
= true;
337 // If we have a timeout event, a cancel has already been dispatched
338 // for the current touch stream.
339 if (HasTimeoutEvent())
342 // Fake a TouchCancel to cancel the touch points of the touch event
343 // that is currently being acked.
344 // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
345 // are in the scope of PopTouchEventToClient() and that no touch event
346 // in the queue is waiting for ack from renderer. So we can just insert
347 // the touch cancel at the beginning of the queue.
348 touch_queue_
.push_front(new CoalescedWebTouchEvent(
349 ObtainCancelEventForTouchEvent(
350 dispatching_touch_ack_
->coalesced_event()), true));
351 } else if (type
== blink::WebInputEvent::GestureScrollEnd
||
352 type
== blink::WebInputEvent::GestureFlingStart
) {
353 no_touch_to_renderer_
= false;
357 void TouchEventQueue::FlushQueue() {
358 DCHECK(!dispatching_touch_ack_
);
359 DCHECK(!dispatching_touch_
);
360 while (!touch_queue_
.empty())
361 PopTouchEventToClient(kDefaultNotForwardedAck
, ui::LatencyInfo());
364 bool TouchEventQueue::IsPendingAckTouchStart() const {
365 DCHECK(!dispatching_touch_ack_
);
366 if (touch_queue_
.empty())
369 const blink::WebTouchEvent
& event
=
370 touch_queue_
.front()->coalesced_event().event
;
371 return (event
.type
== WebInputEvent::TouchStart
);
374 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled
,
375 size_t ack_timeout_delay_ms
) {
377 // Avoid resetting |timeout_handler_|, as an outstanding timeout may
378 // be active and must be completed for ack handling consistency.
379 ack_timeout_enabled_
= false;
383 ack_timeout_enabled_
= true;
384 if (!timeout_handler_
)
385 timeout_handler_
.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms
));
388 bool TouchEventQueue::HasTimeoutEvent() const {
389 return timeout_handler_
&& timeout_handler_
->HasTimeoutEvent();
392 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
393 return timeout_handler_
&& timeout_handler_
->IsTimeoutTimerRunning();
396 const TouchEventWithLatencyInfo
&
397 TouchEventQueue::GetLatestEventForTesting() const {
398 return touch_queue_
.back()->coalesced_event();
401 void TouchEventQueue::PopTouchEventToClient(
402 InputEventAckState ack_result
,
403 const ui::LatencyInfo
& renderer_latency_info
) {
404 DCHECK(!dispatching_touch_ack_
);
405 if (touch_queue_
.empty())
407 scoped_ptr
<CoalescedWebTouchEvent
> acked_event(touch_queue_
.front());
408 touch_queue_
.pop_front();
410 if (acked_event
->ignore_ack())
413 // Note that acking the touch-event may result in multiple gestures being sent
414 // to the renderer, or touch-events being queued.
415 base::AutoReset
<CoalescedWebTouchEvent
*>
416 dispatching_touch_ack(&dispatching_touch_ack_
, acked_event
.get());
418 for (WebTouchEventWithLatencyList::iterator iter
= acked_event
->begin(),
419 end
= acked_event
->end();
420 iter
!= end
; ++iter
) {
421 iter
->latency
.AddNewLatencyFrom(renderer_latency_info
);
422 client_
->OnTouchEventAck((*iter
), ack_result
);
426 bool TouchEventQueue::ShouldForwardToRenderer(
427 const WebTouchEvent
& event
) const {
428 if (HasTimeoutEvent())
431 if (no_touch_to_renderer_
&&
432 event
.type
!= blink::WebInputEvent::TouchCancel
)
435 // Touch press events should always be forwarded to the renderer.
436 if (event
.type
== WebInputEvent::TouchStart
)
439 for (unsigned int i
= 0; i
< event
.touchesLength
; ++i
) {
440 const WebTouchPoint
& point
= event
.touches
[i
];
441 // If a point has been stationary, then don't take it into account.
442 if (point
.state
== WebTouchPoint::StateStationary
)
445 if (touch_ack_states_
.count(point
.id
) > 0) {
446 if (touch_ack_states_
.find(point
.id
)->second
!=
447 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS
)
450 // If the ACK status of a point is unknown, then the event should be
451 // forwarded to the renderer.
459 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent
& event
,
460 InputEventAckState ack_result
) {
461 // Update the ACK status for each touch point in the ACKed event.
462 if (event
.type
== WebInputEvent::TouchEnd
||
463 event
.type
== WebInputEvent::TouchCancel
) {
464 // The points have been released. Erase the ACK states.
465 for (unsigned i
= 0; i
< event
.touchesLength
; ++i
) {
466 const WebTouchPoint
& point
= event
.touches
[i
];
467 if (point
.state
== WebTouchPoint::StateReleased
||
468 point
.state
== WebTouchPoint::StateCancelled
)
469 touch_ack_states_
.erase(point
.id
);
471 } else if (event
.type
== WebInputEvent::TouchStart
) {
472 for (unsigned i
= 0; i
< event
.touchesLength
; ++i
) {
473 const WebTouchPoint
& point
= event
.touches
[i
];
474 if (point
.state
== WebTouchPoint::StatePressed
)
475 touch_ack_states_
[point
.id
] = ack_result
;
480 } // namespace content