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 IsNewTouchSequence(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_->client_->SendTouchEventImmediately(cancel_event);
85 SetPendingAckState(PENDING_ACK_NONE);
86 touch_queue_->UpdateTouchAckStates(timeout_event_.event, ack_result);
89 case PENDING_ACK_CANCEL_EVENT:
90 SetPendingAckState(PENDING_ACK_NONE);
96 bool HasTimeoutEvent() const {
97 return pending_ack_state_ != PENDING_ACK_NONE;
100 bool IsTimeoutTimerRunning() const {
101 return timeout_monitor_.IsRunning();
105 pending_ack_state_ = PENDING_ACK_NONE;
106 timeout_monitor_.Stop();
110 enum PendingAckState {
112 PENDING_ACK_ORIGINAL_EVENT,
113 PENDING_ACK_CANCEL_EVENT,
117 SetPendingAckState(PENDING_ACK_ORIGINAL_EVENT);
118 touch_queue_->FlushQueue();
121 // Skip a cancel event if the timed-out event had no consumer and was the
122 // initial event in the gesture.
123 bool AckedTimeoutEventRequiresCancel(InputEventAckState ack_result) const {
124 DCHECK(HasTimeoutEvent());
125 if (ack_result != INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
127 return !IsNewTouchSequence(timeout_event_.event);
130 void SetPendingAckState(PendingAckState new_pending_ack_state) {
131 DCHECK_NE(pending_ack_state_, new_pending_ack_state);
132 switch (new_pending_ack_state) {
133 case PENDING_ACK_ORIGINAL_EVENT:
134 DCHECK_EQ(pending_ack_state_, PENDING_ACK_NONE);
135 TRACE_EVENT_ASYNC_BEGIN0("input", "TouchEventTimeout", this);
137 case PENDING_ACK_CANCEL_EVENT:
138 DCHECK_EQ(pending_ack_state_, PENDING_ACK_ORIGINAL_EVENT);
139 DCHECK(!timeout_monitor_.IsRunning());
140 DCHECK(touch_queue_->empty());
141 TRACE_EVENT_ASYNC_STEP_INTO0(
142 "input", "TouchEventTimeout", this, "CancelEvent");
144 case PENDING_ACK_NONE:
145 DCHECK(!timeout_monitor_.IsRunning());
146 DCHECK(touch_queue_->empty());
147 TRACE_EVENT_ASYNC_END0("input", "TouchEventTimeout", this);
150 pending_ack_state_ = new_pending_ack_state;
154 TouchEventQueue* touch_queue_;
156 // How long to wait on a touch ack before cancelling the touch sequence.
157 base::TimeDelta timeout_delay_;
159 // The touch event source for which we expect the next ack.
160 PendingAckState pending_ack_state_;
162 // The event for which the ack timeout is triggered.
163 TouchEventWithLatencyInfo timeout_event_;
165 // Provides timeout-based callback behavior.
166 TimeoutMonitor timeout_monitor_;
170 // This class represents a single coalesced touch event. However, it also keeps
171 // track of all the original touch-events that were coalesced into a single
172 // event. The coalesced event is forwarded to the renderer, while the original
173 // touch-events are sent to the Client (on ACK for the coalesced event) so that
174 // the Client receives the event with their original timestamp.
175 class CoalescedWebTouchEvent {
177 CoalescedWebTouchEvent(const TouchEventWithLatencyInfo& event,
179 : coalesced_event_(event),
180 ignore_ack_(ignore_ack) {
181 events_.push_back(event);
182 TRACE_EVENT_ASYNC_BEGIN0(
183 "input", "TouchEventQueue::QueueEvent", this);
186 ~CoalescedWebTouchEvent() {
187 TRACE_EVENT_ASYNC_END0(
188 "input", "TouchEventQueue::QueueEvent", this);
191 // Coalesces the event with the existing event if possible. Returns whether
192 // the event was coalesced.
193 bool CoalesceEventIfPossible(
194 const TouchEventWithLatencyInfo& event_with_latency) {
198 if (!coalesced_event_.CanCoalesceWith(event_with_latency))
201 TRACE_EVENT_INSTANT0(
202 "input", "TouchEventQueue::MoveCoalesced", TRACE_EVENT_SCOPE_THREAD);
203 coalesced_event_.CoalesceWith(event_with_latency);
204 events_.push_back(event_with_latency);
208 const TouchEventWithLatencyInfo& coalesced_event() const {
209 return coalesced_event_;
212 WebTouchEventWithLatencyList::iterator begin() {
213 return events_.begin();
216 WebTouchEventWithLatencyList::iterator end() {
217 return events_.end();
220 size_t size() const { return events_.size(); }
222 bool ignore_ack() const { return ignore_ack_; }
225 // This is the event that is forwarded to the renderer.
226 TouchEventWithLatencyInfo coalesced_event_;
228 // This is the list of the original events that were coalesced.
229 WebTouchEventWithLatencyList events_;
231 // If |ignore_ack_| is true, don't send this touch event to client
232 // when the event is acked.
235 DISALLOW_COPY_AND_ASSIGN(CoalescedWebTouchEvent);
238 TouchEventQueue::TouchEventQueue(TouchEventQueueClient* client)
240 dispatching_touch_ack_(NULL),
241 dispatching_touch_(false),
242 touch_filtering_state_(TOUCH_FILTERING_STATE_DEFAULT),
243 ack_timeout_enabled_(false) {
247 TouchEventQueue::~TouchEventQueue() {
248 if (!touch_queue_.empty())
249 STLDeleteElements(&touch_queue_);
252 void TouchEventQueue::QueueEvent(const TouchEventWithLatencyInfo& event) {
253 // If the queueing of |event| was triggered by an ack dispatch, defer
254 // processing the event until the dispatch has finished.
255 if (touch_queue_.empty() && !dispatching_touch_ack_) {
256 // Optimization of the case without touch handlers. Removing this path
257 // yields identical results, but this avoids unnecessary allocations.
258 if (touch_filtering_state_ == DROP_ALL_TOUCHES ||
259 (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
260 !IsNewTouchSequence(event.event))) {
261 client_->OnTouchEventAck(event, kDefaultNotForwardedAck);
265 // There is no touch event in the queue. Forward it to the renderer
267 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
268 TryForwardNextEventToRenderer();
272 // If the last queued touch-event was a touch-move, and the current event is
273 // also a touch-move, then the events can be coalesced into a single event.
274 if (touch_queue_.size() > 1) {
275 CoalescedWebTouchEvent* last_event = touch_queue_.back();
276 if (last_event->CoalesceEventIfPossible(event))
279 touch_queue_.push_back(new CoalescedWebTouchEvent(event, false));
282 void TouchEventQueue::ProcessTouchAck(InputEventAckState ack_result,
283 const ui::LatencyInfo& latency_info) {
284 DCHECK(!dispatching_touch_ack_);
285 dispatching_touch_ = false;
287 if (timeout_handler_ && timeout_handler_->ConfirmTouchEvent(ack_result))
290 if (touch_queue_.empty())
293 if (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED &&
294 touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT)
295 touch_filtering_state_ = FORWARD_ALL_TOUCHES;
297 const WebTouchEvent& acked_event =
298 touch_queue_.front()->coalesced_event().event;
299 UpdateTouchAckStates(acked_event, ack_result);
300 PopTouchEventToClient(ack_result, latency_info);
301 TryForwardNextEventToRenderer();
304 void TouchEventQueue::TryForwardNextEventToRenderer() {
305 DCHECK(!dispatching_touch_ack_);
306 // If there are queued touch events, then try to forward them to the renderer
307 // immediately, or ACK the events back to the client if appropriate.
308 while (!touch_queue_.empty()) {
309 const TouchEventWithLatencyInfo& touch =
310 touch_queue_.front()->coalesced_event();
311 if (ShouldForwardToRenderer(touch.event)) {
312 ForwardToRenderer(touch);
315 PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
319 void TouchEventQueue::ForwardToRenderer(
320 const TouchEventWithLatencyInfo& touch) {
321 DCHECK(!dispatching_touch_);
322 DCHECK_NE(touch_filtering_state_, DROP_ALL_TOUCHES);
324 if (IsNewTouchSequence(touch.event)) {
325 touch_filtering_state_ =
326 ack_timeout_enabled_ ? FORWARD_TOUCHES_UNTIL_TIMEOUT
327 : FORWARD_ALL_TOUCHES;
328 touch_ack_states_.clear();
331 // A synchronous ack will reset |dispatching_touch_|, in which case
332 // the touch timeout should not be started.
333 base::AutoReset<bool> dispatching_touch(&dispatching_touch_, true);
334 client_->SendTouchEventImmediately(touch);
335 if (dispatching_touch_ &&
336 touch_filtering_state_ == FORWARD_TOUCHES_UNTIL_TIMEOUT &&
337 ShouldTouchTypeTriggerTimeout(touch.event.type)) {
338 DCHECK(timeout_handler_);
339 timeout_handler_->Start(touch);
343 void TouchEventQueue::OnGestureScrollEvent(
344 const GestureEventWithLatencyInfo& gesture_event) {
345 if (gesture_event.event.type != blink::WebInputEvent::GestureScrollBegin)
348 // We assume that scroll events are generated synchronously from
349 // dispatching a touch event ack. This allows us to generate a synthetic
350 // cancel event that has the same touch ids as the touch event that
351 // is being acked. Otherwise, we don't perform the touch-cancel optimization.
352 if (!dispatching_touch_ack_)
355 if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE)
358 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
360 // Fake a TouchCancel to cancel the touch points of the touch event
361 // that is currently being acked.
362 // Note: |dispatching_touch_ack_| is non-null when we reach here, meaning we
363 // are in the scope of PopTouchEventToClient() and that no touch event
364 // in the queue is waiting for ack from renderer. So we can just insert
365 // the touch cancel at the beginning of the queue.
366 touch_queue_.push_front(new CoalescedWebTouchEvent(
367 ObtainCancelEventForTouchEvent(
368 dispatching_touch_ack_->coalesced_event()), true));
371 void TouchEventQueue::OnHasTouchEventHandlers(bool has_handlers) {
372 DCHECK(!dispatching_touch_ack_);
373 DCHECK(!dispatching_touch_);
376 if (touch_filtering_state_ == DROP_ALL_TOUCHES) {
377 // If no touch handler was previously registered, ensure that we don't
378 // send a partial touch sequence to the renderer.
379 DCHECK(touch_queue_.empty());
380 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
383 // TODO(jdduke): Synthesize a TouchCancel if necessary to update Blink touch
384 // state tracking (e.g., if the touch handler was removed mid-sequence).
385 touch_filtering_state_ = DROP_ALL_TOUCHES;
386 if (timeout_handler_)
387 timeout_handler_->Reset();
388 if (!touch_queue_.empty())
389 ProcessTouchAck(kDefaultNotForwardedAck, ui::LatencyInfo());
390 // As there is no touch handler, ack'ing the event should flush the queue.
391 DCHECK(touch_queue_.empty());
395 bool TouchEventQueue::IsPendingAckTouchStart() const {
396 DCHECK(!dispatching_touch_ack_);
397 if (touch_queue_.empty())
400 const blink::WebTouchEvent& event =
401 touch_queue_.front()->coalesced_event().event;
402 return (event.type == WebInputEvent::TouchStart);
405 void TouchEventQueue::SetAckTimeoutEnabled(bool enabled,
406 size_t ack_timeout_delay_ms) {
408 // Avoid resetting |timeout_handler_|, as an outstanding timeout may
409 // be active and must be completed for ack handling consistency.
410 ack_timeout_enabled_ = false;
414 ack_timeout_enabled_ = true;
415 if (!timeout_handler_)
416 timeout_handler_.reset(new TouchTimeoutHandler(this, ack_timeout_delay_ms));
419 bool TouchEventQueue::HasTimeoutEvent() const {
420 return timeout_handler_ && timeout_handler_->HasTimeoutEvent();
423 bool TouchEventQueue::IsTimeoutRunningForTesting() const {
424 return timeout_handler_ && timeout_handler_->IsTimeoutTimerRunning();
427 const TouchEventWithLatencyInfo&
428 TouchEventQueue::GetLatestEventForTesting() const {
429 return touch_queue_.back()->coalesced_event();
432 void TouchEventQueue::FlushQueue() {
433 DCHECK(!dispatching_touch_ack_);
434 DCHECK(!dispatching_touch_);
435 if (touch_filtering_state_ != DROP_ALL_TOUCHES)
436 touch_filtering_state_ = DROP_TOUCHES_IN_SEQUENCE;
437 while (!touch_queue_.empty())
438 PopTouchEventToClient(kDefaultNotForwardedAck, ui::LatencyInfo());
441 void TouchEventQueue::PopTouchEventToClient(
442 InputEventAckState ack_result,
443 const ui::LatencyInfo& renderer_latency_info) {
444 DCHECK(!dispatching_touch_ack_);
445 if (touch_queue_.empty())
447 scoped_ptr<CoalescedWebTouchEvent> acked_event(touch_queue_.front());
448 touch_queue_.pop_front();
450 if (acked_event->ignore_ack())
453 // Note that acking the touch-event may result in multiple gestures being sent
454 // to the renderer, or touch-events being queued.
455 base::AutoReset<CoalescedWebTouchEvent*>
456 dispatching_touch_ack(&dispatching_touch_ack_, acked_event.get());
458 for (WebTouchEventWithLatencyList::iterator iter = acked_event->begin(),
459 end = acked_event->end();
460 iter != end; ++iter) {
461 iter->latency.AddNewLatencyFrom(renderer_latency_info);
462 client_->OnTouchEventAck((*iter), ack_result);
466 bool TouchEventQueue::ShouldForwardToRenderer(
467 const WebTouchEvent& event) const {
468 if (HasTimeoutEvent())
471 if (touch_filtering_state_ == DROP_ALL_TOUCHES)
474 if (touch_filtering_state_ == DROP_TOUCHES_IN_SEQUENCE &&
475 event.type != WebInputEvent::TouchCancel) {
476 if (IsNewTouchSequence(event))
481 // Touch press events should always be forwarded to the renderer.
482 if (event.type == WebInputEvent::TouchStart)
485 for (unsigned int i = 0; i < event.touchesLength; ++i) {
486 const WebTouchPoint& point = event.touches[i];
487 // If a point has been stationary, then don't take it into account.
488 if (point.state == WebTouchPoint::StateStationary)
491 if (touch_ack_states_.count(point.id) > 0) {
492 if (touch_ack_states_.find(point.id)->second !=
493 INPUT_EVENT_ACK_STATE_NO_CONSUMER_EXISTS)
496 // If the ACK status of a point is unknown, then the event should be
497 // forwarded to the renderer.
505 void TouchEventQueue::UpdateTouchAckStates(const WebTouchEvent& event,
506 InputEventAckState ack_result) {
507 // Update the ACK status for each touch point in the ACKed event.
508 if (event.type == WebInputEvent::TouchEnd ||
509 event.type == WebInputEvent::TouchCancel) {
510 // The points have been released. Erase the ACK states.
511 for (unsigned i = 0; i < event.touchesLength; ++i) {
512 const WebTouchPoint& point = event.touches[i];
513 if (point.state == WebTouchPoint::StateReleased ||
514 point.state == WebTouchPoint::StateCancelled)
515 touch_ack_states_.erase(point.id);
517 } else if (event.type == WebInputEvent::TouchStart) {
518 for (unsigned i = 0; i < event.touchesLength; ++i) {
519 const WebTouchPoint& point = event.touches[i];
520 if (point.state == WebTouchPoint::StatePressed)
521 touch_ack_states_[point.id] = ack_result;
526 } // namespace content